【问题标题】:How can a program call undecorated stdcall DLL exports when compiling with MSVC?使用 MSVC 编译时,程序如何调用未修饰的 stdcall DLL 导出?
【发布时间】:2019-10-02 05:26:28
【问题描述】:

我有一个用 x86 汇编语言编写的 DLL。所有导出都引用stdcall 函数。所有出口均未装饰。

我特别需要使用导入库将这个 DLL 按原样链接到 C 项目。 LoadLibrary 或更改导出名称等不是一种选择。

我有两个问题

  1. 当我从具有导出名称的 DEF 文件为 DLL 创建导入库 (.lib) 文件时,所有名称都被修饰为 cdecl!我找不到将它们指定为未装饰stdcall 的方法。 DLL 中不存在这些修饰名称,因此在运行时,即使可以编译也不会起作用。
  2. 当我在 MSVC 中编写代码调用 DLL 函数时,无论是否指定 stdcall,编译器都会修改函数的名称。所以对ExampleFunction 的调用变成了_ExampleFunction@12,我只需要它是ExampleFunction

幸运的是,我需要的东西必须是可能的,因为 Windows 系统 DLL(例如 kernel32.dll、user32.dll 等)遵循完全相同的约定。每个导出的函数都是 stdcall 并且没有修饰。

我已经读过这个:https://qualapps.blogspot.com/2007/08/how-to-create-32-bit-import-libraries.html

我遇到了这些问题:

  1. 使用extern "C" 不适用于此处,因为这是一个 C 项目。该说明符甚至不起作用,尽管这是一个 C 项目,但名称还是会被修改。
  2. 从只包含原始函数名称的 DEF 文件创建 .lib 文件不起作用。名称被修改。

example-lib.def 文件如下所示:

LIBRARY example-lib.dll
EXPORTS
ExampleFunction

.lib 文件是用这个命令生成的:

lib.exe /def:example-lib.def /OUT:example-lib.lib

使用dumpbin.exe /headers example-lib.lib时lib文件中的入口是这样的

Version      : 0
Machine      : 14C (x86)
TimeDateStamp: 5CDBA30C Wed May 15 06:26:36 2019
SizeOfData   : 00000016
DLL name     : example-lib.dll
Symbol name  : _ExampleFunction
Type         : code
Name type    : no prefix
Hint         : 20
Name         : ExampleFunction

我通过以下声明来导入:

extern void __stdcall ExampleFunction(int a, int b, int c);

我这样调用函数:

ExampleFunction(1, 2, 3);

它所在的 lib 文件和目录在 VC 项目设置中指定。

当调用ExampleFunction 的代码编译时,链接器不会完成,因为它在我的DEF 文件中找不到_ExampleFunction@12。即使在 DEF 文件中,程序也不会运行,因为_ExampleFunction@12 没有在实际 DLL 的导出部分中导出。只导出普通的ExampleFunction

那么如何让 C 程序从 DLL 中调用 ExampleFunction 呢?当 C 项目从 user32.dll 调用函数时,这是如何完成的?我可以使用相同的程序来实现这一点吗?

【问题讨论】:

  • 在这里使用 def 文件。请展示一个完整但最小的示例。
  • @DavidHeffernan DLL 的 DEF 文件如下所示。有没有办法告诉lib.exe ExampleFunction 是未修饰的标准调用? LIBRARY example-dll EXPORTS ExampleFunction
  • @manuell 是的,这是我开始制作 lib 文件时看到的第一件事。
  • @DavidHeffernan 我现在已经扩展了这个问题以包含一个可重现的示例。

标签: c dll dllimport calling-convention


【解决方案1】:

事实证明,我在上面发布的文章链接中的信息确实有效。 (https://qualapps.blogspot.com/2007/08/how-to-create-32-bit-import-libraries.html)

如文章所述,我已经创建了一个虚拟/存根库...但是我没有以应有的方式将模块定义文件添加到虚拟库中。我忽略了这样做,但我什至不明白为什么它会起作用!我在 Visual Studio 之外创建了它,并在 Visual Studio 之外为它生成了 .lib。没有运气。在 Visual Studio 中完成这一切使其工作。

只需添加如下所示的模块定义...

LIBRARY example-lib.dll
EXPORTS
ExampleFunction

... 导致输出的 .lib 文件报告此问题,而不是我最初在问题中列出的内容:

Version      : 0
Machine      : 14C (x86)
TimeDateStamp: FFFFFFFF
SizeOfData   : 00000019
DLL name     : example-lib.dll
Symbol name  : _ExampleFunction@12
Type         : code
Name type    : undecorate
Hint         : 0
Name         : ExampleFunction

这很奇怪! 正如文章提到的,这基本上是无证行为,但它确实解决了我的问题。

如果您像我一样在阅读本文时遇到问题,拥有一个带有未修饰 stdcall 导入的 .lib 文件的解决方案是:

  1. 创建一个新的 Visual C DLL 项目(不是静态库),将其命名为与要为其创建 .lib 的 DLL 相同。

  2. 在主源代码文件中,这是一个 C++ 文件(不管是这种情况),复制所需函数的签名,如下所示。例如-lib,这是整个源文件!

    include "stdafx.h"
    extern "C" __declspec(dllexport) void _stdcall ExampleFunction(int a, int b, int c) { }
    
  3. 这是关键部分。在 Visual C 的解决方案资源管理器中,右键单击 Source Files > Add > New Item... 在出现的对话框中,选择 Code > Module Definition File (.def)。使其与您的 DLL 名称和导出名称匹配,如下所示。由于此步骤不需要进行任何设置更改来引用模块定义,我真的不知道为什么会这样。但确实如此......似乎没有官方文档证明这会起作用,但该功能至少从 2005 年至 2007 年(撰写本文时)就已存在于 MSVC 中。

    LIBRARY example-lib.dll
    EXPORTS
    ExampleFunction
    
  4. 设置为发布模式并构建库。在 Windows 资源管理器中导航到项目目录,您现在应该会看到 .dll 文件和 .lib 文件。您可以运行dumpbin /headers example-lib.lib 来验证ExampleFunction 是否未装饰,如上所示。

  5. 要使用虚拟 .lib 文件来引用真实的 DLL,请转到项目属性 > 链接器 > 输入 > 附加依赖项(并在必要时添加依赖项目录),将其添加到您的原始项目中。然后在这样的代码中声明对它的引用:

    extern void __stdcall ExampleFunction(int a, int b, int c);
    

构建您的项目,它应该可以工作。如果您在实用程序中查看您的项目 EXE/DLL,您会看到 .idata 部分将使用未修饰的函数名称引用您的 DLL。这是因为编译器总是会生成一个符号引用,如_ExampleFunction@12,但是当它到达微软的链接器并结合专用的.lib 文件时,它会看到name typeundecorate,它应该将此符号解析为真实姓名ExampleFunction.

【讨论】:

    猜你喜欢
    • 2016-07-04
    • 2011-05-15
    • 2011-01-24
    • 1970-01-01
    • 1970-01-01
    • 2020-06-30
    • 2021-11-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多