2

我需要对以下代码进行一些说明。

在下面的代码中,我从 DLL 中检索接口的私有实现。

在 DLL 被卸载后(CASE #2),从接口检索到的字符串和接口本身都变得无效,并且在访问它们时会发生(预期的)访问冲突。

但令我困惑的是,当我重新加载 DLL 时,我可以再次使用接口和字符串,而无需从 DLL 中重新检索它们。

这应该以这种方式工作吗?

内存怎么会变得无效,一旦再次加载DLL,它们又突然变得有效?

这只是运气,并且由于我的测试程序相对简单,DLL 第二次方便地加载到内存中完全相同的位置?

int main(int argc, int *argv[])
{
    Interface *itf;
    const char *name;

    {
        // loads the dll and gets functions
        PersistenInterface pi("PersistentInterface.dll");

        // returns a private implementation of the interface
        itf = pi.CreateInterface();
        name = itf->GetName();

        // CASE #1
        cout << name << endl; // suceeds

    } // dll is unloaded in ~PersistenInterface()

    // CASE #2
    //cout << name << endl; // crashes
    //cout << itf->GetName() << endl; // crashes

    {
        PersistenInterface pi("PersistentInterface.dll");

        // CASE #3
        cout << name << endl; // suceeds !?
        cout << itf->GetName() << endl; // suceeds !?
    }

    return 0;
}
4

2 回答 2

2

我希望您不会从 DLL 返回堆栈上的本地对象。它肯定会使您的系统崩溃,因为本地对象在函数从 DLL 返回后被破坏。它与DLL的概念无关。如果您在包含函数返回后尝试引用本地对象,您将看到同样的问题。

如果内存是动态分配在 DLL 内部的堆上的,那么在 DLL 未初始化后它仍然有效。您仍然可以在主应用程序中使用内存,因为 DLL 本身并不拥有任何东西。请记住,堆属于进程,而不是 DLL。

但是,一般规则是该内存的所有者(创建该内存的人,例如您的情况下的 DLL)负责释放相同的内存。在 DLL 中分配内存并在另一侧释放内存可能会给您带来不必要的麻烦,如果两侧使用不同的 CRT 实例,例如

于 2013-09-02T04:50:52.953 回答
1

答案有点复杂。实际上,如果您加载一个 DLL,它并没有真正加载到您的堆内存中。它被加载到全局内存中,并且该内存被映射到您的地址空间。通过这种机制,操作系统可以节省内存,因为它可以将相同的代码映射到不同的进程中。因此,如果您访问 DLL 空间内的地址,访问将被重定向到 DLL 真正存在的全局内存中。卸载后,该地址不再有内存。您的访问将不再被重定向,将引发异常。这与 malloc 和 free(或 new 和 delete)不同,其中只有内存被标记为未使用,但其所有数据仍然存在(除非激活了归零内存)。静态 DLL 数据(如常量字符串)和 DLL 代码的行为如上所述。再次加载 DLL 后,您拥有的地址,这显然直接指向静态数据或代码,再次变得有效。至少 Windows XP 总是在相同的地址空间加载一个 DLL。但不要依赖这个!出于安全原因,现代操作系统可以决定在每次使用 loadLibrary 或每次启动进程时将进程的 DLL 映射到不同的地址。据我所知,在调试器或 Windows XP 中,DLL 总是映射到同一个地址,这就是代码行为的原因。它不是错误或功能,它是 DLL 概念。据我所知,在调试器或 Windows XP 中,DLL 总是映射到相同的地址,这就是代码行为的原因。它不是错误或功能,它是 DLL 概念。据我所知,在调试器或 Windows XP 中,DLL 总是映射到相同的地址,这就是代码行为的原因。它不是错误或功能,它是 DLL 概念。

于 2013-09-02T05:46:16.997 回答