如何从未修饰的stdcall DLL自动生成导入库?

时间:2016-03-17 19:02:31

标签: c++ dll name-mangling stdcall

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

是否有一种有效的自动化方式?

1 个答案:

答案 0 :(得分:4)

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

我描述的过程可以用作生成DLL的正确implib的一般方法,不仅适用于stdcall unmangled;不过,它们是最难破解的坚果,所以我认为它们应该得到一些特殊待遇

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

MinGW用户注意:

如果你正在使用一个新的MinGW (一些古代版本不支持它,但所有最近的版本都是这样做的),你通常 必须创建导入LIB以便能够与DLL链接!您只需要:< / p>

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具有适当的受损/未拼写/装饰/未修饰状态集。
  4. 1

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

    gendef %1.dll
    
    瞧,瞧。请注意,虽然DEF 必须具有没有_前缀的函数名称,但如果您正在处理未修饰的stdcall DLL,则不会出现错误等等,@size后缀
    ,见下文。

    2

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

    请注意,如果您有标题但不想使用存根方法,则可以创建一个调用所有所需导入的虚方法 - 链接器将引发错误,这些错误将为您提供包含的装饰名称其中@size。或者,您可以从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(人类友好的)&amp; here(硬文档)。