Сбой после преобразования консольного приложения C++/Win32 в DLL

Недавно я преобразовал многопоточное неуправляемое консольное приложение Win32 C++ (MediaServer.exe) в неуправляемую DLL Win32 (MediaServer.dll). Я размещаю и отлаживаю эту DLL в отдельном неуправляемом консольном приложении Win32, и все компилируется и запускается, но примерно через минуту я получаю случайный сбой в бессмысленном месте с явно поврежденным вызовом. куча. Эти сбои происходят в самых разных местах и ​​в довольно случайное время: но общим является то, что (очевидно поврежденный) стек вызовов всегда где-то содержит различные функции libxml2.dll, например, сбой может произойти в строке, которая выглядит нравится:

xmlDoc * document = xmlReadMemory(message.c_str(), message.length(), "noname.xml", NULL, 0);

Или вот так:

xmlBufferPtr buffer = xmlBufferCreate();

И стек вызовов может выглядеть так:

feeefeee()  
libxml2.dll!000eeec9()  
[Frames below may be incorrect and/or missing, no symbols loaded for libxml2.dll]   
libxml2.dll!00131714()  
libxml2.dll!001466b6()  
libxml2.dll!00146bf9()  
libxml2.dll!00146c3c()  
libxml2.dll!0018419e()  

Или, если повезет, вот так:

ntdll.dll!_RtlpWaitOnCriticalSection@8()  + 0x99 bytes  
ntdll.dll!_RtlEnterCriticalSection@4()  - 0x15658 bytes 
libxml2.dll!1004dc6d()  
[Frames below may be incorrect and/or missing, no symbols loaded for libxml2.dll]   
libxml2.dll!10012034()  
libxml2.dll!1004b7f7()  
libxml2.dll!1003904c()  
libxml2.dll!100393a9()  
libxml2.dll!10024621()  
libxml2.dll!10036e8f()  
MediaServer.dll!Controller::parse(std::basic_string<char,std::char_traits<char>,std::allocator<char> > message)  Line 145 + 0x20 bytes  C++
MediaServer.dll!Controller::receiveCommands()  Line 90 + 0x25 bytes C++
MediaServer.dll!MediaServer::processCommands()  Line 88 + 0xb bytes C++
MediaServer.dll!MediaServer::processCommandsFunction(void * mediaServerInstance)  Line 450 + 0x8 bytes  C++
MediaServer.dll!CustomThread::callThreadFunction()  Line 79 + 0x11 bytes    C++
MediaServer.dll!threadFunctionCallback(void * threadInstance)  Line 10 + 0x8 bytes  C++
kernel32.dll!@BaseThreadInitThunk@12()  + 0x12 bytes    
ntdll.dll!___RtlUserThreadStart@8()  + 0x27 bytes   
ntdll.dll!__RtlUserThreadStart@8()  + 0x1b bytes    

Сам сбой обычно сообщает что-то вроде «Необработанное исключение по адресу 0x77cd2239 (ntdll.dll) в MediaServerConsole.exe: 0xC000005: место записи нарушения прав доступа 0x00000014».

Излишне говорить, что этого не произошло, когда я компилировал модуль как консольное приложение.

Есть ли что-то, что я мог упустить из виду при преобразовании проекта в DLL? Это не то, что я делал раньше, поэтому я не удивлюсь, если есть что-то очевидное, чем я пренебрег. Любая помощь приветствуется.


