[ English | Español | Pyccκuú ]

ImpLib SDK

[ Скачать ]

Дата публикации:
Последнее обновление: 2024-07-23
Автор:

Как это использовать?
DLL2DEF
Моды для Visual Basic и PureBasic
Туториал: MSVCRT.lib для MASM32 и NASM
Туториал: OpenAL PureBasic Userlib

 

ImpLib SDK ImpLib SDK — это набор утилит для создания библиотек импорта, предоставляющий расширенные возможности, как то: переходники cdecl2stdcall, импорт по ординалу, отключение оригинальных санков (original thunk).
  • Импорт по ординалу. Например, Вы собираетесь вызвать следующую функцию из DLL: MYDLL!dummy_function ORD:20. Символьное имя функции — dummy_function, а ординал — 20. Обычно не советуют импортировать функции по ординалу, т.к. значение ординала может измениться к следующей версии DLL. Но, если есть уверенность, что версия DLL не изменится, или разработчик гарантирует совместимость по ординалам (например, если это ваша DLL и Вы фиксированно назначаете ординалы или всегда используете одну и ту же версию), тогда Вы можете заметно повысить скорость загрузки ваших экзешников при использовании ординалов вместо символьных имён. В добавок к тому, ещё и размер секции импорта получается меньше. Кстати, если Вы всегда используете один и тот же билд DLL, тогда ещё лучше использовать байнд (bind) модулей, чтобы максимально поднять скорость загрузки.
  • Совмещение символов из разных DLL в одной библиотеке импорта. Например, Вы можете собрать в одной библиотеке, назвав её, скажем, win32api.lib, символы из основных системных DLL, как то: KERNEL32, USER32, GDI32 и других библиотек Windows API. Тогда Вы сможете добавлять в опции линкера эту единственную библиотеку вместо комбинации из kernel32.lib, user32.lib, gdi32.lib и т.д.
  • При использовании не-STDCALL DLL в Visual Basic 6, PureBasic и других популярных средах разработки, соглашение о вызове той или иной функции может не поддерживаться компилятором. ImpLib умеет генерировать легковесные переходники для приведения CDECL и других соглашений к стандартному STDCALL.
  • ImpLib поддерживает создание библиотек без оригинальных санков (original thunk). После линковки со "стрипнутой" библиотекой, экзешник также будет лишён оригинальных санков. Таким образом, экзешник получается меньшего размера. Линкер polink полностью поддерживает "стрипнутые" библиотеки импорта. Линкер MS (тот, что входит в Visual Studio) также воспринимает без-санковые библиотеки, но с одним немаловажным ограничением. Иногда, при совмещении "стрипнутых" и "нестрипнутых" библиотек импорта в одном и том же проекте, таблицы импорта получаются "бракованными". Поэтому, если Вы используете линкер MS, не рискуйте совмещать полноценные библиотеки импорта со "стрипнутыми": используйте либо те, либо другие, но не оба вида сразу. Линкер GNU (LD) поддерживает "стрипнутые" библиотеки, но тоже не любит совмещение их с обычными. Последние версии в этом плане более надёжны. Чтоб библиотека получилась без оригинальных санков, просто добавьте следующий флаг: KEEP_ORIGINAL_THUNKS equ 0 сразу после include 'implib.inc'. "Стрипнутый" импорт нельзя байндить, но во всех остальных смыслах он полноценен и работает во всех Windows.

Как это использовать?

Прежде всего нужно написать скрипт с определениями экспортируемых символов (т.е. функций) из заданной DLL. Синтаксис довольно прост и похож на формат DEF Microsoft. Давайте рассмотрим пример с функциями ввода-вывода из библиотеки KERNEL32:

include 'implib.inc'

   ; KERNEL32.DLL
   implib kernel32.dll, CreateFileA
   implib kernel32.dll, CreateFileW
   implib kernel32.dll, ReadFile
   implib kernel32.dll, SetFilePointer
   implib kernel32.dll, CloseHandle

