【问题标题】:AccessViolationException reading memory allocated in C++ application from C++/CLI DLLAccessViolationException 从 C++/CLI DLL 读取 C++ 应用程序中分配的内存
【发布时间】:2017-04-17 22:06:12
【问题描述】:

我有一个 C++/CLI DLL 的 C++ 客户端,它初始化一系列 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::string 应该已经到达它的目的地 ok

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

【问题讨论】:

  • 不是一个答案,因为我不能确定,但​​我将赌注押在标准库实现不匹配上:您的 C++ 代码使用的std::string 定义很可能不是与您的 C++/CLI 代码使用的 std::string 定义相同。确保您在两个项目中都使用了/MD,有关详细信息,请参阅herehere
  • 好主意,但事实并非如此。两个调试版本都使用 /MDd,两个发布版本都使用 /MD。
  • 继续发布第二个链接作为答案。这是相关的。我们第一次遇到这个问题时,有人开始在 Visual Studio 2013 中编译库,而客户端仍在 2010 年编译。我一直认为 2013 是一个问题,所以当客户端在 2013 年开始编译时,我继续编译2010 年的库,但问题在于不匹配,而不是特定版本。在 2013 年编译,它工作正常。
  • 如果它只是相关但不能解决你当前的问题,那么它就不是一个真正的答案(你说的是“第一次”所以我的理解是您当前的问题尚未解决)。此外,在没有答案的问题上,您将获得更多观看次数。
  • 我认为它已经修复,直到有人决定在较新版本的 VS 中重新编译这两者之一。

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


【解决方案1】:

我相信 2013 年对 STL 容器的内部数据格式进行了很多更改,以减少内存使用并提高性能。我知道vector 变小了,string 基本上是一个美化的vector&lt;char&gt;

Microsoft acknowledges the incompatibility

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

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

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

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

【讨论】:

【解决方案2】:

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

是的,这就是 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 个字节是由迭代器调试功能引起的,可以在 Preprocessor Definitions 中使用 _HAS_ITERATOR_DEBUGGING=0 禁用它。这不是您很快就想放弃的功能,但它使得 EXE 及其 DLL 的调试和发布版本的混合非常致命。

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

不要混用。

【讨论】:

    【解决方案3】:

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-06
      • 1970-01-01
      • 2021-03-15
      • 1970-01-01
      • 1970-01-01
      • 2015-12-28
      相关资源
      最近更新 更多