Сбой смешанного режима C ++ / CLI: повреждение кучи в atexit (статическая регистрация деструктора)

Я работаю над развертыванием программы, а кодовая база представляет собой смесь C ++ / CLI и C #. C ++ / CLI поставляется во всех вариантах: нативный, смешанный ( / clr ) и безопасный ( / clr: safe ). В своей среде разработки я создаю DLL всего кода C ++ / CLI и ссылаюсь на нее из кода 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 , которая непреднамеренно требуется из-за статического инициализатора.


Fix

Как Бен Фойгт указал на использование любых CRT функции (включая собственные статические инициализаторы) требуют правильной инициализации CRT (что происходит в mainCRTStartup , WinMainCRTStartup или _DllMainCRTStartup ). Я добавил смешанный файл C ++ / CLI, который имеет C ++ main или 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);
}

После этого программа немного продвинулась вперед, но все еще имеет проблемы (которые будет рассмотрен в другом месте):

  • Когда программа написана исключительно на C #, она отлично работает, а также когда она просто вызывает методы C ++ / CLI, получает свойства C ++ / CLI и создает управляемые объекты C ++ / CLI.
  • События, добавленные C # в C ++ / Код CLI никогда не срабатывает (хотя и должен)
  • Еще одна странная ошибка заключается в том, что возникает исключение InvalidCastException, говорящее о невозможности преобразования из X в X (где X совпадает с X ...)

Однако поскольку повреждение кучи исправлено (путем инициализации CRT), вопрос решен.

5
задан Ben Voigt 9 February 2011 в 23:22
поделиться