endlib

Всё, что начинается с ';', является комментарием. Перед списком символов нужно указать ссылку на файл implib.inc. Нужно указывать полный или относительный путь, если только данный файл не расположен в одном со скриптом каталоге. В конце списка не забудьте указать замыкающую команду endlib. Каждое определение символа начинается с команды implib, среди параметров которой можно выделить в первую очередь имя DLL и имя экспортируемого символа. Вместо символьного имени можно указать ординал — для этого предусмотрена приставка 'ord.':

   implib kernel32.dll, ord.5

Если Вы хотите, чтобы данная функция была доступна линкеру под каким-либо другим именем, укажите это имя в дополнительном параметре:

   implib kernel32.dll, CreateFileA, CreateFile

Таким образом, при обращении к функции CreateFile, линкер на самом деле создаст ссылку на KERNEL32!CreateFileA. Таким образом, можно опустить суффикс 'A' в обьектном файле. Данная фишка полезна в основном для совместимости с различного рода манглингом. Например, компиляторы MS полагают, что функция CreateFileA с 7ю параметрами по соглашению STDCALL должна называться _CreateFileA@28. Давайте исправим наш первоначальный пример для совместимости с соглашением MS:

include 'sdk\implib.inc'

   ; KERNEL32.DLL
   implib kernel32.dll, CreateFileA,    _CreateFileA@28
   implib kernel32.dll, CreateFileW,    _CreateFileW@28
   implib kernel32.dll, ReadFile,       _ReadFile@20
   implib kernel32.dll, SetFilePointer, _SetFilePointer@16
   implib kernel32.dll, CloseHandle,    _CloseHandle@4

endlib

Как видите, имена фукций STDCALL начинаются с приставки '_' и заканчиваются суффиксом '@#', где # — это количество параметров умноженное на 4. На самом деле # — это размер параметров в байтах, занимаемый ими в стеке, но в большинстве случаев это равняется количеству параметров на 4.

Сохраните скрипт. Давайте назовём его kernel32.def и запустим компилятор из командной строки:

   bin\fasm kernel32.def kernel32.lib
   flat assembler  version 1.67.23  (533081 kilobytes memory)
   3 passes, 4247 bytes.

Получаем файл с именем kernel32.lib! Кстати, FASM на самом деле является ассемблером, но его препроцессор настолько развит, что на нём можно разрабатывать скриптинговые движки вроде ImpLib :-)

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

DLL2DEF

Это утилита для извлечения в текстовом виде экспортируемых символов из динамических библиотек DLL.

Так как в больших библиотеках может быть очень много полезных функций, описывать их вручную может оказаться весьма нудным занятием. Лучше воспользоваться утилитой в каталоге \bin. Для начала, откроем командный интерпретатор и введём dll2def без параметров, чтобы получить следующее сообщение:

 USAGE: dll2def [options] file [output]
        file   - input file name.
        output - optional output file name.
        options:
         /COMPACT - Don't place any comments.
         /PB      - Enable PureBasic mod.
         /VB      - Enable Visual Basic 6 mod.
 Example:
 dll2def \windows\system32\kernel32.dll test.def

Итак, давайте запустим:

   dll2def c:\windows\system32\kernel32.dll

Получаем файл kernel32.def, в котором уже определены все символы, которые экспортируются данной библиотекой. Ссылка на implib.inc содержит полный путь (только если implib.inc находится в одном каталоге с dll2def.exe), чтобы можно было свободно перемещать скрипт в любой другой каталог, не заботясь о пути к implib.inc. Перед каждым символом есть пара строчек с комментариями:

   ; KERNEL32.AddVectoredExceptionHandler ORD:9
   ; -> NTDLL.RtlAddVectoredExceptionHandler
   implib kernel32.dll, AddVectoredExceptionHandler

