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

Den*_*ore 8 .net c++ 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.

我很确定问题不在我看到异常的位置,代码在成功和失败的构建之间没有变化,但我们应该检查完成.忽略事物的名称,我对这些字符串的设计没有多少控制权.抱歉混淆,但请注意_bridge并且bridge是不同的事情.由于这个问题已经太长,所以缺少大量代码.

在库中定义:

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 **/
    );
Run Code Online (Sandbox Code Playgroud)

在客户端功能:

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);
Run Code Online (Sandbox Code Playgroud)

请注意,对崩溃的库的调用与向量声明,结构声明,字符串赋值和向量回推的范围相同.此代码段中没有线程调用,但还有其他线程在运行其他操作.这里没有指针数学,除了标准库之外,区域中没有堆分配.

我可以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
}
Run Code Online (Sandbox Code Playgroud)

bee如果我在列表声明/赋值newConfigs[i].bee的赋值temp或注释之外移出,我会获得相同的访问异常.

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

为什么我的try/catch没有抓住这个异常

/sf/answers/64322401/

通用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++客户端的堆栈上分配内存.我试图使矢量不是参考参数,强制矢量复制到显式库控制的内存空间,没有帮助.
  • "冒泡到托管代码的非托管代码中的访问冲突始终包含在AccessViolationException中." - 事实,不是解决方案.

Han*_*ant 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;
}
Run Code Online (Sandbox Code Playgroud)
  • VS2010调试:32
  • VS2010发布:28
  • VS2013调试:28
  • VS2013发布:24

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

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

不要混.


Aik*_*rum 6

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

Microsoft承认不兼容:

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

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

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

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