我有一个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.
我很确定问题不在我看到异常的地方,成功的构建和失败的构建之间的代码都没有改变,但是我们应该对其进行完整的检查。忽略事物的名称,我对这些字符串的功能设计没有太多控制权。很抱歉造成混淆,但是请注意_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 **/
);
在客户端功能中:
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);
请注意,对崩溃的库的调用与 vector 声明,结构声明,字符串分配和 vector 推回的作用域相同
在本节代码中没有线程调用,但是其他线程正在运行以执行其他操作。这里没有指针数学,除了标准库内部之外,该区域中没有堆分配。
我可以在调试器中运行直到
Bridge_GetConfiguredDefaultsImplementationPointer
调用的代码,并且configs
vector 的内容在调试器中看起来正确。回到库的第一个子函数中,调试器不会发光,我将失败的语句分解为几个控制台打印。
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
时也会遇到相同的异常。仅供引用, vector 的结构中的std::string应该已经到达目的地了
为什么我的try/catch没有捕获此异常
https://stackoverflow.com/a/918891/2091951
通用AccessViolationException相关问题
以上问题的建议
最佳答案
是的,这是VS中的黑字定律。不幸的是,您只是错过了VS2012内置的对策,它将该错误变成可诊断的链接器错误。以前(以及在VS2010中),CRT会使用HeapAlloc()分配自己的堆。现在(在VS2013中),它使用默认的进程堆,即GetProcessHeap()返回的堆。
当您在Vista或更高版本上运行应用程序时,它本身足以触发AVE,从一个堆分配内存,然后从另一个堆释放内存,则在运行时触发AVE,在启用Debug Heap进行调试时,调试器中断。
这不是结束之处,另一个重要问题是两个版本之间的std::string对象布局不相同。您可以通过一些测试程序发现一些东西:
#include <string>
#include <iostream>
int main()
{
std::cout << sizeof(std::string) << std::endl;
return 0;
}
我对Stephen Lavavej提到std::string对象的大小减小有一个模糊的内存,这是作为一种功能提供的,但是我找不到它。调试版本中多余的4个字节是由迭代器调试功能引起的,可以使用“预处理程序定义”中的
_HAS_ITERATOR_DEBUGGING=0
禁用它。这不是您很快想放弃的功能,但是它使EXE及其DLL的Debug和Release版本的混合使用非常致命。不用说,当在使用标准C++库的一个版本构建的DLL中创建Config对象并在另一个版本中使用Config对象时,不同的对象大小会严重占用字节。很多不幸的事,最基本的一个是代码将简单地从错误的偏移量读取Config::bee成员。 (几乎)有保证。当代码分配Config对象的较小样式但写入std::string的较大样式时,这会更加痛苦,这会随机破坏堆或堆栈帧。
不要混