В комментариях указано полное имя символа (если доступно), ординал и ссылка на настоящий символ, если это цепочка форварда (т.н. forwarder chain). Сведения об ординале полезны для замены символьного имени на ординал, если у Вас есть на то веские основания. Цепочка форварда означает, что символ на самом деле находится в другой DLL. В предыдущем примере символ AddVectoredExceptionHandler, который экспортируется в KERNEL32.DLL, в конечном итоге принадлежит NTDLL. Вы можете переписать определение форварда дабы направить импорт на настоящую DLL, но с точки зрения совместимости этого делать не стоит. Если Вам не нужны лишние комментарии в скрипте, просто укажите опцию /COMPACT в командной строке DLL2DEF:

   sdk\dll2def /COMPACT c:\windows\system32\kernel32.dll

Моды

На данный момент сыществуют 2 модификации ImpLib:

Вместо команды implib в скрипте укажите команду vbimplib или pbimplib для включения модов Visual Basic или PureBasic соответственно. DLL2DEF поддерживает ключи /VB и /PB, с помощью которых можно задать режим совместимости с тем или иным модом. Обратите внимание на следующие туториалы, чтобы скорее понять что к чему.

Туториалы

Если Вы уже прочитали введение, выберите какой-либо из следующих кратких туториалов для рассмотрения примеров применения:

MSVCRT.lib для MASM32 и NASM

Библиотека Microsoft Visual C Run-Time (MSVCRT.DLL) экспортирует более 700 функций, упрощающих многие стандартные задачи, как то: форматирование консольного вывода, работа с динамической памятью, манипуляция текстовых строк, сортировка и т.д. Конечно, всё это можно реализовать вручную через стандартные функции API, но раз уж библиотека C Run-Time практически всегда предустановлена в любой версии Windows (кроме Windows 95), обычно имеет смысл использовать сишные функции вместо изобретения велосипедов printf(), malloc() и др. Итак, целью нашего первого туториала будет написание парочки простых примеров на MASM32 и NASM с использованием MSVCRT.DLL.

Давайте начнём с MASM32. В комплекте последнего релиза MASM32 имеется заголовочный файл msvcrt.inc для импорта из библиотеки MSVCRT, но нет файла msvcrt.lib, которого требует линкер. Если у Вас установлена Visual Studio, можете извлечь динамическую версию msvcrt.lib из неё, но в этом случае возникнет небольшой конфликт связанный с именами символов div() и fabs(). В MASM32 и NASM нельзя использовать div или fabs в качестве имени функции потому, что эти имена зарезервированы для мнемоник x86. Аналогичная ситуация возникает при использовании имён, состоящих из одних цифр, и зарезервированных слов, вроде proc, label и т.д. Библиотеки импорта из комплекта Visual Studio зачастую создают дополнительные зависимости от OLDNAMES.lib, UUID.lib и других вспомогательных библиотек. Есть подозрение, что библиотеки из Visual Studio не подлежат свободному распространению, поэтому msvcrt.lib и не включена в состав MASM32.

Давайте создадим свою собственную MSVCRT.lib, не подпадающую под какие-либо копирайты и с именами символов, полностью совместимыми с MASM и NASM.

Первым делом нужно создать скрипт со всеми именами публичных символов MSVCRT.DLL:

   dll2def /COMPACT \windows\system32\msvcrt.dll

Теперь нужно исправить конфликт имён символов. Откройте в блокноте msvcrt.def и задайте поиск следующей строки:

   implib msvcrt.dll, div

Так как div является мнемоникой инструкции беззнакового целочисленного деления, нужно переименовать эту функцию. Hutch предлагает использовать приставку '_crt_' (на официальном форуме masm32 есть топик, посвящённый данной теме). Итак, давайте добавим приставку:

   implib msvcrt.dll, div, _crt_div

Аналогичным образом исправляем описание функции fabs. Рекомендуется добавить приставку '_' к именам санков всех функций, которые не содержат этой приставки. Таким образом эти функции можно будет вызывать через invoke. Например:

   ; MSVCRT.printf ORD:742
   implib msvcrt.dll, printf

