На этом шаге мы рассмотрим алгоритм динамической компоновки 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); } }
Текст DLL и все сопуствующие файлы можно взять 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); }
Замечание.
Не забудьте установить платформу Win32 для приложения.
Со следующего шага мы начнем приводить примеры приложений с использованием изученного материала.