Страницы

Поиск по вопросам

вторник, 27 ноября 2018 г.

Как создать библиотеку импорта для существующей DLL

Есть несколько DLL, требуется использовать функции из них в проекте на Си++. Доступа ни к исходникам ни к разработчикам у меня нет. То есть в наличии просто набор DLL как вешь в себе и документ с описанием интерфейса к ним.
Сейчас я получаю адреса функций вручную c помощью LoadLibrary и GetProcAdrress, но под мою задачу было бы удобнее иметь библиотеки импорта для них. Чтобы никаких телодвижений по инициализации не делать, не следить за ними и так далее. Так вот, как библиотеки импорта создать и вообще как они работают?


Ответ

В принципе, это неподдерживаемый сценарий: например, функции могут быть декорированы нестандартным образом, или иметь нетрадиционное соглашение о вызове.
Но тем не менее, если вы знаете точную сигнатуру функций, то можно попытаться сделать, как описано здесь
Идея №1: создать DEF-файл вручную. Это подойдёт, если функции, которые вы импортируете, есть C-функции, и вы знаете их calling convention (например, __cdecl или PASCAL (__stdcall)).
Для начала, установим, что это за функции. Для этого можно воспользоваться стандартной утилитой dumpbin с ключом /exports (не забывайте, что её нужно запускать из-под Visual Studio command prompt) или очень полезной при нативной разработке под Windows-платформу утилитой depends.exe
Для dumpbin вы получите примерно такой вывод:
...> dumpbin /exports "C:\Program Files\Far\Plugins\arclite\arclite.dll" Microsoft (R) COFF/PE Dumper Version 11.00.61030.0 Copyright (C) Microsoft Corporation. All rights reserved.
Dump of file C:\Program Files\Far3\Plugins\arclite\arclite.dll
File Type: DLL
Section contains the following exports for arclite.dll
00000000 characteristics 54BA6E68 time date stamp Sat Jan 17 15:15:04 2015 0.00 version 1 ordinal base 19 number of functions 19 number of names
ordinal hint RVA name
2 0 00016170 AnalyseW 3 1 00016920 CloseAnalyseW 4 2 000175F0 ClosePanelW 5 3 000182A0 ConfigureW 6 4 00017DA0 DeleteFilesW 1 5 000185E0 ExitFARW 7 6 00017B00 FreeFindDataW 8 7 00017B60 GetFilesW 9 8 000179F0 GetFindDataW 10 9 00015F20 GetGlobalInfoW 11 A 000177C0 GetOpenPanelInfoW 12 B 00016030 GetPluginInfoW 13 C 00017EC0 MakeDirectoryW 14 D 00016970 OpenW 15 E 00017FD0 ProcessHostFileW 16 F 00018180 ProcessPanelInputW 17 10 00017C80 PutFilesW 18 11 000178A0 SetDirectoryW 19 12 00015FB0 SetStartupInfoW
Summary
5000 .data 16000 .rdata 9000 .reloc 1000 .rsrc 87000 .text
Отсюда вы берёте имена функций: AnalyseW, CloseAnalyseW и т. д., и превращаете их в .def-файл:
EXPORTS ; EntryName [=InternalName] [@Ordinal] [NONAME] [CONSTANT] AnalyseW CloseAnalyseW
Так работает для функций с calling convention __cdecl (она обычно принята по умолчанию). Для других вы должны задекорировать имя функции самостоятельно, согласно таблице
| Calling convention | extern "C" or .c file | .cpp, .cxx or /TP | ------------------------------------------------------------------------------------- | C convention (__cdecl) | _test | ?test@@ZAXXZ | | Fastcall convention (__fastcall) | @test@0 | ?test@@YIXXZ | | Standard call convention (__stdcall) | _test@0 | ?test@@YGXXZ | | Vector call convention (__vectorcall) | test@@0 | ?test@@YQXXZ |
— и прописать их как алиасы
EXPORTS ; EntryName [=InternalName] [@Ordinal] [NONAME] [CONSTANT] CdeclFunction PascalFunction=_PascalFunction@8
(число после @ означает количество байт в стеке, отводимое под параметры).
Теперь можно использовать команду lib /def:yourfile.def из командной строки Visual Studio, чтобы построить .exp и .lib для линковки.

Идея №2 — это создать фейк-библиотеку с такими же сигнатурами функций, построить её, и использовать её .exp и .lib вместо отсутствующих. Для этого вам придётся построить С- (или хуже того, C++-) сигнатуры нужных функций. Для сишных функций (ну или тех, которые были определены как extern "C" в исходнике) это просто: вы смотрите на декорированное имя (например, в том же depends.exe или dumpbin), определяете по таблице сверху их calling convention, и кладёте такую функцию в исходник. Не забудьте указать __declspec(dllexport) для всех, и добавочно extern "C" для сишных функций. Реализуйте функции как угодно, чтобы компилятор скомпилировал это.
Если у вас декорированные C++-сигнатуры (они выглядят как-то так: ?test@@YGXXZ) их можно превратить в правильные C++-декларации при помощи утилиты undname
...> undname ?test@@YGXXZ Microsoft (R) C++ Name Undecorator Copyright (C) Microsoft Corporation. All rights reserved.
Undecoration of :- "?test@@YGXXZ" is :- "void __stdcall test(void)"
Если у вас есть header, идущий с библиотекой, то ничего угадывать (с риском ошибиться и получить креш) не надо, просто возьмите прототипы функций оттуда (только не забудьте поменять __declspec(dllimport) на __declspec(dllexport)).
Скомпилируйте полученный файл в .obj при помощи команды
cl /c /Ob0 dllname.cpp
Ключ /c нужен, чтобы компилятор произвёл .obj, а /Ob0 — чтобы не занилайнил случайно какие-нибудь функции, которые ему покажутся ненужными (это может быть нужно в случае экспорта классов, и в любом случае не повредит).
Имея .obj, вы можете получить .lib и .def при помощи команды
lib /def: dllname.obj

Комментариев нет:

Отправить комментарий