Unity开发进行C、C++源码交互,支持跨平台

前言

公司新项目,要和做C++算法的人一起合作开发,起初项目定于Windows平台,就看了一些C++和DLL交互的一些资料,做了一套生成DLL交互的接口,后来项目写方案由于设备又定到安卓平台,尝试过打包之后,DLL打包不到安卓平台,试过将dll改名打AB包然后用Assembly.Load的方式,但这种方式只适用于C#的DLL,后来经过多方调研,在某q群抛出这个问题才知道有和C++源码交互的方式,但自己几经尝试都调用失败,差点放弃想在安卓平台编译成so来交互,元旦过后,某天梦中想到这个问题,应该去q群继续抛出这个问题请教别人是怎么做的,最终经过两天的不懈努力,尝试编写了C接口和解决打包过程中的各种问题,最终总结了这几天来的成果。


需求分析

先上个图

在这里插入图片描述
这是一个基于Unity渲染的数智人项目,前端使用Unity来显示和交互,一些输入和数字人的表现都是C#层开发,比如动画系统、麦克风输入、语音数据同步嘴唇等等,后端算法主要采用C++开发,比如文本和语音互转、AI、算法模型训练等,这就涉及到了C#和C++之间的交互,也就是相互调用的问题。

所以我最终的流程就是

  1. Unity用麦克风监听玩家说话,并把说话内容发给C++算法
  2. C++得到文本,进行智能对话系统的训练
  3. 得到训练的对话文本后,文本转语音,发送到Unity
  4. Unity根据语音数据驱动说话,包括嘴唇变化、肢体动作的表现
  5. AI数智人回答完毕又开始循环到步骤1

具体实现

1.C#层接口定义

C#层代码:因为函数与生成的 C++ 代码链接在一起,所以没有单独的 DLL 可进行 _P/Invoke 调用。因此,可使用"__Internal"关键字代替 DLL 名称,从而使 C++ 链接器负责解析函数,而不是在运行时加载函数,如下例所示:

  • 定义了初始化函数,给C++传递C#层的接口,并缓存在C++来使用C#的回调,比如Unity的日志输出、Unity内接收语音数据的方法,从而缓存后使得C++具有调用C#回调函数(委托)的能力。
[DllImport("__Internal")]
    static extern int Init(
        ULogCallback logCallback
        ,UReceivesAI_DialogueCallback diaogueCallback
   );
    • 两个初始化作为回调的参数,可以自行拓展,然后给C++缓存起来,其中的ULogCallback 为Unity的日志回调的委托,diaogueCallback为接收AI对话的委托,ULogCallback 的原型为:
 /// <summary>
    /// Unity日志调用的回调委托
    /// </summary>
    /// <param name="level"></param>
    /// <param name="msg"></param>
    public delegate void ULogCallback(LogLevel level, string msg);

    public enum LogLevel
    {
        Info,
        Warn,
        Error
    };
    • 而diaogueCallback的原型为:
 /// <summary>
    /// Unity接收的AI对话语音的回调委托
    /// </summary>
    /// <param name="voiceDatas">语音数据</param>
    public delegate void UReceivesAI_DialogueCallback(byte[] voiceDatas);
    • 调用的时候我们默认发送Unity的日志委托,我们封装了InitDLL方法,然后回调参数可以自行拓展,但是要对应C++接口一起修改。
[MonoPInvokeCallback(typeof(void))]
    /// <summary>
    /// 输出日志
    /// </summary>
    /// <param name="level">等级</param>
    /// <param name="msg">消息</param>
    static void UnityLog(LogLevel level, string msg)
    {
        msg = $"Unity回调收到C++输出日志:{msg}";
        if (level == LogLevel.Info)
        {
            Debug.Log(msg);
        }
        else if (level == LogLevel.Warn)
        {
            Debug.LogWarning(msg);
        }
        else
        {
            Debug.LogError(msg);
        }
    }

public static void InitDLL(UReceivesAI_DialogueCallback UDialogueCallback)
    {
        /*int init = Init(
            Marshal.GetFunctionPointerForDelegate((Delegate)(ULogCallback)UnityLog),
            Marshal.GetFunctionPointerForDelegate((Delegate)UDialogueCallback)
            );*/

        int init = Init(
            UnityLog//这个方法回调基本不变,不做参数
            ,UDialogueCallback
            );
    }

注意事项:

