Внешний вид сайта:

Управление загрузкой DLL

Когда Вы пишете динамически загружаемые библиотеки и необходимо контролировать загрузкой и выгрузкой DLL, или если Вы хотите подготовить DLL при инициализации, у Вас может быть функция с именем DllMain, которая будет делать инициализацию.

Windows вызывает функцию DllMain из DLL в четырех случаях:

  1. Когда процесс присоединяет DLL
  2. Когда поток присоединяет DLL
  3. Когда процесс отсоединяет DLL
  4. Когда нить отсоединяет DLL

Каждая DLL имеет точку входа. Эта точка входа реализована как функция обратного вызова. Она вызывается системой, когда событие происходит. Функция имеет имя по умолчанию DllMain. DLL, которая не должна быть загружена по определению, может просто возвращать False в функции DllMain() как только она обнаруживает, кто вызывает ее.

В Delphi, DLLProc используется для определения процедуры, которая вызывается каждый раз, когда вызывается точка входа библиотеки DLL. Процедура назначенная на DLLProc получает один параметр целое число (Reason).

Функция API GetModuleFileName() возвращает название вызывающего модуля, если Вы передаете 0 как его первый аргумент. Этот параметр - дескриптор модуля, имя которого Вы хотите знать.

Когда параметр Reason - DLL_PROCESS_ATTACH, устанавливая ExitCode в ненулевое значение, заставляет точку входа возвращать False.

Вот пример кода, который позволяет только MyCallingApp.exe загружать библиотеку (Примечание: у DLL в этом примере нет НИКАКОГО кода кроме процедуры DLLMain):

library OnlyMyDLL;

 uses
    SysUtils, Windows;

 procedure DllMain(reason: integer) ;
 var
   buf : array[0..MAX_PATH] of char;
   loader : string;
 begin
    case reason of
      DLL_PROCESS_ATTACH:
      begin
        GetModuleFileName(0, buf, SizeOf(buf)) ;
        loader := buf;
        if Pos('MyCallingApp.exe', loader) > 0 then
          ExitCode := -1
      end;
      DLL_PROCESS_DETACH
      begin
        // DLL выгружается...
      end;
    end;
 end; (*DllMain*)

 begin
    DllProc := @DllMain;
    DllProc(DLL_PROCESS_ATTACH) ;


    // другой код для этой DLL...
 end.

Пару слов об использовании DllMain: Дело в том, что DllMain вызывается в действительно уникальный момент времени. К этому времени загрузчик ОС нашёл и спроецировал файл с диска, но (по обстоятельствам) в некотором смысле ваш модуль может быть ещё не "полностью рождён". Тут дело тонкое.

В двух словах, когда вызывается DllMain, загрузчик ОС находится в весьма подвешенном состоянии. Во-первых, он держит блокировку на свои внутренние структуры для защиты их от повреждения, а во-вторых, некоторые из ваших зависимостей могут быть ещё не разрешены (т.е. не загружены все DLL, которые требуются вашему модулю). Перед тем, как загрузить модуль, загрузчик ОС просматривает его статические зависимости. Если они также требуют дополнительные зависимости, то он просматривает и их тоже. В результате загрузчик получает последовательность, в которой он будет вызывать DllMain-ы этих модулей, для которых их нужно вызывать. Загрузчик довольно сообразителен и чаще всего, если вы не следуете правилам, описанным в MSDN, то вам это сойдёт с рук - но не всегда (*).

Суть в том, что порядок загрузки вам неизвестен, но что более важно, что порядок основывается только на информации из статического импорта. Если в вашей DllMain происходит динамическая загрузка во время DLL_PROCESS_ATTACH, и вы делаете вызов в чужой модуль, то все ставки уже сделаны. Нет никаких гарантий, что DllMain этого модуля будет вызвана до того, как вы получите через GetProcAddress функцию в этом модуле и вызовите её. Результаты будут полностью непредсказуемыми - например, глобальные переменные остаются не инициализированными. Наиболее вероятно, что вы получите AV.

Другой сценарий: вы создаёте новый поток в DLL_THREAD_ATTACH и ожидаете, пока он закончит инициализацию с помощью какой-либо техники инициализации. Такие действия держат ваш поток в DllMain пока загрузчик ОС держит свою блокировку. В сумме это может привести к deadlock-у.

В целом: если что-то - что угодно - пойдёт не так в DllMain хотя бы одного модуля, то весь процесс обречён.

Комментарии

Нет комментариев. Ваш будет первым!