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

ImpLib SDK

[ Скачать ]

Дата публикации:
Последнее обновление: 2025-01-20
Автор:

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

 

ImpLib SDK ImpLib SDK — это набор утилит для создания персонализированных библиотек импорта для DLL Windows (32 и 64 бит). 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). После линковки с такой «урезанной» библиотекой, исполнимый файл также будет лишён переходников. Таким образом, экзешник получается меньшего размера. Чтоб библиотека получилась без переходников вызова, просто добавьте следующую опцию: KEEP_ORIGINAL_THUNK equ 0 перед первым включением макроса implib. «Урезанный» импорт нельзя привязывать (bind), но во всех остальных смыслах он полноценен и работает во всех версиях Windows.

Библиотеки импорта, созданные с помощью ImpLib SDK, совместимы с любым линкером, поддерживающим формат объектного файла MS COFF. Следующие линкеры были тщательно протестированы на предмет совместимости:

  • Pelle's Linker (также известный как polink) — это бесплатный надежный линкер, созданный Пелле Ориниусом, разработчиком Pelle's C. Никаких проблем совместимости при использовании 32- или 64-битных, обычных либо «урезанных» библиотек импорта не обнаружено.
  • Линкер MS, который поставляется вместе с Visual C++ и Windows SDK. Поддерживаются все версии. 32-битная версия линкера может выдавать ошибку LNK1102 при загрузке библиотек импорта, размер которых превышает определённое значение. ImpLib SDK выдаст предупреждение о совместимости, если библиотека импорта превысит максимальный размер, поддерживаемый 32-битным линкером MS. Обычно это может произойти, когда DLL экспортирует более 1500 символов. Размер каждого символа также имеет значение. Для обхода данного ограничения можно использовать 64-битный линкер. При использовании линкера MS не рекомендуется одновременно линковать полные и «урезанные» библиотеки импорта. Безопаснее использовать только «урезанные» или только полные библиотеки, но не оба вида сразу. В противном случае таблицы импорта в конечном исполняемом файле могут получиться «бракованными».
  • GNU Linker (или LD) доступен в дистрибутивах MinGW. Поддерживаются как 32-, так и 64-битные DLL. Этот линкер можно использовать для кросс-компиляции под Win32 или Win64 из Linux или другой ОС. Поддерживаются «урезанные» библиотеки.
 

Установка и применение

Распакуйте релиз ImpLib SDK в любой каталог. Затем запустите build_libs.bat. Этот пакетный файл собирает или пересобирает все включенные примеры библиотек импорта: MSVCRT, а также примеры системных библиотек для Win32 и Win64. Процесс сборки может занять несколько минут из-за большого размера данных библиотек.

Выходные файлы сохраняются в подкаталоге lib. Для каждого входного файла создаются две библиотеки импорта: обычная библиотека импорта, включающая переходники (original thunk), и «урезанная» версия (без original thunk). Последняя сохраняется в подкаталоге stripped.

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

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

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

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 (для 32-битных DLL), либо на implib64.inc (для 64-битных DLL). Нужно указывать полный или относительный путь, если только данный файл не расположен в одном каталоге со скриптом. В конце списка не забудьте указать замыкающий макрос endlib. Каждое определение символа начинается с макроса implib, в котором указывается имя DLL и имя экспортируемого символа. Вместо символьного имени можно указать ординал — для этого предусмотрена приставка 'ord.':

   implib kernel32.dll, ord.5

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

   implib kernel32.dll, CreateFileA, CreateFile

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

include 'src\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

Как видите, имена 32-битных функций STDCALL начинаются с символа подчёркивания (_) и заканчиваются суффиксом из символа собаки и десятичного числа (@#). Десятичное число — это количество байтов в списке аргументов. Обычно оно равно количеству аргументов умноженному на 4, поскольку большинство аргументов в стеке либо 32-битные, либо выровнены по 32-битной границе (32 бита = 4 байта).

Это соглашение не распространяется на 64-битные библиотеки. Для линковки к 64-битной KERNEL32.DLL список будет выглядеть следующим образом:

include 'src\implib64.inc'

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

endlib

Как можете заметить, DLL называется KERNEL32, хотя целевой платформой является Win64. Microsoft намеренно использует одинаковые имена DLL.

Сохраните def-файл (32- или 64-битную версию). Назовем его kernel32.def, но можно выбрать любое другое имя. Теперь мы готовы скомпилировать его из командной строки:

   bin\fasm kernel32.def kernel32.lib
   flat assembler  version 1.67.23  (533081 kilobytes memory)
   DLL symbols indexed: 5
   3 passes, 4313 bytes.

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

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

DLL2DEF

Это утилита для извлечения в текстовом виде экспортируемых символов из динамических библиотек DLL. DLL2DEF поддерживает как 32-, так и 64-битные библиотеки 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

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

   bin\dll2def c:\windows\system32\kernel32.dll

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

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

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

   bin\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) экспортирует более 1300 функций, упрощающих многие стандартные задачи, как то: форматирование консольного вывода, работа с динамической памятью, манипуляция текстовых строк, сортировка и т.д. Конечно, всё это можно реализовать вручную через стандартные функции 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:

   bin\dll2def /COMPACT \windows\system32\msvcrt.dll

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

   implib msvcrt.dll, div

Так как div является мнемоникой инструкции беззнакового целочисленного деления, нужно переименовать эту функцию. Стив Хатчессон (Hutch), создатель MASM32 SDK, предложил добавлять приставку '_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'а, а def-файл содержит большое количество функций, компиляция может затянуться на несколько минут:

   \bin\fasm msvcrt.def msvcrt.lib
   flat assembler  version 1.67.23  (518320 kilobytes memory)
   DLL symbols indexed: 1372
   3 passes, 21.4 seconds, 845227 bytes.

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

Чтобы вызвать функцию CRT в MASM32 с использованием invoke, необходимо предварительно указать прототип. \src\MSVCRT32\test_masm\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>

На этот раз вызов 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.asm
   \masm32\bin\link /SUBSYSTEM:CONSOLE test.obj
   test
   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\nasm -fwin32 test.asm
   \nasm\polink /ENTRY:start /SUBSYSTEM:CONSOLE test.obj msvcrt.lib
   test
   Hello, world!

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

OpenAL PureBasic Userlib

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

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

   bin\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 просто не может угадать количество аргументов и соглашение о вызове. Использование "STDCALL, 0" отключает автоматическое восстановление стека после вызова данной функции через переходник. Правильный прототип:

   pbimplib OpenAL32.dll, CDECL, 5, alBuffer3f

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

   bin\fasm openal32.def Pbopenal.lib

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

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

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

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

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

   ; Имя каталога, в котором находится справка для API-функций данной библиотеки.
   Help

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

   LibraryMaker .\Pbopenal.desc

Примечание. Убедитесь, что путь к LibraryMaker не содержит пробелов.

Если сборка прошла успешно, готовую Userlib нужно поместить в [PureBasic]\PureLibraries\UserLibraries. Тестовые примеры с использованием OpenAL Userlib можно найти в PureBasic OpenAL SDK и uFMOD (или µFMOD).

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

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