Шаг 67.
Библиотека OWL.
Построение простой DLL

    На этом шаге мы рассмотрим пример создания и использования DLL.

    Другие различия между DLL и выполняемой программой показаны в исходном коде DLL.


    Пример. Текст DLL shapelib.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 и все сопуствующие файлы можно взять здесь.

    Для получения DLL нужно выполнить пункт меню Project | Build all.


    Пример 1. Пример приложения, использующего созданную DLL.
#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. Результат работы приложения


    Замечание. Не забудьте разместить DLL в той папке, где находится исполняемый файл.

    Теперь, когда вы знаете, что делает эта программа, рассмотрим описания функций в верхней части примера.

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 "С" и не допустить искажения имени.


    Замечание. Если вы считаете, что ваши DLL могут быть вызваны программами, написанными на любом языке программирования и с помощью любого компилятора, вы не должны допустить, чтобы произошло искажение имени С++-компилятором. Чтобы избежать искажения имени, используйте объявление 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 ("удаленные"). Экспортируя функцию, вы делаете ее видимой для других модулей, а не привязываете к тому модулю, в котором она описывается. Эти функции должны быть описаны как far ("удаленные"), так как они будут вызваны из разных частей кода.

    В функциях, которые составляют тело 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.




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