8

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.

Ramon Zarazua B.
  • 7,195
  • 4
  • 22
  • 26

5 Answers5

10

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.

Peter Huene
  • 5,758
  • 2
  • 34
  • 35
  • /ENTRY is not specified. I was assuming that from Microsoft's documentation of DllMain. I'll try to call it explicitly. – Ramon Zarazua B. Mar 08 '11 at 00:42
  • 1
    Well this seems to work. Microsoft's documentation is very confusing. Ended up following steps in http://support.microsoft.com/kb/94248 Section 2, and that worked. Thanks for the help. – Ramon Zarazua B. Mar 08 '11 at 00:52
10

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).

atzz
  • 17,507
  • 3
  • 35
  • 35
  • 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). – Peter Huene Feb 25 '11 at 08:18
  • Lazy init won't help - you'd end up calling CRT functions to free the memory in DllMain() when the DLL is unloaded – bdonlan Feb 25 '11 at 08:58
  • 1
    @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 Feb 25 '11 at 09:10
  • 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). – bdonlan Feb 25 '11 at 10:25
  • @atzz - with statically-linked runtime leaks will be erased on DLL unload. In that case lazy initialization works just well. – andi Apr 02 '13 at 07:17
2

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.

rob
  • 36,896
  • 2
  • 55
  • 65
  • 2
    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. – MSalters Feb 25 '11 at 08:58
  • 1
    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. – rob Feb 25 '11 at 10:01
  • 1
    @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. – user396672 Feb 25 '11 at 10:03
  • 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. – rob Feb 25 '11 at 10:06
  • @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. – user396672 Feb 25 '11 at 10:15
0

Although I'm not sure why initialization fails, one workaround would be to explicitly create and initialize the variables in your DllMain. As per Microsoft's best practices paper, you should avoid using CRT allocation function in DllMain, so I use windows heap allocation functions instead, with a custom allocator (note: untested code, but should be more or less right):

template<typename T> struct HeapAllocator
{
    typedef T value_type, *pointer, &reference;
    typedef const T *const_pointer, &const_reference;
    typedef size_t size_type;
    typedef ptrdiff_t difference_type;

    HeapAllocator() throw() { }
    HeapAllocator(const HeapAllocator&) throw() { }
    typedef<typename U>
    HeapAllocator(const HeapAllocator<U>&) throw() { }

    pointer address(reference x) const { return &x; }
    const_pointer address(const_reference x) const { return &x; }

    pointer allocate(size_type n, HeapAllocator<void>::const_pointer hint = 0)
    {
        LPVOID rv = HeapAlloc(GetProcessHeap(), 0, n * sizeof(value_type));
        if (!rv) throw std::bad_alloc();
        return (pointer)rv;
    }

    void deallocate(pointer p, size_type n)
    {
        HeapFree(GetProcessHeap(), 0, (LPVOID)p);
    }

    size_type max_size() const throw()
    {
        // Make a wild guess...
        return (2 * 1024 * 1024 * 1024) / sizeof(value_type);
    }

    void construct(pointer p, const_reference val)
    {
        new ((void*)p) T(val);
    }

    void destroy(pointer p)
    {
        p->~T();
    }
};

std::map<foo, HeapAllocator> *myMap;

BOOL WINAPI DllMain(HANDLE hInst, ULONG ul_reason, LPVOID lpReserved)
{
    switch(ul_reason) {
        case DLL_PROCESS_ATTACH:
            myMap = (std::map<foo, HeapAllocator> *)HeapAlloc(GetProcessHeap(), 0, sizeof(*myMap));
            if (!myMap) return FALSE; // failed DLL init

            new ((void*)myMap) std::map<foo, HeapAllocator>;
            break;
        case DLL_PROCESS_DETACH:
            myMap->~map();
            HeapFree(GetProcessHeap(), 0, (LPVOID)myMap);
            break;
    }
    return TRUE;
}
bdonlan
  • 224,562
  • 31
  • 268
  • 324
  • 3
    Allocating dynamic memory in DllMain is unsafe and should be avoided. E.g. see this MS article: http://go.microsoft.com/FWLink/?LinkId=84138 – atzz Feb 25 '11 at 08:15
  • 2
    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. – atzz Feb 25 '11 at 10:48
-1

The "classic" simple singelton implemention will work:

std::map<Key,Value>& GetMap() {
  static std::map<Key,Value> the Map;
  return theMap;
}

Of course, you shouldn't call this from DllMain.

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • 1
    It still has the problem with deallocation, which will happen in DllMain on DLL unload (and may have funny consequences: http://blogs.msdn.com/b/oldnewthing/archive/2010/01/22/9951750.aspx). – atzz Feb 25 '11 at 09:14
  • 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. – MSalters Feb 25 '11 at 09:23
  • 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. – atzz Feb 25 '11 at 09:44
  • @atzz: I just come from fixing a bug coming from these consequences. Can only confirm that! – xtofl Feb 25 '11 at 12:44