C# 混合模式C++/CLI崩溃:atexit中的堆损坏(静态析构函数注册)

C# 混合模式C++/CLI崩溃:atexit中的堆损坏(静态析构函数注册),c#,linker,c++-cli,main,atexit,C#,Linker,C++ Cli,Main,Atexit,我正在部署一个程序,代码库是C++/CLI和C#的混合体。C++/CLI有各种风格:原生、混合(/clr)和安全(/clr:safe)。在我的开发环境中,我创建了一个包含所有C++/CLI代码的DLL,并从C#代码(EXE)中引用它。这种方法完美无瑕 对于我希望发布单个可执行文件的版本(简单说明“为什么不将DLL和EXE分开?”是不可接受的) 到目前为止,我已经成功地用所有不同的源代码编译了EXE。但是,当我运行它时,我会看到“XXXX已停止工作”对话框,其中包含联机检查、关闭和调试选项。问题详

我正在部署一个程序,代码库是C++/CLI和C#的混合体。C++/CLI有各种风格:原生、混合(
/clr
)和安全(
/clr:safe
)。在我的开发环境中,我创建了一个包含所有C++/CLI代码的DLL,并从C#代码(EXE)中引用它。这种方法完美无瑕

对于我希望发布单个可执行文件的版本(简单说明“为什么不将DLL和EXE分开?”是不可接受的)

到目前为止,我已经成功地用所有不同的源代码编译了EXE。但是,当我运行它时,我会看到“XXXX已停止工作”对话框,其中包含联机检查、关闭和调试选项。问题详情如下:

Problem Event Name:       APPCRASH
Fault Module Name:        StackHash_8d25
Fault Module Version:     6.1.7600.16559
Fault Module Timestamp:   4ba9b29c
Exception Code:           c0000374
Exception Offset:         000cdc9b
OS Version:               6.1.7600.2.0.0.256.48
Locale ID:                1033
Additional Information 1: 8d25
Additional Information 2: 8d25552d834e8c143c43cf1d7f83abb8
Additional Information 3: 7450
Additional Information 4: 74509ce510cd821216ce477edd86119c
如果我调试并将其发送到Visual Studio,它会报告:

Unhandled exception at 0x77d2dc9b in XXX.exe: A heap has been corrupted
选择break会导致它在ntdll.dll停止!77d2dc9b(),无其他信息。如果我告诉VisualStudio继续,程序会正常启动,并且似乎可以正常工作,可能是因为现在附加了调试器

你怎么看这个?如何避免此堆损坏?除此之外,该计划似乎运作良好

我的简略编译脚本如下(为了简洁起见,我省略了错误检查):


更新1

使用调试符号编译并重试后,我确实获得了更多信息。调用堆栈是:

