我有一个在运行时加载的DLL。 DLL依赖于内部工作的静态变量(它是一个std :: map),这个变量是在DLL中定义的。
当我在加载后从DLL调用第一个函数时,我从DLL中获取了一个SegFault,从未初始化地图。从我从DLL加载中读取的所有内容,静态和全局数据初始化应该在调用DLLMain之前发生。
为了测试静态初始化,我添加了一个打印出消息的静态结构,甚至还提供了一个断点以便进行测量。
static struct a
{
a(void) { puts("Constructing\n"); }
}statica;
在DLLMain或调用函数之前没有消息或中断。
这是我的加载代码:
dll = LoadLibrary("NetSim");
//Error Handling
ChangeReliability = reinterpret_cast<NetSim::ChangeReliability>
(GetProcAddress(dll, "ChangeReliability"));
ChangeReliability(100);
我已经验证了dll版本是正确的,多次重建整个项目,但没有区别。我是新鲜的想法。
答案 0 :(得分:9)
链接DLL时,是否指定了/ ENTRY开关?如果是这样,它将覆盖链接器的默认值DllMainCRTStartup。因此,_CRT_INIT将不会被调用,反过来,将不会调用全局初始值设定项,这将导致未初始化的全局(静态)数据。
如果您为自己的入口点指定/ ENTRY,则需要在进程附加和进程分离期间调用_CRT_INIT。
如果你没有指定/ ENTRY,链接器应该使用CRT的入口点,在调用你的DllMain之前在进程附加/分离时调用_CRT_INIT。
答案 1 :(得分:8)
我想指出应该避免DLL中的复杂静态对象。
请记住,DLL中的静态初始化器是从DllMain调用的,因此对DllMain代码的所有限制都适用于静态对象的构造函数和析构函数。特别是,std :: map可以在构造期间分配动态内存,如果C ++运行时尚未初始化(如果您使用动态链接的运行时),则可能导致不可预测的结果。
关于编写DllMain的文章很好:Best Practices for Creating DLLs。
我建议将静态地图对象更改为静态指针(可以安全地进行零初始化),并添加单独的DLL导出函数进行初始化,或使用延迟初始化(即在访问之前检查指针和创建对象,如果它为null)。
答案 2 :(得分:1)
实际上,你有可能做出错误的假设:
加载,静态和全局数据初始化应该在调用DLLMain之前发生。
见有效C ++第4项:
初始化的顺序 中定义的非本地静态对象 不同的翻译单位是 未定义
原因是确保“正确”的初始化顺序是不可能的,因此C ++标准只是放弃它,并将其保留为未定义。
因此,如果您的DllMain与声明静态变量的代码位于不同的文件中,则行为未定义,并且在初始化实际完成之前您很有可能调用DllMain。
解决方案非常简单,并在Effective C ++的相同项目中概述(顺便说一句:我强烈建议您阅读该书!),并且需要在函数内声明静态变量,只需返回它。
答案 3 :(得分:0)
虽然我不确定为什么初始化失败,但一种解决方法是在DllMain中显式创建和初始化变量。根据{{3}}论文,您应该避免在DllMain中使用CRT分配函数,因此我使用Windows堆分配函数,使用自定义分配器(注意:未经测试的代码,但应该或多或少正确):
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;
}
答案 4 :(得分:0)
“经典”简单单例实现将起作用:
std::map<Key,Value>& GetMap() {
static std::map<Key,Value> the Map;
return theMap;
}
当然,你不应该从DllMain那里打电话。