C++ 堆分配在用户DLL/EXE中失败

C++ 堆分配在用户DLL/EXE中失败,c++,dll,exe,new-operator,heap-memory,C++,Dll,Exe,New Operator,Heap Memory,正确链接的DLL和EXE应该有一个freestore,它们都可以从中分配基于堆的对象。以下是Chis Becke的答案: ……是C++运行时负责创建它的空闲和决定。 如何分配。 具体来说,如果使用Dll运行时选项,则单个Dll(msvcrtxx.Dll)管理单个Dll 在链接到该dll的所有dll和exe之间共享的freestore 既然这是真的,那么我应该能够在其他DLL/EXE中定义的DLL/EXE中创建新对象。根据Chris的说法,msvcrtxx.dll和编译时/运行时链接器负责获取所有

正确链接的DLL和EXE应该有一个freestore,它们都可以从中分配基于堆的对象。以下是Chis Becke的答案:

<>……是C++运行时负责创建它的空闲和决定。 如何分配。 具体来说,如果使用Dll运行时选项,则单个Dll(msvcrtxx.Dll)管理单个Dll 在链接到该dll的所有dll和exe之间共享的freestore


既然这是真的,那么我应该能够在其他DLL/EXE中定义的DLL/EXE中创建新对象。根据Chris的说法,msvcrtxx.dll和编译时/运行时链接器负责获取所有dll/EXE的联合自由存储

这对我不起作用

为了测试这一点,我生成了两个MFC对话框程序:和。执行新操作时,运行访问NewFailMfc1的Www函数的NewFailMfc2失败

比我更了解DLL/EXE freestore的工作原理的人知道问题出在哪里吗

我曾经尝试在全局函数中问过一次这个问题::operator new在MyApp1和MyApp2中编译时失败。在询问过程中,我发现问题的发生比std库中更普遍

编辑1:

在MSDN中为我找到了一个不错的虚拟代理。不幸的是,它推荐的唯一解决方案是使用/MD编译器选项编译所有程序,而不是/MT,后者使用CRT的多个副本,这会自动导致越界和内存访问冲突


对于像我这样的应用开发者来说,这不是好消息。我需要的是一个最佳实践,这样我就可以应用它并在交付截止日期之前完成,而不必处理神秘的低级内存问题。我怎么知道std:random\u设备类型中存在对global::operator new的隐藏调用?我不会,直到它被侵犯。直到现在,在所有这些研究之后,我才意识到,通过调用globalnew,它跨越了一个边界,这给了我的DLL/EXE一个访问冲突。非常隐晦

编辑2:


我在Visual Studio中提交了一份关于std::random_设备实现的错误报告。请参阅std::random_设备实例化在某些情况下会导致访问冲突。

忘记这一切。如果它以某种方式起作用,这只是你的运气

更好的方法是运行COM进行内存共享。

没那么容易

如本规范前面所述,当 分配的内存通过COM要求的接口传递 将内存分配给特定的。。。。

想在发布的samplewin95中提到你收集iAlloc ole的次数吗
地面阶段的接口。它可能被认为是微软Windows源代码的一部分。Idk了解今天的内置iAlloc,但不确定它是否相同。

无论这意味着什么,都有可能跨越边界:首先,你需要了解发生了什么

当您分配内存时,实际上CRT可以比您要求的分配多一点。例如,至少在过去,流行的做法是分配4个字节,以系统比特数替换,在开始时写入分配内存的大小,并将ptr+4返回给您。因此,当您释放内存时,系统知道应该释放多少内存

这是一张有点简化的图片。不同的编译器、同一编译器的不同版本以及同一编译器同一版本的不同配置可以以不同方式执行此操作。例如,调试配置可以使用一些填充来检测缓冲区溢出和其他技巧。因此,当您在一个二进制文件中分配内存并在另一个二进制文件中取消分配内存时,如果使用不同的编译器,在最好的情况下,这可能会导致损坏的内存立即崩溃

这一点和许多其他原因导致了一个共同的建议:在分配内存的二进制文件中释放内存。这通常是通过提供API类的发布成员函数并将析构函数设置为私有,或者通过使用自定义删除器的唯一\u ptr或共享\u ptr,或者其他技术来实现的


