C#与C++类型互转
一、C#调用DLL文件时参数对应表
Wtypes.h 中的非托管类型 非托管 C 语言类型 托管类名 说明
HANDLE void* System.IntPtr 32 位
BYTE unsigned char System.Byte 8 位
SHORT short System.Int16 16 位
WORD unsigned short System.UInt16 16 位
INT int System.Int32 32 位
UINT unsigned int System.UInt32 32 位
LONG long System.Int32 32 位
BOOL long System.Int32 32 位
DWORD unsigned long System.UInt32 32 位
ULONG unsigned long System.UInt32 32 位
CHAR char System.Char 用 ANSI 修饰。
LPSTR char* System.String 或 System.StringBuilder 用 ANSI 修饰。
LPCSTR Const char* System.String 或 System.StringBuilder 用 ANSI 修饰。
LPWSTR wchar_t* System.String 或 System.StringBuilder 用 Unicode 修饰。
LPCWSTR Const wchar_t* System.String 或 System.StringBuilder 用 Unicode 修饰。
FLOAT Float System.Single 32 位
DOUBLE Double System.Double 64 位
二、C#调用C++编写的DLL函数, 以及各种类型的参数传递
- 如果函数只有传入参数,比如:
1. //C++中的输出函数
2. int __declspec(dllexport) test(const int N)
3. {
4. return N+10;
5. }
对应的C#代码为:
C# Code Copy Code To Clipboard
1. [DllImport("test.dll", EntryPoint = "#1")]
2. public static extern int test(int m);
3.
4. private void button1_Click(object sender, EventArgs e)
5. {
6. textBox1.Text= test(10).ToString();
7. }
2. 如果函数有传出参数,比如:
1. //C++
2. void __declspec(dllexport) test(const int N, int& Z)
3. {
4. Z=N+10;
5. }
对应的C#代码:
C# Code Copy Code To Clipboard
1. [DllImport("test.dll", EntryPoint = "#1")]
2. public static extern double test(int m, ref int n);
3.
4. private void button1_Click(object sender, EventArgs e)
5. {
6. int N = 0;
7. test1(10, ref N);
8. textBox1.Text= N.ToString();
9. }
3. 带传入数组:
1. void __declspec(dllexport) test(const int N, const int n[], int& Z)
2. {
3. for (int i=0; i<N; i++)
4. {
5. Z+=n[i];
6. }
7. }
C#代码:
1. [DllImport("test.dll", EntryPoint = "#1")]
2. public static extern double test(int N, int[] n, ref int Z);
3.
4. private void button1_Click(object sender, EventArgs e)
5. {
6. int N = 0;
7. int[] n;
8. n = new int[10];
9. for (int i = 0; i < 10; i++)
10. {
11. n[i] = i;
12. }
13. test(n.Length, n, ref N);
14. textBox1.Text= N.ToString();
15. }
4. 带传出数组:
C++不能直接传出数组,只传出数组指针,
1. void __declspec(dllexport) test(const int M, const int n[], int *N)
2. {
3. for (int i=0; i<M; i++)
4. {
5. N[i]=n[i]+10;
6. }
7. }
对应的C#代码:
1. [DllImport("test.dll", EntryPoint = "#1")]
2. public static extern void test(int N, int[] n, [MarshalAs(UnmanagedType.LPArray,SizeParamIndex=1)] int[] Z);
3.
4. private void button1_Click(object sender, EventArgs e)
5. {
6. int N = 1000;
7. int[] n, Z;
8. n = new int[N];Z = new int[N];
9. for (int i = 0; i < N; i++)
10. {
11. n[i] = i;
12. }
13. test(n.Length, n, Z);
14. for (int i=0; i<Z.Length; i++)
15. {
16. textBox1.AppendText(Z[i].ToString()+"n");
17. }
18. }
这里声明函数入口时,注意这句 [MarshalAs(UnmanagedType.LPArray,SizeParamIndex=1)] int[] Z
在C#中数组是直接使用的,而在C++中返回的是数组的指针,这句用来转化这两种不同的类型.
关于MarshalAs的参数用法以及数组的Marshaling,可以参见这篇转帖的文章: http://www.kycis.com/blog/read.php?21
- 传出字符数组:
C++定义:
1. void __declspec(dllexport) test(int i, double &a, double &b, char t[5])
C#对应声明:
1. [DllImport("dll.dll", EntryPoint = "test")]
2. public static extern void test(int i, ref double a, ref double b, [Out, MarshalAs(UnmanagedType.LPArray)] char[] t);
4. char[] t = new char[5];
5. test(i, ref a, ref b, t);
字符数组的传递基本与4相似,只是mashalAs 时前面加上Out。
三、
1.普通传值,如下面代码中MotionDetect的第4个参数;
2.传引用,MotionDetect的第3个参数,nNum传进动态库后赋值再传回来;
3.引用传一个结构体,MotionDetect的第2个参数,这里其实是传一个结构体的数组,具体像加ref的传参还真不会;
4.传一个有分配内存的变量,需要用到GCHandle,因为C#是可以自动回收内存的,而GCHandle在这里的作用就是把它的内存空间Pin住,传递给C++动态库后再手动回收资源。
using UnityEngine; using System.Collections; using System.Runtime.InteropServices; public class D : MonoBehaviour { struct Color_32 { public byte r; public byte g; public byte b; public byte a; } Color_32[] imageDataResult; GCHandle pixelsHandle; [DllImport("MotionDetectionDll")] private static extern bool MotionDetect( System.IntPtr colors, Color_32[] imageDataResult, ref int nNum, int nChannels ); void Start() { imageDataResult = new Color_32[128*128]; } void Update () { int nNum = 0; pixelsHandle = GCHandle.Alloc(pixels, GCHandleType.Pinned); bool wfDf = MotionDetect( pixelsHandle.AddrOfPinnedObject(), imageDataResult, ref nNum, 4 ); pixelsHandle.Free(); } } C++中是这么写的 //头文件中多加个结构体定义 #include "stdafx.h" struct Color_32 { byte r; byte g; byte b; byte a; }; extern "C" _declspec(dllexport) bool MotionDetect ( char* imageData, Color_32 imageDataResult[], int *nNum, int nChannels ); // CPP文件中 extern "C" _declspec(dllexport) bool MotionDetect ( char* imageData, Color_32 imageDataResult[], int *nNum, int nChannels ) { IplImage* imgSrc = cvCreateImageHeader(cvSize(width, height), IPL_DEPTH_8U, 4); if(!imgSrc) { return false; } cvSetData( imgSrc, imageData, imgSrc->widthStep ); ...... for ( int i=0; i< ......; i++ ) { imageDataResult[i].r = ......; imageDataResult[i].g = ......; imageDataResult[i].b = ......; imageDataResult[i].a = ......; } ...... *nNum = 5; nChannels = 4; }
在托管代码和本地代码之间传递数组,是interop marshaling中间比较复杂的一个问题。本文从数组的定义开始,介绍数组marshalling的三种方法,并对blittable类型等概念做进一步的讨论。
当托管代码需要和本地代码互操作时,我们就进入了interop的领域。interop的场景形形色色,不变的是我们需要把数据从一个世界marshal到另一个世界。
在讨论数组marshalling之前,请各位和我一起思考一个问题,什么是数组?之所以要讨论这个问题,原因在于不同的术语在不同的语境中含有不同的意思。在使用c语言的时候,我认为数组就是一个指针。但是熟悉c#的朋友可能不同意我的观点,数组是System.Array或者Object[]。我认为,这两种回答都是出自语言领域的正确观点。那么如果有一个项目含有两个模块,一个用本地代码撰写,另一个用托管代码撰写,两者之间的接口要求传递一个数组,这个”数组”包含着怎样的语义呢?我觉得有两点是很重要的:
1. 如何访问数组元素。就好比c语言中的数组指针,c#中的数组引用,都是访问数组必不可少的线索。
2. 数组的大小。数组的大小不仅仅是System.Array.Length。它还可以包括诸如数组的维数,每个维上的启始边界和结束边界。
.NET在marshal数组的时候,很大程度上也是从以上两点出发,架起托管世界和本地代码之间的桥梁。根据操作的具体数据类型不同,数组marshal又可以分为以下两个大类,三个小类,我们分别介绍:
1. 数组作为参数传递
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可以用来指定数组元素的类型
2. 数组作为字段传递
很久以来,对于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;
- }
原文出处:http://www.kycis.com/blog/read.php?21
数组作为一种常用的数据结构,各种高级语言都提供了相应的支持,在这些高级语言之间交互操作的时候,数组也是传送集合类型数据的重要结构,希望今天的内容能对大家有所帮助。