在开发unity的时候,要想在C#里调用C++的方法,我一般采用PInvoke的方式。
在C++里,需要在需要被导出的函数前添加“extern C”,这样抱住函数定义的名字和导出的名字一致,否则如果按照默认方式导出,函数名将很混乱导致我们的程序无法找到入口。
1、原理
不同语言直接的互相调用,只要将一种语言的数据的指针--内存地址传递给另外一种语言,在另一种语言里根据通信协议再将指针所指的数据存到对应长度的数据类型。
2、基本数据类型的传递
C/C++ | C# | 长度(Bytes) |
short | short | 2 |
int | int | 4 |
long | int | 4 |
bool | bool | 1 |
char | byte | 1 |
wchar_t | char | 2 |
float | float | 4 |
double | double | 8 |
3、指针的传递
我们可以在方法中直接传递指针
示例:
//传入一个整型指针,将其所指向的内容加1
EXTERN_C TESTCPPDLL_API void __stdcall AddInt(int *i);
//传入一个整型数组的指针以及数组长度,遍历每一个元素并且输出
EXTERN_C TESTCPPDLL_API void __stdcall AddIntArray(int *firstElement,int arraylength);
//在C++中生成一个整型数组,并且数组指针返回给C#
EXTERN_C TESTCPPDLL_API int* __stdcall GetArrayFromCPP();
对应调用的C#代码如下所示:
[DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "AddInt")]
extern unsafe static void AddInt(int* i);
[DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "AddIntArray")]
extern unsafe static void AddIntArray(int* firstElement, int arraylength);
[DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "GetArrayFromCPP")]
extern unsafe static int* GetArrayFromCPP();
4、函数指针的传递
利用PInvoke我们也可以实现函数指针的传递,C#中并没有函数指针的概念,但是可以使用委托delegate来代替函数指针。
C#代码:
//定义一个委托,返回值为空,存在一个整型参数
public delegate void CSCallback(int tick);
//定义一个用于回调的方法,与前面定义的委托的原型一样
//该方法会被C++所调用
static void CSCallbackFunction(int tick)
{
Console.WriteLine(tick.ToString ());
}
//定义一个委托类型的实例,
//在主程序中该委托实例将指向前面定义的CSCallbackFunction方法
static CSCallback callback;
在CS的主程序中让callback指向CSCallbackFunction方法,代码如下所示:
//调用委托所指向的方法
callback = CSCallbackFunction;
在C#中添加SetCallback函数的声明,代码如下所示:
//这里使用CSCallback委托类型来兼容C++里的CPPCallback函数指针
[DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "SetCallback")]
extern static void SetCallback(CSCallback callback);
在C#中的调用过程如下所示:
//让委托指向将被回调的方法
callback = CSCallbackFunction;
//将委托传递给C++
SetCallback(callback);
SetCallback方法被执行后,在C#中定义的CSCallbackFunction就会被C++所调用。
C/C++代码:
//定义一个函数指针
typedef void (__stdcall *CPPCallback)(int tick);
//定义一个用于设置函数指针的方法,
//并在该函数中调用C#中传递过来的委托
EXTERN_C TESTCPPDLL_API void SetCallback(CPPCallback callback);
SetCallback函数的实现在TestCPPDLL.cpp中,代码如下所示:
TESTCPPDLL_API void SetCallback(CPPCallback callback)
{
int tick=rand();
//下面的代码是对C#中委托进行调用
callback(tick);
}
5、结构体的传递
-
要传递的成员为公有的值类型字段
-
C#中结构体字段类型与C++结构体中的字段类型相兼容
-
C#结构中的字段顺序与C++结构体中的字段顺序相同,要保证该功能,需要将C#结构体标记为[StructLayout( LayoutKind.Sequential)]
C++
[StructLayout( LayoutKind.Sequential)]
struct Vector3
{
public float X, Y, Z;
}
该结构体表示一个3D向量,包括X,Y,Z三个float类型的分量。
然后在TestCPPDLL.h中也定义一个相同结构的结构体,代码如下所示:
struct Vector3
{
float X,Y,Z;
};
在TestCPPDLL.h中声明一个用于传递Vector3结构体的一个函数,代码如下所示:
EXTERN_C TESTCPPDLL_API void __stdcall SendStructFromCSToCPP(Vector3 vector);
在TestCPPDLL.cpp中将其实现,代码如下所示:
TESTCPPDLL_API void __stdcall SendStructFromCSToCPP(Vector3 vector)
{
cout<<"got vector3 in cpp,x:";
cout<<vector.X;
cout<<",Y:";
cout<<vector.Y;
cout<<",Z:";
cout<<vector.Z;
}
在C#中添加对SendStructFromCSToCPP函数的声明,代码如下所示:
[DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "SendStructFromCSToCPP")]
extern static void SendStructFromCSToCPP(Vector3 vector);
C#中的调用过程如下所示:
//建立一个Vector3的实例
Vector3 vector = new Vector3() { X =10,Y=20,Z=30 };
//将vector传递给C++并在C++中输出
SendStructFromCSToCPP(vector);
完整代码:
TestCPPDLL.h:
#define TESTCPPDLL_API __declspec(dllexport)
EXTERN_C TESTCPPDLL_API int __stdcall Add(int a,int b);
EXTERN_C TESTCPPDLL_API void __stdcall WriteString(wchar_t*content);
//传入一个整型指针,将其所指向的内容加1
EXTERN_C TESTCPPDLL_API void __stdcall AddInt(int *i);
//传入一个整型数组的指针以及数组长度,遍历每一个元素并且输出
EXTERN_C TESTCPPDLL_API void __stdcall AddIntArray(int *firstElement,int arraylength);
//在C++中生成一个整型数组,并且数组指针返回给C#
EXTERN_C TESTCPPDLL_API int* __stdcall GetArrayFromCPP();
//定义一个函数指针
typedef void (__stdcall *CPPCallback)(int tick);
//定义一个用于设置函数指针的方法,
//并在该函数中调用C#中传递过来的委托
EXTERN_C TESTCPPDLL_API void __stdcall SetCallback(CPPCallback callback);
struct Vector3
{
float X,Y,Z;
};
EXTERN_C TESTCPPDLL_API void __stdcall SendStructFromCSToCPP(Vector3 vector);
TestCPPDLL.CPP:
#include "stdafx.h"
#include <iostream>
#include "TestCPPDLL.h"
using namespace std;
TESTCPPDLL_API int __stdcall Add(int a,int b)
{
return a+b;
}
TESTCPPDLL_API void __stdcall WriteString(wchar_t*content)
{
wprintf(content);
printf("\n");
}
TESTCPPDLL_API void __stdcall AddInt(int *i)
{
(*i)++;
}
TESTCPPDLL_API void __stdcall AddIntArray(int *firstElement,int arrayLength)
{
int*currentPointer=firstElement;
for (int i = 0; i < arrayLength; i++)
{
cout<<*currentPointer;
currentPointer++;
}
cout<<endl;
}
int *arrPtr;
TESTCPPDLL_API int* __stdcall GetArrayFromCPP()
{
arrPtr=new int[10];
for (int i = 0; i < 10; i++)
{
arrPtr[i]=i;
}
return arrPtr;
}
TESTCPPDLL_API void __stdcall SetCallback(CPPCallback callback)
{
int tick=100;
//下面的代码是对C#中委托进行调用
callback(tick);
}
TESTCPPDLL_API void __stdcall SendStructFromCSToCPP(Vector3 vector)
{
cout<<"got vector3 in cpp,x:";
cout<<vector.X;
cout<<",Y:";
cout<<vector.Y;
cout<<",Z:";
cout<<vector.Z;
}
完整的C#代码如下所示:
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
[DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "Add")]
extern static int Add(int a, int b);
[DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "WriteString")]
extern unsafe static void WriteString(char* c);
[DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "AddInt")]
extern unsafe static void AddInt(int* i);
[DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "AddIntArray")]
extern unsafe static void AddIntArray(int* firstElement, int arraylength);
[DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "GetArrayFromCPP")]
extern unsafe static int* GetArrayFromCPP();
//定义一个委托,返回值为空,存在一个整型参数
public delegate void CSCallback(int tick);
//定义一个用于回调的方法,与前面定义的委托的原型一样
//该方法会被C++所调用
static void CSCallbackFunction(int tick)
{
Console.WriteLine(tick.ToString());
}
//定义一个委托类型的实例,
//在主程序中该委托实例将指向前面定义的CSCallbackFunction方法
static CSCallback callback;
//这里使用CSCallback委托类型来兼容C++里的CPPCallback函数指针
[DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "SetCallback")]
extern static void SetCallback(CSCallback callback);
[StructLayout(LayoutKind.Sequential)]
struct Vector3
{
public float X, Y, Z;
}
[DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "SendStructFromCSToCPP")]
extern static void SendStructFromCSToCPP(Vector3 vector);
static void Main(string[] args)
{
int c = Add(1, 2);
Console.WriteLine(c);
//因为使用指针,因为要声明非安全域
unsafe
{
//在传递字符串时,将字符所在的内存固化,
//并取出字符数组的指针
fixed (char* p = &("hello".ToCharArray()[0]))
{
//调用方法
WriteString(p);
}
}
unsafe
{
// 调用C++中的AddInt方法
int i = 10;
AddInt(&i);
Console.WriteLine(i);
//调用C++中的AddIntArray方法将C#中的数据传递到C++中,并在C++中输出
int[] CSArray = new int[10];
for (int iArr = 0; iArr < 10; iArr++)
{
CSArray[iArr] = iArr;
}
fixed (int* pCSArray = &CSArray[0])
{
AddIntArray(pCSArray, 10);
}
//调用C++中的GetArrayFromCPP方法获取一个C++中建立的数组
int* pArrayPointer = null;
pArrayPointer = GetArrayFromCPP();
for (int iArr = 0; iArr < 10; iArr++)
{
Console.WriteLine(*pArrayPointer);
pArrayPointer++;
}
}
//让委托指向将被回调的方法
callback = CSCallbackFunction;
//将委托传递给C++
SetCallback(callback);
//建立一个Vector3的实例
Vector3 vector = new Vector3() { X = 10, Y = 20, Z = 30 };
//将vector传递给C++并在C++中输出
SendStructFromCSToCPP(vector);
Console.Read();
}
}
}