AccessViolationException从C++/CLI DLL读取C++应用程序中分配的内存

AccessViolationException reading memory allocated in C++ application from C++/CLI DLL

本文关键字:C++ 分配 应用程序 内存 DLL CLI AccessViolationException 读取      更新时间:2023-10-16

我有一个C++客户端到一个C++/CLI DLL,它初始化一系列C#DLL。

这以前是有效的。失败的代码没有更改。在引发异常之前,不会调用已更改的代码。我的编译环境已经改变,但在与旧环境类似的机器上重新编译仍然失败。(编辑:正如我们在答案中看到的,这并不完全正确,我只是在旧环境中重新编译库,而不是将库和客户端一起重新编译。客户端项目已经升级,无法轻松返回。)

除了我之外,还有人重新编译了库,我们开始遇到内存管理问题。The pointer passed in as a String must not be in the bottom 64K of the process's address space.我重新编译了它,一切都很好,没有代码更改。(警报#1)最近它被重新编译,字符串的内存管理问题再次出现,这次它们不会消失。新错误为Unhandled Exception: System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

我很确定问题不在我看到异常的地方,代码在成功和失败的构建之间没有变化,但我们应该检查它是否完整。忽略事物的名称,我无法控制它对这些字符串的设计。很抱歉造成混淆,但请注意,_bridgebridge是不同的东西。因为这个问题已经太长了,所以缺少了很多行代码。

在库中定义:

struct Config
{
std::string aye;
std::string bee;
std::string sea;
};
extern "C" __declspec(dllexport) BridgeBase_I* __stdcall Bridge_GetConfiguredDefaultsImplementationPointer(
const std::vector<Config> & newConfigs, /**< new configurations to apply **/
std::string configFolderPath, /**< folder to write config files in **/
std::string defaultConfigFolderPath, /**< folder to find default config files in **/
std::string & status /**< output status of config parse **/
);

在客户端功能:

GatewayWrapper::Config bridge;
std::string configPath("./config");
std::string defaultPath("./config/default");
GatewayWrapper::Config gwtransport;
bridge.aye = "bridged.dll";
bridge.bee = "1.0";
bridge.sea = "";
configs.push_back(bridge);
_bridge = GatewayWrapper::Bridge_GetConfiguredDefaultsImplementationPointer(configs, configPath, defaultPath, status);

请注意,对正在崩溃的库的调用与向量声明、结构声明、字符串赋值和向量推回在同一范围内这段代码中没有线程调用,但有其他线程在运行,做其他事情。这里没有指针数学,区域中没有堆分配,除了在标准库中。

我可以在调试器中运行直到Bridge_GetConfiguredDefaultsImplementationPointer调用的代码,并且configs矢量的内容在调试器中看起来是正确的。

回到库中,在调试器不亮的第一个子函数中,我将失败的语句分解为几个控制台打印。

System::String^ temp
List<CConfig^>^ configs = gcnew List<CConfig ^>((INT32)newConfigs.size());
for( int i = 0; i< newConfigs.size(); i++)
{
std::cout << newConfigs[i].aye<< std::flush; // prints
std::cout << newConfigs[i].aye.c_str() << std::flush; // prints
temp = gcnew System::String(newConfigs[i].aye.c_str());
System::Console::WriteLine(temp); // prints
std::cout << "Testing string creation" << std::endl; // prints
std::cout << newConfigs[i].bee << std::flush; // crashes here
}

如果我将newConfigs[i].bee移到temp的赋值之上,或者注释掉列表声明/赋值,那么我在访问bee时也会遇到同样的异常。

只是为了参考向量中结构中的std::字符串应该已经到达其目的地ok

  • std::vector是否使用push_back复制对象
  • 结构中的std::string-复制/赋值问题
  • http://www.cplusplus.com/reference/vector/vector/operator=/
  • 在C中将一个结构分配给另一个结构

为什么我的try/catch没有捕获此异常

https://stackoverflow.com/a/918891/2091951

Generic AccessViolationException相关问题

  • 如何处理AccessViolationException
  • 程序随机获取System.AccessViolationException
  • https://connect.microsoft.com/VisualStudio/feedback/details/819552/visual-studio-debugger-throws-accessviolationexception
  • 查找System.AccessViolationException的原因
  • https://msdn.microsoft.com/en-us/library/ms164911.aspx
  • 正在捕获访问违规异常
  • 从C使用C++DLL时发生AccessViolationException#

