在C#中调用C(C++)类的DLL的时候,有时候C的接口函数包含不少参数,并且有的时候这些参数有多是个结构体,并且有多是结构体指针,那么在C#到底该如何安全的调用这样的DLL接口函数呢?本文将详细介绍如何调用各类参数的方法。数组
int fnAdd(int num1,int num2);
那么在C#调用这种函数最简单了,直接用函数原型便可,以下:安全
[DllImport("你的dll名称", EntryPoint = "fnAdd", CallingConvention = CallingConvention.Cdecl)] public static extern int fnAdd(int num1, int num2);
这样在C#的方法内能够放心的使用这个dll函数了。函数
你们都知道C#为了安全起见,隐形的避开了指针(其实在C#彻底可使用指针的,只是为了安全),采用了引用的方式来取代指针,引用的好处就是能够和指针同样操做参数原地址内的数据,而且这些数据在调用函数返回时还存活,可是引用不能够想指针那样++或者--到此PC指针乱跑,引出的一系列问题,下面举例来操做普通变量的指针,以下:布局
int fnAdd(int *p_n1,int *p_n2);
上文已经说了C#采用引用来代替指针,那么好了调用接口能够这么写了:字体
[DllImport("你的dll名称", EntryPoint = "fnAdd", CallingConvention = CallingConvention.Cdecl)] public static extern int fnAdd(ref int num1,ref int num2);
对,就这样的简单,这样C#即可以调用带指针的普通变量了。spa
在C的头文件内包含这样一个简单的结构体指针
struct mybuf { int num1; int num2; }
接口函数以下:调试
int fnAdd(struct mybuf mydata);
那么这样在C#该如何调用这样的接口函数呢? 首先在C#咱们要声明一个结构体,在C#结构体并无被抛弃,只不过在使用结构体时须要注意一些细节,好比要调用C的DLL那么最好在C#内定义的结构体前加上一些修饰符,以下:code
[StructLayout(LayoutKind.Sequential)] public struct MyBuf { public int num1; public int num2; public MyBuf(int n1,int n2) { num1 = n1; num2 = n2; } } [DllImport("你的dll名称", EntryPoint = "fnAdd", CallingConvention = CallingConvention.Cdecl)] public static extern int fnAdd(MyBuf mydata);
你们可能会发现怎么这个结构体这么像个类啊,是的啊在C#中结构体确实是个特殊的类,也有构造函数,如上例子中的public MyBuf(int n1,int n2)这样的构造函数;blog
你们也可能看到定义结构体前咱们使用StructLayout这样的结构体布局修饰符,这
个实际上是颇有用的,咱们使用了LayoutKind.Sequential这个属性,这在dll的参数是指针的时候特别有用,由于你的C中的结构体内存是顺序布局的,所以咱们在C#内也要采用顺序布局,这样传递指针的时候在C dll内就不会出错了(也不必定)。
另外你们看到结构体的成员变量咱们都用来public修饰符,当没有public只有int num1这样的语句的时候,C#默认成员变量是保护的,那么你在C#中其余方法内定义这个结构体就不能随便的访问修改其成员变量了(只能经过构造函数new的时候进行初始化),所以须要使用public来修饰一下成员变量。
nt fnAdd(struct mybuf *p_mydata),或者写成int fnAdd(void *p_mydata)
上面两个函数实际上是同样的,由于C规定void类型的指针能够指向任何数据类型,只不过在c函数实体内强制为你的数据类型便可,好比:
struct mybuf*p = (structmybuf*)p_mydata;
那么在C#内该如何调用该函数接口呢?很简单触类旁通ref嘛……
好了,代码以下:
[StructLayout(LayoutKind.Sequential)] public struct MyBuf { public int num1; public int num2; public MyBuf(int n1,int n2) { num1 = n1; num2 = n2; } } [DllImport("你的dll名称", EntryPoint = "fnAdd", CallingConvention = CallingConvention.Cdecl)] public static extern int fnAdd(ref MyBuf mydata);
对这样就OK了。
内声明结构体的时候须要特别处理,暂时就不增长这样的难度了。
为了继续增长点难度,下面继续补充几种状况,来涨点姿式……
以下的结构体
struct mybuf { int a; int b; bool bl; int arr[200]; char ch[100]; };
dll内接口原型为int fnAdd(struct mybuf mydata),那么这种状况在C#下该如何调用呢?
在C#中数据的布局和C(C++)中的数据布局有很大的不一样,所以当用户须要在C#和C代码间进行数据传递时,必须手动的告诉C#的老大.NET,这批数据该怎么传递给C的DLL来使用;所以这就涉及了C#的历史遗留问题(数据封送)。好很少说先上代码,在C#该怎么声明这样一个结构体呢,以下:
[StructLayout(LayoutKind.Sequential)] public struct MyBuf { public int num1; public int num2; public bool flg; // 整形数组 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 200)] public int[] buf; // 字符数组 [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)] public char[] ch; public MyBuf(int n1, int n2, bool bl) { num1 = n1; num2 = n2; flg = bl; buf = new int[200]; ch = new char[100]; } };
是的,你可能奇怪的发现每一个数值的声明前,增长了一个[MarshalAsxxxx]字段,这是干吗用的呢?这就是前面红色字体标注的数据封送格式,简单介绍一下,MarshalAs的属性告诉了.NET如何将下面的数据进行封送到dll接口中,当UnmanagedType的值为ByValArray时,就是告诉下面的数据是一个数组,而且使用这个ByValArray值后面必须跟上SizeConst来告诉.NET这个数组的大小,如上;其实VS2010内写代码的时候当输入UnmanagedType以后按【.】以后VS会自动弹出框里面会列举不少数据封送格式,每一个格式都有中文的tooltip来讲明,本身看看就会明白的;前段时间看到字符数组和整形数据数据封送格式不同,整形用ByValArray,而字符使用ByValTStr,可是实际我测下来当字符使用ByValTStr时调试的时候回报错,说非法的封送格式,把字符封送也改成ByValArray后就OK了,不晓得啥问题?还要继续研究。那么继续,在C#把结构体封装好了,就能够直接调用了,不管是结构体仍是结构体指针按照前面的方法就可使用了。