Операционная система Microsoft Windows 3.1 для программиста© Александр Фролов, Григорий ФроловТом 13, М.: Диалог-МИФИ, 1993, 284 стр. 3.4. Приложение DLLCALLИсходный текст простейшей DLL-библиотеки приведен в листинге 3.1. Как видно из листинга, в библиотеке определены всего три функции - LibMain, WEP и Msg. Листинг 3.1. Файл dllcall/dllsrc.cpp #define STRICT #include <windows.h> // ======================================================== // Функция LibMain // Получает управление только один раз при // загрузке DLL-библиотеки в память // ======================================================== #pragma argsused int FAR PASCAL LibMain(HINSTANCE hInstance, WORD wDataSegment, WORD wHeapSize, LPSTR lpszCmdLine) { // После инициализации локальной области данных // функция LibEntry фиксирует сегмент данных. // Его необходимо расфиксировать. if(wHeapSize != 0) // Расфиксируем сегмент данных UnlockData(0); // Возвращаем 1. Это означает, что инициализация // DLL-библиотеки выполнена успешно return 1; } // ======================================================== // Функция WEP // Получает управление только один раз при // удалении DLL-библиотеки из памяти // ======================================================== #pragma argsused int FAR PASCAL WEP(int bSystemExit) { return 1; } // ======================================================== // Функция Msg // Выводит на экран диалоговую панель с сообщением // ======================================================== void FAR PASCAL _export Msg(LPSTR lpszMsg) { MessageBox(NULL, lpszMsg, "DLLSRC", MB_OK); } Функция LibMain проверяет размер локальной области данных, заказанной для DLL-приложения функцией LibEntry. Этот размер определяется значением, указанным в файле определения модуля DLL-библиотеки при помощи оператора HEAPSIZE и передается функции LibMain через параметр wHeapSize. Если DLL-библиотека имеет локальную область данных, на этапе инициализации ее необходимо расфиксировать для того чтобы разрешить Windows при необходимости произвольно изменять логический адрес сегмента данных в процессе перемещения сегментов. Использованный способ расфиксирования сегмента данных был описан в предыдущей главе и основан на использовании макрокоманды UnlockData. После инициализации, которая в нашем простейшем примере всегда выполняется успешно, функция WinMain возвращает признак успешной инициализации - значение 1. Задача функции WEP в нашем случае сводится к возврату значения 1, означающего успешное завершение. Функция Msg - единственная функция предназначена для вывода строки сообщения, адрес которой передается ей в качестве параметра. Так как эта функция определена с ключевым словом _export, она может быть вызвана любым приложением. Именно эту функцию мы будем вызывать из приложения DLLCALL, исходные тексты которого приведены ниже. Файл определения модуля для DLL-библиотеки имеет некоторые особенности (листинг 3.2). Листинг 3.2. Файл dllcall/dll.def ; ============================= ; Файл определения модуля ; ============================= LIBRARY DLLSRC DESCRIPTION 'DLL-библиотека DLLSRC, (C) 1994, Frolov A.V.' EXETYPE windows CODE preload moveable discardable DATA preload moveable single HEAPSIZE 1024 Во-первых, в нем нет оператора NAME, предназначенного для идентификации приложения. Вместо него используется оператор LIBRARY. Этот оператор указывает, что данный файл описывает не приложение, а DLL-библиотеку. Во-вторых, в операторе DATA указан параметр single, так как в памяти может находиться только одна копия DLL-библиотеки и, соответственно, может существовать только один сегмент данных. И в-третьих, в файле определения модуля нет оператора STACKSIZE, так как DLL-библиотека не может иметь стек. Для создания библиотеки вы можете воспользоваться файлом dllsrc.prj, который есть на дискете, продаваемой вместе с книгой в каталоге dllcall. В файла проекта DLL-библиотеки, сделанной с помощью системы разработки Borland Turbo C++ for Windows, вам необходимо указать правильный тип приложения. Для этого в меню "Options" выберите строку "Application..." и в появившейся не экране диалоговой панели "Application Options" нажмите на кнопку "Windows DLL" (рис. 3.7). После этого нажмите на кнопку "OK".
Рис. 3.7. Диалоговая панель "Application Options" Обратите внимание на то, что по умолчанию при создании DLL-библиотеки все функции, определенные в ней, становятся экспортируемыми (Windows DLL all functions exportable). Это означает, что все функции, определенные вами в DLL-библиотеке, имеют специальный пролог и эпилог, предназначенный для правильной загрузки регистра DS (в этот регистр записывается селектор сегмента данных DLL-библиотеки). Кроме того, все эти функции перечисляются в заголовке загрузочного dll-файла библиотеки. По умолчанию для DLL-библиотеки используется компактная модель данных. В этой модели существует один сегмент кода и несколько сегментов данных. Кроме того, для ссылки на данные всегда используются дальние указатели, состоящие из селектора и смещения. Последнее обстоятельство очень важно. Вспомним, что при вызове функции из DLL-библиотеки регистр DS указывает на локальный сегмент данных библиотеки, а регистр SS - на сегмент данных вызывающего приложения. Так как трансляторы языка С передают параметры через стек, а также используют стек для размещения локальных переменных, вы не сможете адресовать параметры и локальные переменные через регистр DS (он указывает на сегмент данных DLL-библиотеки, а параметры и локальные переменные находятся в сегменте данных приложения). Очевидный выход - использование компактной или большой модели памяти, в которых используются дальние указатели на переменные. Исходный текст приложения DLLCALL, вызывающего функцию Msg из нашей DLL-библиотеки, приведен в листинге 3.3. Листинг 3.3. Файл dllcall/dllcall.cpp // ---------------------------------------- // Вызов функции, расположенной в // DLL-библиотеке // ---------------------------------------- #define STRICT #include <windows.h> // Объявление функции, расположенной в DLL-библиотеке extern void FAR PASCAL Msg(LPSTR lpszMsg); // ===================================== // Функция WinMain // ===================================== #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { // Вызываем функцию из DLL-библиотеки Msg((LPSTR)"Вам привет из DLL!"); return 0; } Обратите внимание на то, что функция Msg описана как внешняя: extern void FAR PASCAL Msg(LPSTR lpszMsg); Файл определения модуля для приложения DLLCALL приведен в листинге 3.4. Он не имеет никаких особенностей. Листинг 3.4. Файл dllcall/dllcall.def ; ============================= ; Файл определения модуля ; ============================= NAME DLLCALL DESCRIPTION 'Приложение DLLCALL, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple Файл проекта приложения DLLCALL включает в себя библиотеку импорта dllsrc.lib, которую мы сделали из DLL-библиотеки dllsrc.dll при помощи приложения Import Lib, входящего в состав системы разработки Borland Turbo C++ for Windows. |