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

时间:2011-12-16 13:33:42

标签: c# c++ dllexport

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

//作品:

extern "C" __declspec(dllexport)

//无法工作

__declspec(dllexport)

C ++ Export:

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

C#import:

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

为什么我需要 extern“C”

4 个答案:

答案 0 :(得分:23)

这是由于C ++完成了名称修改。

extern“C”vs vs extern“C”

这里的例子是CFF Explorer为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

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

ABI&标准化

正如其他答案中所解释的那样,C ++标准指定 A bstract B inary I nterface 。这意味着设计他们的工具的供应商几乎可以做任何他们想要的功能,当涉及到如何处理函数调用以及如何在引擎盖下工作 - 只要它表现出标准所规定的预期行为。 / p>

使用所有这些不同的编码方案,其他语言无法使用C ++编译的模块。用一个C ++编译器编译的Heck模块不太可能与另一个一起工作!编译器供应商可以自行决定在版本之间自由更改编码。

此外,没有共同的ABI意味着没有共同的预期方式来调用这些函数/方法。例如,一个编译器可能会在堆栈上传递其参数,而另一个编译器可能会在寄存器上传递它。有人可能会从左到右传递论据,而另一个可能会被逆转。如果这些方面中的一个与调用者和被调用者之间不完全匹配,则您的应用程序将崩溃...即如果您很幸运。供应商只是通过使用不同的编码强制构建错误而不是处理此问题而不是处理此问题。

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

例如,函数签名中的__cdecl修饰被理解为遵循特定的调用约定。参数从右向左推,然后被调用者负责清理堆栈。 extern "C"类似,但据了解,调用者负责清理堆栈。

底线

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

如果互操作性对于相关模块并不重要 - 它只会与构建它的工具一起使用,那么就会省略原型中的{{1}}装饰。

答案 1 :(得分:11)

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

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

答案 2 :(得分:1)

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

答案 3 :(得分:0)

我尝试使用 1参数 的功能,你必须使用

  

[DllImportAttribute(“whatever.Dll”,CallingConvention = CallingConvention。 Cdecl )]

用于在C#中导入工作。 Stdcall语句仅适用于没有参数且返回void的函数(在所呈现的情况下,而不是一般情况下)。 在vs2012 express edition中测试过。

  

作为旁注,可以从http://www.dependencywalker.com/

下载依赖性walker