Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Textnew2

.pdf
Скачиваний:
13
Добавлен:
06.02.2018
Размер:
1.48 Mб
Скачать

действий установления связей с DLL библиотекой. В процессе установления такой связи указанная библиотека загружается в оперативную память - если ранее не была помещена в нее другим процессом. После помещения библиотеки в память (или обнаружения ее) уже загруженной процесс, запросивший связь с этой библиотекой, получает ее хэндл, с помощью которого с такой библиотекой можно работать, в частности выполнять ее подпрограммы.

ВOS/2 для задания установления связи с DLL библиотекой предназначена функция с прототипом

APIRET DosLoadModule(UCHAR *BufferError,

ULONG cbBufEr, PSTR NameDLL, HMODULE* hdll);

Здесь первые два аргументы предназначены для передачи информации о предварительно заготовленном массиве, предназначенном разместить текстовую информацию о ошибке, если она возникает (BufferError - адрес буфера для такой информации, cbBufEr - его размер). Третий аргумент функции передает имя библиотеки, с которой требуется установить связь (параметр задает адрес этого имени в памяти),

апоследний параметр используется для возврата хэндла подключаемой библиотеки. Обычным образом возвращаемое функцией значений используется для определения удачного запроса (значение кода возврата равно 0) или ошибки при его выполнении.

Воперационных системах MS Windows установления связи с DLL библиотекой предназначена функция с прототипом

HINSTANCE LoadLibrary(LPCTSTR lpLibFileName);

Получение информации об ошибки - во время выполнения этой функции - достигается немедленным вызовом функции GetLastError после получения нулевого значения кода возврата от LoadLibrary. Этот код при ненулевом значении дает информацию о номере экземпляра библиотеки для процесса, вызывающего функцию LoadLibrary.

Воперационной системе Linux для запроса установления связи DLL библиотекой служит функция

void* dlopen(char * namedll, int mode);

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

авторой - режим ее подключения. В простейшем случае рекомендуется для задания такого режима использовать символическую константу RTLD_LAZY, т.е. указывать отсроченное подключение, выполняемое только тогда, когда потребуется кака- я-то функция из этой библиотеки.

Подключение библиотеки необходимый, но недостаточный этап. Для работы с процедурами, размещенными в DLL, процессу требуется получить их действительные адреса. Для этих целей в OS/2 служит функция DosQueryProcAddr с прототипом

APIRET DosQueryProcAddr(HMODULE hdll, ULONG nfun, UCHAR *NameProc, PFN* afun).

201

Здесь hdll - хэндл библиотеки, NameProc - имя требуемой процедуры, задаваемое адресом этого имени, afun - возвращаемый адрес процедуры для его последующего использования для вызова процедуры. Параметр nfun предназначен для порядкового номера функции в DLL библиотеке. Этот параметр почти не используется и был предназначен для некоторого ускорения получения адреса функции из библиотек, в которым содержится множество таких функций. В данном изложении эта возможность рассматриваться не будет.

ВMS Windows для получения адрес процедуры, находящейся в DLL библиотеке, служит функция с прототипом

FARPROC GetProcAddress(HMODULE hModule, LPCSTR lpProcName);

где hModule - хэндл библиотеки, полученный ранее от функции LoadLibrary, а lpProcName - адрес имени требуемой функции. Функция GetProcAddress возвращает в качестве своего значения адрес запрошенной функции или нулевое значение - при неудаче.

Воперационной системе Linux для получения адреса процедуры предназначена функция

void* dlsym(void *hdll, char *nameproc),

где hdll -хэндл библиотеки, полученный из функции dlopen, nameproc - адрес имени процедуры, заданного символьным текстом. Функция эта возвращает в качестве своего значения адрес требуемой процедуры или 0 - в случае неудачи.

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

Практическое освобождение происходит только тогда, когда оказывается, что процесс требующий освобождения - последний из использовавших ее (остальные уже раньше выдали требования освобождения этой библиотеки или иных процессов, использующих ту же библиотеку - не было). Для таких сложных действий, связанных с учетом сколько процессов в настоящий момент используют библиотеку, внутри библиотеки содержится специальный счетчик подключений. Каждое открытие увеличивает его на 1, а каждое освобождение - уменьшает на 1. После обнуления этого счетчика библиотека выгружается из памяти.

