将 dll 方法从C++导出到 C#.为什么我需要:“外部”C”

本文关键字:外部 为什么 方法 dll C++ | 更新日期: 2023-09-27 18:18:08

在我的dll中,有一个我想导出的方法。

//工程:

extern "C" __declspec(dllexport)

行不通

__declspec(dllexport)

C++出口:

 extern "C" __declspec(dllexport) int Test();

C# 导入:

[DllImport("CircleGPU2_32.DLL", EntryPoint = "Test", 
    CallingConvention = CallingConvention.StdCall)]
public static extern int Test();

为什么我需要外置"C">

将 dll 方法从C++导出到 C#.为什么我需要:“外部”C”

这是

由于C++完成的名称重整。

外部"C">

与没有外部"C">

此处的示例是 CFF 资源管理器为 dll 的导出表显示的内容。第一个是用borland的c++编译器构建的。第二个是用msvc构建的。

Ordinal     FunctionRVA     Name RVA    Name
00000001    0020E140        0032C2B6    createDevice
00000002    0020E244        0032C2C3    createDeviceEx
0000000D    00328DA4        0032C0C1    @irr@core@IdentityMatrix
0000000E    00328DE4        0032C28A    @irr@video@IdentityMaterial

0000000C    000F9C80        001EE1B6    createDevice
0000000D    000F9CE0        001EE1C3    createDeviceEx
00000001    00207458        001EDDC7    ?IdentityMaterial@video@irr@@3VSMaterial@12@A
00000002    001F55A0        001EDDF5    ?IdentityMatrix@core@irr@@3V?$CMatrix4@M@12@B

前 2 个函数createDevicecreateDeviceEx在其原型中包含extern "C"签名,而其他函数则不包含。 请注意使用C++重整时编码的差异。差异实际上比这更深

ABI & 标准化

正如其他答案中所解释的,C++标准没有指定AbstractBinaryInterface。这意味着设计工具的供应商在如何处理函数调用以及如何在引擎盖下工作时几乎可以做任何他们想做的事情 - 只要它表现出标准规定的预期行为。

有了所有这些不同的编码方案,另一种语言就不可能有任何希望使用C++编译的模块。用一个C++编译器编译的模块不太可能与另一个编译器一起使用!编译器供应商可以自行决定在版本之间自由更改其编码。

此外,没有通用的 ABI 意味着没有通用的预期方法来调用这些函数/方法。例如,一个编译器可能会在堆栈上传递其参数,而另一个编译器可能会在寄存器上传递参数。一个可以从左到右传递参数,而另一个可以颠倒。如果调用方和被调用方之间只有这些方面之一不完全匹配,那么您的应用将崩溃......那是如果你幸运的话。供应商没有处理这个问题,而是简单地通过强制使用不同的编码来构建错误来拒绝。

OTOH,虽然 C 也没有标准化的 ABI,但相比之下,C 是一种更简单的语言。大多数 C 编译器供应商以类似的方式处理函数修饰和调用机制。因此,即使标准中没有明确指定 ABI,也存在一种"事实上"的标准。有了这种通用性,它使其他语言更容易与用C编译的模块进行交互。

例如,函数签名中的__stdcall修饰被理解为遵循特定的调用约定。参数从右向左推送,被调用方负责在之后清理堆栈。 __cdecl类似,但可以理解,调用方负责清理堆栈。

底线

如果所讨论的模块必须与C++以外的语言互操作,那么适当地修饰它并将其公开为 C API 是最好的选择。 请注意,这样做会放弃一些灵活性。特别是,您将无法重载这些函数,因为编译器无法再为具有名称重整的每个重载生成唯一符号。

如果互操作性对于所讨论的模块并不重要 - 它只会与构建它的相同工具一起使用,那么从原型中省略extern "C"装饰。

主要原因是防止C++名称管理器篡改函数的名称。

尝试在没有extern "C"的情况下导出它,并在依赖关系Walker中检查生成的DLL,您将看到导出的函数的名称完全不同。

编译器通常会修饰导出的名称,以包含有关类和签名的信息。 extern "C"告诉编译器不要这样做。

我已经用一个带有 1 个参数的函数尝试过这个,你必须使用

[DllImportAttribute("whatever.Dll",调用约定 = 调用约定。Cdecl(]

以使 C# 中的导入正常工作。Stdcall 语句仅适用于(在当前情况下,而不是一般情况下(没有参数且返回 void 的函数。在 vs2012 速成版中测试。

作为旁注,可以从 http://www.dependencywalker.com/下载依赖步行者