从C#使用C库的提示

Ben*_*jol 9 .net c c# interop

我在C中有一个库,我想用它来使用C#.

从我从互联网上收集的内容来看,一个想法是将它包装在C++ DLL中,然后用DllImport包装它.

问题是我想调用的函数有一个相当讨厌的参数集:包括对函数的引用(将是一个.NET函数),以及一些数组(一些写,一些读).

int lmdif(int (*fcn)(int, int, double*, double*, int*), 
          int m, int n, double* x, double ftol, double xtol, double gtol, 
          int maxfev, double epsfcn, double factor)
Run Code Online (Sandbox Code Playgroud)

鉴于这样的界面,我应该注意哪些恶意?(还有解决方案,请)

你为什么不......

- 用C#重写?我开始了,但它已经从FORTRAN机器翻译了,我不太喜欢编码我无法理解的东西.

- 使用现有的.NET库?我现在正在尝试,但结果并不完全相同

- 用C++重新编译?我正在考虑它,但它看起来很痛苦

Ben*_*jol 5

根据记录,我获得了大部分方法来实现此功能,然后最终将适当的 C 文件拉入 C++ 项目(因为我正在与我根本不需要的代码中的兼容性问题作斗争)。

以下是我一路上学到的一些技巧,可能对遇到这个问题的人有所帮助:

编组阵列

double*在 C 中,指向 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)
Run Code Online (Sandbox Code Playgroud)

C 需要数组长度的额外信息,您必须将其作为单独的参数传递。编组还需要知道该大小在哪里,因此您指定SizeParamIndex,它指示参数列表中大小参数的从零开始的索引。

您还可以指定数组要传递的方向。在此示例中,x被传递 Foo,它“发回” y

调用约定

您实际上不需要了解这意味着什么的更详细的细节(换句话说,我不需要),您只需要知道存在不同的调用约定,并且它们需要在双方上匹配。C# 默认为StdCall,C 默认为Cdecl。这意味着您只需要显式指定调用约定(如果它与您使用的哪一方的默认调用约定不同)。

在回调的情况下,这尤其棘手。如果我们将回调传递给Cfrom C#,我们打算使用 来调用该回调StdCall,但是当我们传递它时,我们使用的是Cdecl。这会产生以下签名(有关上下文,请参阅此问题):

//=======C-code======
//type signature of callback function
typedef int (__stdcall *FuncCallBack)(int, int);

void SetWrappedCallback(FuncCallBack); //here default = __cdecl

//======C# code======
public delegate int FuncCallBack(int a, int b);   // here default = StdCall 

[DllImport(@"PathToDll", CallingConvention = CallingConvention.Cdecl)]
private static extern void SetWrappedCallback(FuncCallBack func);
Run Code Online (Sandbox Code Playgroud)

封装回调

显而易见,但对我来说并不是立即显而易见:

int MyFunc(int a, int b)
{
   return a * b;
}
//...

FuncCallBack ptr = new FuncCallBack(MyFunc);
SetWrappedCallback(ptr);
Run Code Online (Sandbox Code Playgroud)

.def 文件

您想要从 C++ 项目公开的任何函数(待DllImport编辑)都需要在ModDef.def文件中说明,其内容如下所示:

LIBRARY MyLibName
EXPORTS
    SetWrappedCallback  @1
Run Code Online (Sandbox Code Playgroud)

外部“C”

如果您想在 C++ 中使用 C 函数,则必须将它们声明为extern "C". 如果您要包含 C 函数的头文件,请像这样:

extern "C" {
  #include "C_declarations.h"
}
Run Code Online (Sandbox Code Playgroud)

预编译头文件

为了避免编译错误,我必须做的另一件事是为每个“C”文件Right-click -> Properties -> C/C++ -> Precompiled Headers设置Precompiled header“不使用预编译头”


lep*_*pie 3

唯一“讨厌”的参数是函数指针。但幸运的是,.NET 通过委托很好地处理了它们。

唯一的问题是调用约定。在 C# 中,它仅发出一种类型 (iirc stdcall),而 C 代码可能期望cdecl. 后一个问题可以在 IL 级别上处理(或使用Reflection.Emit)。

这是一些通过它实现的代码Reflection.Emit(这是为了帮助理解需要将哪些伪属性放置在委托的Invoke方法上)。