Для освобождения библиотеки в OS/2 служит функция с прототипом APIRET DosFreeModule(HMODULE hdll),

в операционных системах MS Windows - функция BOOL FreeLibrary(HMODULE hLibModule),

а в операционной системе Linux - функция с прототипом int dlclose(void* hdll);

Все эти функции в качестве единственного параметра используют хэндл освобождаемой библиотеки, а возвращаемое значение определяет успешность выполнения операции, причем неуспех кодируется обычным для рассматриваемых ОС образом: в OS/2 и MS Windows нулевым значением кода возврата и значением (-1) - в Linux.

202

На рис. 8.4.1 представлена программа, предназначенная для следующего примера использования в составе библиотеки DLL.

#include <windows.h> #include <string.h> char attr=0x2e;

int wiwoda(char *pt, int n) {HANDLE hstdout; COORD coor;

DWORD cbwritten;

char text[ ]="Мы находимся внутри DLL!!! var 3 Ura!";

hstdout=GetStdHandle(STD_OUTPUT_HANDLE); coor.X=20; coor.Y=10; FillConsoleOutputAttribute(hstdout, attr, strlen(text), coor,

&cbwritten);

WriteConsoleOutputCharacterA(hstdout, text, strlen(text), coor, &cbwritten);

coor.Y++;

FillConsoleOutputAttribute(hstdout, attr, strlen(pt), coor, &cbwritten);

WriteConsoleOutputCharacterA(hstdout, pt, strlen(pt), coor, &cbwritten);

return 20*n;

}

Рис. 8.4.1. Программа wpd3.c библиотеки DLL для MS Windows

Программа использования библиотеки представлена на рис. 8.4.2. #include <windows.h>

#include <stdio.h> #include <stdlib.h>

int (*afun)(char *pt, int n); void main()

{int rc;

char ttt[ ]="My text for DLL 3"; HANDLE hdll;

printf("Begin\n");

hdll = LoadLibrary("wpd3.dll");

if (!hdll) {printf("Error Load DLL\n"); exit(1);} afun=(void*)GetProcAddress(hdll, "_wiwoda");

if (afun==NULL) {printf("Error Get Proc Addr\n"); exit(1);}

203

rc=afun(ttt, 37); printf("Result=%d\n",rc); Sleep(3000); FreeLibrary(hdll); return;

}

Рис. 8.4.2. Программа wexd3.c использования библиотеки DLL.

Для изготовления библиотеки DLL из программы wpd3.c можно использовать командные вызовы

bcc32 -c wpd3.c

и

ilink32 -Tpd c0d32 wpd3,wpd3,, import32 cw32, wpd3.def где файл определения модуля wpd3.def приведен на рис. 8.4.3.

library wpd3 exports _wiwoda

Рис. 8.4.3. Файл wpd3.def определения библиотеки DLL.

Чтобы построить программу wexd3.exe из исходного текста на рис. 8.4.1, можно воспользоваться командными вызовами

bcc32 -c wexd3.c

ilink32 c0x32 wexd3,wexd3,,import32 cw32

Более простым вариантом получения исполняемого файла wexd3.exe из исходного файла wexd3.c является вызов командной строки

bcc32 wexd3.c

Последние версии систем разработки программ для MS Windows (Borland C 5.01, С-Builder и MS Visual C++) предлагают в качестве рекламируемой удобной возможности для построения и использования DLL библиотек использовать специальные дополнительные языковые конструкции. Эти конструкции имеют вид __declspec(dllexport) и __declspec(dllimport). Они должны записываться перед заголовком тех функций, которые предполагается экспортировать или, соответственно импортировать из библиотеки DLL. При разработке DLL применение таких конструкций (именно __declspec(dllexport) перед заголовками функций) позволяет отказаться от использования файлов определения модуля, что с точки зрения разработчиков этих средств должно способствовать облегчению труда программиста. При разработке же программа, использующих DLL, конструкции __declspec(dllimport) оказываются недостаточными, т.к. не содержат информации в какой DLL будет находится данная библиотечная функция.

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

204

Библиотека импорта используется в общем списке библиотек при компоновке исполняемого файла. Например, формирование файла wexd3.exe можно использовать командную строку

TLINK32 c0x32 wexd3,wexd3,,IMPORT32 CW32 WPD3

