用户堆分配失败 DLL/EXE
Heap allocation failing in user DLL/EXE
正确链接的 DLL 和 EXE 应该有一个自由存储区,它们都可以从中分配基于堆的对象。这是 Chis Becke 在 Who allocates heap to a DLL? 中的回答:
… it is the C++ runtime that is responsible for creating its freestore and deciding
how to allocate it.
Specifically, if you use the Dll runtime option, then a single dll - msvcrtxx.dll - manages a single
freestore that is shared between all dll's, and the exe, that are linked against that dll
既然是这样,那我应该可以new
对象在DLL/EXEs定义在其他DLL/EXEs。根据 Chris 的说法,msvcrtxx.dll
和 compile-time/runtime 链接器负责在何处获取所有 DLL/EXEs 的联合自由存储。
这对我不起作用。
为了对此进行测试,我生成了两个 MFC 对话框程序:NewFailMfc1 and NewFailMfc2。 运行 NewFailMfc2
在执行 new
.
时访问 NewFailMfc1
的 Www
函数失败
// Code in NewFailMfc1.
void Www()
{
char* ch { nullptr };
ch = new char[ 100 ]; // error: attempts to allocate memory somewhere else than in the prescribed joint DLL/EXE freestore
ch[ 0 ] = '[=11=]';
}
// Calling code in NewFailMfc2.
Www();
比我更了解 DLL/EXE freestore 工作原理的人知道问题出在哪里吗?
(之前在“Global function ::operator new
fails when compiled in MyApp1
and MyApp2
中试过问过一次。问的过程中发现问题出在比 <random>
标准库更普遍。)
编辑 1:
在 MSDN 中,为我找到了一个不错的虚拟代理 Potential Errors Passing CRT Objects Across DLL Boundaries。不幸的是,它推荐的唯一解决方案是使用 /MD
编译器选项编译所有程序,而不是 /MT
,它使用 CRT 的多个副本,这会自动导致 越界 和内存访问冲突。
这对像我这样的应用开发者来说可不是什么好消息。我需要的是最佳实践,这样我就可以应用它并按时完成交付,而不必处理神秘的低级内存问题。我怎么知道 std:random_device
类型中存在对全局 ::operator new
的隐藏调用?我不会,直到它访问被侵犯。直到现在,在所有这些研究之后,我才意识到通过调用全局 new
,它 跨越了边界 ,这给了我的 DLL/EXE 访问冲突。很晦涩。
编辑2:
我已经在 Visual Studio 中提交了关于 std::random_device 实施的错误报告。参见 "std::random_device instantiation causes an access-violation in certain cases"。
忘记这个 all.if 这会以某种方式起作用,这只是你的运气。
更好的方法是 运行 COM
记忆 sharing.Just 看这里的 IMalloc
示例。
ftp://210.212.172.242/Digital_Library/CSE/Computer,%20Technology%20and%20Engineering%20eBooks/Books7/petzold_rus.part1%20(2)/DISK/CODE/CHAP20/
没那么容易是的。
As was articulated earlier in this specification, when ownership of
allocated memory is passed through an interface, COM requires
that the memory be allocated with a specific....
http://www.opengroup.org/comsource/techref2/CHP05GDC.HTM
想在发布的样本(win95 次)中提到你正在收集 IMalloc
ole
来自地面阶段的接口。它可以被视为 MS Windows 源代码的一部分。不知道今天的内置 n IMalloc
,不确定是否相同。
无论这意味着什么,都可以跨越边界:) 首先,您需要了解发生了什么。
当你分配内存时,实际上 CRT 可以分配比你要求的多一点。例如。流行的做法(至少在过去)是多分配 4 个字节(用你的系统位数代替),在开头写下分配内存的大小,然后 return ptr + 4
给你。所以当你释放内存时,系统知道它应该释放多少。
图片有点简化。不同的编译器、相同编译器的不同版本和相同编译器相同版本的不同配置可以不同地执行此操作。例如。调试配置可以使用一些填充来检测缓冲区溢出和其他技巧。因此,当您在一个二进制文件中分配内存并在另一个二进制文件中释放内存时,如果使用不同的编译器,这可能会导致内存损坏(在最好的情况下会立即崩溃)。
这个和许多其他原因导致了一个常见的建议:释放分配内存的二进制文件中的内存。这通常通过提供 API class 的 Release
成员函数并将析构函数设为私有来实现,或者通过 unique_ptr
(或 shared_ptr
)使用自定义删除器来实现,或其他技术。
现在关于 /MD
建议。 /MD
表示动态 CRT(= 在 dll 中),并且由于不可能在同一进程中加载相同的 dll 两次,这意味着相同的 CRT 将用于分配和释放。这仍然不是针对不同版本或不同编译器的解决方案。例如。许多应用程序使用插件系统,在这种情况下,要求所有插件都由特定的 compiler/version/config
编译并不是一个好主意
多线程调试 DLL 堆将是每个进程,因此 NewFailMfc1 和 NewFailMfc2 将拥有自己的私有堆,即使这两个应用程序都与多线程调试 DLL 链接。
使用Multi-threaded Debug DLL heap只是解决了同一个进程地址space内跨多个heap的跨界问题,并不是一种可以跨进程共享heap的机制。
显式实例化 强制编译器为模板化 class 或函数的特定参数列表生成代码。如果没有该代码,我导入的 DLL/EXE
二进制文件在 ch = new char[ 100 ]
和 std::random_device rd;
等运行时实例化时会失败,这隐含地执行全局 ::operator new
。对于为什么会发生这种情况,我没有找到有用的解释。遗憾的是,IPC 讨论并未明确区分涉及多个 运行 进程的客户端-服务器运行时,以及导入在别处编译和导出的二进制代码 (DLL/EXE
) 的运行时代码。
解决方案是向失败的 class 添加一个模板参数,并向显式实例化 class 的每个模块添加一个 .cpp 文件,例如 MyClsModule1.cpp
,MyClsModule2.cpp
,等等。在这些文件中,我为该模块显式实例化了 class。我还为每个包含 extern
的模块添加了 .h
文件,例如 MyClsModule1.h
、MyClsModule2.h
,因此在特定模块中不会发生重复代码生成。使用这种方法,每个模块都会在编译时生成特定于模块的 class 代码,这会强制模块的线程允许访问该模块进程堆的堆实例化。
这个现代 C++ 解决方案对我来说很优雅,因为它使我不必在我的应用程序代码中重新引入复杂的 IPC 解决方案,例如 COM
。
从应用程序开发人员的角度来看,我认为我的原始代码应该有效,或者至少产生了错误,这些错误可以提供更多有关问题所在的信息,因此我将留下 [=33= 中提到的错误报告]EDIT2 有效。
正确链接的 DLL 和 EXE 应该有一个自由存储区,它们都可以从中分配基于堆的对象。这是 Chis Becke 在 Who allocates heap to a DLL? 中的回答:
… it is the C++ runtime that is responsible for creating its freestore and deciding how to allocate it. Specifically, if you use the Dll runtime option, then a single dll - msvcrtxx.dll - manages a single freestore that is shared between all dll's, and the exe, that are linked against that dll
既然是这样,那我应该可以new
对象在DLL/EXEs定义在其他DLL/EXEs。根据 Chris 的说法,msvcrtxx.dll
和 compile-time/runtime 链接器负责在何处获取所有 DLL/EXEs 的联合自由存储。
这对我不起作用。
为了对此进行测试,我生成了两个 MFC 对话框程序:NewFailMfc1 and NewFailMfc2。 运行 NewFailMfc2
在执行 new
.
NewFailMfc1
的 Www
函数失败
// Code in NewFailMfc1.
void Www()
{
char* ch { nullptr };
ch = new char[ 100 ]; // error: attempts to allocate memory somewhere else than in the prescribed joint DLL/EXE freestore
ch[ 0 ] = '[=11=]';
}
// Calling code in NewFailMfc2.
Www();
比我更了解 DLL/EXE freestore 工作原理的人知道问题出在哪里吗?
(之前在“Global function ::operator new
fails when compiled in MyApp1
and MyApp2
中试过问过一次。问的过程中发现问题出在比 <random>
标准库更普遍。)
编辑 1:
在 MSDN 中,为我找到了一个不错的虚拟代理 Potential Errors Passing CRT Objects Across DLL Boundaries。不幸的是,它推荐的唯一解决方案是使用 /MD
编译器选项编译所有程序,而不是 /MT
,它使用 CRT 的多个副本,这会自动导致 越界 和内存访问冲突。
这对像我这样的应用开发者来说可不是什么好消息。我需要的是最佳实践,这样我就可以应用它并按时完成交付,而不必处理神秘的低级内存问题。我怎么知道 std:random_device
类型中存在对全局 ::operator new
的隐藏调用?我不会,直到它访问被侵犯。直到现在,在所有这些研究之后,我才意识到通过调用全局 new
,它 跨越了边界 ,这给了我的 DLL/EXE 访问冲突。很晦涩。
编辑2:
我已经在 Visual Studio 中提交了关于 std::random_device 实施的错误报告。参见 "std::random_device instantiation causes an access-violation in certain cases"。
忘记这个 all.if 这会以某种方式起作用,这只是你的运气。
更好的方法是 运行 COM
记忆 sharing.Just 看这里的 IMalloc
示例。
ftp://210.212.172.242/Digital_Library/CSE/Computer,%20Technology%20and%20Engineering%20eBooks/Books7/petzold_rus.part1%20(2)/DISK/CODE/CHAP20/
没那么容易是的。
As was articulated earlier in this specification, when ownership of allocated memory is passed through an interface, COM requires that the memory be allocated with a specific.... http://www.opengroup.org/comsource/techref2/CHP05GDC.HTM
想在发布的样本(win95 次)中提到你正在收集 IMalloc
ole
来自地面阶段的接口。它可以被视为 MS Windows 源代码的一部分。不知道今天的内置 n IMalloc
,不确定是否相同。
无论这意味着什么,都可以跨越边界:) 首先,您需要了解发生了什么。
当你分配内存时,实际上 CRT 可以分配比你要求的多一点。例如。流行的做法(至少在过去)是多分配 4 个字节(用你的系统位数代替),在开头写下分配内存的大小,然后 return ptr + 4
给你。所以当你释放内存时,系统知道它应该释放多少。
图片有点简化。不同的编译器、相同编译器的不同版本和相同编译器相同版本的不同配置可以不同地执行此操作。例如。调试配置可以使用一些填充来检测缓冲区溢出和其他技巧。因此,当您在一个二进制文件中分配内存并在另一个二进制文件中释放内存时,如果使用不同的编译器,这可能会导致内存损坏(在最好的情况下会立即崩溃)。
这个和许多其他原因导致了一个常见的建议:释放分配内存的二进制文件中的内存。这通常通过提供 API class 的 Release
成员函数并将析构函数设为私有来实现,或者通过 unique_ptr
(或 shared_ptr
)使用自定义删除器来实现,或其他技术。
现在关于 /MD
建议。 /MD
表示动态 CRT(= 在 dll 中),并且由于不可能在同一进程中加载相同的 dll 两次,这意味着相同的 CRT 将用于分配和释放。这仍然不是针对不同版本或不同编译器的解决方案。例如。许多应用程序使用插件系统,在这种情况下,要求所有插件都由特定的 compiler/version/config
多线程调试 DLL 堆将是每个进程,因此 NewFailMfc1 和 NewFailMfc2 将拥有自己的私有堆,即使这两个应用程序都与多线程调试 DLL 链接。 使用Multi-threaded Debug DLL heap只是解决了同一个进程地址space内跨多个heap的跨界问题,并不是一种可以跨进程共享heap的机制。
显式实例化 强制编译器为模板化 class 或函数的特定参数列表生成代码。如果没有该代码,我导入的 DLL/EXE
二进制文件在 ch = new char[ 100 ]
和 std::random_device rd;
等运行时实例化时会失败,这隐含地执行全局 ::operator new
。对于为什么会发生这种情况,我没有找到有用的解释。遗憾的是,IPC 讨论并未明确区分涉及多个 运行 进程的客户端-服务器运行时,以及导入在别处编译和导出的二进制代码 (DLL/EXE
) 的运行时代码。
解决方案是向失败的 class 添加一个模板参数,并向显式实例化 class 的每个模块添加一个 .cpp 文件,例如 MyClsModule1.cpp
,MyClsModule2.cpp
,等等。在这些文件中,我为该模块显式实例化了 class。我还为每个包含 extern
的模块添加了 .h
文件,例如 MyClsModule1.h
、MyClsModule2.h
,因此在特定模块中不会发生重复代码生成。使用这种方法,每个模块都会在编译时生成特定于模块的 class 代码,这会强制模块的线程允许访问该模块进程堆的堆实例化。
这个现代 C++ 解决方案对我来说很优雅,因为它使我不必在我的应用程序代码中重新引入复杂的 IPC 解决方案,例如 COM
。
从应用程序开发人员的角度来看,我认为我的原始代码应该有效,或者至少产生了错误,这些错误可以提供更多有关问题所在的信息,因此我将留下 [=33= 中提到的错误报告]EDIT2 有效。