将dll方法从C++导出到C#.为什么我需要:"extern"C""

Ped*_*o77 11 c# c++ dllexport

在我的DLL中有一个我想要导出的方法.

//作品:

extern "C" __declspec(dllexport)
Run Code Online (Sandbox Code Playgroud)

//不会工作

__declspec(dllexport)
Run Code Online (Sandbox Code Playgroud)

C++导出:

 extern "C" __declspec(dllexport) int Test();
Run Code Online (Sandbox Code Playgroud)

C#import:

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

为什么我需要外部"C"

gre*_*olf 24

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

extern"C"vs ex 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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)

第2个功能createDevicecreateDeviceEx包含extern "C"签名在它的原型,而有的则没有.注意使用C++修改时的编码差异.差异实际上比这更深刻.

ABI与标准化

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

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

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

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

例如,__stdcall功能签名中的装饰被理解为遵循特定的调用约定.参数从右向左推,然后被调用者负责清理堆栈.__cdecl是类似的,但据了解,调用者负责清理堆栈.

底线

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

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


Dav*_*nan 11

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

尝试导出它而不用extern "C"并在Dependency Walker中检查生成的DLL,您将看到导出函数的完全不同的名称.