Создание DLL (Dynamic Link Library) – библиотек динамической компоновки.

 

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

1. Выберите в меню команду File, New. Появится диалоговое окно New Items.

2. Выберите значок DLL и нажмите <Enter>.

3. Появится новый проект с именем по умолчанию PROJECT 1. Выберите в меню команду File, Save As. Появится диалоговое окно Save Project As.

4. Используйте комбинированный список Save In для выбора папки. Наберите FIRSTDLL.DPR в строке редактирования FileName. Нажмите Save.

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

Модуль SHAREMEM и DELPHIMM.DLL

Первоначально в исходном тексте модуля FIRSTDLL содержится созданное Delphi сообщение в виде комментария. Оно включает в себя важную информацию, которая не может быть получена из других источников. Сообщение говорит о том, что при определенных условиях модуль SHAREMEM должен стоять первым в операторе uses. Эти условия таковы: если любая из экспортируемых процедур требует в качестве параметра данные строкового типа или возвращает такие данные в качестве результата.

Вы можете и проигнорировать это сообщение. Если вы решили использовать SHAREMEM в операторе uses вашего проекта, вы должны будете распространять библиотеку DELPHIMM.DLL вместе с приложением. DELPHIMM.DLL находится в каталоге \DELPHI\BIN. SHAREMEM -это скомпилированный модуль Delphi. Его полное название SHAREMEM.DCU и он находится в каталоге \DELPHI\LIB. SHAREMEM является модулем импорта, это означает, что он включает в себя код, необходимый для загрузки файла DELPHIMM.DLL. Он также импортирует все требуемые функции, экспортируемые DELPHIMM.DLL.

Замечание

Начиная с Delphi 2, введен новый, принимаемый по умолчанию, тип строковых данных. Это автоматическая переменная; этот термин означает, что она автоматически заново размещается в зависимости от того, что ей присваивается. Delphi поддерживает ссылку на строку, что позволяет при копировании не создавать ее заново, а второй раз сослаться на уже существующую. Строки также автоматически удаляются из памяти. Введение автоматических переменных, таких, как новый тип строк, несет с собой и определенные хлопоты. В данном случае это необходимость распространять DELPHIMM.DLL. Эта библиотека DLL вместе с модулем SHAREMEM содержит процедуры, поддерживающие в Delphi соответствующие автоматические тиы переменных.

В отличие от старых строк, новый тип допускает создание строковых переменных практически неограниченной длины. Память для строк динамически выделяется из кучи; они завершаются нулевым символом (null — terminated). Старые строки хранили длину в байте со смещением 0. В отличие от старого, новый тип совместим и с типом PChar. Благодаря этому, вы можете легко создавать переменные, которые нужно передавать в функции, в частности в функции Windows API MessageBox().

Пойдем дальше и включим модуль SHAREMEM в проект. Если угодно, можно удалить сообщение, сгенерированное Delphi и заменить его на одно из собственных. Делайте такие сообщения содержательными и полезными для вас как программиста.

Установка каталога назначения

По умолчанию при компиляции проекта файл DLL помещается в тот же каталог, где содержатся файлы проекта. Это правило может быть изменено. Чтобы изменить каталог, куда будет посылаться библиотека DLL, выберите пункт меню Project, Options. Когда появится диалоговое окно Project Options, щелкните на закладке Directories/Conditionals. К примеру, введите свой личный.

Теперь, когда проект создан, библиотека DLL может быть сгенерирована выбором пункта меню Project, Compile. Файл DLL будет находиться в выбранном вами каталоге.

Добавления к главному исходному модулю (файлу .DPR)

Диалоговое окно Delphi New Items дало возможность создать шаблон проекта библиотеки DLL. Пока проект в основном пуст; он содержит главный исходный модуль под названием FIRSTDLL.DPR и все. Файл FIRSTDLL.DPR почти не содержит кода — оператор library, оператор uses и пустое пространство, заключенное между begin и end.

Оператор library говорит Delphi о том, что проект предназначен для создания DLL. Если он присутствует, в модуле SYSTEM Delphi логическая переменная под названием isLibrary устанавливается в True. Она доступна вам, так как FIRSTDLL содержит модуль SYSTEM в операторе uses по умолчанию (неявно).

Оператор uses содержит список модулей в проекте. Когда вы добавляете новый модуль, его имя автоматически добавляется в список модулей после оператора uses. Если вы хотите добавить уже существующий модуль, вы можете просто добавить его имя или использовать окно Project Manager.

Вы также можете добавить модуль в ваш проект DLL путем выбора пункта меню Project, Add to Project. В этой главе подразумевается, что мы не добавляем в проект ничего готового. Если же вы хотите это сделать, то можете использовать описанную возможность.

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

1. Выберите пункт меню File, New. Появится диалоговое окно New Items.

2. Выберите значок Unit. Нажмите Ok.

К проекту будет добавлен новый модуль под стандартным названием UNIT1.PAS.

3. Выберите File, Save для того, чтобы сохранить файл. Сохранить его нужно в том же каталоге, где и остальные части проекта, под именем ACCESS.PAS.

4. Теперь добавим в проект форму. Выберите пункты меню File, New и проделайте те же операции, что и при добавлении нового модуля, но в диалоговом окне New Items выберите значок Form.

5. Для сохранения файла выберите File, Save. Сохраните его там, где находится остальной проект, под именем DYNAWIN.PAS.

Следуя приведенной в этом разделе последовательности операций, в проект можно добавить столько модулей и форм, сколько необходимо для создания вашей библиотеки DLL.

Код инициализации

Итак, мы создали проект, который состоит из главного исходного файла FIRSTDLL.DPR; модуля ACCESS.PAS и формы DYNAWIN.PAS.

Файл FIRSTDLL.DPR содержит пустой блок begin...end. С этого места начинается инициализация модуля. Сюда может быть вставлен код, который будет выполняться каждый раз, когда процесс загружает модуль. В листинге 1 в секции инициализации FIRSTDLL вызывается функция Delphi ShowMessage (). Для компиляции этого кода в оператор uses должен быть добавлен модуль Dialogs.

 

Листинг 1. Эта модификация файла DPR включает комментарий для программиста, использование ShareMem и Dialogs в секции uses и ВЫЗОВ SendMessage() при инициализации

library FIRSTDLL;

{Модуль ShareMem необходим, чтобы экспортировать длинные строки}

uses ShareMem, Dialogs, SysUtils, Classes, ACCESS in 'ACCESS.pas', DynaWin in 'DynaWin .pas' {Form1};

begin

ShowMessage ('Initialization Code Called');

end;

Если процесс успешно загрузил FIRSTDLL, вызов SendMessage () выполняется немедленно. Это происходит один раз для загружающего процесса. Если модуль загружают несколько процессов, SendMessage() выполняется один раз для каждого процесса при начальной загрузке.

 

Модель "выполнения при загрузке"

Рассмотрим пример незарегистрированной условно-бесплатной (Shareware) библиотеки DLL. Вы можете включить в код инициализации модулей выдачу сообщения, рекомендующего программисту приобрести зарегистрированную версию. Это можно сделать путем замены в предыдущем примере вызова SendMessage () на вызов MessageDlg () .

begin

MessageDlg ( ' Please sent for registered version of FIRSTDLL.DLL',

mtInformation, [mbOk], 0);

end.

Библиотека DLL функционирует нормально для того, чтобы пользователи могли обучаться и осваивать ее. Но они не рискнут использовать незарегистрированную версию в своем приложении — их выдаст ваше сообщение. Мы будем называть код инициализации моделью "выполнения при загрузке (Execute-on-load)".

