C#调用C++编写的DLL函数, 以及各种类型的参数传递

1.如果函数只有传入参数,比如

//C++中的输出函数 
int __declspec(dllexport) test(const int N) 
{ 
	return N+10; 
} 

对应的C#代码为:

[DllImport("test.dll", EntryPoint = "#1")] 
public static extern int test(int m);  
private void button1_Click(object sender, EventArgs e) 
{ 
	textBox1.Text= test(10).ToString(); 
} 

2. 如果函数有传出参数,比如:

C/C++ Code Copy Code To Clipboard

//C++
void __declspec(dllexport) test(const int N, int& Z) 
{ 
	Z=N+10; 
} 

对应的C#代码:

C# Code Copy Code To Clipboard

[DllImport("test.dll", EntryPoint = "#1")] 
public static extern double test(int m, ref int n);  
private void button1_Click(object sender, EventArgs e) 
{ 
	int N = 0; 
	test1(10, ref N); 
	textBox1.Text= N.ToString(); 
} 

3. 带传入数组:

C/C++ Code Copy Code To Clipboard

void __declspec(dllexport) test(const int N, const int n[], int& Z) 
{ 
	for (int i=0; i<N; i++) 
	{ 
		Z+=n[i]; 
	} 
} 

C#代码:

[DllImport("test.dll", EntryPoint = "#1")] 
public static extern double test(int N, int[] n, ref int Z); 
 
