一、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#转换的结构体需要用LayoutKind.Sequential这个属性修饰,因为在C/C++的结构体内存是顺序布局的,所以需要C#转换的结构体内存也按照顺序布局,这样传递指针时dll内部不会出错。
例如:
C的结构体声明为
struct demobuf
C#中的结构体声明为
[StructLayout(LayoutKind.Sequential)] public struct DemoBuf
C#转换的结构体成员需要用public修饰符,如果不添加public修饰符,C#成员变量默认是保护的,在其它方法内定义这个结构体就不能随便访问修改其成员变量。并且在C的结构体中对其内部成员变量的访问权限只能是public,C++中允许public/protected/private。
C的结构体为
struct demobuf { int a; int b; bool c; }
C#的结构体为
[StructLayout(LayoutKind.Sequential)] public struct DemoBuf { public int a; public int b; public bool c; }
当转换的结构体成员中包含数组时,需要获取转换数组的大小,用到MarshalAs属性。
C的结构体为
struct demobuf { int a; int b; bool c; int arr[9]; char ch[9]; }
[StructLayout(LayoutKind.Sequential)] public struct DemoBuf { public int a; public int b; public bool c; [MarshalAs(UnmanagedType.ByValArray,SizeConst=9)] public int[] arr; [MarshalAs(UnmanagedType.ByValTStr,SizeConst=9)] public char[] ch; }
经常用到的比如说:
GCHandle[] hArr = new GCHandle[4];
hArr = GCHandle.Alloc(Bytes,GCHandleType.Pinned);
hArr[i].AddrOfPinnedObject();
IntPtr Ptr = IntPtr.Zero;
Ptr = Marshal.AllocHGlobal(size);
Marshal.StructToPtr(struct,Ptr,true);
或者指针数组
Int64 Addr = IntPtr.ToInt64();
IntPtr pstIndex = new IntPtr(Addr);
Marshal.StructToPtr(struct,pstIndex,true);
pstIndex = pstIndex + Marshal.SizeOf(struct);
二、C#调用C++编写的DLL函数, 以及各种类型的参数传递
- 如果函数只有传入参数,比如:
//C++中的输出函数 int __declspec(dllexport) test(const int N) { return N+10; } 对应的C#代码为: C# Code Copy Code To Clipboard [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++ 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. 带传入数组: 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++不能直接传出数组,只传出数组指针, 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#代码: [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,可以参见这篇转帖的文章: http://www.kycis.com/blog/read.php?21
- 传出字符数组:
C++定义: 1. void __declspec(dllexport) test(int i, double &a, double &b, char t[5]) C#对应声明: [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。
三、
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; }