Шаг 70.
Библиотека OWL.
Динамическая компоновка DLL

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

    Перечислим основные отличия. Прежде всего отметим, что функция 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);
}


    Замечание. Не забудьте установить платформу Win32 для приложения.

    Со следующего шага мы начнем приводить примеры приложений с использованием изученного материала.




Предыдущий шаг Содержание Следующий шаг