Рекомендуется переписать следующим образом:

   ; MSVCRT.printf ORD:742
   implib msvcrt.dll, printf, _printf

А вот как это будет выглядеть с использованием ординала:

   ; MSVCRT.printf ORD:742
   implib msvcrt.dll, ord.742, _printf, __imp__printf

Так как настоящее имя функции пропадает при использовании ординала, рекомендуется явно указывать имя санка (_printf) и публичное имя импорта (__imp__printf). ImpLib не выдаст ошибку, если Вы не укажите явно эти имена, но в противном случае вызвать эти функции окажется нетривиальной задачей ;-)

Скомпилируйте библиотеку. Так как ImpLib выполняется интерпретатором FASM'а, а скрипт содержит более 700 функций, компиляция может затянуться на несколько минут:

   \sdk\fasm msvcrt.def msvcrt.lib
   flat assembler  version 1.67.23  (518320 kilobytes memory)
   3 passes, 35.4 seconds, 460585 bytes.

Ну, вот мы и получили нашу собственную версию msvcrt.lib :-) Или список синтаксических ошибок, повторяющихся имён или других невесёлых сообщений. Просто исправьте ошибки и повторите попытку. Готовый к употреблению msvcrt.def и уже откомпилированная msvcrt.lib находятся в \src\test.

Чтобы вызвать функцию CRT в MASM32 с использованием invoke, необходимо предварительно указать прототип. \src\test\msvcrt.inc содержит набор прототипов для CRT. Вместо externdef можно использовать PROTO, если Вы по какой-то причине предпочитаете строить импорт через санки. Например:

   printf PROTO C :DWORD,:VARARG

При вызове printf() с использованием предыдущего прототипа, компилятор сгенерирует call <jmp msvcrt!printf> вместо прямого call msvcrt!printf. Подобный косвенный вызов и называется вызовом через санк. Использование санков обычно влечёт за собой небольшую потерю производительности, поэтому лучше использовать более эффективный externdef:

   cdecl_dword_vararg typedef PROTO C :DWORD,:VARARG
   externdef _imp__printf:PTR cdecl_dword_vararg
   printf equ <_imp__printf>

На этот раз вызов будет прямым.

Вот пример под MASM32 содержащий вызов MSVCRT!printf:

   .386
   .model flat,stdcall

   ; MSVCRT API
   include msvcrt.inc
   includelib msvcrt.lib

   .CODE
   format db "Hello, world!",0

   start:
      invoke printf,OFFSET format
      ret
   END start

Давайте попробуем откомпилировать, слинковать и запустить этот пример из командной строки:

   \masm32\bin\ml /c /coff test_masm32.asm
   \masm32\bin\link /SUBSYSTEM:CONSOLE test_masm32.obj
   test_masm32
   Hello, world!

Тоже самое, но для NASM:

   EXTERN __imp__printf
   %define printf [__imp__printf]

   section .text
   format db "Hello, world!",0

   GLOBAL _start
   _start:
      push format
      call printf
      add esp,4 ; fix the stack
      ret

Собрать, слинковать, запустить:

   \nasm\nasmw -fwin32 test_nasm.asm
   \nasm\polink /ENTRY:start /SUBSYSTEM:CONSOLE test_nasm.obj msvcrt.lib
   test_nasm
   Hello, world!

Туториал по MSVCRT.LIB подошёл к концу.

OpenAL PureBasic Userlib

Задача: создать Userlib для PureBasic с обращением ко внешней DLL. В качестве примера будет использована OPENAL32.DLL из дистрибутива OpenAL v1.0/v1.1. Так как данная DLL активно использует соглашение CDECL, мы не можем просто воспользоваться утилитой DLL Importer. Конечно, можно обновить PureBasic до версии 4 и использовать новую конструкцию ImportC совместно с OpenAL32.lib из комплекта OpenAL SDK, или OpenLibrary и CallCFunctionFast при каждом обращении к какой-либо функции OpenAL. Можно придумать и более сложные варианты, вроде враппера над DLL и т.д. В этом туториале мы создадим Userlib для PureBasic для прямого доступа к OpenAL с минимальными затратами на переходниковый (санковый) код. Переходник будет более быстрым, чем аналогичный вызов через CallCFunctionFast, с проверкой прототипа на стадии компиляции, контекстными подсказками и справкой и любыми дополнительными возможностями, которые только может предоставить отдельный Userlib. Поддержка 3й версии PureBasic гарантируется.

