问题描述
在Unity3d脚本中实现对C++动态库的调用。假设需要在脚本中调用dll里面的一个函数,函数名下:
int PathPlanning(INPUT *input, OUTPUT *output); //其中INPUT 和 OUTPUT 是自定义的结构体(或者类,一样的)
简单介绍一下这个函数:
这是用于机器人路径规划的一个函数,传给它一个INPUT结构体(里面是一些输入参数),它返回给我一个OUTPUT结构体(我们所需要的输出)。函数的int返回值代表路径规划是否成功。
unity中调用动态链接库的两种方式
根据官方文档https://docs.unity3d.com/Manual/Plugins.html,unity可以对两种dll进行调用——Managed Plugins和Native Plugins。一种是托管代码编译的dll,一种是非托管代码编译的dll。
所以我们可以有两种思路:1是用CLR对再封装一层,使之成为托管代码,然后调用CLR编译出的dll。2是在unity3D中直接调用c++的dll。这二者有什么区别呢?
第二种方式只能调用C++dll中的函数,不能使用它里面的类。第一种方式即可使用函数和类,然而,如果C++代码中有指针,编译出的CLR只能是unsafe的,在Unity3D 2018.1之前的版本并不支持。
所以,我们只能用二种方式——直接在Unity3D中调用C++的dll。
unity中调用C++的dll
c++的头文件如下:
extern "C" {
typedef struct PathPlanInputForUnity{
int PlanMode;
double PlanStep;
double Acc_des;
double Vel_max;
int PlanJoint;
double Joint_ini[6];
double Joint_des[6];
double PEend_mid[6];
double PEend_des[6];
}INPUT;
typedef struct PathPlanOutputForUnity{
int totalstep;
double plantime;
double ResultJointVel[20000];
double ResultJoint[20000];
double endPE[20000];
}OUTPUT;
}
extern "C" _declspec(dllexport) int _stdcall PathPlanning(INPUT *input, OUTPUT *output);
extern "C"是为了让编译器按照C语言的方式编译dll,_stdcall定义调用方式。
在unity中调用:
//定义INPUT和OUTPUT结构体
[StructLayout(LayoutKind.Sequential)]
struct INPUT
{
public int PlanMode;
public double PlanStep;
public double Acc_des;
public double Vel_max;
public int PlanJoint;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
public double[] Joint_ini;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
public double[] Joint_des;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
public double[] PEend_mid;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
public double[] PEend_des;
};
[StructLayout(LayoutKind.Sequential)]
struct OUTPUT
{
public int totalstep;
public double plantime;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 20000)]
public double[] ResultJointVel;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 20000)]
public double[] ResultJoint;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 20000)]
public double[] endPE;
};
//引入dll中的函数
[DllImport("PathPlanningCplus", EntryPoint = "PathPlanning", CallingConvention = CallingConvention.StdCall)]
private static extern int PathPlanning(IntPtr pv1, IntPtr pv2);
下面调用上面声明的int PathPlanning(IntPtr pv1, IntPtr pv2)函数。其中IntPtr是通用平台指针,结构体必须转换为IntPtr才能作为参数传递给函数。
void Start () {
//INPUT对象
INPUT pIn = new INPUT();
pIn.PlanMode = 1;
pIn.PlanStep = 50;
pIn.Acc_des = 0.01;
pIn.Vel_max = 0.05;
pIn.Joint_ini = new double[6] { 10, -16, 45, 112, 23, 30 };
pIn.Joint_des = new double[6] { 58, 50, 45, 112, 23, 54 };
pIn.PEend_des = new double[6] { 3.5, 6, 3, -80, -76, -90 };
//把INPUT对象转换为IntPtr(即托管内存转换为非托管内存)
int sizeIn = Marshal.SizeOf(typeof(INPUT));
IntPtr pBuffIn = Marshal.AllocHGlobal(sizeIn);
Marshal.StructureToPtr(pIn, pBuffIn, true);
//给OUTPUT分配非托管内存
int sizeOut = Marshal.SizeOf(typeof(OUTPUT));
IntPtr pBuffOut = Marshal.AllocHGlobal(sizeOut);
//调用路径规划函数
int result2 = PathPlanningStartWorking(pBuffIn, pBuffOut);
//将OUTPUT的非托管内存转换为托管内存
OUTPUT pOut = (OUTPUT)Marshal.PtrToStructure(pBuffOut, typeof(OUTPUT)); //得到了我们所需要的pOut
}