#include"../Library/Library.h"#include <iostream>
typedef Library* (*LibFactoryPtr)();
typedef void (*ReleasePtr)();
class Loader
{
public:
Loader():lib_(0)
{
std::cout<<"Loader .ctor"<<std::endl;
LibFactoryPtr getInstanceProc(0);
std::string name("../debug/Library.dll");
hinstLib_ = LoadLibrary(name.c_str());
// If the handle is valid, try to get the function address.if(hinstLib_ == NULL)
throw std::exception("Error loading DLL ", GetLastError());
getInstanceProc = (LibFactoryPtr)GetProcAddress(hinstLib_, "getInstance");
if(getInstanceProc == NULL)
throw std::exception("Error calling function 'getInstance' from DLL ", GetLastError());
lib_ = (getInstanceProc)();
if (lib_ == NULL)
throw std::exception("NULL was returned by function 'getInstance' from DLL", GetLastError());
}
bool unload()
{
std::cout<<"Loader::unload()"<<std::endl;
ReleasePtr releaseProc = (ReleasePtr) GetProcAddress(hinstLib_, "release");
if (releaseProc != NULL)
{
(releaseProc)();
}
std::cout<<"FreeLibrary()..."<<std::endl;
// Free the DLL module.
FreeLibrary(hinstLib_);
return true;
}
Library* get()
{
return lib_;
}
private:
Library* lib_;
HINSTANCE hinstLib_;
};
Loader loader;
class Dummy
{
public:
Dummy()
{
std::cout<<"Dummy .ctor"<<std::endl;
}
bool go(Library* lib)
{
std::cout<<"Dummy::go()"<<std::endl;
try
{
lib->doAction();
}
catch(const std::exception& ex)
{
std::cout<<"Exception occured..."<<std::endl;
std::cout<<ex.what()<<std::endl;
loader.unload();
std::cout<<"Rethrowing..."<<std::endl;
throw;
}
return false;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
try
{
Library* lib = loader.get();
Dummy dumb;
dumb.go(lib);
}
catch(const std::exception& ex)
{
std::cout<<"Dude...we f*cked up...["<<ex.what()<<"]"<<std::endl;
return 1;
}
return 0;
}
dll бросает исключение.
Его ловит Dummy::go() и в catch выгружает dll (в реальном приложении выгрузка происходит в другом потоке ниже по стеку,но результат тот же).
Из Dummy::go() делается попытка пробросить исключение дальше и получается AV.
call stack
msvcr80d.dll!FindHandler(EHExceptionRecord * pExcept=0x0012f8c4, EHRegistrationNode * pRN=0x0012ff5c, _CONTEXT * pContext=0x0012f8e4, void * pDC=0x0012f898, const _s_FuncInfo * pFuncInfo=0x00419e7c, unsigned char recursive=0, int CatchDepth=0, EHRegistrationNode * pMarkerRN=0x00000000) Line 740 + 0x6 bytes C++
msvcr80d.dll!__InternalCxxFrameHandler(EHExceptionRecord * pExcept=0x0012f8c4, EHRegistrationNode * pRN=0x0012ff5c, _CONTEXT * pContext=0x0012f8e4, void * pDC=0x0012f898, const _s_FuncInfo * pFuncInfo=0x00419e7c, int CatchDepth=0, EHRegistrationNode * pMarkerRN=0x00000000, unsigned char recursive=0) Line 524 + 0x25 bytes C++
msvcr80d.dll!__CxxFrameHandler3(EHExceptionRecord * pExcept=0x7c9032a8, EHRegistrationNode * pRN=0x0012f8c4, void * pContext=0x0012ff5c, void * pDC=0x0012f8e4) Line 365 + 0x1f bytes C++
ntdll.dll!_ZwQueryInformationProcess@20() + 0xc bytes
ntdll.dll!ExecuteHandler@20() + 0x24 bytes
ntdll.dll!ExecuteHandler2@20() + 0x26 bytes
ntdll.dll!ExecuteHandler@20() + 0x24 bytes
ntdll.dll!_KiUserExceptionDispatcher@8() + 0xe bytes
kernel32.dll!_RaiseException@16() + 0x52 bytes
msvcr80d.dll!_CxxThrowException(void * pExceptionObject=0x0012fc64, const _s__ThrowInfo * pThrowInfo=0x10004bd4) Line 161 C++
10001170()
> Application.exe!Dummy::go(Library * lib=0x00367360) Line 71 + 0xe bytes C++
Application.exe!main(int argc=1, char * * argv=0x003671d0) Line 94 C++
Application.exe!__tmainCRTStartup() Line 597 + 0x19 bytes C
Application.exe!mainCRTStartup() Line 414 C
kernel32.dll!_BaseProcessStart@4() + 0x23 bytes
exception кидать в одном и ловить в другом модуле такая же bad practice как и память выделять в одном и освобождать в другом дефолтовым (а значит неизвестно каким в общем случае) аллокатором
Как много веселых ребят, и все делают велосипед...
Здравствуйте, Centaur, Вы писали:
C>Здравствуйте, blackhearted, Вы писали:
B>>Что я упустил?
C>Классы вообще и исключения в частности нельзя использовать через границу DLL.
т.е. нужно редизайнить ?
Какая best practice в таких случаях (обработка исключений брошенных в dll)?
Здравствуйте, Centaur, Вы писали:
B>>Что я упустил?
C>Классы вообще и исключения в частности нельзя использовать через границу DLL.
Бред. Правильно писать "Классы вообще и исключения в частности нельзя использовать через границу DLL, если к тому нет веских причин". Часто такие причины есть, и польза от использования классов и исключений через границы dll многократно перевешивает вред.
Одним из 33 полных кавалеров ордена "За заслуги перед Отечеством" является Геннадий Хазанов.
Здравствуйте, Centaur, Вы писали:
C>Классы вообще и исключения в частности нельзя использовать через границу DLL.
Можно.
В данном случае ошибка, скорее всего, произошла из-за того, что у DLL и EXE разные CRT.
Как следствие,
— нарушение ODR в реализации std::exception (в частности, vtable); попытка вызвать virtual what() и virtual ~exception() из уже несуществующей таблицы
— два менеджера кучи; строка-параметр исключения была создана одним malloc'ом, а освобождается другим
Решение очевидно: компилировать оба проекта с /MD или /MDd.
Здравствуйте, Кодт, Вы писали:
К>В данном случае ошибка, скорее всего, произошла из-за того, что у DLL и EXE разные CRT. К>Как следствие, К>- нарушение ODR в реализации std::exception (в частности, vtable); попытка вызвать virtual what() и virtual ~exception() из уже несуществующей таблицы К>- два менеджера кучи; строка-параметр исключения была создана одним malloc'ом, а освобождается другим
К>Решение очевидно: компилировать оба проекта с /MD или /MDd.
Здравствуйте, Centaur, Вы писали:
C>Здравствуйте, blackhearted, Вы писали:
B>>Что я упустил?
C>Классы вообще и исключения в частности нельзя использовать через границу DLL.
— классы через границы DLL использовать можно и нужно. Не совсем классы конечно, интерфейсы. Пример — COM.
Здравствуйте, blackhearted, Вы писали:
B>dll бросает исключение. B>Его ловит Dummy::go() и в catch выгружает dll (в реальном приложении выгрузка происходит в другом потоке ниже по стеку,но результат тот же). B>Из Dummy::go() делается попытка пробросить исключение дальше и получается AV.
Выгружать dll из которой вылетело исключение перед тем как пробрасывать пойманное исключение дальше — это не очень хорошая идея.
Я бы так переписал:
Здравствуйте, blackhearted, Вы писали:
B>PS поведение сохраняется если ловить не const std::exception& , а просто std::exception. B>И даже если сделать B>
B>std::exception e = ex;
B>std::cout<<"Rethrowing..."<<std::endl;
B>throw e;
B>
А вот так вот пробовали?
std::exception e = ex;
std::cout<<"Rethrowing..."<<std::endl;
loader.unload();
throw e;
Здравствуйте, alsemm, Вы писали:
A>Выгружать dll из которой вылетело исключение перед тем как пробрасывать пойманное исключение дальше — это не очень хорошая идея.
В примере приведен минимальный код
На самом деле всё гораздо сложнее и либа выгружается из другого потока
Так не бывает, в указанной строке нечему падать. Можно предположить, что на самом деле упало на call eax. eax чему равен — не 0x10004be0 (из первого сообщения) случаем? У lib __vptr или как его там не порушен ли уже?
Одним из 33 полных кавалеров ордена "За заслуги перед Отечеством" является Геннадий Хазанов.
Здравствуйте, blackhearted, Вы писали:
B>dll бросает исключение. B>Его ловит Dummy::go() и в catch выгружает dll (в реальном приложении выгрузка происходит в другом потоке ниже по стеку,но результат тот же).
Можно посмотреть на реальный вариант, включая call-stack?
То, что происходит сейчас, понятно: B>
B>Unhandled exception at 0x1024c61c (msvcr80d.dll) in Application.exe: 0xC0000005: Access violation reading location 0x10004be0.
Падает msvcr80d.dll!FindHandler при попытке распарсить структуру _s__ThrowInfo. Она находится в dll, которая на этот момент выгружена.
Этот контекст был при первом throw std::exception("EX REASON"); — B>
B> msvcr80d.dll!_CxxThrowException(void * pExceptionObject=0x0012fc64, const _s__ThrowInfo * pThrowInfo=0x10004bd4) Line 161 C++
B> 10001170()
throw; перевыбрасывая исключение, восстанавливает первоначальный контекст, но dll уже нет.
Выгружать dll придётся где-то позже Dummy::go(). Другой тред может не дождаться раскрутки при брошенном исключении.
B>PS поведение сохраняется если ловить не const std::exception& , а просто std::exception.
Что бы отделить влияние диспетчера исключений от разных менеджеров куч, можно пробовать бросать например int.
.
People who are more than casually interested in computers should have at least some idea of what the underlying hardware is like. Otherwise the programs they write will be pretty weird (c) D.Knuth
Здравствуйте, blackhearted, Вы писали:
B>Здравствуйте, Кодт, Вы писали:
К>>В данном случае ошибка, скорее всего, произошла из-за того, что у DLL и EXE разные CRT. К>>Как следствие, К>>- нарушение ODR в реализации std::exception (в частности, vtable); попытка вызвать virtual what() и virtual ~exception() из уже несуществующей таблицы К>>- два менеджера кучи; строка-параметр исключения была создана одним malloc'ом, а освобождается другим
К>>Решение очевидно: компилировать оба проекта с /MD или /MDd.
B>Оба откомпилированы с MDd.
Угу. Позволю сделать предположение, что STL'ные классы не являются частью CRT, поэтому опция MD(d) — что мертвому припарка: таблица виртуальных методов std::exception, порожденной в DLL физически находится в этой (выгруженной) DLL, отсюда и свал при попытке обратиться к виртуальному методу. Правила работы с менеджером памяти (хотя и являются здравым советом) в данном конкретном случае ни при чем.
Попробуй не пробрасывать существующее исключение (throw; ), а кинуть его копию (throw ex; )
А вообще, корректное использование всех фич С++ через границы DLL возможно лишь при соблюдении одного неприятного условия — оба модуля должны быть откомпилированными одним и тем же компилятором. Не разными версиями одного и того же, а строго одним и тем же.
Здравствуйте, Кодт, Вы писали:
К>Тогда надо под отладчиком запустить и посмотреть в дизассемблере, что именно отстреливается.
Боюсь, это придётся делать из WinDbg, MS позаботились что бы отладчик Студии не заходил в диспетчер исключений (для этого там функции _NLG_Notify и т.п.)
.
People who are more than casually interested in computers should have at least some idea of what the underlying hardware is like. Otherwise the programs they write will be pretty weird (c) D.Knuth
K_O>А вообще, корректное использование всех фич С++ через границы DLL возможно лишь при соблюдении одного неприятного условия — оба модуля должны быть откомпилированными одним и тем же компилятором. Не разными версиями одного и того же, а строго одним и тем же.
Кроме компилятора еще бывают разные версии STL, BOOST
Кроме того бывают например такие STL, которые замещают стандартный аллокатор своим, который создает свою кучу, в каждом модуле свою...
Кроме того немного непонятно зачем делать проект состоящий из кучи разных длл, если условием нормальной работы которого является то, чтобы они были скомпилены все вместе одним компилятором и с одним и тем же набором разных либ, то есть фактически — чтоб это былоа единая компиляция. Не проще ли в таком случае сделать монолитный модуль, к чему эти длл?
Как много веселых ребят, и все делают велосипед...
Здравствуйте, Kh_Oleg, Вы писали:
K_O>Угу. Позволю сделать предположение, что STL'ные классы не являются частью CRT, поэтому опция MD(d) — что мертвому припарка: таблица виртуальных методов std::exception, порожденной в DLL физически находится в этой (выгруженной) DLL, отсюда и свал при попытке обратиться к виртуальному методу. Правила работы с менеджером памяти (хотя и являются здравым советом) в данном конкретном случае ни при чем. K_O>Попробуй не пробрасывать существующее исключение (throw; ), а кинуть его копию (throw ex; )
K_O>А вообще, корректное использование всех фич С++ через границы DLL возможно лишь при соблюдении одного неприятного условия — оба модуля должны быть откомпилированными одним и тем же компилятором. Не разными версиями одного и того же, а строго одним и тем же.
Они откомпилированы строго одним и темже компилятором с разницей в 10 секунд