Loading DLL not initializing static C++ classes

11,040

Solution 1

When you linked your DLL, was the /ENTRY switch specified? If so, it'll override the linker's default which is DllMainCRTStartup. As a result, _CRT_INIT won't be called and, in turn, the global initializers won't be called which will result in uninitialized global (static) data.

If you are specifying /ENTRY for your own entry point, you need to call _CRT_INIT during process attach and process detach.

If you are not specifying /ENTRY, the linker should be using the CRT's entry point which calls _CRT_INIT on process attach/detach before calling into your DllMain.

Solution 2

I'd like to point out that complex static objects in DLLs should be avoided.

Remember that static intializers in a DLL are called from DllMain, and thus all restrictions on DllMain code apply to constructors and destructors of static objects. In particular, std::map can allocate dynamic memory during construction, which can lead to unpredictable results if C++ runtime is not initialized yet (in case you are using dynamically-linked runtime).

There is a good article on writing DllMain: Best Practices for Creating DLLs.

I would suggest changing your static map object to a static pointer (which can be safely zero-initialized), and either adding a separate DLL-exported function for initialization, or using lazy initialization (i.e. check the pointer before accessing it and create the object if it's null).

Solution 3

Actually, chances are you are making a wrong assumption:

Loading, static and global data initialization should happen before even the call to DLLMain.

See item 4 of Effective C++:

The order of initialization of non-local static objects defined in different translation units is undefined

The reason is that ensuring a "correct" initialization order is impossible, and therefore the C++ standard simply gives up on it, and just leave that as undefined.

So, if your DllMain is in a different file than the code where the static variable is declared, the behavior is undefined, and you have very good chances of getting DllMain called before the initialization is actually done.

Solution is quite simple, and outlined in the same item of Effective C++ (btw: I strongly recommend you reading that book!), and requires declaring the static variable inside a function, that simply returns it.

Share:
11,040
Ramon Zarazua B.
Author by

Ramon Zarazua B.

I am a video game programmer, interested in software architecture, and tool development. I am currently a Software Development Engineer in Test at Microsoft working in Backend Service testing. My goal is to create solid code that can be used as a foundation for larger projects, and that helps other developers, make their jobs easier.

Updated on July 29, 2022

Comments

  • Ramon Zarazua B.
    Ramon Zarazua B. almost 2 years

    I have a DLL that is being loaded at run-time. The DLL relies on a static variable for internal workings (it is a std::map), this variable is defined within the DLL.

    When I call the first function from the DLL after loading, I get a SegFault from the DLL, the map was never initialized. From everything I have read from DLL Loading, static and global data initialization should happen before even the call to DLLMain.

    To test static initialization I added a static struct that prints out a message, and even threw in a breakpoint for good measure.

    static struct a
    {
      a(void) { puts("Constructing\n"); }
    }statica;
    

    There was no message, or break before DLLMain or the function is called.

    Here is my loading code:

      dll = LoadLibrary("NetSim");
      //Error Handling
    
      ChangeReliability   = reinterpret_cast<NetSim::ChangeReliability>
                            (GetProcAddress(dll, "ChangeReliability"));
    
    
    ChangeReliability(100);
    

    I have verified that the dll version is the correct one, rebuilt the entire project multiple times, but no difference. I am fresh out of ideas.

  • atzz
    atzz about 13 years
    Allocating dynamic memory in DllMain is unsafe and should be avoided. E.g. see this MS article: go.microsoft.com/FWLink/?LinkId=84138
  • Peter Huene
    Peter Huene about 13 years
    Voting this up since atzz brings up an excellent point. You really want to avoid doing much of anything under the loader lock and even then limit yourself to actions you know are inherently safe (the list is not long, that's for sure).
  • bdonlan
    bdonlan about 13 years
    Lazy init won't help - you'd end up calling CRT functions to free the memory in DllMain() when the DLL is unloaded
  • MSalters
    MSalters about 13 years
    You're confusing standard C++ (which doesn't even know about DLLs) and Windows. In Windows, static and global initialization still happens in unspecified order, but it does happen from within DllMain.
  • atzz
    atzz about 13 years
    @bdonlan - that depends on the implementation. The way I phrased it above, there is no deallocation at all and the object will be leaked on DLL unload :). But I agree with your point, that using lazy initialization here complicates cleanup.
  • atzz
    atzz about 13 years
    It still has the problem with deallocation, which will happen in DllMain on DLL unload (and may have funny consequences: blogs.msdn.com/b/oldnewthing/archive/2010/01/22/9951750.aspx‌​).
  • MSalters
    MSalters about 13 years
    You sure? That's about critical sections. As far as I understood, modern CRT's don't care about memory deallocation during shutdown. The OS will clean up anyway.
  • atzz
    atzz about 13 years
    On DLL unload, destructor of theMap will be called from DllMain. This destructor will deallocate the dynamic memory held by the map. CRT heap is protected with a critical section, so the effects described in the link can occur. Also, destructors of Key and Value objects within map will be called too, and we don't know whether they are DllMain-safe or not.
  • rob
    rob about 13 years
    The order of initialization is compiler specific. Windows does know anything about the initialization (and how it could?) - it just calls DllMain with DLL_PROCESS_ATTACH. It is possible that a compiler will initialize all static variable before DllMain, but it is not guaranteed. At least, I remember that with VC 6.0 (yes, lots of time ago) I had the very same issue as the reported one, and the culprit finally was a wrong assumption about static initialization.
  • user396672
    user396672 about 13 years
    @Roberto: declaring static variables inside functions is quite dangerous because it is not thread safe (and therefore may lead to double initialization). Static initializers inside functions should be used for simple POD variables only.
  • rob
    rob about 13 years
    If you want to ensure thread safety, you need to use some synchronization object. Relying on non-local static data for thread safety is definitely not a solution.
  • user396672
    user396672 about 13 years
    @Roberto: why? Global variables are initialized in a single thread. (I'm doubt that CRT may be not initialized even using dynamic linking) and some restrictive initialization may be done at this time.
  • bdonlan
    bdonlan about 13 years
    indeed. See my updated answer for another possibility - if the map is allocated using a custom C++ allocator based on the process heap, there's no problem with initialization order (kernel32.dll will always be initialized first).
  • atzz
    atzz about 13 years
    Yes, but what about objects stored within map? They have destructors of their own, which will be called from DllMain during unload. This approach can be made to work, but personally I would reserve it for extreme cases when everything else is not an option.
  • xtofl
    xtofl about 13 years
    @atzz: I just come from fixing a bug coming from these consequences. Can only confirm that!
  • Ramon Zarazua B.
    Ramon Zarazua B. about 13 years
    /ENTRY is not specified. I was assuming that from Microsoft's documentation of DllMain. I'll try to call it explicitly.
  • Ramon Zarazua B.
    Ramon Zarazua B. about 13 years
    Well this seems to work. Microsoft's documentation is very confusing. Ended up following steps in support.microsoft.com/kb/94248 Section 2, and that worked. Thanks for the help.
  • andi
    andi about 11 years
    @atzz - with statically-linked runtime leaks will be erased on DLL unload. In that case lazy initialization works just well.