Для начала нужно создать скрипт с описанием всех функций:

   dll2def /PB /COMPACT \windows\system32\OpenAL32.dll

Получаем файл с именем openal32.def, в котором находятся все публичные символы из OpenAL32.dll. Например:

   pbimplib OpenAL32.dll, STDCALL, 0, alBuffer3f

Синтаксис команды pbimplib:

   pbimplib dllname, convention, numarguments, real_name, PureBasics_name, public_name

Параметр dllname определяет имя файла DLL. Параметр convention определяет соглашение о вызове данной функции. Поддерживаются следующие соглашения:

numarguments указывает количество 32-битных ячеек, которые нужно выделить на стеке под аргументы функции. Обычно это значение соответствует количеству параметров функции, за исключением случаев с 64-битными аргументами (double, __int64 и т.д.). Этот параметр не имеет значения для STDCALL. real_name — это настоящее имя функции, определённое в таблице экспорта DLL. Последние 2 параметра факультативны. PureBasics_name позволяет назначить данной функции другое имя, под которым она будет доступна в PureBasic. По умолчанию, если не указывать этот параметр, используется real_name. public_name зарезервирована для специальных случаев, обычно связанных с низкоуровневым программированием. Этот аргумент позволяет задать имя символа для прямого вызова, не через санк. Обычно нет необходимости заботиться об этом значении. Давайте снова взглянем на наш скрипт:

   pbimplib OpenAL32.dll, STDCALL, 0, alBuffer3f

alBuffer3f() не использует соглашение STDCALL, да и принимает несколько аргументов, а не 0. DLL2DEF просто не может угадать количество аргументов и соглашение о вызове, потому что не умеет читать "OpenAL Programmer's Guide" :-) Использование "STDCALL, 0" просто отключает автоматическое восстановление стека после вызова данной функции через санк. Правильный прототип:

   pbimplib OpenAL32.dll, CDECL, 5, alBuffer3f

Можете исправить остальные прототипы или просто воспользоваться готовым скриптом в src\PureBasic\openal32.def. Давайте скомпилируем его:

   fasm openal32.def pbopenal.lib

Без ошибок? — Вот и отлично. Теперь нужно создать описание для библиотеки и назвать его точно также как библиотеку импорта, но с расширением .desc. В данном случае этот файл нужно назвать pbopenal.desc. Загляните в PureBasic\Library SDK\Readme.txt, если не знакомы с утилитой LibraryMaker.

   ; ЯП, на котором создана библиотека: ASM или C. В данном случае нужно указать C.
   C

   ; Кол-во внешних DLL, от которых зависит данная библиотека: ни одной в данном случае.
   0

   ; Формат библиотеки (OBJ или LIB). Нужно указать LIB, ведь pbopenal.lib — это LIB и есть.
   LIB

Далее нужно указать символы, которые эспортируются библиотекой. Полный пример находится в src\PureBasic\pbopenal.desc. Наконец, нужно запустить утилиту LibraryMaker.exe, чтобы получить Userlib:

   LibraryMaker.exe pbopenal.desc /COMPRESSED

Готовую Userlib нужно поместить в PureBasic\PureLibraries\UserLibraries. Тестовые примеры с использованием OpenAL Userlib можно найти в пакете (или µFMOD).

Благодарю за внимание! Надеюсь, данный материал оказался Вам полезен.
 

Наверх
© 2006 — 2024 Владимир Каменяр
Все права защищены