14 апреля 2010

Delphi 2010: Отсроченная загрузка DLL

Долгие годы программы на Delphi умели загружать DLL двумя способами: статически и динамически. В Delphi 2010 появился третий способ – отсроченная загрузка.

Сделаем простенькую DLL, которая экспортирует некую функцию, например, CalcFee:

library TestDLL;
Function CalcFee(const iID: Integer): Currency;
  begin
    Result := ...;
  end;
Exports
  CalcFee;
end.

и рассмотрим все три способа.

1. Статическая загрузка.
Это самый простой способ вызвать функцию из DLL. Для его реализации необходимо всего лишь описать внешнюю функцию:

Function CalcFee (const iID: Integer): Currency; external 'TestDLL.dll';

И после этого её можно вызывать:

fFee := CalcFee(1234)

Наша DLL загружается при запуске программы и остается загруженной до завершения ее работы.
За этой простотой скрывается большая проблема – если DLL удалить или испортить, то программа просто не запустится.


2. Динамическая загрузка.
Этот способ – более продвинутый, но требует написания значительно большего количества кода:

Var
  CalcFee: function(const iID: Integer): Currency;
  hDLL: THandle;
begin
  hDLL := LoadLibrary('TestDLL.dll');
  If hDLL <> 0
    then try
      @CalcFee := GetProcAddress(hDLL, 'CalcFee');
      If @CalcFee = nil
        then ShowMessage('В TestDLL.dll не найдена функция CalcFee')
      else fFee := CalcFee(1234);
    finally
      FreeLibrary(hDLL);
    end
    else ShowMessage('Ошибка при загрузке TestDLL.dll')

В этом коде используются функции Win32 API из Windows.pas:
  - LoadLibrary – загружаем DLL;
  - GetProcAddress – получаем адрес функции по её имени;
  - FreeLibrary – выгружаем DLL из памяти.

Динамическая загрузка позволяет загружать DLL только при необходимости и выгружать её, если она больше не нужна. Второе преимущество и более важное – это возможность обработать ошибки при загрузке DLL и вызове функции. Поэтому даже без DLL или с испорченной DLL программа будет работать. Если перед загрузкой DLL ее удалить, то получим нормальное сообщение:

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

Function CalcFee(const iID: Integer): Currency; external 'TestDLL.dll' delayed;

Недостатки этого способа – это то, что DLL уже нельзя выгрузить и то, что если при вызове функции произойдет ошибка, то пользователь не узнает ее причину. В справке написано "Trying to call a delayed routine that cannot be resolved results in a run-time error (or an exception, if the SysUtils unit is loaded)". Если DLL удалить или если в DLL нет нужной функции, сообщение будет однотипным:
Если функция будет использована в консольной программе, то сообщение об ошибке будет тоже не информативным:

"Runtime error 255 at 7C812AFB "

Но спасение есть. При желании ошибки, возникающие во время загрузки DLL или вызове её функций можно перехватить и обработать с помощью процедур SetDliNotifyHook и SetDliFailureHook из юниты System.

Не знаю, как вам, но мне идея отсроченной загрузки DLL понравилась, и я буду её в будущем использовать.

6 комментариев:

  1. Отсроченная загрузка хороша тем, что не требуется явное связывание с DLL, к которым, возможно, не будет обращения, и вместе с тем не надо управлять динамической загрузкой и динамическим же связыванием. С точки зрения программиста, компилятор диагностирует большее количество возможных ошибок, чем при динамической загрузке/связывании.
    Минус - таки да, не выгрузишь. С третьей стороны, связанные статически DLL тоже не выгрузить, однако страданий по этому поводу не слышно :)

    То есть, в Delphi наконец сделали то, что в других языках (например, в MS C или C++) давным-давно используется.

    ОтветитьУдалить
  2. Главное, не слишком этим увлекаться (впрочем, в Delphi эта проблема сглажена для GUI-приложений из-за наличия "глобального except").

    ОтветитьУдалить
  3. Не согласен! Увлекаться этим можно и нужно, только нужно обрабатывать ошибки. Что бы юзера не ругались, им нужно давать четкое сообщение об ошибке (на подобии 'Ошибка при загрузке TestDLL.dll' или 'В TestDLL.dll не найдена функция CalcFee').

    ОтветитьУдалить
  4. На выходных напишу про SetDliNotifyHook и SetDliFailureHook :)

    ОтветитьУдалить
  5. Анонимный18 января, 2011 15:44

    "Поэтому даже без DLL или с испорченной DLL программа будет работать"
    Смешная фраза. Значит не надо и загружать? :)))

    ОтветитьУдалить
  6. Почему смешная? Не весь же функционал программы в одной DLL. В таком случае, если DLL не грузится, то программа будет работать - а это значительно лучше, чем, если бы она упала или вообще не загрузилась.

    ОтветитьУдалить