现在是关于/MD的建议/MD意味着动态CRT=在dll中,由于不可能在同一进程中加载同一dll两次,这意味着相同的CRT将用于分配和解除分配。对于不同的版本或不同的编译器,这仍然不是一个解决方案。例如,许多应用程序使用插件系统,在这种情况下,要求所有插件都由特定的编译器/version/config编译不是一个很好的主意,多线程调试DLL堆将是每个进程,因此NewFailMfc1和NewFailMfc2将拥有自己的私有堆,即使两个应用程序都与多线程调试DLL链接。 使用多线程调试DLL堆只能解决在同一进程地址空间内跨越多个堆边界的问题,而不是一种可用于跨进程边界共享堆的机制 美国

显式实例化强制编译器为模板化类或函数的特定参数列表生成代码。没有这些代码,我导入的DLL/EXE二进制文件在运行时实例化时失败,比如ch=new char[100]和std::random_device rd;它隐式地执行全局::运算符new。我没有找到任何有用的解释来解释为什么会发生这种情况。遗憾的是,IPC讨论没有明确区分涉及多个运行进程的客户机-服务器运行时和导入在别处编译和导出的二进制代码DLL/EXE的运行时代码

解决方案是向出现故障的类添加一个模板参数,并向显式实例化该类的每个模块添加一个.cpp文件,如MyClsModule1.cpp、MyClsModule2.cpp等。在这些文件中,我显式实例化了该模块的类。我还为每个包含外部代码的模块添加了.h文件,如MyClsModule1.h、MyClsModule2.h,以便在特定模块中不会发生重复的代码生成。使用这种方法,每个模块在编译时生成特定于模块的类代码,这迫使模块的线程允许访问该模块进程堆的堆实例化

这个现代C++解决方案对我来说很优雅,因为它使我不必再将复杂的IPC解决方案像COM引入到我的应用程序代码中。


从应用程序开发人员的角度来看,我认为我的原始代码应该能够工作,或者至少产生了错误,这些错误对于问题是什么提供了更多的信息,因此我保留了EDIT2中提到的错误报告。

据Chris说,msvcrtxx.dll和编译时/运行时链接器负责获取所有dll/EXE的联合自由存储的位置。-这不是他说的,也不是真的。这是过时的信息,自2012年以来就不是真的了。以前,是的。您必须确保使用完全相同的设置以完全相同的VS版本构建所有模块。可以像使用dll的发布版本调试exe一样简单。请确保所有项目都在同一个解决方案中,以便它们都可以使用相同的设置。我相信Hans是正确的-一般来说,我遵循一条经验法则,即每个模块分配和处理自己的内存,因为以这种方式跨进程共享内存比它应该做的工作多得多。至少在不使用内置条款的情况下这样做-不确定Windows是否像Linux一样容易公开。@Hans Passant我知道所有参与的DLL和EXE必须是同一版本。我在MSDN中找到一篇文章,解释了使用堆对象跨越DLL/EXE边界的危险。我需要的是一个最佳实践,这样我就可以应用它并在交付截止日期前完成交付,而不必处理神秘的低级内存问题。您必须是本机编程新手。请澄清您的答案。无法访问petzold链接。您能在您的回答中发布有问题的段落吗?/MDd是我的NewFailMfc1和“NewFailMfc2”生成的测试程序中的默认运行时库选项值,称为多线程调试DLL。这意味着/MD编译器选项不能作为解决边界交叉问题的候选选项。因为实际上只有一个进程在调用另一个EXE中创建的二进制文件,所以在IPC意义上确实没有边界交叉。我现在明白了。那么到底是什么边界导致了访问冲突呢?请参阅edit2iscom跨进程边界共享堆的唯一方法?或者还有其他低级的方法吗?对标准的IPC(如管道)或COM不感兴趣,因为这些工具会将太多的复杂性拖到project.COM中,在这里没有帮助。您需要实现进程之间共享的共享内存模型,并使用C++放置新操作符来构建共享内存中的“销毁对象”。您可以在DLL中实现共享内存模型,以便加载DLL的进程可以访问共享内存。请检查此链接以了解是否在DLL中使用共享内存。您也可以考虑任何其他方法,但需要某种共享内存模型来实现您的需求。下面的链接描述了一种有效的算法,用于跨进程边界从共享内存段分配和释放内存
// 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 ] = '\0';
}

// Calling code in NewFailMfc2.
Www();