Unity和C++dll交互
2021/6/28 22:50:36
本文主要是介绍Unity和C++dll交互,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
前言
我的一个名为嘤嘤嘤的同事,做机器人仿真,让我给他写一个Unity的机械臂仿真程序,整个流程就是我的unity程序开一个TCP的服务器,通过接收他发来的指令,并且解析成对应的动作来操控Unity的机械臂.本身项目很简单,但是问题是我正忙于考研,我作出第一个版本后,他需要改动需求的话我还要改,他不会c#和Unity只会C语言,这种事情当然难不住我.于是就有了下面的奇葩操作.
我在Unity内部只实现操作机械臂的关键API接口,然后接收到通信数据后,将数据的处理以及执行API对应的动作全都放到一个C++编写的DLL里.Unity启动后去加载这个DLL,并且与之通信,这样的话只要用C/C++编写对应的Dll,就能被Unity程序调用并且与之交互,在我提供的API基础上他想怎么改就怎么改,非常的"方便" .就不用浪费我的时间了
需求分析
先上个图
最终要控制的就是这个玩意儿的运动
内容包括控制每个关节的旋转,以及设置一些物体的坐标和显示隐藏等,这对于Unity来说不要再简单,奈何他不会呢
所以我最终整理的流程就是
- Unity端启动TCP服务器
- 他通过TCP向Unity端发送数据
- Unity端接收到数据后不做任何处理,将数据传入到DLL的处理函数
- DLL内进行数据的解析,并且根据解析的内容调用我提供的API进行回调,控制Unity端的行为
具体实现
1.创建C/C++的动态链接库工程
这里使用的是CLion,这里命名为Cplugin
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函数将回调函数注入
8.测试
将DLL编译好后直接放到Unity的对应目录下即可自动加载
可以看到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; } }
这篇关于Unity和C++dll交互的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23增量更新怎么做?-icode9专业技术文章分享
- 2024-11-23压缩包加密方案有哪些?-icode9专业技术文章分享
- 2024-11-23用shell怎么写一个开机时自动同步远程仓库的代码?-icode9专业技术文章分享
- 2024-11-23webman可以同步自己的仓库吗?-icode9专业技术文章分享
- 2024-11-23在 Webman 中怎么判断是否有某命令进程正在运行?-icode9专业技术文章分享
- 2024-11-23如何重置new Swiper?-icode9专业技术文章分享
- 2024-11-23oss直传有什么好处?-icode9专业技术文章分享
- 2024-11-23如何将oss直传封装成一个组件在其他页面调用时都可以使用?-icode9专业技术文章分享
- 2024-11-23怎么使用laravel 11在代码里获取路由列表?-icode9专业技术文章分享
- 2024-11-22怎么实现ansible playbook 备份代码中命名包含时间戳功能?-icode9专业技术文章分享