На этом шаге мы рассмотрим пример создания и использования DLL.
Другие различия между DLL и выполняемой программой показаны в исходном коде DLL.
#include <windows.h> // Используйте extern "C", чтобы // предотвратить искажение имен C++. extern "C" { void far _export Triangle (HDC hDC, int x, int y); void far _export Squiggle (HDC hDC, int x, int y); void far _export Fred (HDC hDC, int x, int y); } // LibMain() // Эта функция является точкой входа в DLL. int far PASCAL LibMain (HINSTANCE hInstance, WORD wDataSeg, WORD cbHeapSize, LPSTR lpstrCmdLine) { // Разблокировать сегмент данных. if (cbHeapSize) UnlockData(0); return 1; } // WEP() // DLL здесь выходит. WEP = Windows Exit Procedure // (процедура выхода). int far PASCAL WEP (int nParam) { return TRUE; } // Fred() // Эта функция рисует Фреда. void far _export Fred (HDC hDC, int x, int y) { Ellipse (hDC, x, y, x+20, y+30); Ellipse (hDC, x+3, y+10, x+8, y+16); Ellipse (hDC, x+12, y+10, x+17, y+16); Ellipse (hDC, x+7, y+16, x+13, y+20); Ellipse (hDC, x+4, y+22, x+16, y+25); Ellipse (hDC, x-3, y+10, x, y+18); Ellipse (hDC, x+20, y+10, x+23, y+18); } // Triangle() // Эта функция рисует треугольник. void far _export Triangle (HDC hDC, int x, int y) { MoveTo (hDC, x, y); LineTo (hDC, x-10, y+20); LineTo (hDC, x+10, y+20); LineTo (hDC, x, y); } // Squiggle() // Эта функция рисует "пилу". void far _export Squiggle (HDC hDC, int x, int y) { MoveTo (hDC, x, y); for (int i=1; i<6; ++i) { LineTo (hDC, x+(i*10), y-10); LineTo (hDC, x+(i*10), y+10); } }
Перед компиляцией проекта установите тип выходного объекта Dynamic Library [.dll], как показано на рисунке 1.
Рис.1. Установка типа приложения
Кроме того создайте файл shapelib.def, который также поместите в проект, со следующим текстом:
LIBRARY SHAPELIB DESCRIPTION 'DLL Shapelib' EXETYPE WINDOWS CODE PRELOAD MOVEABLE DISCARDABLE DATA PRELOAD MOVEABLE SINGLE HEAPSIZE 1024
Для получения DLL нужно выполнить пункт меню Project | Build all.
#include <owl\applicat.h> #include <owl\framewin.h> #include <owl\dc.h> // Определить внешние функции. extern "C" { void far _export Triangle (HDC hDC, int x, int y); void far _export Squiggle (HDC hDC, int x, int y) ; void far _export Fred (HDC hDC, int x, int y); } // Класс приложения. class TApp: public TApplication { public: TApp():TApplication() {} void InitMainWindow(); }; // Класс главного окна, class TWndw: public TFrameWindow { public: TWndw (TWindow *parent, const char far *title); protected: void EvLButtonDown (UINT, TPoint &point); void EvRButtonDown (UINT, TPoint &point); void EvMButtonDown (UINT, TPoint &point); DECLARE_RESPONSE_TABLE (TWndw); }; DEFINE_RESPONSE_TABLE1 (TWndw, TFrameWindow) EV_WM_LBUTTONDOWN, EV_WM_RBUTTONDOWN, EV_WM_MBUTTONDOWN, END_RESPONSE_TABLE; //*********************************** // Реализация окна TWndw. //*********************************** // TWndw::TWndw() // Это конструктор главного окна. TWndw::TWndw (TWindow *parent, const char far *title) : TFrameWindow (parent, title) { // Определить расположение и размеры окна. Attr.X = 40; Attr.Y = 40; Attr.W = GetSystemMetrics (SM_CXSCREEN) / 1.5; Attr.H = GetSystemMetrics (SM_CYSCREEN) / 1.5; } // TWndw::EvLButtonDown() // Эта функция реагирует на сообщение WM_LBUTTONDOWN. void TWndw::EvLButtonDown (UINT, TPoint &point) { // Получить контекст устройства для рабочей области окна. TClientDC DC(HWindow); // Вызвать функцию DLL. Fred(HDC(DC), point.x, point.y); } // TWndw::EvMButtonDown() // Эта функция реагирует на сообщение WM_MBUTTONDOWN. void TWndw::EvMButtonDown (UINT, TPoint &point) { // Получить контекст устройства для рабочей области окна. TClientDC DC(HWindow); // Вызвать функцию DLL. Squiggle(HDC(DC), point.x, point.y); } // TWndw::EvRButtonDown() // Эта функция реагирует на сообщение WM_RBUTTONDOWN. void TWndw::EvRButtonDown (UINT, TPoint &point) { // Получить контекст устройства для рабочей области окна. TClientDC DC(HWindow); // Вызвать функцию DLL. Triangle (HDC(DC), point.x, point.y); } //*********************************** // Реализация класса TApp. //*********************************** // TApp::InitMainWindow() // Эта функция создает главное окно приложения. void TApp::InitMainWindow() { TFrameWindow *wndw = new TWndw (0, "Тестирование DLL"); // Установить указатель окна. SetMainWindow(wndw); } int OwlMain(int,char *[]) { return TApp().Run(); }
Запустив тестовую программу, поместите курсор мыши в окно и нажмите любую кнопку мыши. Если нажать левую кнопку, то в указанном месте появится рожица (т.е. "Фред"). Если нажать правую кнопку, появится треугольник. Наконец, если нажать среднюю кнопку появится пилообразная ломаная линия. На рисунке 2 представлено основное окно программы.
Рис.2. Результат работы приложения
Теперь, когда вы знаете, что делает эта программа, рассмотрим описания функций в верхней части примера.
extern "C" { void far _export Triangle (HDC hDC, int x, int y); void far _export Squiggle (HDC hDC, int x, int y) ; void far _export Fred (HDC hDC, int x, int y); }
Все функции в DLL описываются как extern "С". Такое описание обеспечивает защиту от общего соглашения C++-компилятора, называемого "искажением имени" (name-mangling). Что такое искажение имени? Когда вы компилируете С++-программу, компилятор изменяет имена функций, добавляя информацию о параметрах функций и возвращаемом значении. К сожалению, именно это измененное имя и должна использовать программа при вызове функции.
До сих пор вы не замечали изменения имени, так как компилятор заботится о совпадении имен. Однако помните, что DLL можно вызвать из многих других программ, которые могут быть, а могут и не быть написаны на том же языке и с использованием того же компилятора, что и DLL - вот где может таиться неприятность.
Если вы допустите искажение имени, вызов DLL может быть реализован только теми программами, при написании которых использовался тот же компилятор, что и для DLL. He имеет значения, написаны ли они на одном и том же языке, так как, хотя С++-компиляторы и искажают имя функции, но делают это по-разному. Поэтому, если вы при написании своей DLL использовали C++-компилятор Borland, вы не можете вызвать DLL-функции с помощью программ, написанных с помощью С++-компилятора Microsoft. Обойти эту проблему можно, если использовать объявление extern "С" и не допустить искажения имени.
Обратимся к описанию функций: ключевое слово _export указывает, что эти функции будут использованы в других модулях. Если вы не указали, что это экспортируемые функции, и вы попытаетесь откомпилировать вызывающую их программу, то получите ошибку компоновки.
Теперь рассмотрим текст DLL, который начинается с функции LibMain():
int far PASCAL LibMain (HINSTANCE hInstance, WORD wDataSeg, WORD cbHeapSize, LPSTR lpstrCmdLine) { // Разблокировать сегмент данных. if (cbHeapSize) UnlockData(0); return 1; }
Точно так же, как каждая выполняемая программа Windows должна иметь функцию WinMain(), каждая DLL должна иметь функцию LibMain(), или иногда заменяющую ее функцию DLLEntryPoint(), которая находится там, где начинается выполнение DLL. Именно в функции LibMain() выполняется любая инициализация, которую требует ваша DLL. Функция LibMain() имеет четыре параметра: дескриптор экземпляра DLL, адрес сегмента данных DLL, размер динамической области DLL и командную строку.
В предыдущем тексте LibMain() разблокирует свой собственный сегмент данных, который всегда запирается кодом начальной загрузки DLL. Затем функция LibMain() возвращает TRUE, которая указывает на правильную инициализацию DLL. Если функция LibMain() возвращает FALSE, Windows выгружает DLL. DLL также должны содержать функцию деинициализации, называемую WEP() (Windows Exit Procedure - процедура выхода), которая автоматически вызывается Windows при выгрузке DLL.
int far PASCAL WEP (int nParam) { return TRUE; }
В этом месте вы можете произвести необходимую очистку. Для этой процедуры Windows передает параметр nParam, который может принимать одно из двух значений. Функции WEP() посылается значение WEP_FREE_DLL, когда Windows выгружает DLL, а значение WEP_SYSTEMEXIT посылается, когда Windows полностью прекращает работу. Как и функция LibMain(), функция WEP() должна возвратить TRUE, если все прошло хорошо. В предыдущем примере очистка производиться не будет, поэтому эта функция попросту возвращает TRUE.
В функциях, которые составляют тело DLL, нет ничего необычного, за тем исключением, что каждая экспортируемая функция должна быть описана как far ("удаленная"). Вы пишете эти функции точно так же, как и любые другие функции, что видно из примера с функцией Fred(), рисующей рожицу.
void far _export Fred (HDC hDC, int x, int y) { Ellipse (hDC, x, y, x+20, y+30); Ellipse (hDC, x+3, y+10, x+8, y+16); Ellipse (hDC, x+12, y+10, x+17, y+16); Ellipse (hDC, x+7, y+16, x+13, y+20); Ellipse (hDC, x+4, y+22, x+16, y+25); Ellipse (hDC, x-3, y+10, x, y+18); Ellipse (hDC, x+20, y+10, x+23, y+18); }
В этом тексте функция вызывает функцию Windows GDI Ellipse(), рисующую составные части лица "Фреда". Параметрами функции Fred() являются дескриптор контекста устройства окна и координата "Фреда", х и у. Другие функции фигур, Triangle() и Squiggle(), работают аналогично, поэтому здесь не рассматриваются.
На следующем шаге мы более подробно остановимся на механизме вызова функций из DLL.