procedure TForm1.Button1Click(Sender: TObject); type TImportedProc = procedure; safecall; var DLLFileName: String; DLL: HMODULE; Test: TImportedProc; begin DLLFileName := ExtractFilePath(ParamStr(0)) + 'DLLs' + PathDelim + 'Project2.dll'; DLL := LoadLibrary(PChar(DLLFileName)); Win32Check(DLL <> 0); try try Test := GetProcAddress(DLL, 'Test'); Win32Check(Assigned(Test)); Test; except Application.HandleException(Sender); end; finally Test := nil; FreeLibrary(DLL); DLL := 0; end; end;По сути, этот код просто загружает DLL-библиотеку из подпапки
DLLs
папки приложения и вызывает из неё функцию Test
. Для простоты положим, что функция Test
просто показывает сообщение (MessageBox).Код написан в предположении, что:
- DLL существует и доступ к ней есть;
- Функция с таким именем есть в DLL;
- Сигнатура функции в EXE совпадает с сигнатурой функции в DLL;
- Разрядность DLL совпадает с разрядностью приложения;
- DLL и EXE разрабатываются одним и тем же человеком;
- DLL является простой DLL, это не сборка (assembly), она не имеет манифеста;
- В приложении либо нет манифеста, либо манифест не содержит указания на 'Project2.dll';
- Все проекты Delphi лежат в тех же папках, что и исполняемые файлы; output-каталоги установлены в '.'; Иными словами, нет никаких подпапок
.\Win32\Debug\
или.\Debug\Win32\
, т.е. структура файлов на диске соответствует уже развёрнутому (установленному) приложению.
- Мы загружаем DLL по полному абсолютному пути, а не по относительному;
- Мы проверяем коды ошибок функций Windows API;
- Мы используем соглашение "safecall";
- На случай если DLL всё же облажается - мы обрабатываем исключение из DLL до выгрузки DLL;
- Мы даже очищаем описатель DLL и загруженную функцию, чтобы (ошибочно) не использовать их далее по коду.
P.S. Окей, это не столько самостоятельная задачка, сколько подготовка к очередной статье.
P.P.S. Товарищи, проблема не в исключениях.
Ответ.
Чисто предположение: LoadLibrary вызывает DllMain, что может образовать необработанное исключение.
ОтветитьУдалитьИнформация об исключении внутри Test потеряется, снаружи будет просто "Разрушительный сбой", правда на существенную проблему это не тянет.
ОтветитьУдалитьА на кой хрен safecall-вызов заключать в try?
ОтветитьУдалитьsafecall - это function: HRESULT; stdcall;
УдалитьСоответственно, на стороне DLL должен быть блок try/except, который заворачивает исключения в HRESULT. Но если его там нет (что есть, конечно, ошибка проектирования DLL) - исключение выплывет наружу, в EXE. Больше актуально для не-Delphi DLL, конечно.
Если его там нет, то это не "Сигнатура функции в EXE совпадает с сигнатурой функции в DLL" или хотя бы уточнение сделать надо было.
Удалить"Сигнатура" - это протокол укладки и возврата параметров, соглашение на машинный код. Он соблюдается в любом случае.
УдалитьА как следует вести себя функции - это реализация самой функции, протокол API, понятие более высокого уровня.
В любом случае я же сказал, что импортируемая функция показывает MessageBox. И раз уж все пытаются найти проблему в обработке ошибок, я добавил P.P.S. - что дело не в этом.
Такое ощущение, что компоненты никто не писал...
И кроме того, если Test – это процедура, то откуда у неё возьмётся HRESULT, чтобы вместить в себя отловленное исключение?
ОтветитьУдалитьИ главное, как в таких условиях что-либо вразумительное может прийти в наш except?
Ммм? У любой safecall-процедуры (на самом деле - функции) есть HRESULT - это (реальное) возвращаемое значение, скрываемое за фасадом компилятором Delphi.
УдалитьMessageBox может быть показан в своём собственном цикле обработки очереди сообщений Windows (я точно не помню, но флагами и хендлом там этого легко добиться). Т.е. в не блокирующем режиме. А значит пользователь сможет нажать на кнопку второй раз (пока диалог ещё висит). И вот тут я могу только предположить, что вызывая LoadLibrary второй раз вернётся хендл уже загруженной библиотеки, и FreeLibrary при втором вызове будет освобождать мусор.
ОтветитьУдалитьНу да, вот пример: MessageBox(0, 'hello', 'caption', MB_SYSTEMMODAL);
УдалитьИ можно на кликать таких диалогов сколько угодно - главная форма приложения не заблокируется.
Такой же эффект может возникнуть, если Test каким-то образом будет вызывать Application.ProcessMessages.
Видимо LoadLibrary/FreeLibrary надо оборачивать счётчиком вызовов (если проблема конечно же в этом).
Почитал MSDN. Load/FreeLibrary имеют счётчик вызовов. Хорошая задачка :)
УдалитьХм, я понял в чём дело. И оно стабильно воспроизводится :)
Удалить