Unity和C++dll交互

前言

​ 我的一个名为嘤嘤嘤的同事,做机器人仿真,让我给他写一个Unity的机械臂仿真程序,整个流程就是我的unity程序开一个TCP的服务器,通过接收他发来的指令,并且解析成对应的动作来操控Unity的机械臂.本身项目很简单,但是问题是我正忙于考研,我作出第一个版本后,他需要改动需求的话我还要改,他不会c#和Unity只会C语言,这种事情当然难不住我.于是就有了下面的奇葩操作.

我在Unity内部只实现操作机械臂的关键API接口,然后接收到通信数据后,将数据的处理以及执行API对应的动作全都放到一个C++编写的DLL里.Unity启动后去加载这个DLL,并且与之通信,这样的话只要用C/C++编写对应的Dll,就能被Unity程序调用并且与之交互,在我提供的API基础上他想怎么改就怎么改,非常的"方便" .就不用浪费我的时间了


需求分析

先上个图

最终要控制的就是这个玩意儿的运动
Unity和C++dll交互

内容包括控制每个关节的旋转,以及设置一些物体的坐标和显示隐藏等,这对于Unity来说不要再简单,奈何他不会呢

所以我最终整理的流程就是

  1. Unity端启动TCP服务器
  2. 他通过TCP向Unity端发送数据
  3. Unity端接收到数据后不做任何处理,将数据传入到DLL的处理函数
  4. DLL内进行数据的解析,并且根据解析的内容调用我提供的API进行回调,控制Unity端的行为

具体实现

1.创建C/C++的动态链接库工程

这里使用的是CLion,这里命名为Cplugin

Unity和C++dll交互

2.定义Unity和DLL交互的数据结构

C#端的代码

//用于枚举Unity端的每一个需要被控制的GameObject
public enum ObjectEnum
{
    //对应六个旋转轴
    J1,
    J2,
    J3,
    J4,
    J5,
    J6,
    //对应四个空间坐标系
    Axis1,
    Axis2,
    Axis3,
    Axis4
}

//用于枚举日志类型
public enum LogLevel
{
    Info,
    Warn,
    Error
}

//用于传递Vector3数据
public struct Vec3
{
    public float x;
    public float y;
    public float z;
}

同时也要定义C/C++端的对应数据结构,要严格的和C#的定义一一对应
enum ObjectEnum {
    J1, J2, J3, J4, J5, J6, Axis1, Axis2, Axis3, Axis4

};
enum LogLevel {
    Info,
    Warn,
    Error
};

struct Vec3 {
    float x;
    float y;
    float z;
};

3.定义给DLL调用的C#的回调函数

函数的具体业务细节不重要,就是常规的unity操作,所以没有上传,主要是函数签名

    /// <summary>
    /// 输出日志
    /// </summary>
    /// <param name="level">等级</param>
    /// <param name="msg">消息</param>
    static void cb_unity_log(LogLevel level, [MarshalAs(UnmanagedType.LPStr)] string msg)
    {
        if (level == LogLevel.Info)
        {
            Debug.Log(msg);
        }
        else if (level == LogLevel.Warn)
        {
            Debug.LogWarning(msg);
        }
        else
        {
            Debug.LogError(msg);
        }
    }

    /// <summary>
    /// 设置物体位置
    /// </summary>
    /// <param name="target">目标物体</param>
    /// <param name="pos">位置</param>
    /// <returns></returns>
    static int cb_set_position(ObjectEnum target, Vec3 pos)
    {
        //具体的设置位置的操作
        return 0;
    }

    /// <summary>
    /// 设置物体旋转
    /// </summary>
    /// <param name="target">目标物体</param>
    /// <param name="rot">角度信息</param>
    /// <returns></returns>
    static int cb_set_rotation(ObjectEnum target, Vec3 rot)
    {
     	//具体的设置旋转的操作 下同
        return 0;
    }

    /// <summary>
    /// 获取物体的位置
    /// </summary>
    /// <param name="target">目标物体</param>
    /// <returns>位置信息</returns>
    static Vec3 cb_get_position(ObjectEnum target)
    {
        //some code
    }

    static Vec3 cb_get_rotation(ObjectEnum target)
    {
        //some code
    }

    /// <summary>
    /// 设置物体的现实和隐藏
    /// </summary>
    /// <param name="target"></param>
    /// <param name="isShow"></param>
    /// <returns></returns>
    static int cb_show_hide(ObjectEnum target, bool isShow)
    {
       //some code
    }

