免责声明: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部分:
- 从 DLL 生成股票 DEF 文件,
- 定制 DEF 以使其能够从中生成适当的 LIB(取决于您的运气,您可能可以跳过此步骤),
- 从最终的 DEF 生成 LIB 文件,并确保 LIB 具有正确的损坏/未损坏/装饰/未装饰状态集。
1
第一项任务可以通过多种方式完成。最简单的是使用专用工具 - 例如。 pexports 或 gendef,OSS 和 MinGW 扩展 repo 中可用(默认情况下,AFAIR 未安装在 MinGW 中,除非您安装 everything);使用dumpbin /exports 更加棘手,因为它在重定向等上添加了 cmets。还有许多其他工具可以做到这一点,例如expdef(它们通常足够简单,可以直接包含在更大的应用程序中,请参阅impdef 例如)。 请注意,dlltool 和 nm 通常在这里都会失败!
gendef %1.dll
等等。请注意,虽然 DEF 必须 具有没有 _ 前缀的函数名称,没有修饰等。如果您正在处理未修饰的 stdcall DLL,@size 后缀 是通常需要,见下文。
2
第二个任务有点棘手;需要注意的是,链接器将需要 LIB 中的 symbol@size 符号才能正确链接 stdcall 调用,但您可能会使用自动化工具以完全未修饰的导入结束。 gendef有时可以重新生成该数据 (YMMV) - 如果是这样,您就可以调用 dlltool 或 implib(见下文)。如果没有,并且您既没有原型,也没有可以从中重新生成原型的文档,也没有此特定 DLL 的任何其他类似版本的 LIB,我会说您大多不走运-使用stdcall,您必须从根本上反汇编每种方法才能知道接受的参数的总大小是多少。有时您可以根据使用它的代码猜测/计算(例如,在调用之前计算PUSHes 的数量),但 IMO 不可能正确地自动化它 - 需要手动将值放入 DEF。
请注意,如果您有一个标题但不想使用存根方法,您可以创建一个调用所有所需导入的虚拟方法 - 链接器将引发错误,这将为您提供包含 @ 的修饰名称987654359@ 在其中。或者,您可以从参数等的size_t 计算它。
3
只需使用dlltool 或implib,从固定的 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(硬文档)。