24

我是 Windows 编程的新手,我刚刚“失去”了两个小时来寻找每个人似乎都知道的错误:您不能在 DLL 的堆上创建对象并在另一个 DLL(或主程序)中销毁它.

我几乎可以肯定在 Linux/Unix 上不是这种情况(如果是,请说出来,但我很确定我做了数千次没有问题......)。

在这一点上,我有几个问题:

1) 静态链接的 DLL 是否使用与主程序不同的堆?

2)静态链接的DLL是否映射在主程序的同一个进程空间?(我很确定这里的答案是肯定的,否则将指针从主程序中的函数传递到 DLL 中的函数是没有意义的)。

我说的是普通/常规 DLL,而不是 COM/ATL 服务

编辑:“静态链接”是指我不使用 LoadLibrary 加载 DLL,但我与存根库链接

4

3 回答 3

23

DLL / exe 需要链接到 C 运行时库的实现。

对于 C Windows 运行时库,您可以选择指定是否要链接到以下内容:

  1. 单线程 C 运行时库(现已停止支持单线程库)
  2. 多线程 DLL / 多线程调试 DLL
  3. 静态运行时库。
  4. 更多(您可以查看链接)

它们中的每一个都将引用不同的堆,因此您不允许将从一个运行时库的堆中获取的地址传递给另一个。

现在,这取决于您正在谈论的 DLL 已链接到哪个 C 运行时库。假设您正在使用的 DLL 已链接到静态 C 运行时库,并且您的应用程序代码(包含主函数)已链接到多线程 C 运行时 DLL,那么如果您将指针传递给在DLL 到您的主程序并尝试在那里释放它,反之亦然,它可能导致未定义的行为。因此,基本的根本原因是 C 运行时库。请谨慎选择。

请在此处此处找到有关支持的 C 运行时库的更多信息

来自 MSDN 的引用:

警告 不要混合运行时库的静态和动态版本。在一个进程中拥有多个运行时库副本可能会导致问题,因为一个副本中的静态数据不与另一个副本共享。链接器会阻止您在一个 .exe 文件中同时链接静态和动态版本,但您仍然可以得到两个(或更多)运行时库副本。例如,与运行时库的静态(非 DLL)版本链接的动态链接库在与运行时库的动态 (DLL) 版本链接的 .exe 文件一起使用时可能会导致问题. (您还应该避免在一个进程中混合库的调试和非调试版本。)

于 2012-05-30T16:15:29.970 回答
9

让我们首先了解我们的应用程序/DLL 在 Windows 操作系统上的堆分配和堆栈。传统上,操作系统和运行时库都带有堆的实现。

  1. 在进程开始时,操作系统会创建一个称为进程堆的默认堆。如果没有使用其他堆,则进程堆用于分配块。
  2. 语言运行时还可以在进程中创建单独的堆。(例如,C 运行时创建自己的堆。)
  3. 除了这些专用堆之外,应用程序或许多加载的动态链接库 (DLL) 之一可以创建和使用单独的堆,称为专用堆
  4. 这些堆位于所有虚拟内存系统中操作系统的虚拟内存管理器之上。
  5. 让我们讨论更多关于 CRT 和相关堆的信息:
    1. C/C++ 运行时 (CRT) 分配器:提供 malloc() 和 free() 以及 new 和 delete 运算符。
    2. CRT 为其所有分配创建这样一个额外的堆(此 CRT 堆的句柄在 CRT 库内部存储在名为 _crtheap 的全局变量中)作为其初始化的一部分。
    3. CRT 创建自己的私有堆,它位于 Windows 堆的顶部。
    4. Windows 堆是围绕 Windows 运行时分配器 (NTDLL) 的薄层。
    5. Windows 运行时分配器与虚拟内存分配器交互,虚拟内存分配器保留和提交操作系统使用的页面。

您的 DLL 和 exe 链接到多线程静态 CRT 库。您创建的每个 DLL 和 exe 都有自己的堆,即 _crtheap。分配和解除分配必须从各自的堆中进行。从 DLL 动态分配的,不能从可执行文件中取消分配,反之亦然。

你可以做什么?使用 /MD 或 /MDd 在 DLL 和 exe 中编译我们的代码,以使用运行时库的特定于多线程和特定于 DLL 的版本。因此 DLL 和 exe 都链接到同一个 C 运行时库,因此是一个 _crtheap。分配总是与单个模块中的取消分配配对。

于 2014-04-04T09:41:33.273 回答
4

如果我有一个编译为 .exe 的应用程序并且我想使用一个库,我可以从 .lib 文件静态链接该库或从 .dll 文件动态链接该库。

每个链接的模块(即每个 .exe 或 .dll)都将链接到 C 或 C++ 运行时的实现。运行时本身是一个库,可以静态或动态链接到不同的线程配置。

通过说静态链接的 dll,您是在描述应用程序 .exe 动态链接到库 .dll 并且该库在内部静态链接到运行时的设置吗?我会假设这就是你的意思。

另外值得注意的是,每个模块(.exe 或 .dll)都有自己的静态范围,即 .exe 中的全局静态与 .dll 中具有相同名称的全局静态不同。

因此,在一般情况下,不能假设在不同模块中运行的代码行使用相同的运行时实现,而且它们不会使用任何静态状态的相同实例。

因此,在处理跨越模块边界的对象或指针时,需要遵守某些规则。对于任何给定地址,分配和解除分配必须发生在同一模块中。否则,堆将不匹配并且不会定义行为。

COM 使用引用计数解决了这个问题,当引用计数达到零时,对象会自行删除。这是用于解决匹配位置问题的常见模式。

可能存在其他问题,例如,windows 定义了某些操作,例如如何在每个线程的基础上处理分配失败,而不是在每个模块的基础上。这意味着在模块 B 设置的线程上在模块 A 中运行的代码也可能遇到意外行为。

于 2012-05-30T16:37:27.580 回答