4.在DLL中也要定义对应的回调函数的签名

int _declspec(dllexport)

_stdcall cb_set_rotation(ObjectEnum target, Vec3 rotation); //对应C#的设置物体旋转函数

Vec3 _declspec(dllexport)

_stdcall cb_get_rotation(ObjectEnum target);

Vec3 _declspec(dllexport)

_stdcall cb_get_position(ObjectEnum target);

int _declspec(dllexport)

_stdcall cb_set_position(ObjectEnum target, Vec3 position);


int _declspec(dllexport)

_stdcall cb_show_hide(ObjectEnum target, bool isShow);

void _declspec(dllexport)

_stdcall cb_unity_log(LogLevel level, const char *);

5.定义DLL被C#调用的函数

这里一共定义两个函数,一个是初始化函数,一个数据处理函数

其种初始化函数用于将C#的对应的API通过委托的方式传入DLL,用于回调.数据处理函数则就是用于处理TCP数据的入口

声明宏
#define API __declspec(dllexport)
声明C#回调的函数指针
decltype(cb_unity_log) *unity_log = NULL;
decltype(cb_set_position) *set_position = NULL; //
decltype(cb_set_rotation) *set_rotation = NULL; //
decltype(cb_get_position) *get_position = NULL; //
decltype(cb_get_rotation) *get_rotation = NULL; //
decltype(cb_show_hide) *show_hide = NULL; //
声明函数c
//用于处理C#接收到的TCP发来的数据
extern "C" API void process_data(unsigned char data[], int len) {
    std::ostringstream oss;
    float b = 0;
//    unity_log(LogLevel::Info, oss.str().c_str());
    if (len == 24) {  //2 3 5  x / 1 z / 4 6 y
        for (int i = 0; i < 6; i++) {
            float anInt = *(float *) &data[i * 4];
            Vec3 v;
            v.x = v.y = v.z = 0;
            if (i == 1 || i == 2 || i == 4)
                v.x = anInt;
            else if (i == 0)
                v.z = anInt;
            else if (i == 3 || i == 5)
                v.y = anInt;
            set_rotation((ObjectEnum) i, v);//调用C#的设置旋转API
            oss << "data:" << anInt;
            b = anInt;
        }
        unity_log(LogLevel::Error, oss.str().c_str());//调用C#的日志API
    }
}
//用于设置回调函数
extern "C" API int
init(intptr_t logHandler, intptr_t setPositionHandler, intptr_t setRotationHandler,
     intptr_t getPositionHandler,
     intptr_t getRotationHandler,
     intptr_t showHideHandler
) {
    unity_log = (decltype(cb_unity_log) *) logHandler;
    set_position = (decltype(cb_set_position) *) setPositionHandler;
    set_rotation = (decltype(cb_set_rotation) *) setRotationHandler;
    get_position = (decltype(cb_get_position) *) getPositionHandler;
    get_rotation = (decltype(cb_get_rotation) *) getRotationHandler;
    show_hide = (decltype(cb_show_hide) *) showHideHandler;
    unity_log(LogLevel::Info, "cPlugin initialize successfully");

    return 0;
}

6.C#端定义回调API的函数委托以及导入DLL的函数

//定义与提供的api具有相同函数签名的委托
public delegate void UnityLogDelegate(LogLevel level, string msg);

public delegate int SetPositionDelegate(ObjectEnum t, Vec3 v);

public delegate int SetRotationDelegate(ObjectEnum t, Vec3 v);

public delegate Vec3 GetPositionDelegate(ObjectEnum t);

public delegate Vec3 GetRotationDelegate(ObjectEnum t);

