如何从未修饰的 stdcall DLL 自动生成导入库?
How to automate generating an import library from undecorated stdcall DLL?
处理从 DLL 创建 LIB 的一般方法在 How to make a .lib file when have a .dll file and a header file - still, to create an import library for DLL with undecorated stdcall
functions (e.g. core WinAPI DLL's, kernel32.dll
etc.), one has to go through a rather lengthy and complicated process (as described e.g. here 中描述。对于具有大量功能的 DLL,此过程非常耗时且容易出错 - 当原始 DLL 更改(例如,由于供应商更新)时,在自动构建情况下它也很容易失败。
有没有有效的自动化方法?
免责声明:YMMV。这里描述的所有行为都在很大程度上依赖于 implementation/version/platform,我什至不完全相信我在这里陈述的一些事情。不过,我认为这是一个很好的 collection 的各种想法、方法和见解,其中大多数实际上工作得很好。
我描述的过程可以用作为 DLL 生成适当的 implib 的通用方法,而不仅仅是用于 stdcall unmangled 的;尽管如此,它们仍然是最难破解的坚果,所以我认为它们应该得到一些特殊待遇
我们首先只有一个 "alien"(没有源代码,没有 headers 等)DLL 文件。虽然有一些专用的 GUI 工具可以做到这一点(例如 this),但我假设任务应该在脚本中完成,所以没有任何 GUI,只有文本 shell 命令。
MinGW 用户请注意:
如果 你使用的是 new-ish MinGW (一些古老的版本不支持它,但所有最近的版本都支持),您通常 不必 创建导入 LIB 就可以 link 使用 DLL! 你只需要:
a) 声明你的方法的原型(例如在 .h
文件中),很可能 没有 __declspec(dllimport)
- MinGW 使用一个奇怪的 decoration/mangling 对他们来说,并且(至少在我的设置中)他们不会 link 因为 link er 而不是常规的 __imp__
期望 __imp_
前缀,
b) 使用额外的 -LDllDir -lDllNameWithoutDllExtension --enable-stdcall-fixup
发布您的 compilation/linking,例如
g++ example.cpp -L. -lkernel32 --enable-stdcallfixup
那就行了;该应用程序已针对您的 DLL link 进行了正确的编辑,即使它是一个未修饰的 stdcall。
然而,在某些情况下,您将使用另一个编译器,或者出于某种原因(重定向、避免名称冲突等)只需要 LIB 本身。最简单的方法是 create a stub DLL,即对于您拥有的每个函数原型,创建一个空函数 body(请注意,虽然 void
函数可以简单地包含 {}
body, 其他函数必须至少有 { 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 接口生成它),因此有时无法通过这种方式实现。
如果您没有 headers,则必须生成所有需要的中间文件。该过程可以分为 3 个部分:
- 从 DLL 生成库存 DEF 文件,
- 定制 DEF 使其可以从中生成合适的 LIB(取决于你的运气,你可以跳过这个),
- 从最终的 DEF 生成 LIB 文件,并确保 LIB 具有正确的 mangled/unmangled/decorated/undecorated 状态集。
1
第一个任务可以通过多种方式完成。最简单的是使用专用工具 - 例如pexports
或 gendef
,OSS 和 MinGW 扩展存储库中都可用(默认情况下 AFAIR 未安装在 MinGW 中,除非您安装 everything,尽管如此);使用 dumpbin /exports
要棘手得多,因为它会在重定向等方面添加注释。还有许多其他工具可以做到这一点,例如 expdef (and they are often simple enough to be directly included in a bigger app, see impdef。 请注意,dlltool
和nm
通常都会在这里失败!
gendef %1.dll
瞧瞧。请注意,虽然 DEF 必须 的函数名称没有 _
前缀,没有重整等。如果您正在处理未修饰的 stdcall DLL,@size
后缀 是通常需要的,见下文。
2
第二个任务有点棘手;需要注意的是,linker 将需要 LIB 中的 symbol@size
符号才能正确调用 link stdcall
,但您可能会使用自动化工具以完全未修饰的导入结束。 gendef
可以 有时 重新生成该数据 (YMMV) - 如果是这样,您就可以调用 dlltool
或 implib
(见下文) .如果没有,并且您既没有原型,也没有可以从中重新生成原型的文档,也没有该特定 DLL 的任何其他类似版本的 LIB,我会说您很不走运-使用 stdcall
,您必须从根本上反汇编每个方法才能知道接受的参数的总大小是多少。有时你可以 guess/calculate 基于使用它的代码(例如计算调用前 PUSH
es 的数量),但 IMO 不可能正确地自动化它 - 这些值需要放在 DEF 中手动。
请注意,如果您有一个 header 但不想使用存根方法,您可以创建一个虚拟方法来调用所有所需的导入 - linker 将提高将为您提供装饰 n 的错误包含 @size
的 mes。或者,您可以只根据参数的 size_t
等计算它
3
只需使用dlltool
或implib
,从固定的DEF生成一个LIB。例如 dlltool
的一般语法是(在批处理文件中使用)
dlltool -d %1.def -D %1.dll -l %1.lib
因此,您获得了 LIB。
最后的警告:
- 您可能会注意到,虽然 MinGW linker 乐于接受类似于
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 相同,因此您只需编辑文件中的导入 header,更改相关字节,位于每个导入的导入 header,偏移量 18 - 进一步阅读 here (human-friendly) & here(硬文档)。
处理从 DLL 创建 LIB 的一般方法在 How to make a .lib file when have a .dll file and a header file - still, to create an import library for DLL with undecorated stdcall
functions (e.g. core WinAPI DLL's, kernel32.dll
etc.), one has to go through a rather lengthy and complicated process (as described e.g. here 中描述。对于具有大量功能的 DLL,此过程非常耗时且容易出错 - 当原始 DLL 更改(例如,由于供应商更新)时,在自动构建情况下它也很容易失败。
有没有有效的自动化方法?
免责声明:YMMV。这里描述的所有行为都在很大程度上依赖于 implementation/version/platform,我什至不完全相信我在这里陈述的一些事情。不过,我认为这是一个很好的 collection 的各种想法、方法和见解,其中大多数实际上工作得很好。
我描述的过程可以用作为 DLL 生成适当的 implib 的通用方法,而不仅仅是用于 stdcall unmangled 的;尽管如此,它们仍然是最难破解的坚果,所以我认为它们应该得到一些特殊待遇
我们首先只有一个 "alien"(没有源代码,没有 headers 等)DLL 文件。虽然有一些专用的 GUI 工具可以做到这一点(例如 this),但我假设任务应该在脚本中完成,所以没有任何 GUI,只有文本 shell 命令。
MinGW 用户请注意:
如果 你使用的是 new-ish MinGW (一些古老的版本不支持它,但所有最近的版本都支持),您通常 不必 创建导入 LIB 就可以 link 使用 DLL! 你只需要:
a) 声明你的方法的原型(例如在 .h
文件中),很可能 没有 __declspec(dllimport)
- MinGW 使用一个奇怪的 decoration/mangling 对他们来说,并且(至少在我的设置中)他们不会 link 因为 link er 而不是常规的 __imp__
期望 __imp_
前缀,
b) 使用额外的 -LDllDir -lDllNameWithoutDllExtension --enable-stdcall-fixup
发布您的 compilation/linking,例如
g++ example.cpp -L. -lkernel32 --enable-stdcallfixup
那就行了;该应用程序已针对您的 DLL link 进行了正确的编辑,即使它是一个未修饰的 stdcall。
然而,在某些情况下,您将使用另一个编译器,或者出于某种原因(重定向、避免名称冲突等)只需要 LIB 本身。最简单的方法是 create a stub DLL,即对于您拥有的每个函数原型,创建一个空函数 body(请注意,虽然 void
函数可以简单地包含 {}
body, 其他函数必须至少有 { 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 接口生成它),因此有时无法通过这种方式实现。
如果您没有 headers,则必须生成所有需要的中间文件。该过程可以分为 3 个部分:
- 从 DLL 生成库存 DEF 文件,
- 定制 DEF 使其可以从中生成合适的 LIB(取决于你的运气,你可以跳过这个),
- 从最终的 DEF 生成 LIB 文件,并确保 LIB 具有正确的 mangled/unmangled/decorated/undecorated 状态集。
1
第一个任务可以通过多种方式完成。最简单的是使用专用工具 - 例如pexports
或 gendef
,OSS 和 MinGW 扩展存储库中都可用(默认情况下 AFAIR 未安装在 MinGW 中,除非您安装 everything,尽管如此);使用 dumpbin /exports
要棘手得多,因为它会在重定向等方面添加注释。还有许多其他工具可以做到这一点,例如 expdef (and they are often simple enough to be directly included in a bigger app, see impdef。 请注意,dlltool
和nm
通常都会在这里失败!
gendef %1.dll
瞧瞧。请注意,虽然 DEF 必须 的函数名称没有 _
前缀,没有重整等。如果您正在处理未修饰的 stdcall DLL,@size
后缀 是通常需要的,见下文。
2
第二个任务有点棘手;需要注意的是,linker 将需要 LIB 中的 symbol@size
符号才能正确调用 link stdcall
,但您可能会使用自动化工具以完全未修饰的导入结束。 gendef
可以 有时 重新生成该数据 (YMMV) - 如果是这样,您就可以调用 dlltool
或 implib
(见下文) .如果没有,并且您既没有原型,也没有可以从中重新生成原型的文档,也没有该特定 DLL 的任何其他类似版本的 LIB,我会说您很不走运-使用 stdcall
,您必须从根本上反汇编每个方法才能知道接受的参数的总大小是多少。有时你可以 guess/calculate 基于使用它的代码(例如计算调用前 PUSH
es 的数量),但 IMO 不可能正确地自动化它 - 这些值需要放在 DEF 中手动。
请注意,如果您有一个 header 但不想使用存根方法,您可以创建一个虚拟方法来调用所有所需的导入 - linker 将提高将为您提供装饰 n 的错误包含 @size
的 mes。或者,您可以只根据参数的 size_t
等计算它
3
只需使用dlltool
或implib
,从固定的DEF生成一个LIB。例如 dlltool
的一般语法是(在批处理文件中使用)
dlltool -d %1.def -D %1.dll -l %1.lib
因此,您获得了 LIB。
最后的警告:
- 您可能会注意到,虽然 MinGW linker 乐于接受类似于
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 相同,因此您只需编辑文件中的导入 header,更改相关字节,位于每个导入的导入 header,偏移量 18 - 进一步阅读 here (human-friendly) & here(硬文档)。