记录一下,我已经完成了大部分工作,然后将相应的C文件引入到C++项目中(因为我在处理不需要的代码兼容性问题)。
以下是我整理出来的一些提示,对于那些遇到这个问题的人可能会有帮助:
数组封送
在C语言中,指向double的指针(
double*
)和double数组(
double*
)之间没有区别。当你进行互操作时,你需要能够区分它们。我需要传递
double
数组,所以签名可能如下:
[DllImport(@"PathToDll")]
public static extern Foo(
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)[In] double[] x,
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)[Out] double[] y,
int x_size,
int y_size)
C需要额外的信息来确定数组的长度,你需要将其作为单独的参数传递。编组还需要知道这个大小在哪里,所以你要指定SizeParamIndex
,它表示参数列表中大小参数的从零开始的索引。
你还需要指定数组传递的方向。在这个例子中,x
被传递到Foo
中,而y
则被“发送回来”。
调用约定
你实际上不需要理解这意味着什么更细节的内容(换句话说,我不需要),你只需要知道不同的调用约定存在,并且它们需要在两端匹配。C#的默认值是StdCall
,C的默认值是Cdecl
。这意味着如果与默认值不同,则只需要在使用它的一侧显式指定调用约定。
在回调的情况下,这特别棘手。如果我们从C#
向C
传递回调,则我们打算使用StdCall
调用该回调,但是当我们传递它时,我们使用的是Cdecl
。这导致以下签名(有关上下文,请参见此问题):
typedef int (__stdcall *FuncCallBack)(int, int);
void SetWrappedCallback(FuncCallBack);
public delegate int FuncCallBack(int a, int b);
[DllImport(@"PathToDll", CallingConvention = CallingConvention.Cdecl)]
private static extern void SetWrappedCallback(FuncCallBack func);
封装回调函数
这很显然,但对我来说并不是立即显而易见的:
int MyFunc(int a, int b)
{
return a * b;
}
FuncCallBack ptr = new FuncCallBack(MyFunc);
SetWrappedCallback(ptr);
.def文件
如果你想从C++项目中公开暴露函数(用于DllImport
),需要在ModDef.def
文件中列出,其内容可能如下所示:
LIBRARY MyLibName
EXPORTS
SetWrappedCallback @1
extern "C"
如果你想要在C++中使用C语言函数,你必须声明它们为extern "C"
。如果你正在包含一个包含C函数的头文件,你需要这样做:
extern "C" {
#include "C_declarations.h"
}
预编译头文件
为了避免编译错误,我需要进行另一个操作:右键单击 -> 属性 -> C/C++ -> 预编译头文件
,并将每个'C'文件的预编译头文件
设置为不使用预编译头文件。
.lib
,我原本以为我无法直接从 C# 中访问它。 - Benjol