Есть несколько серьезных вариантов использования модели "выполнения при загрузке". Пожалуй, наилучшим вариантом является назначение глобального обработчика исключительных ситуаций для модуля. Листинг 2 показывает FIRSTDLL.DPR, дополненный установкой обрабатывающей процедуры.

 

           Листинг 2. FIRSTDLL.DPR — Следуя модели "выполнения при загрузке", глобальная процедура обработки исключительных ситуаций устанавливается в коде инициализации

type (Type section.}

TAppException = class (TObject)

private

procedure AppException (Sender: TObject; E: Exception);

end;

{ Глобальный обработчик исключительных ситуаций приложения. }

procedure TAppException.AppException(Sender: TObject; E: Exceptions);

begin

ShowMessage('My Handler caught: '+E.Message);

end;

var {Var Section.}

AppExceptObj: TAppException;

begin

MessageDLG ('Please send for registered version of

FIRSTDLL.DLL', mtInformation, [mbOK] , 0) ;

AppExceptObj := TAppException. Create;

Application. OnException := AppExceptObj. AppException;

end.

 

После того, как была вызвана функция MessageDlg(), создается глобальный объект с именем AppExceptObj. Он имеет тип TAppException, порожденный от TObject. В секции private AppExceptObj имеет единственный метод под названием AppException. В следующем операторе метод AppException присваивается обработчику события OnException глобального объекта, соответствующего приложению.

Замечание

Говоря более простым языком, в каждом проекте есть доступ к глобальному объекту типа TApplication под именем Application. Когда в вашем коде возникает исключительная ситуация, которая не обрабатывается, возникает событие onException объекта Application. Если обработчику события Application. onException ничего не присвоено, вызывается стандартный обработчик. В листинге 2 только описывается класс с методом, совместимым по присваиванию с Application.OnException. Далее создается экземпляр этого класса и его метод присваивается Application.OnException. Теперь в случае возникновения необработанной исключительной ситуации ваш код исполняется вместо стандартного.

Когда глобальный обработчик исключительной ситуации TAppException.AppException получает управление, пользователь видит сообщение, содержащее текст о возникшей ошибке. Описанный сценарий может показаться вам идеальным, но в данном коде пропущен один фундаментальный момент. Это — удаление глобального объекта AppExceptObj, созданного в коде инициализации. В идеале объект должен быть освобожден тогда, когда библиотека DLL окончательно прекратит отображение в адресное пространство процесса. Однако вы пока не знаете, как это сделать. Проясняется это в последующем обсуждении функции DllMain.

Функция DllMain и Delphi

В Win32 определена специальная функция обратного вызова (Callback junction) системного уровня с именем, которое может задаваться пользователем (это пользовательская функция, вызываемая системой). Как правило, на нее ссылаются как на DllEntryPoint. Но DllEntryPoint может иметь практически любое имя; иногда это DllMain. Возможность присвоить этой функции новое имя является прерогативой компоновщика используемой вами среды программирования. К сожалению, Delphi не позволяет программисту задавать имя функции DllEntryPoint. Это не слишком хорошо, так как имя должно быть сообщено Windows до вызова функции. В Delphi часть задач вместо DilEntryPoint может взять на себя код инициализации; но зачем же мы тогда завели разговор о DilEntryPoint?

Когда вызывается функция Windows API LoadLibrary (неважно, системой или программистом), модуль DLL загружается в память. Затем вызывается код инициализации библиотеки. Система проверяет, экспортирует ли программист в модуле функцию DilEntryPoint и задал ли он ее имя при помощи компоновщика. Если имело место и то, и другое, система вызывает функцию DilEntryPoint. Прототип этой функции на языке С выглядит следующим образом:

BOOL WINAPI DilEntryPoint ( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved );

Если бы DilEntryPoint поддерживалась в Delphi, ее прототип мог бы выглядеть так:

function DilEntryPoint (hinstDII: THandle; fdwReason:

DWORD;pReserved: pointer ): Boolean; stdcall; export;

Поскольку в качестве функции обратного вызова функция DilEntryPoint в Delphi не поддерживается, в обсуждении деталей прототипа нет необходимости; но параметр fdwReason все же должен привлечь ваше внимание. Он имеет тип DWORD; в Delphi DWORD определен как четырехбайтовое беззнаковое целое. Параметр fdwReason содержит значение, соответствующее причине, по которой была вызвана функция DilEntryPoint. Система вызывает DilEntryPoint в четырех случаях; соответственно, fdwReason может иметь одно из четырех значений, которые показаны в табл. 1.

Таблица 1. Возможные значения fdwReason

 

Значение fdwReason

Описание

DLL_PROCESS_ATTACH

Процесс отображает DLL в свое адресное пространcтво

DLL_PROCESS_DETACH

Процесс прекращает отображение DLL в свое адресное пространство

DLL_THREAD_ATTACH

Создается поток в адресном пространстве процесса, отобразившего DLL

DLL_THREAD_DETACH

Поток, находившийся в том же адресном пространстве, куда отображена DLL, удаляется

 

 

 

DLL_PROCESS_ATTACH

Библиотека DLL отображается в адресное пространство процесса, который в первый раз загружает этот модуль. Это наилучшее время для инициализации всех глобальных объектов, таких, как обработчики исключительных ситуаций, файлы, отображаемые в память или глобальные переменные. Значение DLL_PROCESS_ATTACH посылается загружающим процессом только один раз; последующие загрузки другими потоками не делают этого. Однако если модуль загружается другим процессом, тот также один раз посылает значение DLL PROCESS_ATTACH.

 

DLL_THREAD_ATTACH

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

Когда процесс создается, изначально существует поток, называемый первичным потоком (Primary thread). Он не посылает библиотекам DLL значения DLL_THREAD_ATTACH; вместо этого при создании процесса посылается DLL_PROCESS_ATTACH. При получении значения DLL_PROCESS_ATTACH можно полагать, что процесс автоматически создал первичный поток. Все последующие создания потоков в процессе влекут за собой посылку загруженным библиотекам DLL значения DLL THREAD_ATTACH.

Иногда процесс загружает библиотеку DLL по прошествии определенных операций; в этом случае могут появиться потоки, созданные до конца загрузки DLL. Потоки, существующие на момент загрузки модуля, не посылают значения DLL_THREAD_ATTACH.

 

DLL_THREAD_DETACH

Когда поток в текущем процессе завершается, это значение посылается всем загруженным в процессе библиотекам DLL. Вопреки тому, что вы ожидаете, это значение можно получать чаще, чем DLL_THREAD_ATTACH.

Предположим, что создается процесс, а вместе с ним и первичный поток. Все загруженные модули получают значение DLL_PROCESS_ATTACH. Когда процесс завершается, значение DLL_THREAD_DETACH посылается в том числе и первичным потоком. Есть и еще одна причина, по которой загруженные DLL получают значение DLL_THREAD_DETACH чаще, чем DLL_THREAD_ATTACH.

Снова предположим, что создается процесс. Первичный поток создается автоматически. Далее, вы создаете три потока, загружающие DLL. Поскольку все три потока создаются перед загрузкой библиотеки DLL, она не получит значения DLL THREAD ATTACH. Пусть теперь три потока завершаются; DLL получит DLL_THREAD_DETACH от каждого из трех потоков, хоть ни от одного из них она не получала DLL_THREAD_ATTACH.

 

DLL_PROCESS_DETACH

Библиотека DLL выгружается из процесса, который ее загрузил. Это может произойти как результат вызова функции Windows API FreeLibrary() или вследствие завершения процесса, когда DLL автоматически выгружаются. Если вы создали любые глобальные объекты во время получения DLL_PROCESS_ATTACH, такие, как обработчики исключительных ситуаций, файлы, отображаемые в память, или глобальные переменные, самое время освободить их.

Но если функция обратного вызова DllEntryPoint не поддерживается в Delphi, к чему вся эта информация? В Delphi описана глобальная переменная под именем DllProc, которая содержится в модуле SYSTEM. Модуль SYSTEM автоматически включается средой Delphi во все проекты неявно для пользователя.

В Delphi некоторые события имеют место перед тем, как выполняется код инициализации файла DPR, Одно из первых — это вызов процедуры _InitDLL. Процедура _InitDLL — это встроенная ассемблерная процедура из модуля SYSTEM. Встроенный ассемблерный код содержится прямо в исходном коде Delphi. Процедура InitDLL решает несколько задач. Она сохраняет значения нескольких расширенных регистров процессора, устанавливает глобальную переменную isLibrary в единицу (True), получает значение экземпляра (Instance) модуля и присваивает его глобальной переменной hInstance, устанавливает значение глобальной переменной DllInitState, получает ряд параметров из стека и проверяет значение DllProc. Эта проверка очень важна. Если этой переменной присвоено значение, Init DLL вызывает DllProc. При этом ей передается параметр, полученный из стека. Этот параметр — то же, что и fdwReason.

Функциональные возможности DllEntryPoint могут быть воспроизведены в коде инициализации файла проекта (.DPR) Delphi. Это достигается путем создания специальной процедуры и присвоения ее адреса глобальной переменной DllProc. Изменения, которые внесены в созданный ранее в этой главе проект FIRSTDLL, приведены в листинге 3. Отметим, что в оператор uses добавлен модуль WINDOWS.

 

Листинг 3. FIRSTDLL.DPR — имитация возможностей DllEntryPoint путем присвоения адреса процедуры глобальной переменной DllProc

library FIRSTDLL;

{Модуль SnareMem необходим, чтобы экспортировать длинные строки}

uses (Uses section.} ShareMem, Windows, Forms, Dialogs, SysUtils, Classes, ACCESS in 'ACCESS, pas', DynaWin in 'DynaWin.pas' {frmLicense};

type (Type section.}

TAppException = class (TObject)

private

procedure AppException(Sender: TObject; E: Exception);

end;

var {Var Section.)

AppExceptObj: TAppException;

{ Глобальный обработчик исключительных ситуаций приложения. }

procedure TAppException.AppException (Sender: TObject; E: Exception); begin

ShowMessage('My Handler caught: '+E.Message);

end;

{Реализация DllMain на Delphi}

procedure DllMain(fdwReason: DWORD);

begin

case fdwReason of

DLL_THREAD_ATTACH : *

ShowMessage('DLL_THREAD_ATTACH' );

DI.L_THREAD_DETACH :

ShowMessage( 'DLL_THREAD_DETACH' );

DLL_PROCESS_DETACH :

begin

ShowMessage( 'DLL_PROCESS_DETACH' );

AppExceptObj. Free;

end;

DLL_PROCESS_ATTACH :

begin

ShowMessage( 'DLL_PROCESS_ATTACH' );

AppExceptObj := TAppException. Create;

Application.OnException:= AppExceptObj. AppException;

end;

end;

end;

{Начало блока инициализации}

begin

MessageDLG ( ' Please send for registered version of

FIRSTDLL.DLL', mtInformation, [mbOK] ,0) ;

DllProc := @DllMain;

DllMain (DLL_PROCESS_ATTACH) ;

end.

 

Сравним листинги 2 и 3. В последнем появилось многое, но функции модуля остались прежними. Весь новый код, по сути, обслуживает удаление объекта AppExceptObj. Посмотрим внимательнее на этот листинг, начиная с кода инициализации.

Первый оператор вызывает функцию MessageDlg; здесь ничего не изменилось. Следующий оператор присваивает адрес процедуры DllMain глобальной переменной DllProc; процедура DllMain описана ранее в этом модуле. Затем DllMain вызывается явно и ей передается параметр DLL_PROCESS_ATTACH.

 

Совет

Вы можете быть удивлены тем, почему в листинге 3 в коде инициализации процедура DllMain вызывается явно. Дело в том, что функция _InitDLL вызывается перед кодом инициализации. Это означает, что когда посылается DLL_PROCESS_ATTACH, переменной DllProc еще не присвоено значение; раз значение не присвоено, то и вызова со стороны _InitDll не будет. После того, как отработает _InitDll, загружаются все модули проекта вместе со всеми загружаемыми ими модулями. Наконец, исполняется код, заключенный между begin и end. Он включает в себя присвоение глобальной переменной DllProc адреса DllMain и затем формальный вызов

DllMain (DLL_PROCESS_ATTACH);

После того, как присвоение адреса произошло, он остается действительным все время, пока модуль отображается в адресное пространство процесса. Поскольку код инициализации исполняется только один раз во время включения библиотеки DLL в состав загружающего ее процесса, не стоит опасаться многократного обращения к DllMain во время этого вызова. После присвоения значения DllProc все будет выполняться правильно.

 

Отметим, что процедура DllMain описана как имеющая один параметр. Это — fdwReason, имеющий тип DWORD. Когда DllMain вызывается из кода инициализации с параметром DLL_PROCESS_ATTACH, оператор case в DllMain получает его через параметр fdwReason.

О получении этого значения информирует вызов ShowMessage(). Затем создается экземпляр объекта типа TAppException и присваивается объектной переменной AppExceptObj. Метод данного объекта AppException присваивается обработчику события OnException глобального объекта Application.

Если в процессе, отобразившем FIRSTDLL.DLL, создается поток, то процедура DllMain снова вызывается; на сей раз автоматически. Параметр fdwReason будет иметь значение DLL_THREAD_ATTACH, и DllMain покажет сообщение об этом. Когда поток уничтожается, операционная система снова вызывает DllMain с параметром fdwReason, равным DLL_THREAD_DETACH. Процедура DllMain покажет сообщение, констатирующее этот факт. Наконец, когда процесс завершается или прекращается отображение DLL, система снова обращается к DllMain. В этот раз параметр fdwReason имеет значение DLL_PROCESS_DETACH; и снова появится сообщение, свидетельствующее об этом. Кроме этого, экземпляр объект

а AppExceptObj, созданный при получении DLL_PROCESS_ATTACH, будет уничтожен.

 

 

Экспорт процедур для доступа к ним

Чтобы другой модуль мог получить доступ к процедуре, вы должны экспортировать ее. Когда процедура экспортируется, ее адрес, имя и порядковый номер запоминаются в получающемся образе файла модуля.

Эта информация добавляется к таблице экспорта в модуле. Вы можете просмотреть ее содержимое с использованием утилиты TDUMP фирмы Borland. Обсуждение утилиты TDUMP в деталях приводится в файле под названием "Tdump.doc ".

 

Замечание

Процедуры могут быть экспортированы как модулями DLL, так и ЕХЕ. Даже если это позволяется с точки зрения синтаксиса как компилятором, так и компоновщиком, нельзя получить доступ к процедурам, экспортируемым из модуля ЕХЕ. Это ограничение Windows, а не Delphi.

 

Чтобы экспортировать процедуру из модуля, нужно проделать две операции. Первая из них — это объявление процедуры с ключевым словом export.

function AddValues(iArgl: integer; iArg2: integer): integer; export;

Следующая операция — включение имени процедуры в секцию exports файла DPR.

exports AddValues;

В созданный ранее в этой главе проект FIRSTDLL мы внесем некоторые изменения. В файл FIRSTDLL.DPR добавим секцию exports перед началом кода инициализации.

exports AddValues; {Начало блока инициализации.}

begin

MessageDLG( 'Please send for registered version of

FIRSTDLL.DLL', mtInformation,[mbOK] ,0);

DllProc := @DllMain;

DllMain (DLL_PROCESS_ATTACH) ;

end.

В этом примере мы поместили exports и AddValues в одной строке. Если нужно экспортировать несколько процедур, их имена нужно разделить запятой, а после имени последней поставить точку с запятой, как например:

exports AddValues, GetStrChar, BeepLoop;

В отличие от зарезервированного слова export, в секции exports не нужно указывать ни параметров, ни типов возвращаемых значений.

 

Замечание

Вы, вероятно, обратили внимание, что зарезервированные слова export и exports отличаются только одним символом s. Следующая полезная информация позволит запомнить, какое из них употребляется в файле DPR, а какое — при описании процедуры.

Зарезервированное слово exports подразумевает множественное число. Оно встречается в файле DPR, и за ним следуют имена экспортируемых процедур

Зарезервированное слово export подразумевает единственное число. Оно встречается один раз на каждое описание прототипа экспортируемой процедуры.

 

Иногда имя экспортируемой процедуры может совпадать (и конфликтовать) с именем другой существующей процедуры. В секции exports перед именем процедуры в качестве префикса может стоять имя модуля, в котором она содержится. Имя модуля должно быть отделено от имени процедуры точкой, например, вот так:

exports Access. AddValues;

Вы ранее добавили в проект FIRSTDLL новый модуль и сохранили его под именем ACCESS.PAS. В модуле ACCESS после зарезервированного слова interface добавьте описание прототипа:

function AddValues(iArgl: integer; iArg2: integer): integer; export;

Обратим внимание, что описание AddValues завершается точкой с запятой, но после нее идет зарезервированное слово export снова с точкой с запятой. Все это нужно для правильного экспорта. Автор забежал вперед и добавил еще две процедуры в модуль ACCESS. Но сначала изменим секцию exports файла FIRSTDLL.DPR, чтобы увидеть там следующее:

exports AddValues, GetStrChar, BeepLoop;

Процедуры AddValues, GetStrChar и BeepLoop служат примерами простых библиотечных процедур. Можно добавить сколько угодно процедур в модуль ACCESS и экспортировать их все или частично. Листинг 4 показывает модуль ACCESS проекта FIRSTDLL с новыми добавлениями.

 

 

Листинг 4. ACCESS.PAS — модуль ACCESS в проекте FIRSTDLL содержит некоторые простые библиотечные процедуры

unit ACCESS;

interface

uses Windows;

{ Прототипы экспортируемых процедур }

function AddValues (iArgl: integer; iArg2: integer): integer; export; function GetStrChar (sVal: string; iIndex: integer): char; export;

procedure BeepLoop (iNumTimes: integer); export;

implementation

{Возвращает сумму переданных ей аргументов}

function AddValues (iArgl: integer; iArg2: integer): integer;

begin

result := (iArgl+iArg2 );

end;

{Возвращает символ, находящийся в положении pos, заданном параметром iIndex. }

function GetStrChar (sVal: string; iIndex: integer): char;

begin

result := CHAR( sVal [iIndex]');

end;

{ Дает звуковой сигнал n раз, n задается параметром iNumTimes. }

procedure BeepLoop (iNumTimes: integer);

begin

while (iNumTimes > 0) do

begin

MessageBeep(0) ;

dec (iNumTimes) ;

end;

end;

end.

 

Описание прототипов экспортируемых процедур находятся в начале файла; обычное их место в модуле — в секции interface. Определяются они в секции implementation.

 

Совет

Самое время скомпилировать проект. Можно использовать утилиту TDUMP, чтобы взглянуть на содержимое таблицы экспорта; она должна содержать все три процедуры. Если их нет в списке, вы неправильно их экспортировали. Обратитесь за информацией по TDUMP к файлу "Tdump.doc ".

 

Соглашение о вызовах

Соглашение о вызовах — это способ, согласно которому одна процедура передает данные другой. При детальном рассмотрении здесь возникают три основные проблемы:

-Как передаются параметр(ы): через стек или через регистры;

-Если используется стек, то в каком порядке вызывающая процедура помещает параметры: как есть, слева направо или справа налево;

-Какая процедура отвечает за очистку стека.

Различные языки и среды программирования посылают переменные в качестве параметров в вызываемые процедуры разными способами. В некоторых случаях параметры помещаются в стек; иногда параметры (хотя бы некоторые) помещаются в регистры процессора. В обоих случаях они доступны вызываемой процедуре. После их использования стек должен быть очищен или вызывающей, или вызываемой процедурой. Помещаемые в стек параметры могут следовать в различном порядке. Порядок зависит от используемого соглашения о вызовах.

В Borland C++ и Delphi поддерживают стандартные соглашения о вызовах. Принятые в них умолчания различны, но каждый язык содержит директиву для смены умолчания. Стандартные директивы, используемые в Delphi, приведены в табл. 3.

 

Таблица 3. Последовательность параметров и порядок очистки стека для стандартных директив

Директива

Последовательность параметров

Очистка стека .

register (по умолчанию)

Первые три параметра помещаются в регистры слева направо

Вызываемая процедура

Cdecl

Помещаются в стек справа налево

Вызывающая процедура

Pascal

Помещаются в стек слева направо

Вызываемая процедура

Stdcall

Помещаются в стек справа налево

Вызываемая процедура

 

 

Используйте эти директивы для совместимости с описанием прототипа функции, которую вы хотите экспортировать (или импортировать).

 

Директива register

Delphi по умолчанию использует соглашение о вызовах под названием "быстрый вызов" (Fast-call). Когда это возможно, соглашение быстрого вызова использует расширенные регистры процессора ЕАХ, EDX и ЕСХ для помещения в них первых параметров, которые могут быть туда помещены — это перечисляемые типы данных длиной 32 разряда и менее, а также указатели. Остальные параметры следуют обычному соглашению о вызовах Pascal. Соглашение быстрого вызова может быть явно задано директивой register. Следующий прототип демонстрирует описание экспортируемой функции с директивой register:

function AddValues(iArgl: integer; iArg2: integer): integer; register; export;

или просто

function AddValues(iArgl: integer; iArg2: integer): integer; export;

В первом случае директива register появляется перед директивой export, отделяясь от нее точкой с запятой; во втором примере ее нет вообще. Однако и там и там будет использовано соглашение быстрого вызова, поскольку оно принимается по умолчанию. Это делает директиву register как бы лишней; с технической точки зрения она не нужна, но может быть использована для внесения ясности, особенно учитывая то, что она впервые появилась в Delphi.

 

Директива cdecl

Директива cdecl извещает компилятор о необходимости генерировать код с поддержкой вызовов в стиле языка С. Об очистке стека тут заботится вызывающая процедура. Она помещает параметры в стек справа налево. В языке С это соглашение о вызовах принято по умолчанию, потому что оно позволяет поддерживать передачу в процедуру переменного числа параметров. Этим он отличается от языка Pascal, где требуется только постоянное число аргументов. В Delphi директива cdecl используется для обращения к экспортируемым библиотечным процедурам, которые используют соглашение о вызовах языка С. Вы также можете экспортировать процедуры в вашем модуле как cdecl, но не сможете на Delphi добиться поддержки переменного числа параметров.

Следующий прототип иллюстрирует описание экспортируемой функций с использованием директивы cdecl:

function AddValues(iArgl: integer; iArg2: integer): integer; cdecl; export;

 

Директива Pascal

Директива Pascal извещает компилятор о необходимости генерировать код с поддержкой вызовов в стиле языка Pascal. За очистку стека отвечает вызываемая процедура. Параметры процедуры помещаются в стек слева направо. Хотя это соглашение очень "быстрое", оно не позволяет осуществлять поддержку переменного числа параметров.

 

Замечание

Во всех соглашениях о вызовах вызывающая процедура помещает параметры в cтек. В зависимости от типа соглашения, вызывающая или вызываемая процедуры очищают стек и возвращают его перед вызовом в исходное состояние.

В соглашениях о вызовах Pascal это делает вызываемая процедура. Вы можете спросить: а что насчет возвращаемого значения? Как оно попадает к вызывающей процедуре? Если оно является 16-разрядной величиной, оно помещается в регистр АХ; в противном случае, для длинных возвращаемых значений (вроде строк), вызывающая процедура создает временную область памяти и помещает указатель на нее в стек перед остальными параметрами. Другими словами, возвращаемое значение не возвращается в стек, поэтому стек может быть очищен до того, как управление вернется к вызывающей процедуре.

 

Следующий прототип демонстрирует описание экспортируемой функции с Использованием директивы pascal:

function AddValue(iArgl: integer; iArg2: integer): integer; pascal; export;

 

Директива stdcall

Эта директива требует от компилятора генерации соглашения о вызовах stdcall. Параметры помещаются в стек слева направо вызывающей процедурой, как в соглашении cdecl. Однако стек очищает вызываемая функция, как в соглашении о вызовах Pascal. Как и в последнем, требуется передача в процедуру правильного числа и типа параметров.

Следующий прототип демонстрирует описание экспортируемой функции с использованием директивы stdcall:

function AddValue(iArgl: integer; iArg2: integer): integer; stdcall; export;

 

Интерфейс API Windows

Процедуры, экспортируемые в модуле ACCESS проекта FIRSTDLL, который обсуждался ранее в этой главе, не задают явно соглашение о вызовах. Они по умолчанию используют соглашение "быстрый вызов", принятое в Delphi.

Стандартным соглашением о вызовах в Windows выше Windows 95 является stdcall. Это означает, что если вы экспортируете функцию, которая будет вызываться системой Windows, в ее прототипе должна стоять директива stdcall. Это верно и в том случае, если вы импортируете функцию из программного интерфейса Windows.

Для того, чтобы модуль DLL был совместим с Windows, вы должны использовать соглашение о вызовах stdcall. Измените описания прототипов экспортируемых процедур в модуле ACCESS проекта FIRSTDLL следующим образом:

function AddValue(iArgl: integer; iArg2: integer): integer; stdcall; export;

function GetStrChar(sVal: string; iindex: integer): char; stdcall; export;

procedure BeepLoop(iNumTimes: integer); stdcall; export;

Теперь каждая из этих функций использует соглашение о вызовах stdcall. Когда вы пишете экспортируемые процедуры, к которым будет осуществляться доступ только из ваших программ, вы можете использовать любое соглашение о вызовах. Однако, чтобы соблюсти совместимость с Windows, нужно стандартизовать соглашения о вызовах и использовать stdcall.

Если вы хотите изменить соглашение о вызовах, к примеру, на cdecl, исходный код нужно изменить (не делайте этого с проектом FIRSTDLL). Изменения выглядят следующим образом:

function AddValues(iArgl: integer; iArg2: integer): integer; cdecl; export;

function GetStrChar(sVal: string; iIndex: integer): char; cdecl; exports;

procedure BeepLoop(iNumTimes: integer); cdecl; export;

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

 

Замечание

Если вы переходите на Delphi с языков С или C++, вы должны хорошо знать функцию API Windows wsprintf. Эта функция очень хорошо подходит для форматирования переменных любого типа в единый буфер при выводе.

Если вы попытаетесь использовать ее в Delphi, произойдет аварийное завершение, хотя wsprintf и является совместимой с Win32. Это происходит из-за того, что она имеет переменное число параметров. Используйте в Delphi функцию Format.

Delphi не поддерживает функции, предназначенные для использования переменного числа параметров, даже если мы можем задать соответствующее соглашение о вызовах, как например cdecl. Delphi может использовать cdecl только в том случае, если число параметров фиксированное.

 

Хранение форм в DLL

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

Поскольку общие диалоговые окна скомпилированы в модуле, любые ваши приложения могут совместно использовать формы, которые там содержатся; это помогает сохранить немало места на диске. Как правило, формы доступны посредством специальных функций доступа. Функции доступа служат интерфейсом между загружающим процессом и библиотекой DLL. Такие функции обсуждаются ниже в разделе под названием «Функции доступа к объектам».

Поскольку все приложения совместно используют библиотеку DLL, все обращаются к одним и тем же функциям доступа. Это сводит обновление форм вашего приложения к простой операции установки новой библиотеки DLL.

 

 

Модальное диалоговое окно приложения

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

Модальная форма приложения, показанная на рис. 3, создается динамически в функции доступа к форме. Затем форма показывается пользователю. Пользователь должен согласиться с условиями лицензии или отказаться от продолжения. После того, как пользователь "проголосовал" нажатием кнопки, диалоговое окно уничтожается. Выбор пользователя возвращается в вызвавший процесс в виде целого числа.

 

Немодальное диалоговое окно

В библиотеках DLL модальные и немодальные диалоговые окна управляются по-разному. Пойдем вперед и добавим к проекту еще одну форму под названием NOMODAL; инструкции по добавлению новых форм в проект библиотеки DLL были даны выше в этом файле.

Предполагаемая форма NOMODAL является разновидностью диалогового окна. Диалоговое окно служит в проекте в качестве заставки. Оформите самостоятельно ее внешний вид изменением цвета, использованием графических изображений и т.п.

 

Функции доступа к объектам

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

Следуя библиотечной модели, внутреннее устройство библиотеки DLL должно быть упрятано в "черный ящик". Это означает, что объекты внутри нее должны инкапсулировать свои функциональные возможности. К примеру, если объект, хранящийся в вашей DLL — это форма, то весь код, необходимый ей для выполнения своих задач, должен быть "похоронен" внутри библиотеки. Более того, объекты, расположенные в DLL, должны быть созданы динамически — чтобы экономить память при загрузке. Чтобы добиться этого, библиотека DLL должна экспортировать функцию, которую вызывающий процесс может вызывать для создания экземпляра объекта. Аналогично, должна существовать функция для его освобождения.

Снова используем проект FIRSTDLL.

Вы создали модальное диалоговое окно приложения в файле DYNAWIN.PAS и немодальное — в файле NOMODAL.PAS. Хотя обе формы являются составными частями проекта, их экземпляры не создаются и обе формы не используются, пока (до настоящего момента) нет методов доступа к ним. Для начала мы обсудим функцию доступа к модальному диалоговому окну лицензионного соглашения.

 

Доступ к модальный формам приложения в библиотеках DLL

Метод доступа к модальному окну приложения должен обладать возможностью обнаруживать и обрабатывать ошибки, возникающие вследствие недостатка ресурсов. В первую очередь, функция отвечает за создание, отображение и удаление формы. Листинг 5 содержит метод доступа к диалоговому окну лицензионного соглашения, созданному ранее.

 

Листинг 5. ACCESS. РАЗ - метод доступа к диалоговому окну лицензионного соглашения, содержащий обработку ошибок

 

function ExecuteLicense(hWnd: THandle): integer;

var

iRetVal: integer;

begin

Application. Handle := hWnd;

Try

try

frmLicense := TfrmLicense. Create (Application);

iRetVal := frmLicense.ShowModal;

finally

frmLicense. Free;

end;

except

on E: Exception do

begin

ShowMessage ('Error Creating or Showing License: '+E .Message);

raise;

end;

end;

result := iRetVal;

end;

 

Функция названа ExecuteLicense, что выглядит логично в свете ее предназначения. В большом проекте легко забыть названия процедур собственного программного интерфейса. Используйте осмысленные имена для описания задачи. Начнем с прототипа функции:

function ExecuteLicense ( hWnd: THandle ): integer; stdcall; export;

Описание прототипа функции ExecuteLicense находится в секции interface модуля ACCESS. Как видите, ExecuteLicense экспортируется с использованием соглашения о вызовах stdcall. Это необходимо, чтобы сохранить совместимость с принятым по умолчанию в Windows соглашением. Функция нуждается в одном параметре типа THandle; это описатель окна. Возвращаемое значение — целое.

Эта возвращаемая целая величина представляет решение пользователя. Он может нажать кнопки I Agree (принимаю условия соглашения) или Cancel. Листинг 5 начинается с описания переменной iRetVal. В конце iRetVal содержит то, что будет передано вызвавшему процессу.

Значение hWnd, получаемое в качестве параметра, присваивается свойству Handle глобального объекта Application. Затем объект Application передается в конструктор TfrmLicense — таково имя класса для диалогового окна лицензионного соглашения.

 

Замечание

Использование параметра hWnd не является обязательным. Вы можете передать в конструктор TfrmLicense значение Nil. Однако при этом модальное поведение будет отличаться. Пусть вы во время выполнения приложения видите главное окно под модальным диалоговым окном; если теперь при помощи <Alt+Tab> вы переключитесь на другое приложение и затем, снова нажав <Alt+Tab>, вернетесь обратно, то увидите только модальное диалоговое окно. Главное окно теперь — позади приложения, на которое вы переключались. 'Это "плохой" стиль модального поведения. Присвоение Application. Handle и помещение объекта Application в конструктор TfrmLicense обеспечивает корректное поведение.

 

Если форма успешно создана, она показывается при помощи вызова метода ShowModal. В этом месте выполнение процедуры ExecuteLicense приостанавливается. Если форма не может быть создана (по причине недостатка памяти) или показана (по причине отсутствия ресурсов), генерируется исключительная ситуация.

Вследствие этого происходит переход к операторам, находящимся в секции finally. Секция finally содержит только вызов метода Free формы, который удаляет ее. Так сделано потому, что все операторы, создающие и показывающие форму, заключены в конструкцию try. .finally.

Поскольку конструкция try..finally на самом деле не обрабатывает исключительную ситуацию, та продолжает требовать обработки. Она происходит на следующем уровне — там, где находится конструкция try. .except. При этом выполняются операторы, стоящие за except.

except

on E: Exception do

begin

ShowMessage ('Error Creating or Showing License: '+E.Messages);

raise;

end;

 

Во время вызова ShowMessage() типовое диалоговое окно показывает текст предупреждения; затем оно снова появляется. Если вы помните, в файле FIRSTDLL.DLL установлен собственный глобальный обработчик исключительных ситуаций под названием TAppException.AppException. Процедура AppException служит следующим уровнем обработки таких ситуаций.

Если форма успешно создана и показана, пользователь увидит ее на своем экране. Он может принять условия лицензионного соглашения или отказаться. Результаты выбора пользователя присваиваются переменной iRetVal, значение которой и возвращается подпрограммой:

iRetVal := frmLicense. ShowModal;

Поскольку пользователю предоставляются только два варианта выбора, существуют два возможных возвращаемых значения. Метод OnСlick и I Agree диалогового окна лицензионного соглашения просто возвращает значение mrOk.

procedure TfrmLicense.ButtonlClick(Sender: TObject);

begin

ModalResult := mrOk;

end;

Метод OnClick кнопки Cancel возвращает значение mrCancel. procedure TfrmLicense.Button2Click(Sender: TObject);

begin

ModalResult := mrCancel;

end;

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

 

Доступ к немодальным формам в библиотеках DLL

Форма-заставка в модуле NOMODAL требует несколько большего труда с точки зрения методов доступа — потому, что показывается эта форма не как модальная.

В отличие от прошлого, модального примера, обработка не прекращается на время показа формы — переход к ней происходит немедленно. Это требует раздельных методов доступа для создания, показа, сокрытия и удаления формы.

Несмотря на большое количество методов, код все же будет не слишком сложным.

 

Создание немодальной формы

Листинг 6 показывает метод доступа, создающий немодальную форму. Отметьте, что код обработки исключительных ситуаций значительно короче, чем в прошлом модальном примере. Также обратите внимание, что имя класса формы — frmSplash.

 

Замечание

Поскольку это не модальное диалоговое окно приложения, то когда файл ЕХЕ показывает форму, пользователь может щелкнуть на заднем плане и форма скроется за главным окном. Этого «можно избежать, установив свойство формы FormStyle в fsStayOnTop. Это удерживает форму поверх других окон и в то же время не мешает им решать задачи в фоновом режиме.

Решением проблемы заставки в стиле Windows было бы создать для ее показа отдельный поток.

 

Листинг 6. ACCESS.PAS — Функция доступа CreateSplash возвращает значение False в случае неудачной попытки создания формы

{Эта функция создает экземпляр формы-заставки}

function CreateSplash (hWnd: THandle): Boolean;

var

bRetVal: Boolean;

begin

bRetVal := True;

Application. Handle := hWnd;

try

frmSplash:= TfrmSplash .Create (Application) ;

except

bRetVal := False

end;

result := bRetVal;

end;

Функция CreateSplash требует и использует параметр hWnd так же, как он используется в модальном примере. Однако возвращаемое значение теперь имеет тип Boolean. Загружающий модуль вызывает эту функцию перед остальными для создания экземпляра формы, позволяя форме создаваться динамически. Если заставка успешно создана, функция передает в вызвавшее приложение значение True. Приложение может проверить это значение и определить, какую обработку проделать дальше.

Если заставка не может быть создана, в центре внимания оказывается возникающая при этом исключительная ситуация. Конструкция try. .except перехватит ошибку. При этом выполняется код за оператором except; в нем возвращаемое значение просто устанавливается в False. Исключительная ситуация далее не обрабатывается и функция возвращает значение False.

 

Показ немодальной формы

Положим, что вызов CreateSplash вернул True, что означает успешное создание формы; теперь она может быть показана вызывающим приложением в любое время. Это делается путем вызова функции доступа ShowSplash (см. листинг 7).

 

Листинг 7. ACCESS.PAS - Функция доступа ShowSplash не требует параметров

{ Эта функция показывает экземпляр формы-заставки }

function ShowSplash: Boolean;

var bRetVal: Boolean;

begin

bRetVal := False;

if (frmSplash <> nil) then

begin

try

frmSplash. Show;

bRetVal := True;

except

bRetVal := False;

end;

end;

result := bRetVal;

end;

Код ShowSplash еще более прост. В нем сначала проверяется, создана ли форма-заставка. Если это так, происходит попытка показать ее с помощью метода show. Если показать форму нельзя, возникает исключительная ситуация. Конструкция try..except передает управление оператору bRetVal := False и происходит выход из функции. Если форма показана успешно, перед возвратом флаг bRetVal устанавливается в True.

Сокрытие немодальной формы

Метод доступа HideSplash прячет форму-заставку. Он не является принципиально необходимым, поскольку при удалении формы она перестает быть видимой. Однако введение функции HideSplash позволяет поддержать симметрию с функцией ShowSplash. Создав форму один раз, вы можете программно переключать ее из видимого в невидимое состояние при помощи этих функций, не создавая каждый раз заново. Код показан в листинге 8.

 

Листинг 8. ACCESS.PAS — функция доступа HideSplash еще менее сложна, чем CreateSplash

{ Эта функция скрывает экземпляр формы-заставки. }

function HideSplash: Boolean;

var '

bRetVal: Boolean;

begin

bRetVal := False;

if (frmSplash <> nil) then

begin

frmSplash. Hide ;

bRetVal := True;

end;

result := bRetVal;

end;

Вы могли отметить, что функции доступа все упрощаются и упрощаются. Эта функция инициализирует флаг результата значением False. Затем она проверяет, создана ли форма. Если это так, она вызывает метод формы Hide и устанавливает флаг в True. Если форма еще не создана, функция возвращает управление.

 

Удаление немодальной формы

Функция FreeSplash в листинге 9 работает почти так же, как и HideSplash. Разница в решаемой ими задаче. Если форма существует, она удаляется посредством вызова метода Free.

Листинг 9. ACCESS.PAS — функция FreeSplash освобождает память,

выделенную для формы

{ Эта функция удаляет экземпляр формы-заставки. }

function FreeSplash: Boolean;

var

bRetVal: Boolean;

begin

bRetVal := False;

if (frmSplash <> nil) then

begin

frmSplash. Free ;

bRetVal := True;

end;

result := bRetVal;

end;

 

Секция exports файла DPR

Методы доступа стали чуть-чуть полезнее, однако для компиляции кода нужны не только они. По-прежнему, есть необходимость внести некоторые изменения в код модуля ACCESS и файл FIRSTDLL. Чтобы функции правильно экспортировались из модуля, секция exports в FIRSTDLL.DPR должна выглядеть так:

exports AddValues, GetStrChar, BeepLoop, ExecuteLicense, CreateSplash, ShowSplash, HideSplash, FreeSpiash;

Секция uses в модуле ACCESS также претерпела некоторые изменения и дополнения:

uses Classes, SysUtils, Forms, Wintypes, DynaWin, Controls, Dialogs, Buttons, Windows, Messages, Graphics, ExtCtris, StdCtrls, CornCtrls, nomodal;

Все экспортируемые процедуры должны быть объявлены в секции interface модуля:

{ Экспортируемые функции доступа к форме. }

function ExecuteLicense ( hWnd: THandle ): integer; stdcall; export;

function CreateSplash (hWnd: THandle): Boolean; stdcall; export;

function ShowSplash: Boolean; stdcall; export;

function HideSplash: Boolean; stdcall; export;

function FreeSplash: Boolean; stdcall; export;

Снова обратите внимание на то, что экспортируемые функции используют Соглашение о вызовах stdcall. Пойдем дальше и скомпилируем проект. Избавьтесь от всех найденных ошибок. Вам может понадобиться применение к нашей библиотеке утилиты TDUMP для того, чтобы удостовериться в том, что функции экспортируются правильно.

 

Импорт и доступ к процедурам модуля

 

Вплоть до этого места вы занимались только написанием кода внутри DLL. Другим важным аспектом темы библиотек DLL является использование созданного модуля. Далее мы создадим две маленькие программы. Одна из них называется AUTOLOAD, другая — LOADER. Программа AUTOLOAD демонстрирует, как неявно загрузить модуль, созданный в проекте FIRSTDLL. Программа LOADER показывает, как загрузить FIRSTDLL явно.

 

Неявная загрузка в программе AUTOLOAD

Вспбмним из предыдущих примеров, что проект FIRSTDLL экспортирует три базовые процедуры собственного программного интерфейса. Программа AUTOLOAD использует их для неявной загрузки модуля FIRSTDLL. Механизм неявной загрузки проще альтернативного — явного — за счет того, что в нем DLL загружается системой. Недостаток его — в слабых возможностях управления загрузкой. Говоря в общем, при загрузке программы система пытается загрузить используемые ею библиотеки DLL. Если DLL не найдена, повреждена или что-либо еще, загрузка заканчивается аварийно и вы извещаетесь сообщением системы. Если загрузка прошла, библиотеку остается загруженной до завершения процесса. Короче говоря, она либо работает, либо нет.

Программа AUTOLOAD содержит единственную форму в AUTOU1.PAS. Содержимое модуля показано в листинге 10.

 

Листинг 10. AUTOU1.PAS — по большей части этот модуль поддерживает значения, принятые в Delphi по умолчанию

 

unit AutoU1;

interface

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;

type

TfrmAutoLoad = class (TForm)

Button1: TButton;

Button2: TButton;

Button3: TButton;

Button4: TButton;

procedure FormClose (Sender: TObject; var Action: TCIoseAction);

procedure Button4Click (Sender: TObject);

procedure Button1Click (Sender: TObject);

procedure Button2Click (Sender: TObject);

procedure Button3Click (Sender: TObject);

private

{ Private declarations }

public

{ Public declarations }

end; .

{ Function Declarations }

function imp_AddValues(iArgl: integer; iArg2: integer): integer; stdcall;

function GetStrChar(sVal: string; iIndex: integer): char; stdcall;

procedure BeepLoop(iNumTimes: integer); stdcall;

var

frmAutoLoad: TfrmAutoLoad;

implementation

{$R *.DFM}

const

FirstDll = 'FIRSTDLL. DLL';

function imp_AddValues; external FirstDll name 'AddValues';

function GetStrChar; external FirstDll;

procedure BeepLoop; external FirstDll;

procedure TfrmAutoLoad. FormClose (Sender: TObject; var Action: TCloseAction) ;

begin

Action := caFree;

end;

procedure TfrmAutoLoad. Button1Click (Sender: TObject);

begin

ShowMessage (IntToStr (imp_AddValues (5,5)));

end;

procedure TfrmAutoLoad.Button2Click (Sender: TObject);

begin

ShowMessage (GetStrChar ('Hello World' , 5) ) ;

end;

procedure TfrmAutoLoad.Button3Click(Sender: TObject);

begin

BeepLoop (5);

end;

procedure TfrmAutoLoad.Button4Click(Sender: TObject);

begin

Close;

end;

end.

Практически все содержимое модуля является типовым. Имя модуля расположено в верхней строке. Секция interface содержит используемые модули в операторе uses. Класс формы описан в секции type. Переменная с типом формы находится в секции var. Единственным нестандартным дополнением в секции interface служат прототипы импортируемых функций.

{ Function Declarations }

function imp_AddValues (iArgl: integer; iArg2: integer): integer; stdcall;

functionGetStrChar(sVal: string; iIndex: integer): char; stdcall;

procedure BeepLoop(iNumTimes: integer); stdcall;

Обратите внимание на то, что прототипы выглядят точно так же, как они выглядели в экспортирующем их модуле FIRSTDLL, зa одним исключением — в конце описания нет директивы export.

Первое, что попадается на глаза в секции implementation модуля — это описание константы со значением 'FIRSTDLL.DLL':

const FirstDil = 'FIRSTDLL.DLL';

Эта константа задает имя загружаемой библиотеки. Убедитесь в том, что задали расширение, используемое в вашей библиотеке; расширение DLL не принимается по умолчанию. Константа First далее используется в коде, отвечающем за импорт. Ниже показаны операторы, использующие константу FirstDll после ключевого слова external:

function AddValues; external FirstDll;

function GetStrChar; external FirstDll;

procedure BeepLoop; .external FirstDll;

В первую очередь определите, что вы хотите импортировать — функцию или процедуру — при помощи соответствующих ключевых слов function или procedure. После этого укажите точное имя функции или процедуры с точкой с запятой в конце. Затем допишите директиву external с именем модуля, в данном случае — с константой FirstDll.

 

Действительное имя импортируемой функции такое же, под каким она экспортируется из библиотеки DLL. Иногда это может вызвать проблемы. Если вы используете модуль, экспортирующий функцию с тем же именем, что и функция в вашем собственном коде, то ваш код пострадает от ухудшения читабельности, а это вполне вероятно и приведет к проблемам. Delphi предлагает альтернативный синтаксис для функции Addvalues.

Прототип в секции interface может быть видоизменен следующим образом:

function imp_AddValues(iArgl: integer; iArg2: integer): integer; stdcall;

Описание импорта в секции implementation может выглядеть так:

function imp_AddValues; external FirstDll name 'AddValues';

Базируясь на этом, вы можете теперь обращаться к imp_AddValues вместо AddValues.

Остаток секции implementation модуля содержит код методов OnClick для каждой из кнопок. Каждая кнопка приводит к запуску одной из импортируемых функций.

Вспомните, как вы вставляли код в секцию инициализации модуля FIRSTDLL.DPR. В этом коде сначала отображается появляющееся один раз сообщение о том, что должна быть приобретена зарегистрированная версия библиотеки DLL. Код также присваивает глобальному указателю DllProc адрес функции DIIMain. Код также извещает вас о значении параметра fdwReason каждый раз, когда вызывается DIIMain. Помните о том, чего ожидать во время исполнения программы AUTOLOAD. Пойдем дальше и выполним код.

1.Сначала появится сообщение о регистрации .

2.Нажмите ОК. Далее вы получите сообщение от DllMain

3.Снова нажмите ОК. Наконец, появится главная форма AUTOLOAD

Kнопка Addvalues ведет к выполнению метода Button1Click. Метод Button1Click после AddValues вызывает ShowMessage(). Addvalues возвращает сумму двух целых чисел; таким образом, результат должен быть преобразован в читаемую форму с использованием IntToStr. Передаваемые, в AddValues значения равны (5,5). После нажатия на Button1 диалоговое окно появится и покажет сумму — 10.

Метод Button2Click выполняется при нажатии на кнопку GetStrChar. Этот метод вызывает ShowMessage(), при этом вызывая импортированную функцию GetStrChar. GetStrChar возвращает символ из строки, переданной в первом параметре. Возвращаемый символ — это символ, стоящий в строке на определенном месте, заданном вторым параметром. Другими словами, GetStrChar возвращает "о", являющийся пятым символом в строке "Hello, World".

Метод Button3Click вызывает импортированную функцию BeepLoop, передавая ей значение 5. Когда вы нажмете на кнопку Button3, вы услышите пять сигналов с вашего динамика (или другого устройства для воспроизведения звука).

Метод Button4Click вызывает метод формы Close и завершает программу. Во время выгрузки программы функция DllMain в FIRSTDLL будет вызвана со значением параметра, равным DLL_PROCESS_DETACH. Затем DllMain известит вас об этом диалоговым окном

 

Явная загрузка в программе LOADER

Явная загрузка модуля подразумевает большие возможности программного управления загрузкой библиотеки DLL. Этот метод позволяет программисту динамически загружать и выгружать модуль во время выполнения приложения в любом месте кода, что и реализовано в программе LOADER.

Листинг 11 показывает секцию implementation модуля LOADERU1, включенного в проект. Полный листинг попробуйте написать сами.

 

Листинг 11. LOADERU1.PAS — секция implementation модуля, который содержит главную форму программы LOADER

implementation

{$R *.DFM}

procedure TfrmLoader.FormClose (Sender: TObject; var Action: TCIoseAction);

begin

Action := caFree;

end;

procedure TfrmLoader.Button1Click (Sender: TObject);

var

dwErr: DWORD;

begin

hDLLInst := 0;

hDLLInst : = LoadLibrary ( ' FIRSTDLL ' ) ;

if (hDLLInst = 0) then

begin

dwErr : = GetLastError ( ) ;

if (dwErr = ERROR_DLL_NOT_FOUMD) then { value of 1157 }

ShowMessage ( ' Error: ' +IntToStr (dwErr) + '. Cannot find

FIRSTDLL or a module required by FIRSTDLL');

end;

end;

procedure TfrmLoader.Button2Click(Sender: TObject) ;

begin

if (hDLLInst <> 0) then

FreeLibrary(hDLLInst);

end;

procedure TfrmLoader.Button3Click(Sender: TObject);

begin

@ExecuteLicense := GetProcAddress (hDLLInst, 'ExecuteLicense');

if (@ExecuteLicense <> nil) then

ExecuteLicense (Self. Handle) ;

end;

procedure TfrmLoader.Button4Click (Sender: TObject);

begin

@CreateSplash := GetProcAddress (hDLLInst, ' CreateSplash ');

CreateSplash (Self .Handle) ;

@ShowSplash :== GetProcAddress (hDLLInst, 'ShowSplash ');

ShowSplash;

end;

procedure TfrmLoader.Button5Click(Sender: TObject);

begin

@HideSplash:=GetProcAddress (hDLLInst, 'HideSplash');

HideSplash;

@FreeSplash:= GetProcAddress (hDLLInst, 'FreeSplash ');

FreeSplash;

end;

end. ___

Файл LOADERU1.PAS совершенно тривиален. Секция interface содержит все ожидаемые элементы, встречающиеся в модуле формы. Единственное дополнение к тому, что сгенерировано Delphi, находится в секции var, не показанной в листинге 11. Речь идет о следующем:

var

hDI.LInst: THandle;

ExecuteLicense: function( hWnd: THandle ): integer stdcall;

CreateSplash: function (hWnd: THandle): Boolean stdcall;

ShowSplash: function: Boolean stdcall;

HideSplash: function: Boolean stdcall;

FreeSplash: function: Boolean stdcall;

Первым идет описание переменной hDLLInst типа THandle. Явная загрузка возможна благодаря вызову функции Windows API Loadlibrary. Она возвращает описатель; этот описатель поддерживается системой внутри области видимости загрузившего процесса. Это возвращаемое значение присваивается hDLLInst.

В секции vаr затем описываются переменные типа функция, которые соответствуют каждой функции, которая экспортируется из DLL. В конце каждого описания стоит директива stdcall. Обратите внимание на то, что между описанием типа возвращаемого результата и директивой stdcall отсутствует точка с запятой.

В секции модуля implementation нет ничего необычного. Как и следовало ожидать, она содержит набор методов главной формы frmLoader модуля LOADER.

Пойдем дальше и запустим программу.

Нажмите на кнопку Load FIRSTDLL. Во время первой загрузки вы сначала увидите диалоговое окно с сообщением о регистрации. После нажатия ОК процедура DllMain отобразит сообщение, показывающее, что параметр fdwReason установлен в DLL_PROCESS_ATTACH.

Появление этих сообщением является надежным признаком того, что библиотека FIRSTDLL на самом Деле загружена. Рассмотрим более подробно код, связанный с методом Buttoniclick.

Загрузка DLL

Первым требующим комментария действием является вызов функции boadLibrary. Просто передайте ей имя библиотеки DLL, в данном случае FIRSTDLL.

hDLLInst : = LoadLibrary ( ' FIRSTDLL ' ) ;

Задание расширения имени файла DLL является необязательным; если оно опущено, то подразумевается DLL. Если вызов LoadLibrary был успешным, то он возвращает описатель модуля. Если он завершился аварийно, возвращается 0. Затем метод проверяет, на самом ли деле вызов LoadLibrary был аварийным.

If ( hDllInst = 0 ) then

Если значение hDlllnst равно нулю, вызывается функция API GetLastError. Она возвращает двойное слово (тип DWORD), содержащее код ошибки. Обычно во время неудачного вызова LoadLobrary библиотека DLL не может быть найдена или не может быть найдена другая DLL, вызываемая из вашей (как например, DELPIMM.DLL).

 

 

 

 

 

 

 

 

 

 

Hosted by uCoz