关于这个问题...
我感兴趣的是如何真正使用 DLL 中的内存,尤其是释放 DLL 后动态内存中剩余的内容。
创建了一个简单的 DLL,它具有make_array在动态内存中创建数组的功能。VC++,静态链接。
然后我这样嘲笑:
dll = LoadLibrary("Dll.dll");
memfunc m = (memfunc)GetProcAddress(dll,"make_array");
int * a = m(5);
for(int i = 0; i < 5; ++i) cout << a[i] << " "; cout << endl;
FreeLibrary(dll);
for(int i = 0; i < 5; ++i) cout << (a[i] *= a[i]) << " "; cout << endl;
dll = LoadLibrary("Dll.dll");
m = (memfunc)GetProcAddress(dll,"make_array");
for(int i = 0; i < 5; ++i) cout << (a[i] *= a[i]) << " "; cout << endl;
delete[] a;
a = m(6);
for(int i = 0; i < 5; ++i) cout << a[i] << " "; cout << endl;
好吧,即 我检查了是否可以删除程序中的内存,以及卸载 DLL 后内存是否仍然可用。事实证明,如果它们是由一个编译器创建的,那么一切都很好,内存管理器是否足够聪明?
我对 Open Watcom 也做了同样的事情——结果是一样的。
但是如果 DLL 来自 OW,而程序来自 VC++,那么除了delete[]. 当然,正如人们所期望的那样——因为不同的内存管理器。
不,我知道内存分配在哪里 - 在那里释放它:)
但是有两个问题我无法回答。
如果内存是由DLL中的内存管理器分配的,那么通过什么机制来避免两个管理器争吵呢?DLL 是否为调度程序分配了自己的内存区域?但是据我了解,在卸载 DLL 时,该区域仍未发布?
如何实际使用智能指针之类的东西?就像,在 DLL 调用make_shared中,其结果随后在程序中使用 - 如何保证 DLL 中的最后删除?出现的一切都太做作了。是否可以从 DLL 中调用删除器?
PS好吧,我没有认真地使用DLL,我没有深入挖掘......
您需要了解的第一件事是运行时。就工作室而言,有MD和MT。在第一种情况下,使用公共运行时。如果你在一个 dll 中创建一个类并在另一个中删除它,一切都会按预期工作。如果每个人都有自己的运行时,那就更难了。如果你在一个地方创建它并在另一个地方删除它,它可能会工作(如果内存管理器足够聪明),它可能会工作,但稍后会崩溃(如果 dll 和程序使用相同的运行时),或者它可能会立即崩溃(如果运行时不同)。
应用程序如何处理内存?通常,内存管理器(它是运行时的一部分)从操作系统请求一大块内存(4Mb 甚至 64Mb),并且在其中已经“切割”成使用 new / malloc 创建的小对象。卸载应用程序时,此内存将返回给操作系统。
如果运行时是动态的,那么内存管理器是共享的。而这个内存是由应用程序自己管理的。
如果使用静态运行时,那么一切都取决于程序员和运行时。dll 知道它正在被卸载并且“应该”释放内存。
智能指针在这种情况下是个好帮手。您只需要确保他们的“自定义删除器”拼写正确(我什至不知道它在俄语中的用法 - 自定义删除器?或自定义删除功能?)。在这种情况下,将在正确的内存管理器中调用正确的函数(无论如何,程序员都有机会破坏一切)。
这是一个棘手的情况。调用时
delete[],内存管理器需要知道要删除多少内存。这个数字需要存储在某个地方。而且我确信 studio 和 gcc 使用不同的、不兼容的模型。因此,如果这样的指针在具有不同管理器的 dll 之间传递,绝对可能发生任何事情。看。
问题是 C++ 没有二进制标准。这意味着同一个类,当由不同的编译器编译时(是的,即使由具有不同标志的相同编译器编译时),可以有不同的二进制布局。虚方法表可能有不同的表示,方法名称的不同修饰,或者仅仅是不同的物理大小。因此,在不同编译器或同一编译器的不同版本编译的组件之间传递这种比简单结构更复杂的东西,自然会出现问题并践踏别人的记忆。在困难的情况下,指定
#pragma pack.这解释了为什么从 DLL 导出的头文件通常包含
extern "C".如果保证DLL被相同版本的编译器用相同的开关编译,那么就可以传递对象。但这会立即引入版本控制问题:更新组件会返回问题。
这就解释了为什么如果不是项目中的所有 DLL 都是用相同版本的编译器编译的,就不可能通过智能指针传递对象。(还有为什么系统 DLL 不使用智能指针、异常和其他 C++ 功能。)
接下来,关于内存所有权的问题。这部分是 Microsoft 特有的。DLL 编译具有静态和动态链接运行时库的模式。
在静态运行时链接的情况下,您有自己的实现
new,并且delete在每个 DLL 中,它们彼此之间一无所知,并且具有非重叠堆。这意味着您必须确保该对象由分配它的同一运行时释放。在运行时动态链接的情况下,每个应用程序都有一个运行时 - 但前提是您的应用程序是使用相同版本的工作室编译的!在这种情况下,您可以在一个 DLL 中分配一个对象,然后在另一个 DLL 中释放。在最坏的情况下,当您拥有由不同工作室版本编译的不同 DLL 时,它们引用不同版本的运行时库,在这种情况下,对于编译其 DLL 的工作室的每个版本,您将在运行程序中拥有一个运行时. 后果很明显。
在我看来,这是很好的覆盖:
当然,如果它们不匹配,就会出现问题 - 对于这种情况,建议使用用于分配和释放已加载库中已分配内存区域的函数(dll->free() 或 dll->freeArray( ))
也就是说,最终,将指向内存的指针传递给已分配内存的 DLL 函数将比在 DLL 本身中分配内存更好。