Создание и использование динамически загружаемых библиотек в Delphi. Преимущества использования, создание простейшей DLL. Статическая и динамическая загрузка DLL, обмен данными с ней. Создание программы, работающей с базой данных клиентов кардиоцентра.
Если метод, в свою очередь, вызывает другой метод, то локальные переменные второго метода добавляются в стек, так же как и список параметров. Например, в C стек очищается в методе, который вызвал второй метод, после окончания его работы. Если *.exe-модуль, созданный на языке C , вызывает метод из DLL, созданный на Delphi, то перед окончанием работы метода в DLL стек будет очищен. Для 32-разрядных приложений Windows API методы реализованы таким образом, что параметры помещаются в стек справа налево и стек очищает вызываемый метод; при этом не используются регистры процессора для передачи данных. При статической загрузке для вызова другого модуля следует в какой-либо из секций описать метод из DLL следующим образом: function Add1(K:integer):integer; stdcall; external "FIRSTLIB.dll" name "CALCULATESUM";С помощью динамически загружаемых библиотек можно оптимизировать ресурсы, необходимые для выполнения приложений; использовать в проектах модули, написанные на различных языках программирования; создавать проекты, которые могут иметь необязательные функции и пункты меню.Главный интерфейс приложения: В отдельную библиотеку была выделена немодальная форма, отвечающая за построение дерева посещений пациентом кардиоцентра. Для построения дерева посещений по выбранному пациенту в библиотеку передается код пациента.
План
Оглавление
Глава 1. Создание и использование динамически загружаемых библиотек в Delphi
Введение
Создание простейшей DLL. Соглашения о вызовах методов
Статическая и динамическая загрузка DLL
Обмен данными с DLL
Вызов методов приложения в DLL
Немодальные формы в DLL
Заключение
Глава 2. Программная реализация
Список литературы
Глава 1. Создание и использование динамически загружаемых библиотек в Delphi
Введение
Динамически загружаемые библиотеки (dynamic-link libraries, DLL) являются одним из наиболее мощных средств создания приложений в Windows. По структуре данных DLL напоминает приложение - exe-файл, но в отличие от *.exe-приложения код в DLL не может выполняться самостоятельно. DLL (как и *.exe-файл) можно загрузить в память компьютера, и работающие приложения могут вызвать методы, экспонируемые в DLL. На основе DLL создаются также элементы управления ACTIVEX.
Преимущества использования DLL: 1. Методы, описанные в DLL, могут одновременно обслуживать несколько приложений. При этом сами методы хранятся в виде одной копии в ОЗУ. Если вызываемый код достаточно велик и имеется несколько приложений, которые вызывают данный код, то вследствие этого можно достичь существенной экономии системных ресурсов.
2. Возможность хранения общих ресурсов. Если несколько приложений работают с одними и теми же ресурсами (например, большие растровые картинки - *.bmp), то при сохранении их в DLL можно иметь эти ресурсы в одном экземпляре.
3. Поддержка новых версий приложений. Если программист сделал какие-либо изменения в реализациях методов, определенных в DLL, то конечному потребителю достаточно передать новую версию DLL- и *.exe-файл можно сохранить прежним. Это особенно актуально сейчас, когда приложения можно обновлять с помощью Internet. В этом случае важно снизить количество информации, посылаемой по Сети. Естественно, что если часть кода реализована в DLL, то при загрузке с сервера только этой DLL трафик, связанный с обновлением версии приложения, будет уменьшен.
4. Возможно использовать различные языки программирования для создания *.exe и *.dll. Например, *.ex-файл может компилироваться из кода, написанного на Delphi, а *.dll, которая им используется, компилируется из кода, написанного на Microsoft Visual C . Если приложение использует несколько DLL, то они могут быть созданы на различных языках программирования. Это значительно упрощает перенос кода в другие приложения.
5. DLL можно загружать в память только тогда, когда они требуются для выполнения приложений, реализуя тем самым динамическую загрузку. При этом опять же достигается экономия системных ресурсов. Так, например, в DLL можно реализовать диалог, с помощью которого изменяются какие-либо параметры приложения. Пользователь может месяцами не обращаться к данному диалогу, и при этом DLL, в которой он реализован, не загружается в память и не потребляет системных ресурсов. Только в момент, когда пользователь обращается к этому диалогу, происходит загрузка DLL в память, но после выполнения диалога эта память освобождается. Используя динамическую загрузку, можно оформить динамический пользовательский интерфейс: соответствующие опции меню приложения появляются в том случае, если найдена данная DLL, и исчезают при ее отсутствии. Такой интерфейс удобен при поставке приложений, в которых пользователь может заказать дополнительные возможности за отдельную плату.
Создание простейшей DLL. Соглашения о вызовах методов
В состав Delphi входит эксперт для создания DLL, который вызывается при выборе команды File/New и пиктограммы DLL на странице New репозитария объектов. При этом возникает заготовка для реализации DLL: library FIRSTLIB;
uses
SYSUTILS, Classes;
begin end.
В приведенном выше коде отсутствует текстовый комментарий, который генерируется экспертом. Заготовка отличается от заготовки для создания кода *.exe-файла тем, что используется служебное слово Library вместо Program. Кроме того, отсутствуют обращение к методам объекта TAPPLICATION (хотя экземпляр этого объекта в действительности создается в DLL!), а также модуль реализации главной формы.
Когда в приложении осуществляется вызов метода, его параметры (как и локальные переменные) помещаются в стек. Стек, представляющий собой зарезервированное место в ОЗУ компьютера, имеет указатель текущей позиции, который при старте приложения устанавливается на начало стека. При вызове метода в стек помещаются все локальные переменные и параметры метода, при этом указатель текущей позиции стека смещается вправо в соответствии с размером помещаемых в него данных. Если метод, в свою очередь, вызывает другой метод, то локальные переменные второго метода добавляются в стек, так же как и список параметров. После окончания работы второго метода происходит освобождение области памяти в стеке - для этого указатель текущей позиции стека смещается влево. И наконец, после окончания работы первого метода указатель текущей позиции стека смещается в первоначальное положение.
Ясно, что если приложение работает нормально, то после окончания выполнения цепочки методов указатель текущей позиции стека должен вернуться в первоначальное состояние, то есть созданный стек должен быть очищен (stack cleanup). Если же указатель не возвращается, то происходит крах стека (stack crash) - этот термин не следует путать с очисткой стека. В этом случае приложение прекращает свою работу (никакие ловушки исключений не помогают) и, если оно выполняется под Windows 95 или Windows 98, чаще всего требуется перезагрузка операционной системы. Понятно, что возврат указателя стека в первоначальное состояние должен происходить по окончании работы метода. Но при этом существуют две возможности - возврат указателя на место может производить как вызываемый метод по окончании работы, так и вызывающий метод после завершения работы вызываемого метода. В принципе, в различных языках программирования реализуются обе указанные возможности - очищать стек могут и вызванный, и вызывающий методы. Поскольку модуль пишется на одном языке программирования, то эти проблемы скрыты от программиста: очистка стека производится по специфичному для данного языка протоколу. Но если используются различные модули, код для которых реализован на различных языках программирования, то возникают проблемы. Например, в C стек очищается в методе, который вызвал второй метод, после окончания его работы. В Delphi же стек очищается в том же самом методе, где он используется, перед окончанием его работы. Если *.exe-модуль, созданный на языке C , вызывает метод из DLL, созданный на Delphi, то перед окончанием работы метода в DLL стек будет очищен. После этого управление передается модулю, реализованном на C , который также попытается очистить стек, - такое действие приведет к краху стека.
Кроме этих возможностей, существует и другой способ, а именно: последовательность помещения в стек параметров метода. Предположим, имеется метод, который использует для работы два параметра: procedure DOSOMETHING(N:integer; D:TDATETIME);
Указанный способ заключается в том, что сначала в стек может быть помещена константа N, а затем D (слева направо) или вначале помещается константа D, а затем N (справа налево). Кроме того, некоторые языки программирования (в частности, Delphi) часть параметров метода вообще не помещают в стек, а передают их через регистры процессора. К тому же в разных языках программирования параметры могут помещаться в стек как слева направо, так и справа налево. Если они были помещены слева направо, а вызываемый метод будет читать справа налево, то получится путаница: в качестве значения константы N вызываемый метод будет считать значение правой половины константы D, а константу D он будет формировать из константы N и левой половины D.
По этой причине в любом языке программирования предусмотрена возможность объявить, какой из методов - вызываемый или вызывающий, будет очищать стек и в какой последовательности параметры метода помещаются в стек. Такое объявление называется соглашением вызова (calling conversion).
Для методов, экспонируемых в DLL, рекомендуется (но не обязательно) использовать то же соглашение вызова, что и в Windows API. Для 32-разрядных приложений Windows API методы реализованы таким образом, что параметры помещаются в стек справа налево и стек очищает вызываемый метод; при этом не используются регистры процессора для передачи данных. Этим условиям удовлетворяет директива stdcall. Если после заголовка метода отсутствует соглашение о вызове, то по умолчанию Delphi использует соглашение register.
Второе служебное слово в заголовке метода - export - информирует компилятор о том, что код для данного метода должен быть создан таким образом, чтобы его можно было вызывать из других модулей. Эта директива требуется при реализации DLL в Delphi 3; в Delphi 4 и 5 ее можно опустить.
Для того чтобы внешний модуль мог выбрать конкретный метод, в DLL должна присутствовать специальная секция, которая имеет заголовок exports.
Для экспонирования метода в секции exports просто приводится его название, после которого следует либо служебное слово index с целочисленным идентификатором после него (идентификатор должен быть больше нуля), либо служебное слово name с текстовым идентификатором, либо оба вместе. Внешний модуль может обращаться к конкретному методу как по индексу, так и по имени.
Статическая и динамическая загрузка DLL
Модуль может вызывать методы другого модуля, а тот, в свою очередь, - следующего и т.д. Например, приложение вызывает DLL, а эта DLL вызывает методы другой DLL: так можно формировать длинные цепочки вызовов. Для вызова метода из другого модуля необходимо сначала загрузить его в память, а затем определить адрес метода. Существует два способа загрузки и определения адреса метода - статический и динамический.
При статической загрузке для вызова другого модуля следует в какой-либо из секций описать метод из DLL следующим образом: function Add1(K:integer):integer; stdcall; external "FIRSTLIB.dll" name "CALCULATESUM";
или function Add1(K:integer):integer; stdcall; external "FIRSTLIB.dll" index 1;
При таком определении метода DLL будет загружена немедленно после старта приложения и выгружена вместе с его завершением. В приведенном выше примере следует обратить внимание на то, что после имени динамической библиотеки указано ее расширение (FIRSTLIB.dll). Такая конструкция необходима для загрузки библиотеки в Windows NT, поскольку без расширения *.dll файл не будет найден! В Windows 95 расширение не обязательно.
При поиске DLL для загрузки первоначально определяется, была ли данная DLL уже загружена в память другим модулем. Если была - то извлекается адрес метода и передается приложению. Если же нет - то операционная система начинает ее поиск на диске. При этом, если путь при имени DLL не указан в явном виде, система ищет библиотеку в каталоге модуля, который старается загрузить DLL. Если не находит, то продолжает поиски в директориях WINDOWS и WINDOWS\SYSTEM (или WINNT, WINNT\SYSTEM, WINNT\SYSTEM32). После этого происходит поиск в каталогах, определенных в переменной среды Path. Если библиотека с заданным именем будет найдена, то она загрузится и приложение стартует. Если же нет - происходит исключение и приложение прекратит свою работу. Приложение прекращает работу также и в том случае, если не будет найден метод с данным именем (или индексом, если он импортируется по индексу).
Динамическая загрузка DLL позволяет загружать библиотеку только в тот момент, когда она требуется. Кроме того, если не будет найдена библиотека или метод, то это можно проанализировать и запустить приложение и в этом случае. Конечно, в такой ситуации следует информировать пользователя о невозможности вызвать метод из DLL - например, сделав невидимым элемент меню, который обращается к данному методу. Пример динамической загрузки DLL выглядит следующим образом: type
end else SHOWMESSAGE("Method with name CALCULATESUM was not found");
end else SHOWMESSAGE("Can not load library FIRSTLIB.dll");
finally if HLIB>HINSTANCE_ERROR then FREELIBRARY(HLIB);
end;
end;
Первоначально определяется новый процедурный тип, например TADDFUNCTION, который имеет такой же список параметров и такие же договоренности вызова, что и метод в DLL. Delphi в процессе компиляции приложения никак не может проверить соответствие процедурного типа методу, вызываемом из DLL. Проверка может быть осуществлена только во время выполнения, а при несоответствии формальных параметров или неврно указанной договоренности вызова происходит крах стека, что программист обнаружит очень быстро.
Далее в коде приложения вызывается метод LOADLIBRARY, который в качестве параметра использует имя библиотеки. После успешной отработки данного метода и загрузки библиотеки в память указатель на загруженную библиотеку помещается в переменную HLIB. Если библиотеку не удается найти (или загрузить), то в эту же переменную помещается код ошибки. Чтобы определить, была ли загружена библиотека, переменную HLIB следует сравнить с константой HINSTANCE_ERROR, которая определена в модуле Windows. Если библиотека была успешно загружена, то осуществляется попытка найти адрес метода в памяти компьютера при помощи вызова метода GETPROCADDRESS. Указанный метод возвращает адрес того метода, имя которого указано во втором параметре GETPROCADDRESS. Если же метод не был найден, то возвращается nil-адрес.
Соответственно вызов метода Add1 осуществляется только в случае, если он был успешно найден. Затем, поскольку при загрузке библиотеки были заняты системные ресурсы, их необходимо вновь вернуть операционной системе, выгрузив библиотеку из памяти. Для этого вызывается метод FREELIBRARY. При загрузке DLL производится подсчет ссылок, а именно: при каждом успешном обращении к методу LOADLIBRARY в DLL счетчик ссылок увеличивается на единицу, а при каждом вызове метода FREELIBRARY счетчик ссылок уменьшается на единицу. Как только счетчик ссылок станет равным нулю, библиотека выгружается из памяти компьютера. Следовательно, каждому успешному вызову LOADLIBRARY должно соответствовать обращение к FREELIBRARY - иначе DLL не выгрузится из памяти компьютера даже после окончания работы приложения. Поэтому данные методы были помещены в защищенный блок try…finally..end; - это гарантирует вызов метода FREELIBRARY, если происходит исключение.
Теперь следует рассмотреть, каким образом загружаемая DLL размещается в ОЗУ компьютера. При загрузке DLL осуществляется резервирование памяти, необходимое для хранения кода методов. Кроме того, резервируется место для всех глобальных переменных и выполняются секции инициализации в модулях DLL. Если другой процесс также пытается загрузить DLL, то вновь происходит резервирование памяти для хранения глобальных переменных. Однако копирование методов DLL не осуществляется; также не выполняется и секция инициализации. Другими словами, одна копия метода в ОЗУ обслуживает несколько приложений. Глобальные переменные являются уникальными для каждого приложения, и если одно приложение изменит их значение при помощи вызова какого-нибудь метода, то другое приложение этого не заметит. Поскольку секция инициализации выполняется только при первой загрузке DLL, ее нельзя использовать для установки начальных значений глобальных переменных. Вышесказанное необходимо учитывать при разработке DLL.
Обмен данными с DLL
DLL имеет общее адресное пространство с приложением, из которого вызываются его методы. Из этого следует, что указатель на какой-либо объект в памяти DLL является легальным внутри приложения, и наоборот. Это позволяет передать, например, адрес метода или адрес данных, чего нельзя сделать без использования маршрутизации при взаимодействии двух приложений. Имеется, однако, существенное отличие от передачи данных между двумя методами одного модуля: в различных модулях разными являются и диспетчеры памяти (memory manager). Это означает, что если в каком-то модуле (например, в DLL) был вызван метод GETMEM, то освободить системные ресурсы вызовом метода FREEMEM можно только в том же самом модуле. Если попытаться вызвать метод FREEMEM в приложении (для примера выше), то происходит исключение. Поскольку при создании экземпляров класса также происходит обращение к диспетчеру памяти, то их деструкторы нельзя вызвать за пределами модуля. В частности, если в DLL происходит исключение, то создается объект в диспетчере памяти DLL. Если не использовать ловушки исключений, то этот объект попадает в приложение и после показа пользователю сообщения приложение попытается его разрушить. В результате вновь произойдет исключение. Поэтому все экспонируемые в DLL методы, в которых могут произойти исключения, должны иметь ловушку исключения: Типичный обмен текстовой информацией с DLL выглядит следующим образом. Если необходимо передать какую-либо строку в метод DLL, то можно просто использовать в методе указатель на строку: procedure SENDSTRING(P:pchar); stdcall; external "FIRSTLIB.dll" name "SENDSTRING";
procedure TFORM1.Button5Click(Sender: TOBJECT);
var
S:string;
begin
S:="This test string will be sended to DLL";
SENDSTRING(pchar(S));
end;
Метод SENDSTRING в DLL реализован следующим образом: procedure SENDSTRING(P:pchar); stdcall; export;
var
S:string;
begin
S:=STRPAS(P);
SHOWMESSAGE(S);
end;
При запуске данного примера появится сообщение, которое показывает содержимое строки, созданной в *.exe-модуле. Для того чтобы получить текстовую информацию из DLL, в приложении обычно создается буфер, который заполняется в DLL. Естественно, размер буфера должен быть достаточным для хранения всей текстовой информации. Чтобы обезопасить буфер от переполнения, вместе с буфером в качестве параметра чаще всего посылается его размер.
Вызов методов приложения в DLL
Ранее мы рассматривали только такие варианты, когда методы DLL вызываются из приложения. Но часто требуется, чтобы DLL вызывала методы приложения, например для нотификационных сообщений.
Если необходимо вызвать из приложения метод, который при этом не является методом класса, то достаточно передать указатель на метод в DLL: var
NSUM:integer=0;
function CALCULATESUM(RETURNCALLBACK:pointer):integer; stdcall;
external "FIRSTLIB.dll" name "Sum";
function GETNEXTVALUE:integer; stdcall;
begin if NSUM<200 then begin
Inc(NSUM);
Result:=NSUM;
end else Result:=-1;
end;
procedure TFORM1.Button9Click(Sender: TOBJECT);
begin
Caption:=INTTOSTR(CALCULATESUM(@GETNEXTVALUE));
end;
В приложении создается метод (не метод класса!), адрес которого передается в DLL. Для того чтобы данный метод можно было вызывать из DLL, созданных на других языках программирования, желательно использовать соглашение stdcall вызова при реализации указанных методов (так называемые callback-методы). Использование вызова метода в DLL можно проиллюстрировать на таком примере: type
TRETURNNEXTMETHOD=function:integer; stdcall;
function CALCULATESUM(RETURNCALLBACK:pointer):integer; stdcall; export;
var
N:integer;
RETURNNEXT:TRETURNNEXTMETHOD;
begin
Result:=0;
if RETURNCALLBACK=nil then Exit;
N:=0;
RETURNNEXT:=TRETURNNEXTMETHOD(RETURNCALLBACK);
while N>=0 do begin
N:=RETURNNEXT;
if N>=0 then Result:=Result N;
end;
end;
При необходимости вызвать метод объекта следует учитывать, что данный метод объекта характеризуется двумя адресами - адресом метода и адресом данных. Следовательно, необходимо передавать два указателя. Но вместо передачи двух указателей можно воспользоваться структурой TMETHOD, определенной в модуле SYSUTILS.pas: type
TMETHOD = record
Code, Data: Pointer;
end;
Код в приложении для приведенного выше примера выглядит так: type
TGETNEXTVALUEOBJECT=function:integer of object; stdcall;
function CALCULATESUMOBJECT(Method:TMETHOD):integer; stdcall;
external "FIRSTLIB.dll" name "SUMOBJECT";
function TFORM1.GETNEXTVALUEOBJECT:integer; stdcall;
Код в DLL, осуществляющий вызов метода объекта, выглядит следующим образом: type
TRETURNNEXTMETHODOBJECT=function:integer of object; stdcall;
function CALCULATESUMOBJECT(Method:TMETHOD):integer; stdcall; export;
var
N:integer;
RETURNNEXT:TRETURNNEXTMETHODOBJECT;
begin
Result:=0;
N:=0;
RETURNNEXT:=TRETURNNEXTMETHODOBJECT(Method);
while N>=0 do begin
N:=RETURNNEXT;
if N>=0 then Result:=Result N;
end;
end;
Следует учитывать, что методы объекта являются зависимыми от языка, то есть в разных языках программирования генерируются различные способы передачи данных в метод объекта. Поэтому данный пример следует использовать только в случае, если и приложение, и DLL написаны на Delphi (или на одном и том же языке программирования).
И наконец, само приложение может экспонировать методы таким же способом, что и DLL. В приложении можно создать секцию exports и объявить имена (и/или индексы) методов. После этого в DLL можно воспользоваться методом GETPROCADDRESS для получения указателя на метод и вызвать его. Для описанного выше примера код приложения будет такой: function GETNEXTVALUEEXPORT:integer; stdcall; export;
begin if NSUM<10 then begin
Inc(NSUM);
Result:=NSUM;
end else Result:=-1;
end;
function CALCULATESUMEXPORT(HINSTANCE:integer; METHODNAME:pchar):integer; stdcall;
exports {!! The section is announced in Application !!}
GETNEXTVALUEEXPORT index 1 name "GETNEXTVALUEEXPORT";
Теперь в приложении (в проекте, где будет генерироваться *.exe-файл[U2] [NE3] ) определена секция exports! Соответствующий код в DLL для тестирования данного метода выглядит следующим образом: type
При запуске этого проекта приложение автоматически загружает DLL и находит адрес метода в DLL CALCULATENEXTEXPORT (который экспортируется по имени SUMEXPORT). Загруженная DLL в качестве параметров получает заголовок модуля приложения и имя метода, под которым [U4] он экспортируется в приложении. Далее DLL использует метод GETPROCADDRESS для нахождения адреса метода, экспортируемого приложением. Для того чтобы был возвращен адрес метода, в приложении объявляется секция exports, где описано внешнее имя метода. Этот пример показывает формально одинаковые структуру и способ формирования *.exe- и *.dll-файлов.
Немодальные формы в DLL
Показ немодальных форм традиционно осуществляется со статической загрузкой DLL. Вот типичный код для показа немодальной формы в DLL: procedure SHOWNONMODALFORM(APPHANDLE:THANDLE); stdcall; export;
begin
Application.Handle:=APPHANDLE;
with TFORM1.Create(Application) do Show;
end;
Как и при показе модальных форм, необходимо присвоить дескриптор окна главного приложения приложению, создаваемому в DLL, - иначе на панели задач будут показаны две иконки. Затем просто создается форма и вызывается ее метод Show. Такой способ показа немодальных форм приводит к тому, что из главного приложения можно неоднократно вызвать данный метод и тем самым создать несколько экземпляров форм. Зачастую это оправданно, поскольку одинаковые типы форм могут содержать, например, разные документы. Но при этом способе показа рекомендуется сделать обработчик события ONCLOSE для TFORM1 и присвоить параметру CLOSEACTION значение CAFREE - в противном случае при закрытии форма будет спрятана [NE5][U6] на экране без освобождения системных ресурсов.
Для показа единственного экземпляра немодальной формы следует немного изменить код: procedure SHOWSINGLENONMODALFORM(APPHANDLE:THANDLE); stdcall; export;
begin
Application.Handle:=APPHANDLE;
if Assigned(Form2) then Form2.Show else begin
Form2:=TFORM2.Create(Application);
Form2.Show;
end;
end;
Первоначально необходимо проверить, была ли создана форма ранее. Если была - просто вызывается ее метод Show; если нет - вызывается тот же метод после отработки конструктора. Вызов метода Show для уже созданного экземпляра формы имеет смысл, поскольку пользователь может обратиться к команде показа формы в тех случаях, когда уже имеющийся экземпляр перекрыт другими окнами и незаметен на экране, - использование команды Show приводит к всплытию формы. Переменная Form2 является глобальной переменной.
Оба описанных выше способа вызова немодальных форм не требуют создания специальной процедуры для их разрушения. Ресурсы будут корректно освобождены при закрытии приложения, так как приложение является владельцем форм. Код приложения для тестирования этих методов выглядит следующим образом: procedure SHOWNONMODALFORM(APPHANDLE:THANDLE); stdcall;
external "NMSTAT.dll" name "SHOWSINGLENONMODALFORM";
procedure TFORM1.Button1Click(Sender: TOBJECT);
begin
SHOWNONMODALFORM(Application.Handle);
end;
procedure TFORM1.Button2Click(Sender: TOBJECT);
begin
SHOWSINGLENONMODALFORM(Application.Handle);
end;
Иногда возникает необходимость показа немодальных форм из динамически загружаемых DLL, например при редком использовании в приложении немодальных форм для экономии ресурсов. Если реализовать код так же, как и при показе модальных диалогов, то форма будет создана и, может быть, даже показана на экране. Но после этого произойдет выгрузка DLL, а затем немедленно последуют исключения, поскольку в памяти компьютера будет отсутствовать код для работы с элементами управления формы. Традиционное решение этой проблемы выглядит следующим образом: загружается динамическая библиотека, в качестве одного из параметров передается адрес метода главного приложения, который будет вызван при закрытии немодальной формы - обычно в обработчике события ONDESTROY. Этот метод должен информировать главное приложение о необходимости выгрузки DLL из памяти компьютера, но DLL должна выгружаться после завершения его работы (и, следовательно, после завершения работы деструктора формы) - иначе возможно исключение изза отсутствия кода в памяти компьютера. Выгрузка DLL после завершения работы приложения [U7] достигается с использованием асинхронной развязки - посылки сообщения методом POSTMESSAGE какому-либо окну приложения, обычно главной форме. Приведем код реализации данной технологии в DLL: type
TNOTIFYCLOSE=procedure; stdcall;
TFORM1 = class(TFORM)
Memo1: TMEMO;
procedure FORMDESTROY(Sender: TOBJECT);
procedure FORMCLOSE(Sender: TOBJECT; var Action: TCLOSEACTION);
begin if FHLIB<=HINSTANCE_Error then FHLIB:=LOADLIBRARY("NMDYN1.dll");
if FHLIB>HINSTANCE_Error then begin
DM:=GETPROCADDRESS(FHLIB,"DYNNONMODAL");
if Assigned(DM) then DM(Application.Handle,@RECEIVECLOSENOTIFY);
end;
end;
Очевидно, что код получается довольно громоздким: в главном приложении необходимо реализовывать три метода вместо одного. Альтернативный вариант можно предложить исходя из того, что в DLL имеется объект TAPPLICATION, который может поддерживать цикл выборки сообщений. Но в DLL нельзя создать форму, используя метод TAPPLICATION. CREATEFORM, так как соответствующая закладка диалога Project/Options/Forms отсутствует в проектах Delphi 4 и 5 и неактивна в Delphi 3. Однако можно вызвать все методы объекта Tapplication, вручную дописав соответствующий код в DLL: procedure SHOWNMAPPLICATION; stdcall; export;
begin if Assigned(Form1) then begin
Form1.Show;
Exit;
end else begin
Application.Initialize;
Application.CREATEFORM(TFORM1, Form1);
Application.Run;
Form1.Free;
Form1:=nil;
end;
end;
Следует обратить внимание, что дескриптор главного приложения не присваивается в данном проекте дескриптору TAPPLICATION в DLL. Это реально приводит к появлению двух пиктограмм на панели приложений. Правда, в некоторых случаях это полезно - так легче добраться до перекрытых окон. Интересно отметить, что в Delphi 3 после написания данного кода становятся доступными элементы управления диалога Project/Options/Forms, где можно определить автоматически создаваемые формы и главную форму приложения. Код главного приложения, использующий данную DLL, таков: type
В отличие от предыдущего примера, динамическая загрузка DLL и ее выгрузка осуществляются в одном методе, да и объем написанного кода существенно меньше.
Вывод
С помощью динамически загружаемых библиотек можно оптимизировать ресурсы, необходимые для выполнения приложений; использовать в проектах модули, написанные на различных языках программирования; создавать проекты, которые могут иметь необязательные функции и пункты меню. Вызов методов из DLL не представляет трудностей, за исключением того, что следует обращать особое внимание на исключительные ситуации: не допускать попадания экземпляра - потомка Exception в главный модуль, обязательно вызывать команду FREELIBRARY при наличии исключений.
Список литературы
программа база данных
1.
2.
3.
Размещено на .ru
Вы можете ЗАГРУЗИТЬ и ПОВЫСИТЬ уникальность своей работы