这里要注意个问题,在使用DLL交互时没有问题,但是用源码交互时会出现报错:NotSupportedException: To marshal a managed method, please add an attribute named ‘MonoPInvokeCallback’ to the method definition. The method we’re attempting to marshal is…,查了下资料发现需要在传递的委托函数上加上[MonoPInvokeCallback(typeof(…))],里面的类型我目前填void,或者只有一个函数参数的情况下填那个参数的类型。

参考:
在这里插入图片描述

  • 定义了给C++发送语音数据的接口,以下接口封装都比较简单,不做过多介绍
 [DllImport("__Internal")]
    public static extern void ReceivingMicrophoneSpeech(byte[] voiceDatas);
  • 定义了给C++发送文本测试的接口
[DllImport("__Internal")]
    public static extern void TEST_Call(string msg);

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

由于之前交互C++DLL,创建过动态链接库工程,所以接下来Unity中的C++代码都从链接库工程里拷贝过去(后来NativeCode我改名为CInterface),关于如何新建链接库工程,可以参考文末链接:Unity 之 C#与C++/C交互指针函数指针结构体交互
在这里插入图片描述

3.C++层对应C#层定义接口

使用 IL2CPP 脚本后端时,可将 C++ (.cpp) 代码文件直接添加到 Unity 项目中。这些 C++ 文件将充当 Plugin Inspector 中的插件。如果将 C++ 文件配置为与 Windows 播放器兼容,则 Unity 会将这些文件与从托管程序集生成的 C++ 代码一起编译。单击 .cpp 文件,然后在 Inspector 窗口的 Platform settings 部分中选择平台设置。
在这里插入图片描述

在C#层定义了交互接口后,同时也要定义C/C++端的对应数据结构,要严格的和C#的定义一一对应,可以参考文末链接:C#与C++之间类型的对应
CInterface.h

#ifndef __NativeCode_H__
#define __NativeCode_H__
//#ifndef EXPORT_DLL
//#define EXPORT_DLL  __declspec(dllexport) //导出dll声明
//#endif

enum class LogLevel {
	Info,
	Warn,
	Error
};


//定义回调函数指针
typedef void(__stdcall* ULogCallback)(LogLevel level ,const char*);//Unity日志输出函数
typedef void(__stdcall* UReceivesAI_DialogueCallback)(unsigned char voiceDatas[]);//Unity需要播放的AI对话函数


extern "C" {

	int Init(ULogCallback logCallback ,UReceivesAI_DialogueCallback diaogueCallback);//初始化注册Unity回调函数

	void ReceivingMicrophoneSpeech(unsigned char voiceDatas[]);
	
	void TEST_Call(char* char_Str);//测试方法
}

#endif//__NativeCode_H__

CInterface.cpp

//#include "pch.h"
#include "CInterface.h"

ULogCallback ULog;
UReceivesAI_DialogueCallback UReceivesAI_Dialogue;

extern "C" {
	int Init(ULogCallback logCallback ,UReceivesAI_DialogueCallback diaogueCallback)
	{
		ULog = logCallback;
		UReceivesAI_Dialogue = diaogueCallback;

		//TODO:logCallback支持中文字符
		ULog(LogLevel::Info, "12345");
		ULog(LogLevel::Info, "abc");
		

		return 0;
	}

	void TEST_Call(char * char_Str) {
		ULog(LogLevel::Info, char_Str);
	}

	void ReceivingMicrophoneSpeech(unsigned char voiceDatas[]) {
		ULog(LogLevel::Info, "Call ReceivingMicrophoneSpeech");
	}
}

注意事项:

打包的时候,编译cpp时会出现以下报错:

  • CInterface.h出现不支持__declspec,所以DLL交互时留下__declspec(dllexport) 关键字都去掉
  • CInterface.cpp中的DLL交互留下头文件#include "pch.h"找不到,直接注释掉即可
  • 所有的C++接口都需要extern “C”
  • 集成cpp源码到Unity交互需要在ProjectSetting-Player-OtherSetting-Configuration-ScriptingBackend选择IL2CPP

总结

使用CPP源码可以在Unity引擎打包后编译成对应平台的代码,前提是脚本后端选择IL2CPP,但是在编辑器模式这套方法是用不了的,所以我打算后期在编辑器模式下使用dll交互,加个平台判断,等后期流程上成熟后将继续总结下。


参考资料

Windows 播放器:适用于 IL2CPP 的 C++ 源代码插件

Unity 之 C#与C++/C交互指针函数指针结构体交互

C#与C++之间类型的对应

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值