Файл определения модуля (например wexd3.def) здесь не нужен, а библиотеку WPD3.LIB можно указывать без расширения.

Использование библиотек импорта - широко используемая практика, особенно эффективная, когда DLL-библиотека содержит много импортируемых функций. Тогда перечисление их в файле определения достаточно трудоемко и чревато ошибками. Гораздо проще поручить это перечисление специальной программе с формированием результатов в специальном формате библиотеки импорта. Из рассматриваемых ОС библиотеки импорта применяются в OS/2 и MS Windows, но не в Linux.

При использовании динамической загрузки времени выполнения никакому компоновщику не приходится корректировать адресные поля машинных кодов команд CALL из объектных модулей (как это имеет место для динамической компоновки времени загрузки). Использование адресов функций, получаемых от системного вызова GetProcAddress и подобной ей, выливается в косвенные вызовы подпрограмм.

Косвенные вызовы подпрограмм обеспечивают в архитектуре Intel команды, записываемые на ассемблере в виде

CALL [имяобластиданныхдляадреса]

Например, для приведенной на рис. 8.4.4. программы на языке Си соответствующая ассемблерная программа использует следующие фрагменты

afun DD 0

. . .

// после команды CALL GetProcAddress ее результат в EAX MOV [afun], eax

CALL [afun]

. . .

Здесь для всех приведенных команд их адресные части полностью и без ка- ких-либо проблем формируются на стадии обычной компоновки. Причем они получают не нулевые значения уже в результате трансляции, так как ссылаются на именованные области в своем собственном исходном модуле.

В операционной системе Linux программа использования библиотеки динамической компоновки путем компоновки времени выполнения может быть задана примером, представленном на рис. 8.4.4.

#include <stdio.h> #include <dlfcn.h> int main()

{int k;

205

void *libc;

void (*fun)(char*);

printf("Before call DLL\n"); //sleep(10);

libc = dlopen("mydll1.so.1", RTLD_LAZY); if (!libc)

{printf("Error Load DLL\n"); fputs(dlerror(), stderr); getchar();

exit();

}

fun = dlsym(libc, "wiwoda"); if (!fun)

{printf("Error Load Function\n"); fputs(dlerror(), stderr); getchar();

exit();

}

(*fun)("My Text For DLL mmmm"); //sleep(10);

printf("After DLL mmm k=%d\n",k); dlclose(libc);

return;

}

Рис. 8.4.4. Программа для DLL с процедурами инициализации в Linux.

Заметим, что в Linux компилятор с языка Си не приписывает никаких дополнительных символов внешним именам и поэтому при запросе адреса процедуры из библиотеки DLL используется именно то имя wiwoda, которое было явно присвоено программистом при написании программы процедуры.

При формировании исполняемого файла для использования функций динамической компоновки необходимо задавать подключение соответствующей библиотеки libdl.so, содержащей реализацию функций dlopen, dlclose и dlsym. С учетом нахождения указанной библиотеки в месте размещения большинства инструментальных библиотек, это можно сделать дополнительной опцией -ldl, так что задание всей компиляции будет иметь в командной строке вид

gcc имя.c -lc -ldl

Фактическое использование личной разделяемой библиотеки, например библиотеки myd.so.1, в процессе выполнения исполняемого файла требуется, чтобы автоматически работающий динамический загрузчик сумел найти эту библиотеку в файловой системе. Для этой цели предназначена специальная системная переменная с именем LD_LIBRARY_PATH. Достаточно добавить в нее место расположе-

206

ния разделяемого объекта, который требуется программе, как проблема будет решена. Это добавление можно выполнить для текущей работы в конкретной консоли (конкретной версии командной оболочки - shell) путем выполнения следующих команд

LD_LIBRARY_PATH=путькбиблиотеке:$LD_LIBRARY_PATH export LD_LIBRARY_PATH

Сам путь к этой библиотеке в рассматриваемой установке может быть задан полным именем или использованием каких-либо вспомогательных языковых средств командного языка. Например, для подкаталога mywork основного каталого пользователя можно использовать в качестве такого пути текст ~/mywork. Для систематического использования разработанной библиотеки целесообразно рассмотренные указания включить в состав конфигурационного файла соответствующей оболочки. Так применяя командную оболочку bash, это дополнение следует записать в файл .bash-profile.