msvcr90d.dll!_msize_dbg(void * pUserData, int nBlockUse)  Line 1511 + 0x30 bytes
msvcr90d.dll!_dllonexit_nolock(int (void)* func, void (void)* * * pbegin, void (void)* * * pend)  Line 295 + 0xd bytes
msvcr90d.dll!__dllonexit(int (void)* func, void (void)* * * pbegin, void (void)* * * pend)  Line 273 + 0x11 bytes
XXX.exe!_onexit(int (void)* func)  Line 110 + 0x1b bytes
XXX.exe!atexit(void (void)* func)  Line 127 + 0x9 bytes
XXX.exe!`dynamic initializer for 'Bytes::Null''()  Line 7 + 0xa bytes
mscorwks.dll!6cbd1b5c()
[Frames below may be incorrect and/or missing, no symbols loaded for mscorwks.dll]
...
“导致”这种情况的代码行(字节的动态初始值设定项::Null)是:

在声明为的标头中:

class Bytes { public: static Bytes Null; }
我还尝试在标题中执行全局外部操作,如下所示:

extern Bytes Null; // header
Bytes Null; // cpp file
以同样的方式失败了

似乎是CRT
atexit
函数负责,由于静态初始值设定项的原因,无意中需要该函数


修复

正如Ben Voigt所指出的,使用任何CRT函数(包括本机静态初始化器)都需要正确初始化CRT(发生在
mainCRTStartup
WinMainCRTStartup
中)。我添加了一个混合的C++ + CLI文件,它有一个C++代码>主<代码>或代码>
using namespace System;
[STAThread] // required if using an STA COM objects (such as drag-n-drop or file dialogs)
int main() { // or "int __stdcall WinMain(void*, void*, wchar_t**, int)" for GUI applications
    array<String^> ^args_orig = Environment::GetCommandLineArgs();
    int l = args_orig->Length - 1; // required to remove first argument (program name)
    array<String^> ^args = gcnew array<String^>(l);
    if (l > 0) Array::Copy(args_orig, 1, args, 0, l);
    return XXX::CUI::Program::Main(args); // return XXX::GUI::Program::Main(args);
}
使用名称空间系统;
[STAThread]//如果使用STA COM对象(例如拖放或文件对话框),则需要此选项
用于GUI应用程序的int main(){//或“int u stdcall WinMain(void*,void*,wchar\u t**,int)”
数组^args_orig=Environment::GetCommandLineArgs();
int l=args_orig->Length-1;//删除第一个参数(程序名)需要
数组^args=gc新数组(l);
if(l>0)Array::Copy(args_orig,1,args,0,l);
返回XXX::CUI::Program::Main(args);//返回XXX::GUI::Program::Main(args);
}
完成此操作后,该计划现在会更进一步,但仍存在一些问题(将在其他地方解决):

  • 当程序只使用C#时,它可以正常工作,无论何时只要调用C++/CLI方法、获取C++/CLI属性以及创建托管的C++/CLI对象,都可以正常工作
  • 由C#添加到C++/CLI代码中的事件永远不会触发(即使它们应该触发)
  • 另一个奇怪的错误是,发生的异常是InvalidCastException,表示不能从X强制转换到X(其中X与X相同…)

但是,由于堆损坏已经修复(通过初始化CRT),问题就解决了。

编辑:发现了问题,留下下面建议的调试步骤,以备将来对任何人有所帮助

问题是您已经更改了入口点。您应该使用C++/CLI标准库提供的入口点,该入口点设置内部资源,如onexit列表

移除
/ENTRY
开关,编写一个简单的
main
函数,调用所需的启动例程


虽然最终产品可能不接受使用单独的EXE和DLL,但最好测试这种更简单的配置,看看是否会遇到同样的问题

如果你可以用一个单独的.dll来复制堆损坏,你知道它在你的本地C++代码的某个地方,如果没有C语言混合到同一个文件中,调试就容易得多。 如果无法使用单独的DLL和EXE重现问题,则可能与集成过程有关(或者可能不太明显,因为布局根据链接的内容而变化)

找到并消除堆损坏错误后,可以返回到single.EXE


另一种方法是构建调试数据库,以便在它崩溃时获得更好的堆栈跟踪。即使是发布版本(或者可能特别是发布版本)也应该使用调试信息进行构建。

@傻瓜感谢您更正了标题。我当时正在使用DLL/EXE系统(在Visual Studio中)测试所有内容。我是用C代码的PDB编译的,但是在链接步骤中我没有包含它。我已经使用上面的全部调试信息发布了结果。你的想法是什么?@thaimin:我想我看到了问题所在。如果您还打算使用任何标准库函数,包括
atexit
,则替换CRT入口点函数是不允许的。我不是故意使用atexit的!(尽管后面还使用了其他CRT功能)。此修复会导致出现新问题!请参阅已编辑的问题。@thaimin:既然我们已经解决了您的堆损坏问题,您是否愿意接受此问题的答案并重新开始(或者你现在看到的每个问题都是可能的:我不认为你剩下的问题是从C和C++的集成,但是由于.NET初始化代码初始化了它使用的COM部分,而你的DRAGLUP代码不考虑这个问题)。。我知道您希望继续查找bug,但以后阅读本文的任何人都会希望将
atexit
修复程序整齐地分开并单独解释。感谢您的输入和保持m
extern Bytes Null; // header
Bytes Null; // cpp file
using namespace System;
[STAThread] // required if using an STA COM objects (such as drag-n-drop or file dialogs)
int main() { // or "int __stdcall WinMain(void*, void*, wchar_t**, int)" for GUI applications
    array<String^> ^args_orig = Environment::GetCommandLineArgs();
    int l = args_orig->Length - 1; // required to remove first argument (program name)
    array<String^> ^args = gcnew array<String^>(l);
    if (l > 0) Array::Copy(args_orig, 1, args, 0, l);
    return XXX::CUI::Program::Main(args); // return XXX::GUI::Program::Main(args);
}