呼叫 c++来自 DllMain 或全局初始化程序的运行时
这个问题的灵感来自此讨论。
看来对来自 DllMain(或来自全局变量 ctor 的)的 C++ 运行时调用的担心有点过时了。我经常在 dll 中使用全局初始化程序,没有任何错误,现在我运行了一个特殊的测试程序(用 VC2010 Express w/o SP 编译),其中包含具有静态运行时链接的 exe 模块和具有动态链接的 dll。 Dll 是通过 LoadLibrary() 从 exe 手动加载的。
Dll 在全局初始化期间创建并填充映射对象(因此使用运行时库,至少是内存分配函数)。 DLL代码:
#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 代码(对于发布配置;将运行时库名称更改为 MSVCR100D.DLL 以进行调试):
#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;
}
输出符合预期:
before LoadLibrary: CRT NOT loaded by .exe module
after LoadLibrary: CRT Loaded
map[99]: some text
没有失败、空指针、页面错误等。
使用 DependencyWalker 进行分析还确认仅加载运行时库(MSVCR100.DLL)在 LoadLibrary 调用之后(并且不由 exe 预加载和初始化)。
看起来动态运行时库在全局初始化阶段之前的 dll_example.dll 加载过程中已正确加载和初始化。
有什么想法吗?
附言。我不鼓励将任何重量级初始化代码移至全局初始化阶段;但我认为简单的内存分配代码足够安全(?)。
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 (?).
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
这完全取决于您在 DLLMain 中所做的事情。由于文档拒绝说明什么可以做、什么不能做,并且 CRT 没有做出任何承诺,所以这总是感觉像是一个危险的领域。
就我个人而言,我会将所有全局初始化移至从 DLL 导出的单个例程中,并坚持所有客户端在调用任何其他函数之前调用此例程。
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.
在加载 DLL 时初始化 CRT 是一种非常常见的情况,例如任何 COM 服务器都会发生这种情况。因此,您可以依赖 CRT 显式支持该场景,只要您不需要它使用依赖于危险 api 调用的代码来初始化变量。初始化托管对象是一种著名的失败模式,在持有加载器锁的情况下,CLR 无法初始化。死锁很难诊断,但很容易检测到。一般来说,这是正确的,您可以毫不费力地发现自己遇到了问题。只能找到解决方法。
然而,让主程序和多个 DLL 之一使用不同的 CRT 实例会付出很多代价。这就是您的测试程序中发生的情况。您必须非常仔细地设计 DLL 的导出函数,以免返回任何指针或 C++ 对象。返回 const char* 就可以逃脱惩罚,调用者不应该获得该指针的所有权。想必。
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.