6

处理从 DLL 创建 LIB 的一般方法在How to make a .lib file when have a .dll 文件和头文件中进行了stdcall描述 - 仍然为具有未修饰函数(例如核心 WinAPI DLLkernel32.dll等)的DLL 创建导入库.),一个人必须经历一个相当漫长而复杂的过程(如这里所述)。对于具有大量功能的 DLL,此过程非常耗时且容易出错 - 当原始 DLL 更改(例如,由于供应商更新)时,它在自动构建情况下也很容易失败。

有没有一种有效的方法来自动化它?

4

1 回答 1

5

免责声明:YMMV。这里描述的所有行为都严重依赖于实现/版本/平台,我什至不完全相信我在这里陈述的一些事情。尽管如此,我认为这是一个很好的各种想法、方法和见解的集合,其中大多数实际上运行良好。

我描述的过程可以用作为 DLL 生成适当的 implib 的一般方法,而不仅仅是 stdcall 未损坏的;尽管如此,它们仍然是最难破解的坚果,所以我认为它们应该得到一些特殊的待遇

我们首先只有一个“外星人”(没有源代码、没有头文件等)DLL 文件。虽然有一些专用的 GUI 工具可以做到这一点(例如this),但我假设该任务应该在脚本中完成,所以没有任何 GUI,只有文本 shell 命令。

MinGW 用户注意事项:

如果您使用的是新的 MinGW (一些古老的版本不支持它,但所有最近的版本都支持),您通常根本不需要创建导入 LIB 就可以与 DLL 链接!你只需要:

a) 声明您的方法的原型(例如在.h文件中),很可能没有 __declspec(dllimport)- MinGW 对它们使用奇怪的装饰/修饰,并且(至少在我的设置中)它们不会链接,因为__imp_链接器需要前缀而不是常规的__imp__

b)发布您的编译/链接附加-LDllDir -lDllNameWithoutDllExtension --enable-stdcall-fixup,例如

g++ example.cpp -L. -lkernel32 --enable-stdcallfixup

这就是诀窍;该应用程序已正确链接到您的 DLL,即使它是未修饰的 stdcall 。


然而,在某些情况下,您将使用另一个编译器,或者出于某种原因(重定向、避免名称冲突等)只需要 LIB 本身。最简单的方法是只创建一个存根 DLL,即为您拥有的每个函数原型创建一个空函数体(请注意,虽然void函数可以具有简单的{}主体,但其他函数必须至少具有{ return 0; }或等效,否则会引发错误),检查它是否有__stdcall,将它们全部包装起来extern "C" { /* your prototypes here */ },然后将其编译为 DLL,将带有未修饰名称的 DEF 作为导出传递(有关1.如何执行此操作的简单方法,请参见下文) - 丢弃生成的 DLL,保留 LIB,你是完毕。这可以通过脚本轻松完成(例如,通过替换;{}ignore 'no value returned in non-void function' error并可能通过设置或解析错误日志进行编译以添加return 0;到有问题的行上)。可悲的是,这种方法需要您拥有.h文件(或根据已知的 DLL 接口生成它),所以有时不可能这样做。

如果您没有标头,则必须生成所需的所有中间文件。该过程可以分为3个部分:

  1. 从 DLL 生成股票 DEF 文件,
  2. 定制 DEF 以使其可以从中生成适当的 LIB(取决于您的运气,您可能可以跳过此步骤),
  3. 从最终的 DEF 生成 LIB 文件,并确保 LIB 具有正确的损坏/未损坏/装饰/未装饰状态集。

1

第一项任务可以通过多种方式完成。最简单的方法是使用专用工具 - 例如pexportsgendefOSS 和 MinGW 扩展 repo 中都可用(默认情况下,除非您安装所有东西,否则 AFAIR 不会安装在 MinGW 中);使用起来dumpbin /exports要复杂得多,因为它会在重定向等方面添加注释。还有许多其他工具可以做到这一点,比如expdef(它们通常足够简单,可以直接包含在更大的应用程序中,请参见impdef例如)。请注意,两者dlltool通常nm都会在这里失败!

gendef %1.dll

等等。请注意,虽然 DEF必须具有没有_前缀、没有修饰等的函数名称。如果您正在处理未修饰的 stdcall DLL,@size通常需要后缀,见下文。

2

第二个任务有点棘手;需要注意的是,链接器将需要symbol@sizeLIB 中的符号才能stdcall正确链接调用,但您可能会使用自动化工具以完全未修饰的导入结束。有时gendef可以重新生成该数据(YMMV)——如果是这样,你就可以调用或(见下文)。如果没有,并且您既没有原型,也没有可以从中重新生成原型的文档,也没有此特定 DLL 的任何其他类似版本的 LIB,我会说您大多不走运-使用,您必须基本上反汇编每种方法才能知道接受的参数的总大小是多少。有时您可以根据使用它的代码猜测/计算(例如dlltoolimplibstdcallPUSHes 在调用之前),但 IMO 不可能正确地自动化它 - 这些值需要手动放入 DEF。

请注意,如果您有一个标头但不想使用存根方法,您可以创建一个调用所有所需导入的虚拟方法 - 链接器将引发错误,这将为您提供包含其中的修饰名称@size。或者,您可以从size_t参数等中计算它。

3

只需使用dlltoolor implib,从固定的 DEF 生成 LIB。例如,一般语法dlltool是(在批处理文件中使用)

dlltool -d %1.def -D %1.dll -l %1.lib

结果,您获得了 LIB。


最后的警告:

  • 您可能会注意到,虽然 MinGW 链接器很乐意接受类似于 的 DEF IMPORTS 别名methodname1@size=methodname2,但 Microsoft 将它们视为methodname1@size,而不是别名;在我所知道的 MSVC 中,没有任何方法可以将修饰名称别名为未修饰名称,
  • 声明你的导入cdecl永远不是解决方案 - 符号可能匹配,但除了最微不足道的情况外,调用将在所有情况下失败,
  • 您可能会注意到(使用dumpbin /exports /header)在 MSVC implib 中将生成一个带有 eg 的 LIB Symbol name : _function@size Name type : name Name : _function@size- 这不是您想要的;正确的结果(通过存根实现)是Symbol name : _function@size Name type : undecorate Name : function- 要实际实现未修饰,您必须对 LIB 进行十六进制编辑以更改该标志(我知道没有implib标志或 DEF 设置能够自动执行此操作)。LIB 文件格式实际上与 COFF/PE 相同,因此您只需编辑文件中的导入标头,更改相关字节,位于每个导入的导入标头,偏移量 18 - 进一步阅读此处(人性化)&这里(硬文档)。
于 2016-04-13T23:44:00.583 回答