AccessViolationException从C ++ / CLI DLL

时间:2016-12-02 22:12:59

标签: c++ .net memory-management c++-cli access-violation

我有一个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
}

如果我将bee移到newConfigs[i].bee的分配之上或者注释掉列表声明/作业,我会在访问temp时获得相同的例外。

仅供参考,向量中的结构中的std :: string应该已经到达目的地了

为什么我的try / catch

没有捕获此异常

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

Generic 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 - 我只看到与调试器中的特定崩溃有关的提及,并且Microsoft的帮助说其他AccessViolationException问题可能不相关。 (http://connect.microsoft.com/VisualStudio/feedbackdetail/view/819552/visual-studio-debugger-throws-accessviolationexception
  • 更改Comodo防火墙设置 - 我不使用此软件
  • 将所有代码更改为托管内存 - 不是一个选项。从C ++到C ++ / CLI调用C#的总体设计不能改变。我被特别要求以这种方式设计它以利用现有C ++代码中的现有C#代码。
  • 确保分配内存 - 应在C ++客户端的堆栈中分配内存。我试图使矢量不是参考参数,强制将矢量副本强制显式地存储在库控制的内存空间中,但没有帮助。
  • &#34;在托管代码中冒泡的非托管代码中的访问冲突始终包含在AccessViolationException中。&#34; - 事实,而不是解决方案。

3 个答案:

答案 0 :(得分:8)

  

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

是的,那是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 Debug:32
  • VS2010发布:28
  • VS2013调试:28
  • VS2013发布:24

我对Stephen Lavavej的模糊记忆提到了std :: string对象大小的减少,非常多地作为一个功能呈现,但我无法找回它。 Debug构建中的额外4个字节是由迭代器调试功能引起的,可以在预处理器定义中使用_HAS_ITERATOR_DEBUGGING=0禁用它。不是你想要丢弃的功能,但它使EXE及其DLL的Debug和Release版本混合非常致命。

毋庸置疑,在使用一个版本的标准C ++库构建并在另一个版本中使用的DLL中创建Config对象时,不同的对象大小严重为字节。很多不幸,最基本的是代码只是从错误的偏移量中读取Config :: bee成员。 AVE(几乎)得到保证。当代码分配Config对象的小味道但写出大量的std :: string时会产生更多的痛苦,这会随机破坏堆或堆栈框架。

不要混。

答案 1 :(得分:6)

我相信2013年在STL容器的内部数据格式中引入了很多变化,作为减少内存使用和提高性能的一部分。我知道vector变小了,string基本上是一个荣耀的vector<char>

Microsoft acknowledges the incompatibility

  

&#34;启用新的优化和调试检查,Visual Studio   C ++标准库的实现故意破坏二进制文件   从一个版本到下一个版本的兼容性。因此,当C ++   使用标准库,目标文件和静态库   使用不同版本编译的文件不能混合在一个二进制文件中(EXE   或DLL),C ++标准库对象不能在它们之间传递   使用不同版本编译的二进制文件。&#34;

如果您要在可执行文件和/或DLL之间传递std::*个对象,则必须确保它们使用相同版本的编译器。建议你的客户端和它的DLL在启动时以某种方式进行协商,比较任何可用的版本(例如编译器版本+标志,升级版本,directx版本等),以便快速捕获这样的错误。将其视为跨模块断言。

如果您想确认这是问题所在,您可以选择一些您来回传递的数据结构,并检查客户端与DLL的大小。我怀疑上面的Config课程会在其中一个失败案例中注册不同。

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

答案 2 :(得分:0)

您似乎有内存损坏。 Microsoft Application Verifier在查找损坏方面非常有用。用它来查找你的错误:

  1. 将其安装到您的开发机器上。
  2. 添加你的exe。
  3. 仅选择Basics\Heaps
  4. 按保存。如果您保持应用程序验证程序处于打开状态并不重要。
  5. 运行您的程序几次。
  6. 如果它崩溃,请调试它,这次,崩溃将指向您的问题,而不仅仅是程序中的一些随机位置。
  7. PS:为您的开发项目始终启用Application Verifier是一个好主意。

相关问题