前言
公司新項(xiàng)目,要和做C++算法的人一起合作開(kāi)發(fā),起初項(xiàng)目定于Windows平臺(tái),就看了一些C++和DLL交互的一些資料,做了一套生成DLL交互的接口,后來(lái)項(xiàng)目寫(xiě)方案由于設(shè)備又定到安卓平臺(tái),嘗試過(guò)打包之后,DLL打包不到安卓平臺(tái),試過(guò)將dll改名打AB包然后用Assembly.Load的方式,但這種方式只適用于C#的DLL,后來(lái)經(jīng)過(guò)多方調(diào)研,在某q群拋出這個(gè)問(wèn)題才知道有和C++源碼交互的方式,但自己幾經(jīng)嘗試都調(diào)用失敗,差點(diǎn)放棄想在安卓平臺(tái)編譯成so來(lái)交互,元旦過(guò)后,某天夢(mèng)中想到這個(gè)問(wèn)題,應(yīng)該去q群繼續(xù)拋出這個(gè)問(wèn)題請(qǐng)教別人是怎么做的,最終經(jīng)過(guò)兩天的不懈努力,嘗試編寫(xiě)了C接口和解決打包過(guò)程中的各種問(wèn)題,最終總結(jié)了這幾天來(lái)的成果。
需求分析
先上個(gè)圖
這是一個(gè)基于Unity渲染的數(shù)智人項(xiàng)目,前端使用Unity來(lái)顯示和交互,一些輸入和數(shù)字人的表現(xiàn)都是C#層開(kāi)發(fā),比如動(dòng)畫(huà)系統(tǒng)、麥克風(fēng)輸入、語(yǔ)音數(shù)據(jù)同步嘴唇等等,后端算法主要采用C++開(kāi)發(fā),比如文本和語(yǔ)音互轉(zhuǎn)、AI、算法模型訓(xùn)練等,這就涉及到了C#和C++之間的交互,也就是相互調(diào)用的問(wèn)題。
所以我最終的流程就是
- Unity用麥克風(fēng)監(jiān)聽(tīng)玩家說(shuō)話,并把說(shuō)話內(nèi)容發(fā)給C++算法
- C++得到文本,進(jìn)行智能對(duì)話系統(tǒng)的訓(xùn)練
- 得到訓(xùn)練的對(duì)話文本后,文本轉(zhuǎn)語(yǔ)音,發(fā)送到Unity
- Unity根據(jù)語(yǔ)音數(shù)據(jù)驅(qū)動(dòng)說(shuō)話,包括嘴唇變化、肢體動(dòng)作的表現(xiàn)
- AI數(shù)智人回答完畢又開(kāi)始循環(huán)到步驟1
具體實(shí)現(xiàn)
1.C#層接口定義
C#層代碼:因?yàn)楹瘮?shù)與生成的 C++ 代碼鏈接在一起,所以沒(méi)有單獨(dú)的 DLL 可進(jìn)行 _P/Invoke 調(diào)用。因此,可使用"__Internal"關(guān)鍵字代替 DLL 名稱,從而使 C++ 鏈接器負(fù)責(zé)解析函數(shù),而不是在運(yùn)行時(shí)加載函數(shù),如下例所示:
- 定義了初始化函數(shù),給C++傳遞C#層的接口,并緩存在C++來(lái)使用C#的回調(diào),比如Unity的日志輸出、Unity內(nèi)接收語(yǔ)音數(shù)據(jù)的方法,從而緩存后使得C++具有調(diào)用C#回調(diào)函數(shù)(委托)的能力。
[DllImport("__Internal")]
static extern int Init(
ULogCallback logCallback
,UReceivesAI_DialogueCallback diaogueCallback
);
-
- 兩個(gè)初始化作為回調(diào)的參數(shù),可以自行拓展,然后給C++緩存起來(lái),其中的ULogCallback 為Unity的日志回調(diào)的委托,diaogueCallback為接收AI對(duì)話的委托,ULogCallback 的原型為:
/// <summary>
/// Unity日志調(diào)用的回調(diào)委托
/// </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對(duì)話語(yǔ)音的回調(diào)委托
/// </summary>
/// <param name="voiceDatas">語(yǔ)音數(shù)據(jù)</param>
public delegate void UReceivesAI_DialogueCallback(byte[] voiceDatas);
-
- 調(diào)用的時(shí)候我們默認(rèn)發(fā)送Unity的日志委托,我們封裝了InitDLL方法,然后回調(diào)參數(shù)可以自行拓展,但是要對(duì)應(yīng)C++接口一起修改。
[MonoPInvokeCallback(typeof(void))]
/// <summary>
/// 輸出日志
/// </summary>
/// <param name="level">等級(jí)</param>
/// <param name="msg">消息</param>
static void UnityLog(LogLevel level, string msg)
{
msg = $"Unity回調(diào)收到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//這個(gè)方法回調(diào)基本不變,不做參數(shù)
,UDialogueCallback
);
}
注意事項(xiàng):
這里要注意個(gè)問(wèn)題,在使用DLL交互時(shí)沒(méi)有問(wèn)題,但是用源碼交互時(shí)會(huì)出現(xiàn)報(bào)錯(cuò):NotSupportedException: To marshal a managed method, please add an attribute named ‘MonoPInvokeCallback’ to the method definition. The method we’re attempting to marshal is…,查了下資料發(fā)現(xiàn)需要在傳遞的委托函數(shù)上加上[MonoPInvokeCallback(typeof(…))],里面的類型我目前填void,或者只有一個(gè)函數(shù)參數(shù)的情況下填那個(gè)參數(shù)的類型。
參考:
- 定義了給C++發(fā)送語(yǔ)音數(shù)據(jù)的接口,以下接口封裝都比較簡(jiǎn)單,不做過(guò)多介紹
[DllImport("__Internal")]
public static extern void ReceivingMicrophoneSpeech(byte[] voiceDatas);
- 定義了給C++發(fā)送文本測(cè)試的接口
[DllImport("__Internal")]
public static extern void TEST_Call(string msg);
2.創(chuàng)建C/C++的動(dòng)態(tài)鏈接庫(kù)工程
由于之前交互C++DLL,創(chuàng)建過(guò)動(dòng)態(tài)鏈接庫(kù)工程,所以接下來(lái)Unity中的C++代碼都從鏈接庫(kù)工程里拷貝過(guò)去(后來(lái)NativeCode我改名為CInterface),關(guān)于如何新建鏈接庫(kù)工程,可以參考文末鏈接:Unity 之 C#與C++/C交互指針函數(shù)指針結(jié)構(gòu)體交互
3.C++層對(duì)應(yīng)C#層定義接口
使用 IL2CPP 腳本后端時(shí),可將 C++ (.cpp) 代碼文件直接添加到 Unity 項(xiàng)目中。這些 C++ 文件將充當(dāng) Plugin Inspector 中的插件。如果將 C++ 文件配置為與 Windows 播放器兼容,則 Unity 會(huì)將這些文件與從托管程序集生成的 C++ 代碼一起編譯。單擊 .cpp 文件,然后在 Inspector 窗口的 Platform settings 部分中選擇平臺(tái)設(shè)置。
在C#層定義了交互接口后,同時(shí)也要定義C/C++端的對(duì)應(yīng)數(shù)據(jù)結(jié)構(gòu),要嚴(yán)格的和C#的定義一一對(duì)應(yīng),可以參考文末鏈接:C#與C++之間類型的對(duì)應(yīng)
CInterface.h
#ifndef __NativeCode_H__
#define __NativeCode_H__
//#ifndef EXPORT_DLL
//#define EXPORT_DLL __declspec(dllexport) //導(dǎo)出dll聲明
//#endif
enum class LogLevel {
Info,
Warn,
Error
};
//定義回調(diào)函數(shù)指針
typedef void(__stdcall* ULogCallback)(LogLevel level ,const char*);//Unity日志輸出函數(shù)
typedef void(__stdcall* UReceivesAI_DialogueCallback)(unsigned char voiceDatas[]);//Unity需要播放的AI對(duì)話函數(shù)
extern "C" {
int Init(ULogCallback logCallback ,UReceivesAI_DialogueCallback diaogueCallback);//初始化注冊(cè)Unity回調(diào)函數(shù)
void ReceivingMicrophoneSpeech(unsigned char voiceDatas[]);
void TEST_Call(char* char_Str);//測(cè)試方法
}
#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");
}
}
注意事項(xiàng):
打包的時(shí)候,編譯cpp時(shí)會(huì)出現(xiàn)以下報(bào)錯(cuò):
- CInterface.h出現(xiàn)不支持__declspec,所以DLL交互時(shí)留下__declspec(dllexport) 關(guān)鍵字都去掉
- CInterface.cpp中的DLL交互留下頭文件#include "pch.h"找不到,直接注釋掉即可
- 所有的C++接口都需要extern “C”
- 集成cpp源碼到Unity交互需要在ProjectSetting-Player-OtherSetting-Configuration-ScriptingBackend選擇IL2CPP
總結(jié)
使用CPP源碼可以在Unity引擎打包后編譯成對(duì)應(yīng)平臺(tái)的代碼,前提是腳本后端選擇IL2CPP,但是在編輯器模式這套方法是用不了的,所以我打算后期在編輯器模式下使用dll交互,加個(gè)平臺(tái)判斷,等后期流程上成熟后將繼續(xù)總結(jié)下。
參考資料
Windows 播放器:適用于 IL2CPP 的 C++ 源代碼插件
Unity 之 C#與C++/C交互指針函數(shù)指針結(jié)構(gòu)體交互文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-800993.html
C#與C++之間類型的對(duì)應(yīng)文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-800993.html
到了這里,關(guān)于Unity開(kāi)發(fā)進(jìn)行C、C++源碼交互,支持跨平臺(tái)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!