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

Я работаю над развертыванием программы, а кодовая база представляет собой смесь C ++ / CLI и C #. C ++ / CLI поставляется во всех вариантах: нативный, смешанный (/clr) и безопасный (/clr:safe). В моей среде разработки я создаю DLL всего кода C ++ / CLI и ссылаюсь на него из кода C # (EXE). Этот метод работает без нареканий.

Для моих выпусков, которые я хочу выпустить один исполняемый файл (просто заявив, что «почему бы просто не иметь раздельные DLL и EXE?» Не приемлемо).

До сих пор мне удалось скомпилировать EXE со всеми различными источниками. Однако, когда я запускаю его, я получаю диалоговое окно «ХХХХ перестал работать» с опциями Проверить онлайн, Закрыть и Отладить. Детали проблемы следующие:

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), вопрос решен.

1 Ответ

5 голосов
/ 08 февраля 2011

РЕДАКТИРОВАТЬ: обнаружил проблему, оставив предложенные ниже шаги отладки на случай, если они кому-нибудь помогут в будущем.

Проблема в том, что вы изменили точку входа.Вам следует использовать стандартную библиотечную точку входа C ++ / CLI, которая устанавливает внутренние ресурсы, такие как список onexit.

Удалите переключатель /ENTRY и напишите простую функцию main, которая вызывает желаемый запускрутина.


Хотя использование отдельного EXE и DLL может быть неприемлемым для конечного продукта, было бы хорошо протестировать эту более простую конфигурацию и проверить, не возникнет ли та же проблема.

Если вы можете воспроизвести повреждение кучи с помощью отдельного .DLL, вы знаете, что это где-то в вашем родном коде C ++, и будет гораздо проще отлаживать без C #, смешанного в тот же файл.

Если вы можете 'Воспроизвести проблему с отдельными DLL и EXE, тогда это может быть связано с процессом интеграции (или это может быть менее очевидным, потому что расположение меняется в зависимости от того, что связано).

После того, как вы найдете и раздавитеошибка повреждения кучи, тогда вы можете вернуться к единственному файлу .EXE.

Другим подходом будет сборкаОтладка базы данных, чтобы вы могли получить лучшие трассировки стека, когда она действительно падает.Даже сборки выпуска (или, возможно, особенно сборки выпуска) должны быть построены с отладочной информацией.

...