Есть несколько 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
Комментариев нет:
Отправить комментарий