Более подробную информацию о настройке пути к личной разделяемой библиотеке следует смотреть в файле GCC_HOWTO встроенной справочной системы Linux или ее эквивалентах для других клонов Unix.

8.5.Процедуры инициализации и завершения DLL

Всовременных DLL библиотеках предусмотрены встраиваемые процедуры инициализации и завершающих действий. Эти служебные процедуры вызываются не прямым указанием обращения к ним, как обычные процедуры, а вызываются автоматически операционной системой в определенных ситуациях. Основными такими ситуациями являются загрузка DLL в память и выгрузка из нее, подключение к DLL нового процесса и отключение процесса от DLL, а также ими могут быть первое обращение к такой библиотеки нити процесса и отсоединение нити от библиотеки. В Linux и OS/2 эти процедуры вызываются только при подключении и отключении процесса, а в MS Windows - как при подключении и отключении процесса, так и при подключении и отключении нити.

При написании программ для DLL на ассемблере, вызов такой процедуры осуществляется через метку запуска программного модуля, по содержанию и оформлению совпадающей с заданием метки запуска обычного исполняемого файла. При использовании стандартных компоновщиков OS/2 и компоновщика TLINK32 эта метка может быть любым именем, указанным в программе на соответствующем ассемблере как метка запуска (на ассемблерах TASM и MASM - метка, указанная в завершающей директиве END).

Воперационной системе OS/2 такая служебная процедура инициализации и завершения автоматически вызывается с двумя параметрами длиной в 32-битное слово, уложенным в стек по типу вызова языка Си. Первый параметр представляет собой значение хэндла, присвоенного модулю библиотеки, а второй служит флагом, сигнализирующем о типе ситуации: инициализация это или завершение. Нулевое значение флага указывает ситуацию инициализации, а единичное - ситуацию

207

завершения. Процедура, вызываемая таким образом OS/2, должна обязательно возвращать единичное значение (в регистре EAX) при удачном своем выполнении и нулевое - при неудаче. Неудачное выполнение приводит к сообщению от операционной системы о невозможности загрузки библиотеки.

В ОС Windows соответствующая служебная процедура автоматически вызывается с тремя параметрами, каждый размером в четыре байта. Первый из них после вызова этой процедуры имеет также значение хэндла, присвоенного модулю библиотеки (и в этой ОС значение хэндла равно адресу размещения библиотеки в адресном пространстве конкретного процесса). Второй аргумент задает причину вызова процедуры и может иметь четыре значения, задаваемые символическими константами DLL_PROCESS_ATTACH, DLL_THREAD_ATTACH, DLL_THREAD_ATTACH, DLL_PROCESS_DETACH, которые равны соответственно числовым значениям 1, 2, 3 и 0. Последний аргумент нулевым значением указывает, что вызов служебной процедуры произошел в результате явного вызова функции GetProcAddress. Отличное от нуля значение этого аргумента информируют, что процедура вызвана неявно в процессе компоновки времени загрузки.

Служебная процедура инициализации и завершения DLL, вызываемая Windows, должна обязательно возвращать ненулевое значение (в регистре EAX) при удачном своем выполнении и нулевое - при неудаче. Последний вариант может использоваться для информировании о невозможности выполнить какие-то инициализирующие действия. Неудачное выполнение инициализации приводит к сообщению от операционной системы о невозможности загрузки библиотеки.

При программировании на языке Си в операционной системе MS Windows, служебная процедура для компоновщика фирмы Inprise/Borland должна иметь имя DllEntryPoint. Именно это имя заложено в запускающие модули для построения DLL библиотек этой фирмы, в частности в модули c0d32.obj и c0d32dyn.obj 32-бит- ных систем разработки. Пример построения служебной процедуры вместе с процедурами, явно вызываемыми из библиотеки приложениями приведен на рис. 8.5.1.

#include <windows.h> #include <stdio.h> #include <string.h> char attr=0x2e;

//__declspec(dllexport) int wiwoda(char *pt, int n) {HANDLE hstdout; COORD coor;

DWORD cbwritten;

char text[ ]="Мы находимся внутри DLL!!! var 4 Ura!";

hstdout=GetStdHandle(STD_OUTPUT_HANDLE);

208