上述问题中的建议

  • 更改为.net 3.5,更改目标平台-这些解决方案可能会在大型多项目解决方案中出现严重问题
  • HandleProcessCorruptedStateExceptions-在C++中不起作用,这个修饰是针对C#的,无论如何,捕捉这个错误都可能是一个非常糟糕的主意
  • 更改legacyCorruptedStateExceptionsPolicy-这是关于捕获错误,而不是阻止它
  • 安装.NET 4.5.2-不能,已经有4.6.1了。安装4.6.2没有帮助。在另一台没有安装4.5或4.6的机器上重新编译也无济于事。(尽管在安装Visual Studio 2013之前曾在我的计算机上进行编译和运行,但这强烈表明.NET库存在问题?)
  • VSDebug_DisableManagedReturnValue-我只看到这与调试器中的特定崩溃有关,而微软的帮助表明,其他AccessViolationException问题可能无关。(http://connect.microsoft.com/VisualStudio/feedbackdetail/view/819552/visual-studio-debugger-throws-accessviolationexception)
  • 更改Comodo防火墙设置-我不使用此软件
  • 将所有代码更改为托管内存-不是选项。通过C++/CLI从C++调用C#的总体设计是耐更改的。我被特别要求以这种方式设计它,以利用现有C++代码中的现有C#代码
  • 确保已分配内存-应该在C++客户端的堆栈上分配内存。我试图让向量不是一个参考参数,强迫向量复制到显式库控制的内存空间,但没有帮助
  • "非托管代码中的访问冲突(一直到托管代码)总是包含在AccessViolationException中。"-事实,而不是解决方案

但的问题是不匹配,而不是特定版本

是的,这是VS中的黑体定律。不幸的是,您刚刚错过了VS2012中内置的对策,将此错误转化为可诊断的链接器错误。以前(以及在VS2010中),CRT会使用HeapAlloc()分配自己的堆。现在(在VS2013中),它使用默认的进程堆,即GetProcessHeap()返回的进程堆。

当你在Vista或更高版本上运行应用程序时,这本身就足以触发AVE,从一个堆分配内存并从另一个堆释放内存会在运行时触发AVE。当你启用调试堆进行调试时,调试器会中断。

这还没有结束,另一个重要的问题是,不同版本之间的std::string对象布局不同。你可以通过一个小测试程序发现一些东西:

#include <string>
#include <iostream>
int main()
{
std::cout << sizeof(std::string) << std::endl;
return 0;
}
  • VS2010调试:32
  • VS2010版本:28
  • VS2013调试:28
  • VS2013版本:24

我模糊地记得Stephen Lavavej提到了std::string对象大小缩减,这是一个非常重要的功能,但我找不回来了。Debug构建中额外的4个字节是由迭代器调试功能引起的,可以在预处理器定义中使用_HAS_ITERATOR_DEBUGGING=0禁用它。这不是一个你很快就想放弃的功能,但它使EXE及其DLL的调试和发布版本的混合变得非常致命。

不用说,当Config对象在用一个版本的标准C++库构建的DLL中创建并在另一个版本中使用时,不同的对象大小会严重影响字节数。许多错误,最基本的错误是代码会简单地从错误的偏移量读取Config::bee成员。AVE(几乎)是有保证的。当代码分配Config对象的小风格,但编写std::string的大风格时,会随机损坏堆或堆栈帧,这会带来更多的痛苦。

不要混在一起。

我相信2013年对STL容器的内部数据格式进行了大量更改,作为减少内存使用和提高性能的努力的一部分。我知道vector变得更小了,而string基本上是一个荣耀的vector<char>

微软承认不兼容:

"要启用新的优化和调试检查,Visual StudioC++标准库的实现有意破坏二进制从一个版本到下一个版本的兼容性。因此,当C++使用标准库,对象文件和静态库使用不同版本编译的文件不能混合在一个二进制文件中(EXE或DLL),并且C++标准库对象不能在使用不同版本编译的二进制文件。">

如果要在可执行文件和/或DLL之间传递std::*对象,则必须确保它们使用相同版本的编译器。最好在启动时让您的客户端及其DLL以某种方式进行协商,比较任何可用版本(例如编译器版本+标志、增强版本、directx版本等),以便快速发现此类错误。可以将其视为跨模块断言。

如果你想确认这就是问题所在,你可以选择一些来回传递的数据结构,并在客户端与DLL中检查它们的大小。我怀疑您上面的Config类在其中一种失败情况下会以不同的方式注册。

我还想提一下,在DLL调用中使用智能容器可能是个坏主意。除非你能保证应用程序和DLL不会试图释放或重新分配对方容器的内部缓冲区,否则你很容易遇到堆损坏问题,因为应用程序和DLL都有自己的内部C++堆。我认为这种行为充其量被认为是不明确的。在极少数情况下,即使传递const&参数也可能导致重新分配,因为const不会阻止编译器处理mutable内部。

您的内存似乎已损坏。Microsoft应用程序验证程序在查找损坏方面是非常宝贵的。使用它来查找您的错误:

  1. 将其安装到您的开发机器上
  2. 将您的exe添加到其中
  3. 仅选择BasicsHeaps
  4. 按Save。如果保持应用程序验证程序打开,这并不重要
  5. 运行您的程序几次
  6. 如果它崩溃了,请调试它,这一次,崩溃将指向您的问题,而不仅仅是程序中的某个随机位置

PS:为您的开发项目随时启用应用程序验证程序是个好主意。