Материал: TextmetukSPO

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам

ции /EXPORT, за ключевым словом которой через двоеточие следует имя экспортируемой функции - в форме, принятой для имен языка Си. В нашем случае опция указывается в виде /EXPORT:wiwoda, хотя действительное имя функции (видимое и в объектном файле) есть _wiwoda.

Третья команда командного файла в листинге 6 может быть заменена на команду

link /DLL /DEF:wp2.def wp2.obj kernel32.lib

где файл определения библиотеки может быть задана двумя строками LIBRARY wp2

EXPORTS wiwoda

но может состоять и только из второй строки с оператором EXPORTS, так как оператор LIBRARY для компоновщика фирмы MS не является обязательным.

В данном случае очевидным образом проще первый из рассмотренных вариантов. При импорте многих имен из DLL-библиотеки можно использовать необходимое число раз опцию EXPORT в вызове программы LINK. Возможет еще промежуточный вариант, заключающийся в том, что множество опций вызова программы помещается во вспомогательных файл, например с именем wwpp.lnk, а последний используется при вызове компоновщика в виде

link /DLL wp2.obj kernel32.lib @ wwpp.lnk

Этот файл wwpp.lnk может содержать, например, текст /EXPORT:wiwoda

/EXPORT:funa

/EXPORT:abc

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

Когда исполняемый файл создается из исходного файла wp1.c на языке Си и должен вызывать функции из DLL-библиотеки wp2.DLL, для компоновки может быть использован командный вызов

link /SUBSYSTEM:CONSOLE /ENTRY:main wp1.obj kernel32.lib wp2.lib

При этом предполагается, что объектный файл получен вызовом транслятора cl -c wp1.c. При использовании исходного языка Си имена экспортируемых функций в этой программе и используемых данных экспорта совпадают (в данном случае вместо библиотеки можно было использовать опцию /EXPORT:wiwoda).

Еще проще строить исполняемый файл без явного получения объектного файла, что можно добиться командным вызовом

cl wp1.c wp2.lib

В свою очередь, средствами MS получение библиотеки DLL из исходного файла на языке Си проще всего достичь вызовом

cl имяисх.c /link /DLL /EXPORT:имя_экпортируемое

Но при этом результирующий исполняемый файл, хотя и будет по своему содержанию динамической библиотекой, получить автоматически расширение имени EXE. Чтобы добиться обычного в практике обозначения DLL библиотеки, следует предыдущий вызов задавать в несколько измененном виде, приведенном в следующей строке.

cl /Feимяdll.dll имяисх.c /link /DLL остальные опции и параметры

41

Вчастности из исходного файла wp3.c, приведенного в листинге 6, библиотека wp3.DLL может быть создана вызовом

cl /Fewp3.dll wp3.c /link /DLL /EXPORT:wiwoda

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

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

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

HINSTANCE LoadLibrary(LPCTSTR lpLibFileName);

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

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

FARPROC GetProcAddress(HMODULE hModule, LPCSTR lpProcName);

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

Для освобождения библиотеки в этой операционной системе служит функция BOOL FreeLibrary(HMODULE hLibModule),

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

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

Использование функции wiwoda с прототипом int wiwoda (char *pt, int n)

и находящейся в библиотеке динамической компоновки wpdll3.dll может быть схематически представлено фрагментами программы в листинге 7.

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

{. . .

int rez; HANDLE hdll;

. . .

42

hdll = LoadLibrary("wpdll3.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);} rez=afun(ttt, 37);

.. .

FreeLibrary(hdll);

. . .

}

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

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

bcc32 -c wpd3.c

и

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

library wpd3 exports _wiwoda

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

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

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

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

TLINK32 c0x32 wexd3,wexd3,,IMPORT32 CW32 WPD3

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

43

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

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

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

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

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

afun DD 0

. . .

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

CALL [afun]

.. .

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

-метка, указанная в завершающей директиве END).

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

44

ственно числовым значениям 1, 2, 3 и 0. Последний аргумент нулевым значением указывает, что вызов служебной процедуры произошел в результате явного вызова функции GetProcAddress. Отличное от нуля значение этого аргумента информируют, что процедура вызвана неявно в процессе компоновки времени загрузки.

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

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

Стандартная для инициализации функция 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. Такая процедура встречалась уже в нашем изложении в листинге 7. В программах на языке Си это же имя должно задаваться просто как _DllMainCRTStartup. Аргументы же этой функции служат для тех же целей, что и в выше рассмотренной функции DllEntryPoint (эти функции отличаются исключительно именами, выбранными в конкретной системе разработки).

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

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

45

Источник: https://studfile.net/preview/16469866/