person Ken Smith    schedule 22.07.2011    source источник
comment
Пробовали ли вы запускать какую-либо из предложенных программ в этой теме ? Purify, Insure++ и т. д. могут помочь вам отследить незаметные ошибки в вашей программе.   -  person Adam Rosenfield    schedule 22.07.2011
comment
Нет, но я прямо сейчас скачиваю Visual Leak Detector, чтобы посмотреть, сможет ли он что-то точно определить. (Предположительно, это не тот уровень, что у Purify или Insure++, но это бесплатно...)   -  person Ken Smith    schedule 22.07.2011
comment
Что вызывает сбой? Нарушение прав доступа? Я предполагаю, что да, потому что освобожденная куча помечена 0xFEEFEEE. Похоже, ваш стек переполнен или ваша куча повреждена. Кроме того, вы можете проверить, с каким CRT связана DLL (статическая/динамическая, отладка/выпуск)   -  person Collin Dauphinee    schedule 22.07.2011
comment
Мой обычный контрольный список: выберите все проекты в вашем решении и проверьте: C++->Оптимизация: одинаковые настройки для оптимизации, предпочтительного размера или скорости и оптимизации всей программы. C++->Генерация кода: те же настройки для библиотеки времени выполнения (многопоточная DLL в вашем случае) и выравнивания членов структуры.   -  person Gnawme    schedule 22.07.2011
comment
@dauphic - я отредактировал вопрос, добавив дополнительные детали.   -  person Ken Smith    schedule 22.07.2011
comment
@Gnawme - все это проверил, все совпадает. Я просмотрел все настройки, которые только мог придумать, и все они, кажется, совпадают, и все они выглядят правильно — это, конечно, не значит, что они таковые :-).   -  person Ken Smith    schedule 22.07.2011
comment
Есть ли у вас какие-либо глобальные объекты/статические конструкторы в exe-файле приложения? Вы проверили проблему статического порядка инициализации?   -  person Gangadhar    schedule 22.07.2011
comment
@Gagadhar - Нет, в exe нет глобальных объектов - это такой же голый хост, как я мог его сделать. Я не думаю, что есть какие-то проблемы со статическим порядком инициализации, или, по крайней мере, не должно быть ничего нового только в результате этого перехода от EXE к DLL.   -  person Ken Smith    schedule 22.07.2011
comment
Я бы взял Application Verifier и включил полную проверку кучи. Такие проблемы всегда трудно отследить.   -  person Collin Dauphinee    schedule 22.07.2011
comment
У меня все заработало (см. ниже), но для улыбки я попробовал Application Verifier. К сожалению, единственное сообщение, которое я получил, было непостижимым сообщением о недопустимом индексе TLS, используемом для текущей трассировки стека. Поиск в гугле просто находит людей, которые так же сбиты с толку, как и я.   -  person Ken Smith    schedule 22.07.2011


Ответы (2)


Я бы сказал, что вы инициализируете память в DLL_THREAD_ATTACH вместо DLL_PROCESS_ATTACH. Ситуация может привести к тому, что вы будете использовать указатель или память, выделенную в другом потоке, отличном от исполняемого потока.

Другая вещь - проверить загрузку ваших зависимостей для DLL.

Позволь мне объяснить. CRT выполняет глобальное выделение памяти, когда ваша DLL загружается с помощью loadlibrary. Это необходимо для инициализации всех глобальных переменных в диапазоне от примитивных типов C, которые их инициализируют, до нуля по умолчанию. Затем он выделяет память для структур/классов и при необходимости вызывает их конструкторы.

Затем CRT вызывает ваш метод DLLMain с DLL_PROCESS_ATTACH, чтобы сообщить DLL, которая была загружена вашим процессом. Для каждого потока внутри этого процесса CRT затем вызывает вашу DLL с DLL_THREAD_ATTACH.

Вы сказали, что они остаются пустыми, а затем вызываете экспортированную функцию C. Хотя я вижу, что ваша dll попала в критическую секцию. Это говорит мне, что у вас возникла ситуация тупиковой блокировки с вашими глобальными выделенными переменными и вашим потоком, выделяющим память в Start ().

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

person Chad    schedule 22.07.2011
comment
Это было полезно проверить, но мой DllMain (как, по-видимому, рекомендует MS) является пустой заглушкой. В настоящее время я выделяю память только в одной функции Start(), объявленной как: extern C __declspec (dllexport) int Start(){/* Stuff */ } - person Ken Smith; 22.07.2011
comment
Да, но CRT выделяет глобальную память в другом потоке. Это то, к чему я клоню. Похоже, вы получаете тупик, когда два потока пытаются получить доступ к CriticalSection. - person Chad; 22.07.2011
comment
Тогда, возможно, я что-то упустил :-). Если я ничего не инициализирую в DllMain, как мне убедиться, что материал, который я инициализирую в Start(), инициализируется правильно/в правильном потоке? - person Ken Smith; 22.07.2011
comment
Что ж, похоже, так оно и было, хотя, признаюсь, я не вполне понимаю, почему. Я разделил метод initialize() моего основного класса на initialize() и start() и переместил материал initialize() в DllMain/DLL_Process_Attached, а материал start() в свою экспортированную функцию Start(): и это кажется работать. Хотел бы я понять, почему лучше, но я полагаю, что это придет со временем и больше кирпичных стен :-). Спасибо! - person Ken Smith; 22.07.2011

Я оставлю другой ответ как «принятый», но людям может быть полезно знать, что ключевой частью проблемы был тот факт, что я инициализировал libxml2 не в том потоке. В частности, вам нужно вызвать xmlInitParser() в вашем основном потоке, прежде чем делать какие-либо вызовы. Для меня это означало:

MediaServer::MediaServer() : mProvidePolicyThread  (0),
                         mProcessCommandsThread(0),
                         mAcceptMemberThread   (0)
{
   xmlInitParser();
}

Точно так же вам нужно вызвать xmlCleanupParser() при выходе:

MediaServer::~MediaServer()
{
   xmlCleanupParser();
}

Все это задокументировано здесь: http://xmlsoft.org/threads.html

person Ken Smith    schedule 26.07.2011