混合模式C ++/CLI崩溃:atexit中的堆损坏(静态析构函数注册) [英] Mixed-mode C++/CLI crashing: heap corruption in atexit (static destructor registration)

查看:154
本文介绍了混合模式C ++/CLI崩溃:atexit中的堆损坏(静态析构函数注册)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在部署程序,并且代码库是C ++/CLI和C#的混合体. C ++/CLI具有各种风格:本机,混合(/clr)和安全(/clr:safe).在开发环境中,我创建了所有C ++/CLI代码的DLL,并从C#代码(EXE)引用了该DLL.这种方法完美无缺.

I am working on deploying a program and the codebase is a mixture of C++/CLI and C#. The C++/CLI comes in all flavors: native, mixed (/clr), and safe (/clr:safe). In my development environment I create a DLL of all the C++/CLI code and reference that from the C# code (EXE). This method works flawlessly.

对于我要发布单个可执行文件的版本(简单地指出为什么不仅将DLL和EXE分开?"是不可接受的.)

For my releases that I want to release a single executable (simply stating that "why not just have a DLL and EXE separate?" is not acceptable).

到目前为止,我已经用所有不同的源成功地编译了EXE.但是,当我运行它时,出现"XXXX已停止工作"对话框,其中包含用于联机检查",关闭"和调试"的选项.问题的详细信息如下:

So far I have succeeded in compiling the EXE with all the different sources. However, when I run it I get the "XXXX has stopped working" dialog with options to Check online, Close and Debug. The problem details are as follows:

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,它将报告:

If I debug and send it to Visual Studio, it reports:

Unhandled exception at 0x77d2dc9b in XXX.exe: A heap has been corrupted

选择中断将导致它停止在ntdll.dll!77d2dc9b()上,而没有其他信息.如果我告诉Visual Studio继续,该程序可以正常启动,并且似乎可以正常运行,可能是因为现在已连接了调试器.

Choosing break results in it stopping at ntdll.dll!77d2dc9b() with no additional information. If I tell Visual Studio to continue, the program starts up fine and seems to work without incident, probably since a debugger is now attached.

您对此有何看法?如何避免这种堆损坏?除此之外,该程序似乎运行良好.

What do you make of this? How do I avoid this heap corruption? The program seems to work fine except for this.

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

My abridged compilation script is as follows (I have omitted my error checking for brevity):

@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

在使用调试符号进行编译并再次尝试时,实际上我确实获得了更多信息.调用堆栈为:

Upon compiling this with debug symbols and trying again, I do in fact get more information. The call stack is:

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的动态初始值设定项)的代码行是:

The line of my code that 'causes' this (dynamic initializer for Bytes::Null) is:

Bytes Bytes::Null;

在声明为:

class Bytes { public: static Bytes Null; }

我还尝试过在标头中执行全局外部操作,如下所示:

I also tried doing a global extern in the header like so:

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

以相同的方式失败了.

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

It seems that the CRT atexit function is responsible, being inadvertently required due to the static initializer.

修复

正如Ben Voigt指出的那样,使用任何CRT函数(包括本机静态初始化程序)都需要对CRT进行正确的初始化(这发生在mainCRTStartupWinMainCRTStartup_DllMainCRTStartup中).我添加了具有C ++ mainWinMain的混合C ++/CLI文件:

As Ben Voigt pointed out the use of any CRT functions (including native static initializers) requires proper initialization of the CRT (which happens in mainCRTStartup, WinMainCRTStartup, or _DllMainCRTStartup). I have added a mixed C++/CLI file that has a C++ main or WinMain:

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

执行完此程序后,程序会进一步扩展,但是仍然存在问题(将在其他地方解决):

After doing this, the program now gets a little further, but still has issues (which will be addressed elsewhere):

  • 当程序完全使用C#编写时,以及仅调用C ++/CLI方法,获取C ++/CLI属性以及创建托管C ++/CLI对象的任何时候,它都能正常工作
  • 由C#添加到C ++/CLI代码中的事件永远不会触发(即使它们应该触发)
  • 另一个奇怪的错误是发生了一个异常,它是InvalidCastException,它说不能从X强制转换为X(其中X与X相同...)

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

However since the heap corruption is fixed (by getting the CRT initialized) the question is done.

推荐答案

发现了问题,建议的调试步骤留在下面,以防将来对任何人有帮助.

Spotted the problem, leaving the suggested debugging steps below in case they help anyone in the future.

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

The problem is that you've changed the entry point. You should be using the C++/CLI standard library-provided entry point, which sets up internal resources like the onexit list.

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

Remove the /ENTRY switch and write a simple main function that calls your desired startup routine.

尽管最终产品可能无法使用单独的EXE和DLL,但是最好测试这种更简单的配置,看看是否遇到相同的问题.

Although using a separate EXE and DLL may not be acceptable for the end product, it would be good to test this simpler configuration and see if you get the same problem.

如果您可以使用单独的.DLL重现堆损坏,则可以知道它位于本机C ++代码中,并且无需将C#混合到同一文件中,调试起来会容易得多.

If you can reproduce the heap corruption with a separate .DLL, you know it's somewhere in your native C++ code and it will be much easier to debug without the C# mixed into the same file.

如果无法使用单独的DLL和EXE重现该问题,则可能与集成过程有关(或者可能不太明显,因为布局取决于所链接的内容而改变).

If you can't reproduce the problem with separate DLL and EXE, then it could be related to the integration process (or it could just be less evident because the layout changes depending on what gets linked).

找到并消除了堆损坏错误之后,您可以返回到单个.EXE.

After you find and squash the heap corruption bug, then you can go back to the single .EXE.

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

Another approach would be to build the debug database so you can get better stack traces when it does crash. Even release builds (or maybe especially release builds) should be built with debugging info.

这篇关于混合模式C ++/CLI崩溃:atexit中的堆损坏(静态析构函数注册)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