coor.X=20; coor.Y=10; FillConsoleOutputAttribute(hstdout, attr, strlen(text),

coor, &cbwritten); WriteConsoleOutputCharacterA(hstdout, text, strlen(text),

coor, &cbwritten);

coor.Y++;

FillConsoleOutputAttribute(hstdout, attr, strlen(pt), coor, &cbwritten); WriteConsoleOutputCharacterA(hstdout, pt, strlen(pt), coor,

&cbwritten); return 20*n;

}

BOOL WINAPI DllEntryPoint(HINSTANCE hinstDll,

DWORD fdwReason, PVOID fImpLoad)

{HANDLE hstdout; COORD coor; DWORD cbwritten;

char text1[ ]="Initialize DLL var 4 ..."; char text2[ ]="Terminate DLL var 4..."; char attr=0x2c;

hstdout=GetStdHandle(STD_OUTPUT_HANDLE); switch (fdwReason)

{case DLL_PROCESS_ATTACH: coor.X=10; coor.Y=4;

FillConsoleOutputAttribute(hstdout, attr, strlen(text1), coor, &cbwritten);

WriteConsoleOutputCharacterA(hstdout, text1, strlen(text1), coor, &cbwritten);

break;

case DLL_PROCESS_DETACH: coor.X=30; coor.Y=15;

FillConsoleOutputAttribute(hstdout, attr, strlen(text2), coor, &cbwritten);

WriteConsoleOutputCharacterA(hstdout, text2, strlen(text2), coor, &cbwritten);

break;

}

return 1;

}

Рис. 8.5.1. Программа wpd4.c для MS Windows

209

Здесь следует сделать ряд пояснений. Стандартная для инициализации функция DllEntryPoint должно строго соответствовать прототипу

BOOL WINAPI DllEntryPoint(HINSTANCE hinstDll, DWORD fdwReason, PVOID fImpLoad)

где поступающие в процедуру от операционной системы параметры hinstDll и fdwReason определяют хэндл DLL, выданный для конкретного использования библиотеки в текущем процессе, и флаг причины вызова инициализирующей процедуры (называемой здесь главной точкой входа в процедуру). Флаг причины может принимать четыре возможных значения, задаваемых символическими константами DLL_PROCESS_ATTACH, DLL_PROCESS_DETACH, DLL_THREAD_ATTACH и DLL_THREAD_DETACH. Они обозначают, соответственно, ситуации вызова процедуры инициализации по причине подключения библиотеки к процессу, по освобождению (отключению) библиотеки от процесса, по подключению к библиотеки отдельной нити и по отключению нити от библиотеки (т.е. по выполнению функций LoadLibrary и FreeLibrary в отдельной - не главной нити процесса). К обработке ситуаций подключения и отключения отдельных нитей следует относиться с некоторой осторожностью по причине, описанной в [ ]. Надежней всего просто не пользоваться этими возможностями.

Для компоновщика фирмы Microsoft стандартным именем подпрограммы инициализации DLL библиотек служит имя, задаваемое на ассемблере как __DllMainCRTStartup@12. Такая процедура встречалась уже в нашем изложении на рис. 8.3.2. В программах на языке Си это же имя должно задаваться просто как _DllMainCRTStartup. Аргументы же этой функции служат для тех же целей, что и в выше рассмотренной функции DllEntryPoint (эти функции отличаются исключительно именами, выбранными в конкретной системе разработки).

Вместо служебной процедуры _DllMainCRTStartup программист имеет полное право использовать аналогичную процедуру с другим наименованием, но только, если он программирует на ассемблере. Так, например, назвав эту процедуру именем _StartDll@12 в такой программе, он при компоновке с помощью LINK.EXE должен задать опцию точки входа в исполняемый модуль. Такой вызов компоновщика будет иметь вид

link /DLL /entry:StartDll другие опции и параметры

где параметры вызова компоновщика включают имена объектного файла и используемых библиотек.

В операционной системе Linux процедуры инициализации и завершения работы библиотеки DLL разделены и называются _init и _fini. Они не имеют никаких аргументов и получают управление строго при подключении процесса к библиотеке и при отключении процесса от нее. Пример использования этих процедур приведен в программе на рис. 8.5.2.

GLOBAL wiwoda:function,_init:function,_fini:function

EXTERN printf

SEGMENT .data

210

Соседние файлы в предмете Операционные системы