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

发布于 2024-10-19 10:32:20 字数 680 浏览 3 评论 0原文

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

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

迷路的信 2024-10-26 10:32:20

当您链接 DLL 时,是否指定了 /ENTRY 开关?如果是这样,它将覆盖链接器的默认值 DllMainCRTStartup。因此,_CRT_INIT 将不会被调用,反过来,全局初始化程序也不会被调用,这将导致未初始化的全局(静态)数据。

如果您为自己的入口点指定 /ENTRY,则需要在进程附加和进程分离期间调用 _CRT_INIT。

如果您未指定 /ENTRY,则链接器应使用 CRT 的入口点,该入口点在调用 DllMain 之前在进程附加/分离时调用 _CRT_INIT。

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.

兮子 2024-10-26 10:32:20

我想指出的是,应该避免在 DLL 中使用复杂的静态对象。

请记住,DLL 中的静态初始化程序是从 DllMain 调用的,因此对 DllMain 代码的所有限制都适用于静态对象的构造函数和析构函数。特别是,std::map 可以在构造期间分配动态内存,如果 C++ 运行时尚未初始化(如果您使用动态链接运行时),这可能会导致不可预测的结果。

有一篇关于编写 DllMain 的好文章:创建 DLL 的最佳实践

我建议将静态映射对象更改为静态指针(可以安全地进行零初始化),并添加单独的 DLL 导出函数进行初始化,或使用延迟初始化(即在访问指针之前检查指针并创建对象)如果它为空)。

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

娇纵 2024-10-26 10:32:20

事实上,你很可能做出了错误的假设:

加载、静态和全局数据初始化应该在调用 DLLMain 之前进行。

请参阅《Effective C++》第 4 条:

初始化的顺序
定义在的非局部静态对象
不同的翻译单位是
未定义

原因是确保“正确”的初始化顺序是不可能的,因此 C++ 标准简单地放弃了它,并将其保留为未定义。

因此,如果您的 DllMain 与声明静态变量的代码位于不同的文件中,则行为是未定义的,并且您很有可能在初始化实际完成之前调用 DllMain。

解决方案非常简单,在《Effective C++》的同一项目中进行了概述(顺便说一句:我强烈建议您阅读那本书!),并且需要在函数内声明静态变量,该变量只需返回它。

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.

时间海 2024-10-26 10:32:20

虽然我不确定初始化失败的原因,但一种解决方法是在 DllMain 中显式创建并初始化变量。根据 Microsoft 最佳实践论文,您应该避免在 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;
}

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;
}
水晶透心 2024-10-26 10:32:20

“经典”的简单 singleton 实现将起作用:

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

当然,您不应该从 DllMain 调用它。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文