зультирующего исполняемого файла на этапе компоновки программы. Поэтому компиляция данного программного примера должна выполняться обычным образом, например с помощью вызова
nasmw -f obj wpd.asm -o wpd.obj -l wpd.lst
эта исходная программа преобразуется в объектный файл wpd.obj. В результате компиляции получается обычный объектный файл.
Для компоновки библиотеки динамической компоновки нужно позаботится о описании экспорта и импорта для этой библиотеки. Одним из вариантов такого описания служит файл определения модуля со стандартным расширением .def. Название такого файла могло быть любым, но в большинстве случаев удобно называть его также как исходный файл библиотеки (или один из исходных, если их несколько).
В файле определения модуля для Windows наиболее используемыми внутренними директивами являются директивы внутреннего именования библиотеки с ключевым словом LIBRARY, директива экспорта EXPORT и директива импорта IMPORT. Ключевые слова директив размещаются в отдельных строках файла, а после них через пробелы или символы конца строки перечисляются их операнды. Единственным операндом директивы внутреннего имени библиотеки служит такое имя. Операндами директив экспорта и импорта служат имена экспортируемых или импортируемых объектов. В частном случае экспортирования библиотекой единственного имени wiwoda файл определения модуля может иметь следующей содержимое, приведенное в листинге 4.
library wpdo exports wiwoda
Листинг 4. Файл определения wpd.def для Windows
Если требуется включить в состав DLL несколько функций, которые должны быть доступны снаружи, то имена их перечисляются после строки со служебным словом EXPORTS - каждое на своей строке или через пробелы в одной или нескольких строках. Теперь создать библиотеку можно командным вызовом
TLINK32 -Tpd wpd,wpdo,,import32, wpd.def TLINK32 -Tpd wpd, wpdo,,IMPORT32,wpd.def
В результате работы компоновщика для нашего примера получается файл wpdo.DLL. Причем расширение def в последнем элементе можно было не использовать. Настоятельно рекомендуется использовать одинаковые названия результирующего исполняемого файла и в вызове компоновщика (в нашем примере неявно - через имя объектного модуля и опущенное имя на месте для названия файла результата), и в файле определения.
В тексте программы, использующей библиотеку динамической компоновки, может не быть никаких особенностей, определяющих, что процедуру следует использовать из библиотеки DLL. Относительно процедуры wiwoda в тексте такой программы может быть указано только то, что она внешняя, а поэтому реально может находиться в библиотеке статической компоновки и в составе отдельного объектного модуля, а может быть в составе библиотеки динамической компонов-
36
ки - все это уточняется позже. Файл wexd.asm подобной программы обрабатывается компилятором обычным способом в результате вызова командной строки
nasmw -f obj wexd.asm -o wexd.obj -l wexd.lst
Для компоновки объектного модуля, получаемого при этом, можно использовать файл определения модуля, заданный листингом 5.
name wexd imports wpdo.wiwoda
Листинг 5. Пример файла определения wexd.def для Windows
Файл определения исполняемого файла EXE содержит два специализированных оператора: оператор именования приложения (со служебным словом NAME) и оператор импорта (со служебным словом IMPORTS). Оператор наименования содержит в качестве операнда имя результирующего файла, а оператор импорта - перечисление имен функций, используемых из DLL библиотек, причем перед именем каждой такой функции должно обязательно стоять имя библиотеки, в которой эта функция находится. Имена функций и библиотеки при этом разделяются символом точки - без дополнительных пробелов. Разделителями в перечислении имен функций могут быть как пробельные символы (пробелы и символы табуляции), так и символы перевода на новую строку. Обычно - для наглядности - используется последний вариант.
Получение исполняемого файла wexd.exe достигается командным вызовом TLINK32 wexd,,,IMPORT32, wexd.def
В последнем элементе этого вызова - обозначении файла определения - расширение не обязательно, если он имеет стандартное такое расширение.
Использование файлов определения модуля оказывается неудобным при значительном числе экспортируемых имен. В таком случае предпочтительней оказывается применение библиотек импорта. Библиотеки импорта в системах разработки Inprise/Borland (как и в ряде других систем разработок) создаются с помощью утилиты, которая преобразует DLL-библиотеку в библиотеку импорта. Обычно такая утилита называется IMPLIB. Она используют два аргумента: название создаваемой библиотеки импорта и название исходной библиотеки DLL. Например, из библиотеки wpdo.dll библиотека импорта wpdo.lib может быть построена командным вызовом
IMPLIB WPDO.LIB WPDO.DLL
При использовании библиотеки импорта wpdo.lib (вместо файла wexd.def определения модуля) построение исполняемого файла wexd.exe с помощью компоновщика TLINK32 может быть выполнено вызовом
TLINK32 wexd,,,IMPORT32 wpdo.lib
В общем случае все используемые библиотеки импорта перечисляются, разделяясь пробелами, вместе с обычными библиотеками объектных файлов. Более того, с точки зрения современных технологий компоновки список библиотек может включать только библиотеки импорта. В частности, библиотека IMPORT32.LIB в действительности представляет собой именно библиотеку импорта системных функций ядра операционной системы Windows.
37
Если исходная программа для построения DLL библиотеки строится на языке Си, то ее компиляция в системе разработки Borland/Inprise выполняется обычным образом с опцией -с (вызовом bcc32 -c имяисх.с ). Последующую компоновку можно задать командой
ilink32 -Tpd c0d32 имяисх, имяисх,,IMPORT32 cw32, имяисх.def
где c0d32 - имя специального загрузочного модуля в формате объектного файла для построения DLL, а библиотека cw32 (с полным именем cw32.LIB) необходима только при использовании функций из стандартной библиотеки языка Си (которые почти всегда используются, так как к ним относится, в частности, и функция printf).
Вместо компоновщиков TLINK32.EXE или ILINK32.EXE от разработчика Borland/Inprise можно использовать свободно распространяемый компоновщик ALINK.EXE, уже рассматривавшийся выше. В отличие от компоновщиков от Borland/Inprise и Microsoft этот компоновщик не рассчитан на применение файлов определения модуля. Для достижения ставящихся перед ним целей необходимо передать информацию об экспорте исключительно через объектный файл. Для этого могут быть использованы специальные утилиты, но простейшим путем является указание в самой исходной программе информации об экспортируемых объектах. С этой целью в язык NASM введены директивы экспорта вида
EXPORT имя_объекта
Вчастности, исходную программу можно дополнить строкой текста EXPORT wiwoda
что позволит использовать модифицированный файл для построения библиотеки динамической компоновки. Само указание такой динамической компоновки задается с дополнительной опцией -DLL, так что вместо вызова компоновщика TLINK32.EXE или ILINK32.EXE следует использовать командный вызов
alink -oPE -dll -o wpdo.dll wpd.obj C:\UTIL\win32.lib
Заметим, что опцию -o принудительного именования выходного файла в подобном вызове компоновщика можно не указывать. Тогда в нашем примере вместо файла автоматически образуется файл wpd.dll библиотеки динамической компоновки.
Всвою очередь обеспечения импорта информационных объектов из библиотек динамической компоновки (входов в подпрограммы и имен данных) можно добиться двумя способами. Первый из них предполагает использование библиотек импорта, а второй основан на использовании информации об импорте в составе самого объектного файла. Для ее построения - непосредственно из исходного ассемблерного файла - в язык NASM включена директива импорта. Она задается в виде
IMPORT имя_объекта имя_библиотеки_dll
Вчастности для использования подпрограммы wiwoda - вне определяющей эту подпрограммы библиотеки wpdo.dll - в соответствующем ассемблерном файле нужно записать
import wiwoda wpdo.dll
При этом следует обратить особое внимание на очень важную деталь использования вызова импортируемых подпрограмм с помощью указанной директивы. Совместно с ней следует обязательно применять вызов подпрограммы в виде
38
call [имя_подпрограммы]
В частности, вызов подпрограммы wiwoda внутри ассемблерной программы должен быть записан в виде
call [wiwoda]
(Уточним еще раз, что это требует применение директивы ассемблера IMPORT.) Первый из упомянутых выше способов требует применения вспомогательной утилиты для формирования библиотеки импорта. Кроме уже называвшейся утилиты IMPLIB из директивы от Borland/Inprise для этих целей можно использовать свободно распостраняемую утилиту ALIB.EXE, входящую в современный комплект компоновщика ALINK и созданную тем же разработчиком (Copyright 1998/9 Anthony A.J. Williams.). В этом случае для построения исполняемого файла потребуются командные вызовы
alib wpdo.dll
alink -oPE -subsys console wexd.obj wpdo.lib C:\util\win32.lib
Уточним еще раз, что при использовании библиотеки импорта не нужно вводить в исходный ассемблерный файл - указание о библиотеке и поэтому достаточно воспользоваться объектным файлом wexd.obj.
Если исходная программа для построения DLL библиотеки строится на языке Си, то ее компиляция в системе разработки Borland/Inprise выполняется обычным образом с опцией -с (вызовом bcc32 -c имяисх.с ). Последующую компоновку можно задать командой
ilink32 -Tpd c0d32 имяисх, имяисх,,IMPORT32 cw32, имяисх.def
где c0d32 - имя специального загрузочного модуля в формате объектного файла для построения DLL, а библиотека cw32 (с полным именем cw32.LIB) необходима только при использовании функций из стандартной библиотеки языка Си (которые почти всегда используются, так как к ним относится, в частности, и функция printf).
В последних разработках фирма Microsoft использует новый формат объектных файлов, называемый ею COFF (Common Object File Format), но в ассемблере NASM обозначаемый win32. Для этого формата использована не бросающаяся в глаза особенность, заключающаяся в том, что хотя имена в нем обязательно дополнены префиксными символами подчеркивания, но в директивах компоновки имена указываются с опущенным символом подчеркивания. Практически это значит, что все внешние имена для данного формата, предназначенные для использования в библиотеках динамической компоновки, должны в объектном файле иметь символы подчеркивания (иначе компоновщик не сможет их использовать). При использовании ассемблерных вставок в программе Си имена локальных переменных могли быть в любой момент обозначены также с помощью дополнительного предшествующего символа подчеркивания. Так локальные переменные i и j в ассемблерной вставке должны были обозначаться как _i и _j.
Более того, все действительные имена системных функций ОС Windows должны строиться из имен прототипов на языке Си путем дополнения не только предшествующим символом подчеркивания, но и суффиксом вида @число_байтов_в_области_аргументов. В частности, из имени GetStdHandle при этом получается имя _GetStdHandle@4, а из имени WriteFile получается имя _WriteFile@20. Именно такие "настроенные" имена можно увидеть при просмотре
39
объектного файла, полученного 32-битным транслятором MS из программы Си, которая использует указанные функции GetStdHandle и WriteFile.
Кроме необходимости использовать в ассемблерных программах для DLL оформленных указанным способом имен, в программе для DLL требуется учесть еще одну особенность. Как уже упоминалось, каждая DLL должна содержать специальную процедуру инициализации. По умолчанию компоновщик link.exe фирмы Microsoft принимает для этой процедуры действительное имя __DllMainCRTStartup@12. При желании, можно имя такой инициализирующей процедуры переопределить, используя опцию компоновщика /ENTRY. Она записывается при вызове LINK.EXE в виде /ENTRY:инитимя, но действительное имя точки входа в такую процедуру должно быть написано на ассемблере в виде _ инитимя@12 (т.е. соответствовать описанным выше соглашениям по использованию имен системных функций для формата COFF объектных файлов Microsoft). Подробней с функциями инициализации для DLL в общем случае мы будет разбираться позже. Пока нам достаточно, что эта функция должна быть описана как получающая 12 байтов аргументов и возвращать - для нормальной работы - отличное от нуля значение.
Построение исполняемых файлов из программ wp1.asm и wp2.asm может быть задано командным файлом (или простой последовательность команд оболочки ОС), приведенным в листинге 6.
nasmw -f win32 -o wp1.obj wp1.asm nasmw -f win32 -o wp2.obj wp2.asm
link /DLL /export:wiwoda wp2.obj kernel32.lib
link /SUBSYSTEM:CONSOLE /ENTRY:start wp1.obj kernel32.lib wp2.lib Листинг 6. Командный файл построения и использования DLL
Здесь вызов компоновщика с опцией DLL формирует библиотеку с неявно заданным именем wp2.DLL (собственное имя которой совпадает с именем первого или единственного объектного файла, обрабатываемого компоновщиком link). Кроме того, и это отличительная особенность рассматриваемого компоновщика фирмы MS, одновременно с библиотекой DLL автоматически формируется библиотека импорта, имеющая то же собственное имя, но в качестве расширения буквосочетание LIB. Последняя библиотека используется в последующем вызове компоновщика для формирования исполняемого файла wp1.exe. Он строиться из объектного модуля wp1.obj с помощью библиотеки импорта KERNEL32.LIB для доступа к функциям API Windows в указанном выше соглашении на действительные имена таких функций.
В опциях компоновщика для исполняемого файла wp1.exe указывается, что программа предназначена для выполнения в консольном режиме (опция /SUBSYSTEM:CONSOLE) и что метка начала выполнения программы есть start (опция /ENTRY:start). Заметим, что действительное имя метки начала выполнения программы есть _start, но выше уже объяснялась почему имеется такое расхождение имен.
Для формирования DLL-библиотеки использован один из вариантов задания экспортируемых из библиотеки имен. Этот вариант состоит в использовании оп-
40