Использование и создание DLL в Delphi
Использование и создание DLL в Delphi
Использование и создание DLL в Delphi Введение В связи с бурным развитием технологий программирования, все больше людей сталкиваются с проблемой наращивания возможностей своих программ. Данная статья посвящена именно этому вопросу, а именно - программирование dll в borland delphi. Кроме того, так как мы затронем вопросы по использованию библиотек dll, то попутно коснемся импортирования функций из чужих dll (в том числе и системных, т.е. winapi). Области применения dll Итак, зачем же нужны библиотеки dll и где они используются?.. Перечислим лишь некоторые из областей их применения: Отдельные библиотеки, содержащие полезные для программистов дополнительные функции. Например, функции для работы со строками, или же - сложные библиотеки для преобразования изображений. Хранилища ресурсов. В dll можно хранить не только программы и функции, но и всевозможные ресурсы - иконки, рисунки, строковые массивы, меню, и т.д. Библиотеки поддержки. В качестве примера можно привести библиотеки таких известных пакетов, как: directx, icqapi (api для icq), opengl и т.д. Части программы. Например, в dll можно хранить окна программы (формы), и т.п. Плагины (plugins). - Вот где настоящий простор для мыслей программиста! Плагины - дополнения к программе, расширяющие ее возможности. Например, в этой статье мы рассмотрим теорию создания плагина для собственной программы. Разделяемый ресурс. dll (dynamic link library) может быть использована сразу несколькими программами или процессами (т.н. sharing - разделяемый ресурс) Краткое описание функций и приемов для работы с dll Итак, какие же приемы и функции необходимо использовать, чтобы работать с dll? Разберем два метода импортирования функций из библиотеки: 1 способ. Привязка dll к программе. Это наиболее простой и легкий метод для использования функций, импортируемых из dll. Однако (и на это следует обратить внимание) этот способ имеет очень весомый недостаток - если библиотека, которую использует программа, не будет найдена, то программа просто не запустится, выдавая ошибку и сообщая о том, что ресурс dll не найден. А поиск библиотеки будет вестись: в текущем каталоге, в каталоге программы, в каталоге windowssystem, и т.д. Итак, для начала - общая форма этого приема: implementation ... function functionname(par1: par1type; par2: par2type; ...): returntype; stdcall; external 'dllname.dll' name 'functionname' index funcindex; // или (если не функция, а процедура): procedure procedurename(par1: par1type; par2: par2type; ...); stdcall; external 'dllname.dll' name 'procedurename' index procindex; Здесь: functionname (либо procedurename) - имя функции (или процедуры), которое будет использоваться в Вашей программе; par1, par2, ... - имена параметров функции или процедуры; par1type, par2type, ... - типы параметров функции или процедуры (например, integer); returntype - тип возвращаемого значения (только для функции); stdcall - директива, которая должна точно совпадать с используемой в самой dll; external 'dllname.dll' - директива, указывающая имя внешней dll, из которой будет импортирована данная функция или процедура (в данном случае - dllname.dll); name 'functionname' ('procedurename') - директива, указывающая точное имя функции в самой dll. Это необязательная директива, которая позволяет использовать в программе функцию, имеющую название, отличное от истинного (которое она имеет в библиотеке); index functionindex (procedureindex) - директива, указывающая порядковый номер функции или процедуры в dll. Это также необязательная директива. 2 способ. Динамическая загрузка dll. Это гораздо более сложный, но и более элегантный метод. Он лишен недостатка первого метода. Единственное, что неприятно - объем кода, необходимого для осуществления этого приема, причем сложность в том, что функция, импортируемая из dll достуна лишь тогда, когда эта dll загружена и находится в памяти... С примером можно ознакомиться ниже, а пока - краткое описание используемых этим методом функций winapi: loadlibrary(libfilename: pchar) - загрузка указанной библиотеки libfilename в память. При успешном завершении функция возвращает дескриптор (thandle) dll в памяти. getprocaddress(module: thandle; procname: pchar) - считывает адpес экспоpтиpованной библиотечной функции. При успешном завершении функция возвращает дескриптор (tfarproc) функции в загруженной dll. freelibrary(libmodule: thandle) - делает недействительным libmodule и освобождает связанную с ним память. Следует заметить, что после вызова этой процедуры функции данной библиотеки больше недоступны. Практика и примеры Ну а теперь пора привести пару примеров использования вышеперечисленных методов и приемов: Пример 1. Привязка dll к программе {... Здесь идет заголовок файла и определение формы tform1 и ее экземпляра form1} implementation {Определяем внешнюю библиотечную функцию} function getsimpletext(langrus: boolean): pchar; stdcall; external 'mydll.dll'; procedure button1click(sender: tobject); begin {И используем ее} showmessage(strpas(getsimpletext(true))); showmessage(strpas(getsimpletext(false))); {showmessage - показывает диалоговое окно с указанной надписью; strpas - преобразует строку pchar в string} end; Теперь то же самое, но вторым способом - с динамической загрузкой: Пример 2. Динамическая загрузка dll {... Здесь идет заголовок файла и определение формы tform1 и ее экземпляра form1} var form1: tform1; getsimpletext: function(langrus: boolean): pchar; libhandle: thandle; procedure button1click(sender: tobject); begin {"Чистим" адрес функции от "грязи"} @getsimpletext := nil; {Пытаемся загрузить библиотеку} libhandle := loadlibrary('mydll.dll'); {Если все ok} if libhandle >= 32 then begin {...то пытаемся получить адрес функции в библиотеке} @getsimpletext := getprocaddress(libhandle,'getsimpletext'); {Если и здесь все ok} if @getsimpletext <> nil then {...то вызываем эту функцию и показываем результат} showmessage(strpas(getsimpletext(true))); end; {И не забываем освободить память и выгрузить dll} freelibrary(libhandle); end; ПРИМЕЧАНИЕ: Следует воздерживаться от использования типа string в библиотечных функциях, т.к. при его использовании существуют проблемы с "разделением памяти". Подробней об этом можно прочитать (правда, на английском) в тексте пустого проекта dll, который создает delphi (file -> new -> dll). Так что лучше используйте pchar, а затем при необходимости конвертируйте его в string функцией strpas. Ну а теперь разберем непосредственно саму библиотеку dll: Пример 3. Исходник проекта mydll.dpr library mydll; uses sysutils, classes; {Определяем функцию как stdcall} function getsimpletext(langrus: boolean): pchar; stdcall; begin {В зависимости от langrus возвращаем русскую (true) либо английскую (false) фразу} if langrus then result := pchar('Здравствуй, мир!') else result := pchar('hello, world!'); end; {Директива exports указывает, какие функции будут экспортированы этой dll} exports getsimpletext; begin end. Размещение в dll ресурсов и форм В dll можно размещать не только функции, но и курсоры, рисунки, иконки, меню, текстовые строки. На этом мы останавливаться не будем. Замечу лишь, что для загрузки ресурса нужно загрузить dll, а затем, получив ее дескриптор, - загружать сам ресурс соотвествующей функцией (loadicon, loadcursor, и т.д.). В этом разделе мы лишь немного затронем размещение в библиотеках dll окон приложения (т.е. форм в Дельфи). Для этого нужно создать новую dll и добавить в нее новую форму (file -> new -> dll, а затем - file -> new form). Далее, если форма представляет собой диалоговое окно (модальную форму (bsdialog)), то добавляем в dll следующую функцию (допустим, форма называется form1, а ее класс - tform1): Пример 4. Размещение формы в dll function showmydialog(msg: pchar): boolean; stdcall; ... exports showmydialog; function showmydialog(msg: pchar): boolean; begin {Создаем экземпляр form1 формы tform1} form1 := tform1.create(application); {В label1 выводим msg} form1.label1.caption := strpas(msg); {Возвращаем true только если нажата ok (modalresult = mrok)} result := (form1.showmodal = mrok); {Освобождаем память} form1.free; end; Если же нужно разместить в dll немодальную форму, то необходимо сделать две функции - открытия и закрытия формы. При этом нужно заставить dll запомнить дескриптор этой формы. Создание плагинов Здесь мы не будем подробно рассматривать плагины, т.к. уже приведенные выше примеры помогут Вам легко разобраться в львиной части программирования dll. Напомню лишь, что плагин - дополнение к программе, расширяющее ее возможности. При этом сама программа обязательно должна предусматривать наличие таких дополнений и позволять им выполнять свое предназначение. Т.е., например, чтобы создать плагин к графическому редактору, который бы выполнял преобразование изображений, Вам нужно предусмотреть как минимум две функции в плагине (и, соответственно, вызвать эти функции в программе) - функция, которая бы возвращала имя плагина (и/или его тип), чтобы добавить этот плагин в меню (или в тулбар), плюс главная функция - передачи и приема изображения. Т.е. сначала программа ищет плагины, потом для каждого найденного вызывает его опозновательную функцию со строго определенным именем (например, getpluginname) и добавляет нужный пункт в меню, затем, если пользователь выбрал этот пункт - вызывает вторую функцию, которой передает входное изображение (либо имя файла, содержащего это изображение), а эта функция, в свою очередь, обрабатывает изображение и возвращает его в новом виде (или имя файла с новым изображением). Вот и вся сущность плагина...