【问题标题】:How to automate generating an import library from undecorated stdcall DLL?如何从未修饰的 stdcall DLL 自动生成导入库?
【发布时间】:2016-07-04 07:52:36
【问题描述】:

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

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

【问题讨论】:

  • Mingw 已经为 kernel32.dll 提供了导入库。为什么要重新创建它?
  • 我只是使用kernel32.dll 作为众所周知的示例,因为它清楚地表明使用手动创建的 def 文件从中创建导入库需要做很多工作。任何其他具有大量导出函数的 DLL 都将面临同样的挑战。
  • 我冒昧地将这个问题重写为一个规范问题; OP遇到的问题是一个很常见的问题,并且,因为a)我们在SO上没有真正的规范答案,b)OP无论如何都没有得到任何答案,c)规范答案对OP的帮助甚至超过了解决方案如上所述的问题 - 我已经尝试过了; OTOH,将自己限制在 MinGW 工具上只会使它在这里变得不那么完整。

标签: c++ dll name-mangling stdcall


【解决方案1】:

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

我描述的过程可以用作为 DLL 生成适当的 implibs 的一般方法,而不仅仅是 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 本身。最简单的方法是只使用create a stub 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

第一项任务可以通过多种方式完成。最简单的是使用专用工具 - 例如。 pexportsgendef,OSS 和 MinGW 扩展 repo 中可用(默认情况下,AFAIR 未安装在 MinGW 中,除非您安装 everything);使用dumpbin /exports 更加棘手,因为它在重定向等上添加了 cmets。还有许多其他工具可以做到这一点,例如expdef(它们通常足够简单,可以直接包含在更大的应用程序中,请参阅impdef 例如)。 请注意,dlltoolnm 通常在这里都会失败!

gendef %1.dll

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

2

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

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

3

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

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

因此,您获得了 LIB。


最后的警告:

  • 您可能会注意到,虽然 MinGW 链接器很乐意接受类似于 methodname1@size=methodname2 的 DEF IMPORTS 别名,但 Microsoft 将它们视为 methodname1@size,而不是别名;据我所知,在 MSVC 中没有任何方法可以将修饰名称别名为未修饰名称,
  • 声明您的导入 cdecl永远不会解决方案 - 符号可能匹配,但除了最微不足道的情况外,调用在所有情况下都会失败,
  • 您可能会注意到(使用dumpbin /exports /header)在 MSVC implib 中将生成一个 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 - 进一步阅读here(人性化) & here(硬文档)。

【讨论】:

    猜你喜欢
    • 2019-10-02
    • 2011-01-24
    • 1970-01-01
    • 2014-10-08
    • 2010-11-04
    • 2017-06-21
    • 2011-01-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多