Я работаю над развертыванием программы, а кодовая база представляет собой смесь 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
Выбор break приводит к остановке на 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 Bytes Null; // header
Bytes Null; // cpp file
Который так же провалился.
Похоже, что виновата функция CRT atexit
, которая непреднамеренно потребовалась из-за статического инициализатора.
Исправить
Как указал Бен Фойгт, использование любых функций 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), вопрос решен.