27 апреля 2010

Delphi 2010: Ловим ошибки отсроченной загрузки DLL

   Как я писал раньше, Delphi 2010 научилась вызывать функций из DLL новым способом – с помощью отсроченной загрузки DLL.
   Одним из недостатков этого способа является то, что если при вызове функции произойдет ошибка, то пользователь не узнает ее причину. В случае если DLL не существует, так же как и если в DLL нет нужной функции, то сообщение в программе будет однотипным:

   События, которые происходят при отложенной загрузке DLL и вызове её функций, можно перехватить и обработать самому. Установить свой обработчик на эти события позволяет функция SetDliNotifyHook. Эта функция работает с параметрами процедурного типа DelayedLoadHook описанного в юните System:

DelayedLoadHook = function (dliNotify: dliNotification; pdli: PDelayLoadInfo): Pointer; stdcall;

где:
• dliNotify – это событие которое происходит с DLL:
  - dliNoteStartProcessing – намёк, что будет работа по загрузке DLL;
  – dliNotePreLoadLibrary – идёт загрузка DLL;
  – dliNotePreGetProcAddress – идёт попытка получения адреса вызываемой функции;
  – dliNoteEndProcessing – окончание работы по загрузке функции из DLL;
  – dliFailLoadLibrary – ошибка при загрузке DLL;
  – dliFailGetProcAddress – ошибка при получении адреса вызываемой функции.
• pdli – указатель на структуру типа TDelayLoadInfo с полями:
  – cb – размер структуры;
  – pidd – данные в необработанной форме;
  – ppfn – указатель на адрес функции, которая загружена;
  – szDll – название DLL;
  – dlp – название или номер загружаемой функции;
  – hmodCur – информация о DLL (запись типа TDelayLoadProc);
  – pfnCur – указатель на адрес функции, которая будет вызвана;
  – dwLastError – номер полученной ошибки.

   Более подробно о функции SetDliNotifyHook и связанных с ее работой типах вы можете почитать в справке Delphi 2010. А мы приступим к написанию собственного обработчика ошибок при отложенной загрузке DLL и вызове её функций.
   Т.к. нас интересует только перехват и обработка ошибок, то воспользуемся урезанной версией функции SetDliNotifyHook – SetDliFailureHook, которая устанавливает обработчик событий только на события возникающие при ошибке: dliFailLoadLibrary и dliFailGetProcAddress. Чтобы не писать перехватчик в каждом новом проекте, вынесем его код в отдельную юниту.

Unit dliHandler;

Interface

Uses
  SysUtils;

Type
  // Ошибка при работе с отсроченной загрузкой DLL
  EDliFailure = class(Exception)
                    ErrorCode: Integer;
                    constructor Create(const sMessage: String;
                                              const iErrorCode: Integer);
  end;

Implementation

constructor EDliFailure.Create(const sMessage: String; const iErrorCode: Integer);
begin
  inherited Create(sMessage);
  ErrorCode := iErrorCode;
end;

// Функция возвращает имя или номер импортируемой функции
function ImportName(const AProc: TDelayLoadProc): String;
begin
  if AProc.fImportByName
    then Result := AProc.szProcName
    else Result := '#' + IntToStr(AProc.dwOrdinal);
end;

// Обработчик ошибок генерирующий "красивую" ошибку
function DelayedLoadHook(dliNotify: dliNotification; pdli: PDelayLoadInfo): Pointer; stdcall;
begin
  If dliNotify = dliFailGetProcAddress
    then Raise EDliFailure.Create('В ' + pdli.szDll + ' не найдена функция ' + ImportName(pdli.dlp), 2)
    else Raise EDliFailure.Create('Ошибка при загрузке ' + pdli.szDll, 1); //dliFailLoadLibrary}
end;

Var
  LOldFailureHook: TDelayedLoadHook;

Initialization
  // Устанавливаем свой обработчик
  LOldFailureHook := SetDliFailureHook(DelayedLoadHook);
Finalization
  // На всякий случай вернем старый обработчик
  SetDliFailureHook(LOldFailureHook);
end.

Добавляем dliHandler в любой проект и наслаждаемся автоматической обработкой ошибок при работе с отсроченной загрузкой DLL во всем проекте. Теперь вместо непонятного сообщения "External exception" пользователь увидит:
• если не удалось загрузить DLL
• если не удалось получить адрес функции
   При необходимости, можно обработать ошибку и для каждого конкретного случая вызова функции (специально для этого в dliHandler я определил класс EDliFailure):

Try
  ...
  fFee := CalcFee(1234)
  ...
  Except
    on E: EDliFailure do
      Case E.ErrorCode of
        1: HandleDliFailLoadLibrary;
        2: HandleDliFailGetProcAddress;
      End
    else Raise
End;

где, HandleDliFailLoadLibrary и HandleDliFailGetProcAddress – специфические для данного конкретного случая обработчики ошибок.
   Как видите, научить программу выводить понятные пользователю сообщения об ошибке легко.

Комментариев нет:

Отправить комментарий