public delegate int ShowHideDelegate(ObjectEnum t, bool isShow);
//对应Dll的init函数
[DllImport("cplugin", CallingConvention = CallingConvention.StdCall, EntryPoint = "init")]
public static extern int InitCPlugin(
    IntPtr logHandler, IntPtr setPositionHandler, IntPtr setRotationHandler,
    IntPtr getPositionHandler,
    IntPtr getRotationHandler,
    IntPtr showHideHandler
);
//对应Dll的process_data函数
[DllImport("cplugin", CallingConvention = CallingConvention.StdCall, EntryPoint = "process_data")]
public static extern int ProcessData(byte[] data, int len);

7.C#通过调用DLL的Init函数将回调函数注入

Unity和C++dll交互

8.测试

将DLL编译好后直接放到Unity的对应目录下即可自动加载

Unity和C++dll交互

Unity和C++dll交互

可以看到DLL被成功加载,并且接收数据后,对场景进行了姿态的控制


注意事项

  • 注意DLL和Unity的操作系统位数要相同
  • 注意DLL的名称要和代码中一致
  • C#中的DLL import中的DLL名称不带.dll后缀

总结

得不偿失,简单的功能搞得这么复杂,但是我的同事不会C#我也很无奈啊


完整代码

c++:
#ifndef CPLUGIN_LIBRARY_H
#define CPLUGIN_LIBRARY_H //防止重复包含

#define API __declspec(dllexport)


#include <cstdint>
#include <iostream>
#include <sstream>


enum ObjectEnum {
    J1, J2, J3, J4, J5, J6, Axis1, Axis2, Axis3, Axis4

};
enum LogLevel {
    Info,
    Warn,
    Error
};

struct Vec3 {
    float x;
    float y;
    float z;
};

int _declspec(dllexport)

_stdcall cb_set_rotation(ObjectEnum target, Vec3 rotation);

Vec3 _declspec(dllexport)

_stdcall cb_get_rotation(ObjectEnum target);

Vec3 _declspec(dllexport)

_stdcall cb_get_position(ObjectEnum target);

int _declspec(dllexport)

_stdcall cb_set_position(ObjectEnum target, Vec3 position);


int _declspec(dllexport)

_stdcall cb_show_hide(ObjectEnum target, bool isShow);

void _declspec(dllexport)

_stdcall cb_unity_log(LogLevel level, const char *);

decltype(cb_unity_log) *unity_log = NULL;
decltype(cb_set_position) *set_position = NULL; //
decltype(cb_set_rotation) *set_rotation = NULL; //
decltype(cb_get_position) *get_position = NULL; //
decltype(cb_get_rotation) *get_rotation = NULL; //
decltype(cb_show_hide) *show_hide = NULL; //



extern "C" API void process_data(unsigned char data[], int len) {

    std::ostringstream oss;
    float b = 0;
//    unity_log(LogLevel::Info, oss.str().c_str());
    if (len == 24) {  //2 3 5  x / 1 z / 4 6 y
        for (int i = 0; i < 6; i++) {
            float anInt = *(float *) &data[i * 4];
            Vec3 v;
            v.x = v.y = v.z = 0;
            if (i == 1 || i == 2 || i == 4)
                v.x = anInt;
            else if (i == 0)
                v.z = anInt;
            else if (i == 3 || i == 5)
                v.y = anInt;
            set_rotation((ObjectEnum) i, v);
            oss << "data:" << anInt;
            b = anInt;
        }
        unity_log(LogLevel::Error, oss.str().c_str());
//        if (b > 50) {
//            show_hide(ObjectEnum::J1, true);
//        } else {
//            show_hide(ObjectEnum::J1, false);

//        }
    }
}

extern "C" API int
init(intptr_t logHandler, intptr_t setPositionHandler, intptr_t setRotationHandler,
     intptr_t getPositionHandler,
     intptr_t getRotationHandler,
     intptr_t showHideHandler
) {
    unity_log = (decltype(cb_unity_log) *) logHandler;
    set_position = (decltype(cb_set_position) *) setPositionHandler;
    set_rotation = (decltype(cb_set_rotation) *) setRotationHandler;
    get_position = (decltype(cb_get_position) *) getPositionHandler;
    get_rotation = (decltype(cb_get_rotation) *) getRotationHandler;
    show_hide = (decltype(cb_show_hide) *) showHideHandler;
    unity_log(LogLevel::Info, "cPlugin initialize successfully");

    return 0;
}


