3
votes

The question is inspired by this discussion.
It seems that fears concerning C++ runtime invokations from DllMain (or from global variables ctor's) are sligtly outdated. I'm using global initializers in dlls frequently without any faults, and now I've run a special test program (compiled with VC2010 Express w/o SP) containing exe module with static runtime linkage and dll with dynamic one. Dll is manualy loaded from exe by LoadLibrary().
Dll creates and fills a map object during global initialization (and therefore uses runtime library, at least memory allocatiion functions). Dll code:

#include <map>
using namespace std;

struct A{
  char* p;
  static const int num=1000;
  map<int,string> m;
  A(){ 
    for(int i=0; i<num; ++i){m[i]= *new string("some text");}
  }
};

A a;

extern "C"{
_declspec(dllexport) const char* getText(int i){ return a.m[i].data(); }
}


Exe code (for Release configuration; change runtime library name to MSVCR100D.DLL for Debug):

#include <windows.h>
typedef const char* (*pfunc_t)(int idx);

int main(int argc, TCHAR* argv[])
{
  HMODULE h_crt= GetModuleHandle("MSVCR100.DLL");
  // ensure that runtime library is NOT loaded yet:
  MessageBox(NULL,(NULL==h_crt)? "CRT NOT loaded by .exe module": "CRT Loaded by .exe module"  ,"before LoadLibrary",MB_OK);

  HMODULE hlib=LoadLibrary("dll_example.dll");
  h_crt= GetModuleHandle("MSVCR100.DLL");
  MessageBox(NULL,(NULL==h_crt)? "CRT NOT loaded": "CRT Loaded"  ,"after LoadLibrary",MB_OK);

  pfunc_t pfunc= (pfunc_t)(void*)GetProcAddress(hlib,"getText");
  MessageBox(NULL,pfunc(99),"map[99]",MB_OK);

    return 0;
}

The output is as expected:

before LoadLibrary: CRT NOT loaded by .exe module
after LoadLibrary: CRT Loaded
map[99]: some text

No failures, nullpointers, pagefaults, etc.

Profiling with DependencyWalker also confirms that runtime lib(MSVCR100.DLL) is loaded only after LoadLibrary call (and is not preloaded and initialized by exe).

It seems that dynamic runtime library is loaded and initialized correctly during the dll_example.dll loading process before global initialization phase.

any thoughts?

PS. I don't encourage to move any heavyweight initialization code to global init phase; but I suppose that simple memory allocation code is safe enough (?).

2
Leaking memory isn't a concern? - AJG85
@AJG85: why you suspict memory leak? Of course, there are no proper cleanup in this simple example, but such cleanup may be added. - user396672
@user I'm just being master of the obvious, it's my bad sense of humor. More to the point there is nothing saying you can't use global initialization but there are other ways. - AJG85
Quick tip: Use four spaces indentation, not <pre> to format code - otherwise headers such as <windows.h> are treated as HTML tags and lost. You can use the {} button in the formatting toolbar to quickly format code. - bdonlan
@bdonlan: thank you, i've returned inequality from "&lt;" back to "<" :) - user396672

2 Answers

2
votes

Getting the CRT initialized while loading a DLL is a very common scenario, it happens for any COM server for example. As such you can rely on the CRT explicitly supporting the scenario, as long as you don't require it to initialize your variables with code that in turn depend on the dangerous api calls. Getting managed objects initialized is a famous failure mode, the CLR cannot be initialized while the loader lock is held. The deadlock is very ugly to diagnose but very easy to detect. Which is in general true, you have no trouble finding out that you got a problem. Only finding a workaround for it.

There is however plenty of hell to pay by having the main program and one of more DLLs using different instances of the CRT. Which is what is happening in your test program. You have to very carefully craft the exported functions of the DLL to not return any pointers or C++ objects. You'll get away with returning const char*, the caller isn't supposed to take ownership of that pointer. Presumably.

4
votes

It all depends what you do inside DLLMain. Since the documentation refuses to state what can and cannot be done, and since the CRT doesn't make any promises, this always feels like a risky area.

Personally I would move all my global initialization into a single routine which is exported from the DLL and insist that all clients call this before calling any other function.