编程语言
首页 > 编程语言> > c# – 混合模式C/C++LI崩溃:atexit中的堆损坏(静态析构函数注册)

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

作者:互联网

我正在部署一个程序,代码库是C/C++LI和C#的混合体. C/C++LI有各种风格:native,mixed(/ clr)和safe(/ clr:safe).在我的开发环境中,我创建了一个包含所有C/C++LI代码的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

选择中断会导致它停在ntdll.dll!77d2dc9b(),没有其他信息.如果我告诉Visual Studio继续,程序启动正常并且似乎没有发生任何事故,可能是因为现在附加了调试器.

你是怎么做到的?如何避免这种堆损坏?该程序似乎工作正常,除此之外.

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

@set TARGET=x86
@set TARGETX=x86
@set OUT=%TARGETX%
@call "%VS90COMNTOOLS%\..\..\VC\vcvarsall.bat" %TARGET%

@set WIMGAPI=C:\Program Files\Windows AIK\SDKs\WIMGAPI\%TARGET%

set CL=/Zi /nologo /W4 /O2 /GS /EHa /MD /MP /D NDEBUG /D _UNICODE /D UNICODE /D INTEGRATED /Fd%OUT%\ /Fo%OUT%\
set INCLUDE=%WIMGAPI%;%INCLUDE%
set LINK=/nologo /LTCG /CLRIMAGETYPE:IJW /MANIFEST:NO /MACHINE:%TARGETX% /SUBSYSTEM:WINDOWS,6.0 /OPT:REF /OPT:ICF /DEFAULTLIB:msvcmrt.lib
set LIB=%WIMGAPI%;%LIB%
set CSC=/nologo /w:4 /d:INTEGRATED /o+ /target:module

:: Compiling resources omitted

@set CL_NATIVE=/c /FI"stdafx-native.h"
@set CL_MIXED=/c /clr /LN /FI"stdafx-mixed.h"
@set CL_PURE=/c /clr:safe /LN /GL /FI"stdafx-pure.h"

@set NATIVE=...
@set MIXED=...
@set PURE=...

cl %CL_NATIVE% %NATIVE%
cl %CL_MIXED% %MIXED%
cl %CL_PURE% %PURE%
link /LTCG /NOASSEMBLY /DLL /OUT:%OUT%\core.netmodule %OUT%\*.obj

csc %CSC% /addmodule:%OUT%\core.netmodule /out:%OUT%\GUI.netmodule /recurse:*.cs

link /FIXED /ENTRY:GUI.Program.Main /OUT:%OUT%\XXX.exe ^
/ASSEMBLYRESOURCE:%OUT%\core.resources,XXX.resources,PRIVATE /ASSEMBLYRESOURCE:%OUT%\GUI.resources,GUI.resources,PRIVATE ^
/ASSEMBLYMODULE:%OUT%\core.netmodule %OUT%\gui.res %OUT%\*.obj %OUT%\GUI.netmodule

更新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]
...

我的代码行’导致’这个(Bytes :: Null的动态初始化器)是:

Bytes Bytes::Null;

在标头中声明为:

class Bytes { public: static Bytes Null; }

我也尝试在标题中做一个全局extern:

extern Bytes Null; // header
Bytes Null; // cpp file

哪个以同样的方式失败了.

似乎CRT atexit函数是负责的,由于静态初始化器而无意中需要它.

固定

正如Ben Voigt指出的那样,使用任何CRT函数(包括本机静态初始化器)都需要正确初始化CRT(在mainCRTStartup,WinMainCRTStartup或_DllMainCRTStartup中发生).我添加了一个具有C main或WinMain的混合C/C++LI文件:

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);
}

在这样做之后,程序现在变得更进一步,但仍然存在问题(将在其他地方解决):

>当程序完全在C#中时,它可以正常工作,只需调用C/C++LI方法,获取C/C++LI属性,以及创建托管C/C++LI对象
> C#添加到C/C++LI代码中的事件永远不会触发(即使它们应该)
>另一个奇怪的错误是发生异常的是InvalidCastException,说不能从X转换为X(其中X与X相同……)

但是,由于堆损坏是固定的(通过初始化CRT),问题就完成了.

解决方法:

编辑:发现问题,留下建议的调试步骤,以防他们将来帮助任何人.

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

删除/ ENTRY开关并编写一个简单的主函数,调用所需的启动例程.

尽管使用单独的EXE和DLL可能不适用于最终产品,但测试这种更简单的配置并查看是否遇到同样的问题会更好.

如果您可以使用单独的.DLL重现堆损坏,您知道它在您的本机C代码中的某处,并且在没有将C#混合到同一文件中的情况下调试会更容易.

如果你不能用单独的DLL和EXE重现问题,那么它可能与集成过程有关(或者它可能不那么明显,因为布局根据链接的内容而变化).

找到并压缩堆损坏错误后,您可以返回单个.EXE.

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

标签:c,main,linker,c-cli,atexit
来源: https://codeday.me/bug/20190521/1149201.html