На этом шаге мы рассмотрим алгоритм динамической компоновки DLL.
Приведенный на 67 шаге способ подключения DLL носит название статической компоновки. В этом случае библиотека подключается к приложению на этапе компоновки. Отсутствие DLL приведет к неработоспособности приложения. Вы можете в этом убедиться, перенеся DLL в другую папку. В этом случае при запуске приложения вы получите сообщения такого вида:
Рис.1. Сообщения, получаемые при отсутствии DLL
С появлением 32-разрядных операционных систем наибольшую популярность приобрел способ динамической компоновки DLL. В этом случае библиотека подключается на этапе выполнения программы, и ее ресурсы могут использовать несколько программ. Программа даже может работать без DLL, хотя и не сможеот выполнять некоторые функции. Если вы сохранили копию DLL, то достаточно скопировать ее в нужное место.
Изменим программу, приведенную на 67 шаге, применив динамическую компоновку DLL.
Прежде всего, изменим текст SHAPELIB.CPP, чтобы можно было откомпилировать ее для платформы Win32.
#include <windows.h> // Используйте extern "C", чтобы // предотвратить искажение имен C++. extern "C" { void far _export __stdcall Triangle (HDC hDC, int x, int y); void far _export __stdcall Squiggle (HDC hDC, int x, int y); void far _export __stdcall Fred (HDC hDC, int x, int y); } // // LibMain() // Эта функция является точкой входа в DLL. int far PASCAL LibMain (HINSTANCE hInstance, WORD wDataSeg, WORD cbHeapSize, LPSTR lpstrCmdLine) { return 1; } // WEP() // DLL здесь выходит. WEP = Windows Exit Procedure // (процедура выхода). int far PASCAL WEP (int nParam) { return TRUE; } // Fred() // Эта функция рисует Фреда. extern "C" __stdcall void 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() // Эта функция рисует треугольник. extern "C" __stdcall void Triangle (HDC hDC, int x, int y) { MoveToEx (hDC, x, y, NULL); LineTo (hDC, x-10, y+20); LineTo (hDC, x+10, y+20); LineTo (hDC, x, y); } // Squiggle() // Эта функция рисует "пилу". extern "C" __stdcall void Squiggle (HDC hDC, int x, int y) { MoveToEx (hDC, x, y, NULL); for (int i=1; i<6; ++i) { LineTo (hDC, x+(i*10), y-10); LineTo (hDC, x+(i*10), y+10); } }
Перечислим основные отличия. Прежде всего отметим, что функция MoveTo() в 32-разрядной версии заменена на MoveToEx(), в которой четвертый параметр используется для сохранения старых координат. Нам старые координаты не нужны, поэтому в нашем случае он равен NULL.
Служебное слово __stdcall означает, что очистка стека происходит при выходе из вызванной функции, а параметры, передаваемые в функцию, помещаются в стек справа налево.
Для получения DLL необходимо установить платформу в Win32.
Рис.2. Установка типа приложения
Теперь перейдем к основной программе.
#include <owl\applicat.h> #include <owl\framewin.h> #include <owl\dc.h> typedef void far (*Func)(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); ~TWndw (); protected: void EvLButtonDown (UINT, TPoint &point); void EvRButtonDown (UINT, TPoint &point); void EvMButtonDown (UINT, TPoint &point); private: HINSTANCE L; 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; L = LoadLibrary ("shapelib.dll"); if (!L) MessageBox("Не загрузилась DLL","Ошибка", MB_OK); } // TWndw::~TWndw() // Это деструктор главного окна. TWndw::~TWndw () { // Выгрузить библиотеку, если она загружена. if (L) FreeLibrary(L); } // TWndw::EvLButtonDown() // Эта функция реагирует на сообщение WM_LBUTTONDOWN. void TWndw::EvLButtonDown (UINT, TPoint &point) { // Получить контекст устройства для рабочей области окна. TClientDC DC(HWindow); // Вызвать функцию DLL. Func F =(Func)GetProcAddress(L,"Fred"); if (!F) MessageBox("Не найдена функция Fred","Ошибка", MB_OK); else (*F)(HDC(DC), point.x, point.y); } // TWndw::EvMButtonDown() // Эта функция реагирует на сообщение WM_MBUTTONDOWN. void TWndw::EvMButtonDown (UINT, TPoint &point) { // Получить контекст устройства для рабочей области окна. TClientDC DC(HWindow); // Вызвать функцию DLL. Func F =(Func)GetProcAddress(L,"Squiggle"); if (!F) MessageBox("Не найдена функция Squiggle","Ошибка", MB_OK); else (*F)(HDC(DC), point.x, point.y); } // TWndw::EvRButtonDown() // Эта функция реагирует на сообщение WM_RBUTTONDOWN. void TWndw::EvRButtonDown (UINT, TPoint &point) { // Получить контекст устройства для рабочей области окна. TClientDC DC(HWindow); // Вызвать функцию DLL. Func F =(Func)GetProcAddress(L,"Triangle"); if (!F) MessageBox("Не найдена функция Triangle","Ошибка", MB_OK); else (*F)(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(); }
Результат работы этого приложения полностью аналогичен результату работы приложения из 67 шага. Однако, если вы уберете DLL из папки, где находится приложение, то оно запустится, только по щелчку левой, средней и правой кнопкой мыши будет появляться соотвествующее предупреждающее сообщение.
Опишем основные отличия этой программы от аналогичной из 67 шага.
Прежде всего обратите внимание на последние две строки контруктора главного окна:
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; L = LoadLibrary ("shapelib.dll"); if (!L) MessageBox("Не загрузилась DLL","Ошибка", MB_OK); }
Здесь осуществляется вызов функции API LoadLibrary(), которая загружает DLL с заданным именем. Значением этой функции является логический номер модуля, который однозначно определяет DLL. Если в процессе загрузки DLL произошла ошибка, то возвращается NULL.
По щелчку левой клавишей мыши вызывается функция EvLButtonDown():
void TWndw::EvLButtonDown (UINT, TPoint &point) { // Получить контекст устройства для рабочей области окна. TClientDC DC(HWindow); // Вызвать функцию DLL. Func F =(Func)GetProcAddress(L,"Fred"); if (!F) MessageBox("Не найдена функция Fred","Ошибка", MB_OK); else (*F)(HDC(DC), point.x, point.y); }
Здесь используется функция API GetProcAddress() для получения указателя на функцию, находящуюся в DLL, на которую у нас есть ссылка. Адрес функции, имя которой указано в качестве второго параметра GetProcAddress(), помещается в переменную F, которая описана как указатель на функцию, имеющую три параметра и возвращающую значение void. Этот тип определен следующим образом:
typedef void far (*Func)(HDC hDC, int x, int y);
Если все прошло успешно, то осуществляется косвенный вызов функции с передачей ей необходимых параметров:
(*F)(HDC(DC), point.x, point.y);
Если определить адрес функции не удалось, то F будет иметь значение NULL, и будет выдано сообщение об ошибке.
Осталось выгрузить библиотеку при завершении программы. Это делается в деструкторе:
// TWndw::~TWndw() // Это деструктор главного окна. TWndw::~TWndw () { // Выгрузить библиотеку, если она загружена. if (L) FreeLibrary(L); }
Со следующего шага мы начнем приводить примеры приложений с использованием изученного материала.