- Что такое Dll и зачем оно нам надо?
- Как написать Dll?
- Как подключить и использовать Dll в своей программе?
- Как отладить Dll?
Что такое Dll и зачем оно нам надо?
Ну, начнём с того, что Dll — это аббревиатура от слов Dynamic Link Library, что переводится как «динамически подключаемая библиотека». По-сути, это модуль, содержащий какой-то полезный код. Чаще всего этот полезный код содержит какие-то программные функции, но может содержать и что-то другое, например, изображения или списки локализации и тому подобное.
Главное отличие модуля Dll от обычной программы заключается в том, что сам по себе такой модуль (и содержащиеся в нём функции) не может быть исполнен как отдельная программа, зато к этому модулю могут подключаться другие программы и исполнять содержащиеся в нём куски кода. Это, например, позволяет не разрабатывать каждый раз заново функции для работы с какими-то железками, базами данных, другими программами, а описать их один раз в отдельном модуле Dll и далее использовать эту Dll во всех программах, в которых они нужны.
Программы могут связываться с модулями Dll двумя способами, называемыми: статическим связыванием и динамическим связыванием.
При статическом связывании Dll-ка будет загружаться в память вместе с запускаемым приложением и приложение без этой Dll-ки работать не будет. Далее мы не будем рассматривать такой тип связывания, поскольку гораздо интереснее связывание динамическое. В этом случае приложение загружает Dll-ку только в том случае, если ему нужно получить доступ к какой-то хранящейся в Dll-ке функции, после чего Dll-ка снова может быть выгружена. Более того, если в процессе работы приложению ни разу не потребовались хранящиеся в Dll-ке функции, то Dll-ка может вообще не загружаться в память.
Для создания Dll в C++ Builder есть специальный Wizard. Найти его можно по следующему пути: File->New->Other… и далее на вкладке New нужно выбрать пункт DLL Wizard.
Далее, в визарде нужно поставить галочки C++, Use VCL (будем писать на C++ и пользоваться компонентами библиотеки VCL) и нажать OK:
В итоге получаем файл cpp с заготовкой кода (пара подключенных библиотек, функция, обозначающая точку входа) и длиннющим комментарием. Комментарий читаем внимательно и стираем нафиг, а проект сохраняем в отдельной папке.
Далее идём по тому же пути File-&gb;New-&gbOther…, только теперь выбираем на вкладке New не DLL Wizard, а Header File. Cохраняем этот файл в той же папке, что и весь остальной проект (удобнее всего под тем же именем, что и файл cpp, чтобы потом не запутаться) и дописываем в файл cpp инструкцию #include «имя файла.h».
Дописываем в h-файл вот такой код:
//---------------------------------------------------------------------- #ifndef имя_файлаH #define имя_файлаH //---------------------------------------------------------------------- #include <vcl\Classes.hpp> //---------------------------------------------------------------------- //сюда пишем описания своих функций //---------------------------------------------------------------------- #endif |
Теперь в cpp-файле можно писать какие-то свои функции:
#include "unit1.h" //--------------------------------------------------------------------------- #include <vcl.h> #include <windows.h> #pragma hdrstop //--------------------------------------------------------------------------- #pragma argsused int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved) { return 1; } //--------------------------------------------------------------------------- // Ниже пишем свои функции |
Для того, чтобы функцию можно было экспортировать, её описание в h-файле должно быть вот в таком виде:
тип имя типы входных возвращаемой функции переменных переменной в Dll (через запятую) | | | extern "C" OD_Type __declspec(dllexport) DllFunc1 (ID_Types);
Если на выходе функции нужно получить не одну, а несколько переменных, — можно использовать массивы или структуры, только нужно учесть, что вместо AnsiString лучше пользоваться Char-массивами (об этом как раз был комментарий).
Осталось только скомпилировать проект и на выходе мы получим файл с расширением Dll.
Как подключить и использовать Dll в своей программе?
Для того, чтобы использовать функции из Dll в своей программе, — нужно сделать следующее:
Сначала в заголовочном файле программы (*.h) нужно правильно описать типы импортируемых из Dll функций (чтобы описание в программе совпадало с описанием в самой Dll). Проще всего подглядеть эту информацию в самом хидере dll-ки (*.h), хотя конечно, неважно откуда у вас эта информация, главное чтобы она была. Это нужно для того, чтобы программа правильно понимала какие данные (типы, порядок, количество переменных) она будет в Dll передавать и какие принимать. Когда нужная информация у вас есть, описать в программе типы импортируемых функций можно следующим образом:
typedef OD_Type (__import Proto1(ID_Types)); // первый прототип внешней функции Proto1 *Func1; // объявляем импортируемую функцию описанного выше типа typedef OD_Type (__import Proto2(ID_Types)); // второй прототип внешней функции Proto2 *Func2; // а разных функций второго типа будем импортировать Proto2 *Func3; // целых 3 штуки (так тоже можно, если функции имеют Proto2 *Func4; // одинаковый набор типов входных и выходных параметров) |
Далее, в программе (*.cpp) нужно загрузить Dll из которой мы хотим описанные функции импортировать:
HINSTANCE DllHinst = LoadLibrary("[путь/]имя_файла.dll"); |
Если Dll-ка успешно загрузилась, то LoadLibrary вернёт указатель на на неё (идентификатор, по которому к ней можно обращаться), в противном случае функция вернёт NULL.
Теперь, когда у нас есть указатель на загруженную Dll, осталось найти внутри Dll-ки нужную нам функцию. Это можно сделать так:
указатель на имя функции, которую мы ищем загруженную Dll (с подчеркиванием впереди) | | Func1 = (Proto1 *)GetProcAddress(DllHinst,"_DllFunc1");
Если запрошенная нами функция обнаружится в указанной Dll-ке, то GetProcAddress вернёт нам её адрес, в противном случае будет возвращён NULL.
Вот и всё, теперь мы можем пользоваться функцией из загруженной Dll в своей программе, вызывая её точно также, как буд-то она у нас написана в секции *.cpp.
После того, как мы попользовались функциями из Dll-ки и она нам больше не нужна, можно выгрузить её командой FreeLibrary:
FreeLibrary(DllHinst); |
Последнее, что нам понадобится, — это отладка. Как мы уже говорили, напрямую запустить на исполнение Dll и хранящиеся в ней функции невозможно, её сначала должен кто-то толкнуть. Поэтому для отладки нам понадобится написать какое-то приложение, которое будет использовать отлаживаемый код.
Когда такое приложение есть, — копируем его в папку проекта с Dll-кой, выбираем в меню Run пункт Parameters… , в открывшемся окне щёлкаем на кнопку Browse… , напротив строки Host Application, в открывшемся диалоговом окне выбираем наше приложение (после этого в строке Host Application будет прописан путь к выбранному приложению) и жмём OK.
Всё, теперь можно также как и в обычном приложении расставлять в нашей Dll-ке точки останова и запускать отладку кнопкой F9. При этом сначала загрузится выбранное нами выше приложение, а когда оно дойдёт до использования хранящихся в Dll-ке функций — тут-то и сработают наши точки останова, мы попадём в отладчик и сможем отлаживать написанный в Dll-ке код.
В качестве примера можно посмотреть dll-ку для шлюза, вот из этой статьи (она там с исходниками выложена).