#endif //CPLUGIN_LIBRARY_H
C#:
using System;
using System.Runtime.InteropServices;
using System.Text;
using UnityEngine;

public class CPluginApi
{
    public delegate void UnityLogDelegate(LogLevel level, string msg);

    public delegate int SetPositionDelegate(ObjectEnum t, Vec3 v);

    public delegate int SetRotationDelegate(ObjectEnum t, Vec3 v);

    public delegate Vec3 GetPositionDelegate(ObjectEnum t);

    public delegate Vec3 GetRotationDelegate(ObjectEnum t);

    public delegate int ShowHideDelegate(ObjectEnum t, bool isShow);

    [DllImport("cplugin", CallingConvention = CallingConvention.StdCall, EntryPoint = "init")]
    public static extern int InitCPlugin(
        IntPtr logHandler, IntPtr setPositionHandler, IntPtr setRotationHandler,
        IntPtr getPositionHandler,
        IntPtr getRotationHandler,
        IntPtr showHideHandler
    );

    [DllImport("cplugin", CallingConvention = CallingConvention.StdCall, EntryPoint = "process_data")]
    public static extern int ProcessData(byte[] data, int len);


    
    public static void Init()
    {
        int initCPlugin = InitCPlugin(
            Marshal.GetFunctionPointerForDelegate((Delegate) (UnityLogDelegate) cb_unity_log),
            Marshal.GetFunctionPointerForDelegate((Delegate) (SetPositionDelegate) cb_set_position),
            Marshal.GetFunctionPointerForDelegate((Delegate) (SetRotationDelegate) cb_set_rotation),
            Marshal.GetFunctionPointerForDelegate((Delegate) (GetPositionDelegate) cb_get_position),
            Marshal.GetFunctionPointerForDelegate((Delegate) (GetRotationDelegate) cb_get_rotation),
            Marshal.GetFunctionPointerForDelegate((Delegate) (ShowHideDelegate) cb_show_hide));
    }

    /// <summary>
    /// 输出日志
    /// </summary>
    /// <param name="level">等级</param>
    /// <param name="msg">消息</param>
    static void cb_unity_log(LogLevel level, [MarshalAs(UnmanagedType.LPStr)] string msg)
    {
        if (level == LogLevel.Info)
        {
            Debug.Log(msg);
        }
        else if (level == LogLevel.Warn)
        {
            Debug.LogWarning(msg);
        }
        else
        {
            Debug.LogError(msg);
        }
    }

    /// <summary>
    /// 设置物体位置
    /// </summary>
    /// <param name="target">目标物体</param>
    /// <param name="pos">位置</param>
    /// <returns></returns>
    static int cb_set_position(ObjectEnum target, Vec3 pos)
    {
        RobotController.Instance.SetPosition(target, MyUtils.Vec3ToUnityV3(pos));
        return 0;
    }

    /// <summary>
    /// 设置物体旋转
    /// </summary>
    /// <param name="target">目标物体</param>
    /// <param name="rot">角度信息</param>
    /// <returns></returns>
    static int cb_set_rotation(ObjectEnum target, Vec3 rot)
    {
        RobotController.Instance.SetRotation(target, MyUtils.Vec3ToUnityV3(rot));
        return 0;
    }

    /// <summary>
    /// 获取物体的位置
    /// </summary>
    /// <param name="target">目标物体</param>
    /// <returns>位置信息</returns>
    static Vec3 cb_get_position(ObjectEnum target)
    {
        return MyUtils.UnityV3ToVec3(RobotController.Instance.GetPosition(target));
    }

    static Vec3 cb_get_rotation(ObjectEnum target)
    {
        return MyUtils.UnityV3ToVec3(RobotController.Instance.GetRotation(target));
    }

    /// <summary>
    /// 设置物体的现实和隐藏
    /// </summary>
    /// <param name="target"></param>
    /// <param name="isShow"></param>
    /// <returns></returns>
    static int cb_show_hide(ObjectEnum target, bool isShow)
    {
        RobotController.Instance.ShowAndHide(target, isShow);
        return 0;
    }
}
上一篇:eigen之三维旋转运动表达


下一篇:将 Vue 组件库发布到 npm