加载DLL不初始化静态C ++类

时间:2011-02-25 07:28:37

标签: c++ winapi dll static

我有一个在运行时加载的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版本是正确的,多次重建整个项目,但没有区别。我是新鲜的想法。

5 个答案:

答案 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那里打电话。