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或注释之外移出,我会获得相同的访问异常.
但这是不匹配,而不是问题的具体版本
是的,这是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)
我有一个模糊的记忆,Stephen Lavavej提到std :: string对象大小减少,非常多地作为一个功能呈现,但我找不到它.Debug构建中的额外4个字节是由迭代器调试功能引起的,可以_HAS_ITERATOR_DEBUGGING=0在预处理器定义中禁用它.不是你想要丢弃的功能,但是它使得混合Debug和Release版本的EXE及其DLL非常致命.
毋庸置疑,当在使用一个版本的标准C++库构建并在另一个版本中使用的DLL中创建Config对象时,不同的对象大小严重为字节.很多不幸,最基本的是代码只是从错误的偏移量中读取Config :: bee成员.AVE(几乎)得到保证.当代码分配Config对象的小味道但写出大量的std :: string时会产生更多的痛苦,这会随机破坏堆或堆栈框架.
不要混.
我相信2013年在STL容器的内部数据格式中引入了很多变化,作为减少内存使用和提高性能的一部分.我知道vector变小了,string基本上是一个美化vector<char>.
"为了启用新的优化和调试检查,C++标准库的Visual Studio实现故意破坏了从一个版本到下一个版本的二进制兼容性.因此,当使用C++标准库时,使用不同编译的目标文件和静态库版本不能混合在一个二进制文件(EXE或DLL)中,并且C++标准库对象不能在使用不同版本编译的二进制文件之间传递."
如果您要std::*在可执行文件和/或DLL之间传递对象,则必须确保它们使用相同版本的编译器.建议您的客户端及其DLL在启动时以某种方式进行协商,比较任何可用的版本(例如编译器版本+标志,升级版本,directx版本等),以便快速捕获这样的错误.将其视为跨模块断言.
如果您想确认这是问题所在,您可以选择一些来回传递的数据结构,并检查客户端与DLL的大小.我怀疑Config上面的课程会在其中一个失败案例中注册不同.
我还想提一下,在DLL调用中首先使用智能容器可能是一个坏主意.除非你能保证app和DLL不会试图释放或重新分配其他容器的内部缓冲区,否则很容易遇到堆损坏问题,因为app和DLL都有自己的内部C++堆.我认为这种行为最多被认为是不确定的.即使传递const&参数仍然会在极少数情况下导致重新分配,因为const这并不妨碍编译器对mutable内部进行干扰.
| 归档时间: |
|
| 查看次数: |
1186 次 |
| 最近记录: |