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

这篇具有很好参考价值的文章主要介绍了Unity开发进行C、C++源码交互,支持跨平台。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

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


需求分析

先上个图

unity c++,Unity,unity,c语言,c++,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,或者只有一个函数参数的情况下填那个参数的类型。

参考:
unity c++,Unity,unity,c语言,c++,c#

  • 定义了给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交互指针函数指针结构体交互
unity c++,Unity,unity,c语言,c++,c#

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

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

在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++之间类型的对应文章来源地址https://www.toymoban.com/news/detail-800993.html

到了这里,关于Unity开发进行C、C++源码交互,支持跨平台的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包