private void button1_Click(object sender, EventArgs e) 
{ 
	int N = 0; 
	int[] n; 
	n = new int[10]; 
	for (int i = 0; i < 10; i++) 
	{ 
		n[i] = i; 
	} 
	test(n.Length, n, ref N); 
	textBox1.Text= N.ToString(); 
	} 
	```

4. 带传出数组:

C++不能直接传出数组,只传出数组指针

C/C++ Code Copy Code To Clipboard

void __declspec(dllexport) test(const int M, const int n[], int *N) 
{ 
	for (int i=0; i<M; i++) 
	{ 
		N[i]=n[i]+10; 
	} 
} 

对应的C#代码:

C# Code Copy Code To Clipboard

[DllImport("test.dll", EntryPoint = "#1")] 
public static extern void test(int N, int[] n, [MarshalAs(UnmanagedType.LPArray,SizeParamIndex=1)] int[] Z);  
private void button1_Click(object sender, EventArgs e) 
{ 
	int N = 1000; 
	int[] n, Z; 
	n = new int[N];Z = new int[N]; 
	for (int i = 0; i < N; i++) 
	{ 
		n[i] = i; 
	} 
	test(n.Length, n, Z); 
	for (int i=0; i<Z.Length; i++) 
	{ 
		textBox1.AppendText(Z[i].ToString()+"n"); 
	} 
} 

这里声明函数入口时,注意这句 [MarshalAs(UnmanagedType.LPArray,SizeParamIndex=1)] int[] Z
在C#中数组是直接使用的,而在C++中返回的是数组的指针,这句用来转化这两种不同的类型.
关于MarshalAs的参数用法以及数组的Marshaling,可以参见这篇转帖的文章:
点击这里参考本文末

5. 传出字符数组:

C++定义:

C/C++ Code Copy Code To Clipboard

void __declspec(dllexport) test(int i, double &a, double &b, char t[5])  

C#对应声明

C# Code Copy Code To Clipboard

[DllImport("dll.dll", EntryPoint = "test")]  
public static extern void test(int i, ref double a, ref double b, [Out, MarshalAs(UnmanagedType.LPArray)] char[] t);   
。。。  
            char[] t = new char[5];  
            test(i, ref a, ref b, t);  

字符数组的传递基本与4相似,只是mashalAs 时前面加上Out。

MarshalAs的参数用法以及数组的Marshaling

在托管代码和本地代码之间传递数组,是interop marshaling中间比较复杂的一个问题。本文从数组的定义开始,介绍数组marshalling的三种方法,并对blittable类型等概念做进一步的讨论。
当托管代码需要和本地代码互操作时,我们就进入了interop的领域。interop的场景形形色色,不变的是我们需要把数据从一个世界marshal到另一个世界。
在讨论数组marshalling之前,请各位和我一起思考一个问题,什么是数组?之所以要讨论这个问题,原因在于不同的术语在不同的语境中含有不同的意思。在使用c语言的时候,我认为数组就是一个指针。但是熟悉c#的朋友可能不同意我的观点,数组是System.Array或者Object[]。我认为,这两种回答都是出自语言领域的正确观点。那么如果有一个项目含有两个模块,一个用本地代码撰写,另一个用托管代码撰写,两者之间的接口要求传递一个数组,这个”数组”包含着怎样的语义呢?我觉得有两点是很重要的:

  1. 如何访问数组元素。就好比c语言中的数组指针,c#中的数组引用,都是访问数组必不可少的线索。

  2. 数组的大小。数组的大小不仅仅是System.Array.Length。它还可以包括诸如数组的维数,每个维上的启始边界和结束边界。
    .NET在marshal数组的时候,很大程度上也是从以上两点出发,架起托管世界和本地代码之间的桥梁。根据操作的具体数据类型不同,数组marshal又可以分为以下两个大类,三个小类,我们分别介绍:

  3. 数组作为参数传递

a) c/c++类型的数组

c类型的数组,也就是由指针指明存储空间首地址的数组,是一个自描述很低的数据结构。尽管有些编译器支持在固定偏移量上写入数组的长度,但是因为各个编译器处理的具体方法不同,没有一个标准让CLR来参考。所以我们在marshal一个c类型的数组的时候,不得不用其他方法考虑传递数组的大小,有以下两种:

  1. 约定指针数组长度

这种方法需要调用者和被调用者之间有一个约定,给出一个数组长度的固定值。在托管端声明一个interop方法的时候,只需要用SizeConst这个属性,把这个约定告诉CLR。CLR在进行Marshal的时候,会根据这个值,在本地堆上分配相应的空间。
public static extern void Ex([In, Out][MarshalAs(UnmanagedType.LPArray, SizeParamConst=3)]string[] a);
2)通过一个额外的参数指定数组长度

可能有的朋友觉得,约定一个静态的数组长度还不够灵活,希望能够动态的传递数组的长度,从而使得数组marshalling不必受制于固定数组长度的限制。我们先来看普通的函数调用是如何解决这个问题的:caller和callee通过约定一个额外的参数,来传递数组的长度,这可以被视作是一个调用者和被调用者的约定。由于marshalling需要clr的参与,那么就需要把这个约定用clr能够理解的方式,进行扩充,形成一个三方约定。CLR,用属性SizeParamIndex来描述此类约定。
public static extern void Ex2([In, Out][MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)]string[] a,int len);

b) SafeArray

SafeArray是COM引入的数据类型,是一个自描述度很高的数据结构。他可以很清楚的告诉用户,该数组的元素类型,数组包含了多少维,每一维的起始位置和终止位置。所以marshal这类safearray的时候,只需要通过设定属性,告诉CLR,当前array对应的本地代码是safearray即可。举例如下:

public void DumpSafeArrayStringIn( [In][MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VarEnum.VT_BSTR)]Object[] array);
大家可以看到,SafeArraySubType可以用来指定数组元素的类型

  1. 数组作为字段传递

很久以来,对于interop,一直有这样的评价,简单数据结构的marshalling其实并不复杂,但是一旦进入了struct或者class这种你中有我,我中有你的层叠数据结构之后,marshalling就成了bug的温床。所以在这里,我们也要提提数组作为struct/class的一个字段的方法。在这里首先要给这个stuct/class加一个限制,是byval。由于这个限制,大家可以想象的出,CLR在marshal的时候,做的事情是类似于浅copy的内存复制,所以对数组marshal的时候,也就只支持固定长度的数组marshal。

public class StructIntArray   
    {   
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=4)]   
    public int[] array;   
    } 

数组作为一种常用的数据结构,各种高级语言都提供了相应的支持,在这些高级语言之间交互操作的时候,数组也是传送集合类型数据的重要结构,希望今天的内容能对大家有所帮助。

  • 2
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值