本文介绍Unity和iOS和Windows通信
1. Unity与C++交互
Windows平台的SDK是C++编写的DLL库,所以我们需要Unity和C++进行通信
1.1 基本概念
- 托管(Managed)和非托管(Unmanaged):.Net的运行环境是CLR(Common Language Runtime),运行在CLR上的代码成为托管代码(Managed Code),CLR提供了自动的垃圾回收机制(GC)。而C++是编译后直接由操作系统执行的代码,不运行在CLR上,所以C++属于非托管代码(Unmanaged Code)。
- P/Invoke:P/Invoke(Platform Invoke,平台调用)使得我们可以在托管代码中调用非托管函数,Unity与C++的交互都是通过P/Invoke实现。([DllImport])
1.2 创建Wrapper层C++ DLL
在VS中新建DLL项目,把引用的IM和RTC库拖入项目,在项目属性中设置好*.dll.lib链接地址以及头文件链接地址(如下图)
新建一个类RTCInterface.h和RTCInterface.cpp
//RTCInterface.h
extern "C"
{
__declspec(dllexport) RCRTCEngine *rcrtc_create_engine(void* im_client);
//__declspec(dllexport)必须添加
}
//RTCInterface.cpp
#include "RTCInterface.h"
extern "C"
{
RCRTCEngine *rcrtc_create_engine(void* im_client)
{
//code...
}
}
1.3 C#调用
右键项目生成DLL后,将生成的DLL以及依赖的DLL拷贝到Unity项目对应架构的目录中,设置好对应的架构
在Unity中新建NativeWin.cs脚本
//NativeWin.cs
namespace cn_rongcloud_rtc_unity
{
internal class NativeWin
{
[DllImport("RTCWinWapper", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
internal static extern IntPtr rcrtc_create_engine(IntPtr client);
//DllImport后面的名称需要和引入的DLL名称一致
}
}
这样在C#中调用NativeWin的create方法就可以调用到C++的代码了
1.4 其他情况
- 参数中有类
需要在C#和Wrapper中声明对应的结构体,C#端将C#类转为结构体,Wrapper中将结构体转为C++类
//声明结构体
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
internal struct rtc_engine_setup
{
[MarshalAs(UnmanagedType.U1)]
public bool reconnectable;
public int statsReportInterval;
[MarshalAs(UnmanagedType.U1)]
public bool enableSRTP;
public IntPtr audioSetup;
public IntPtr videoSetup;
public string mediaUrl;
public string logPath;
}
//声明方法
[DllImport("RTCWinWapper", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
internal static extern IntPtr rcrtc_create_engine_with_setup(IntPtr client, ref rtc_engine_setup setup);
//调用方法
internal RCRTCEngineWin(RCRTCEngineSetup setup)
{
//转换成结构体
rtc_engine_setup cobject;
cobject.reconnectable = setup.IsReconnectable();
if (setup.GetAudioSetup() != null)
{
rtc_audio_setup audio;
audio.audioCodecType = (int)setup.GetAudioSetup().GetAudioCodecType();
IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(audio));
Marshal.StructureToPtr(audio, ptr, true);
cobject.audioSetup = ptr;
}
else
{
cobject.audioSetup = IntPtr.Zero;
}
//other paramers...
//调用方法
rtc_engine = NativeWin.rcrtc_create_engine_with_setup(im_client, ref cobject);
if (cobject.audioSetup != IntPtr.Zero)
{
Marshal.FreeHGlobal(cobject.audioSetup);
}
}
注意:
结构体中有Bool需要指定非托管类型为UnmanagedType.U1,可以告知运行时将字段作为 1 字节本机 bool 类型进行封送
结构体中有其他的类需要使用Marshal.AllocHGlobal在非托管内存中分配内存,使用结束后需手动调用Marshal.FreeHGlobal释放
//RTCInterface.cpp
#include "RTCInterface.h"
extern "C"
{
//声明结构体
typedef struct rtc_engine_setup
{
bool reconnectable;
int statsReportInterval;
bool enableSRTP;
rtc_audio_setup* audioSetup;
rtc_video_setup* videoSetup;
const char* mediaUrl;
const char* logPath;
} rtc_engine_setup;
RCRTCEngine *rcrtc_create_engine_with_setup(void *im_client, rtc_engine_setup *csetup)
{
RCRTCEngineSetup *setup = RCRTCEngineSetup::create();
setup->setEnableSRTP(csetup->enableSRTP);
//other params...
RCRTCEngine *engine = RCRTCEngine::create(im_client, setup);
return engine;
}
}
- 回调Unity方法
//C#中声明回调
public delegate void OnRoomJoinedDelegate(int code, String errMsg);
//实现回调方法
[MonoPInvokeCallback(typeof(OnRoomJoinedDelegate))]
private static void on_rtc_room_joined(int code, string message)
{
//code...
}
//声明调用方法
[DllImport("RTCWinWapper", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
internal static extern void rcrtc_set_engine_listeners(OnRoomJoinedDelegate on_rtc_room_joined);
//设置回调
NativeWin.rcrtc_set_engine_listeners(on_rtc_room_joined);
extern "C"
{
//wrapper中声明回调
typedef void (*on_rtc_room_joined)(int code, const char* message);
//声明调用方法
void rcrtc_set_engine_listeners(on_rtc_room_joined _on_rtc_room_joined)
{
//直接调用
_on_rtc_room_joined(0, "error msg");
}
}
2. Unity和iOS交互
Unity和iOS的交互和跟C++的交互过程基本一致,也是通过DllImport调用非托管函数
2.1 创建Wrapper层framework
新建Xcode framework项目,将使用的库拖入项目
新建一个交互类RongUnityRTC.mm
//RongUnityRTC.mm
extern "C" {
//声明结构体
typedef struct rtc_engine_setup {
bool reconnectable;
int statsReportInterval;
bool enableSRTP;
rtc_audio_setup *audioSetup;
rtc_video_setup *videoSetup;
const char *mediaUrl;
} rtc_engine_setup;
//声明创建方法
CFTypeRef rtc_create_engine_with_setup(rtc_engine_setup *csetup) {
RCRTCIWEngineSetup *setup = [[RCRTCIWEngineSetup alloc] init];
setup.enableSRTP = csetup->enableSRTP;
if (csetup->audioSetup != NULL) {
RCRTCIWAudioSetup *audioSetup = [[RCRTCIWAudioSetup alloc] init];
audioSetup.type = (RCRTCIWAudioCodecType)csetup->audioSetup->audioCodecType;
audioSetup.mixOtherAppsAudio = csetup->audioSetup->mixOtherAppsAudio;
setup.audioSetup = audioSetup;
}
//other params...
engine = [RCRTCIWEngine create:setup];
return CFBridgingRetain(engine);
}
//声明回调
typedef void (*on_rtc_room_joined)(int code, const char* message);
//声明调用方法
void rcrtc_set_engine_listeners(on_rtc_room_joined _on_rtc_room_joined)
{
//直接调用
_on_rtc_room_joined(0, "error msg");
}
}
2.2 C#调用
Build wrapper项目,拷贝生成的framework以及依赖的frawork到Unity中,并设置好对应的架构
在Unity中新建NativeIOS.cs脚本
//NativeIOS.cs
namespace cn_rongcloud_rtc_unity
{
//声明结构体
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
internal struct rtc_engine_setup
{
[MarshalAs(UnmanagedType.U1)]
public bool reconnectable;
public int statsReportInterval;
[MarshalAs(UnmanagedType.U1)]
public bool enableSRTP;
public IntPtr audioSetup;
public IntPtr videoSetup;
public string mediaUrl;
public string logPath;
}
internal class NativeIOS
{
[DllImport("__Internal", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
internal static extern IntPtr rtc_create_engine_with_setup(ref rtc_engine_setup setup);
//DllImport后面的名称必须为__Internal
[DllImport("__Internal", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
internal static extern void rcrtc_set_engine_listeners(OnRoomJoinedDelegate on_rtc_room_joined)
}
}
//C#中声明回调
public delegate void OnRoomJoinedDelegate(int code, String errMsg);
//实现回调方法
[MonoPInvokeCallback(typeof(OnRoomJoinedDelegate))]
private static void on_rtc_room_joined(int code, string message)
{
//code...
}
调用跟C++一致
//调用方法
internal RCRTCEngineIOS(RCRTCEngineSetup setup)
{
//设置回调
NativeIOS.rcrtc_set_engine_listeners(on_rtc_room_joined);
//转换成结构体
rtc_engine_setup cobject;
cobject.reconnectable = setup.IsReconnectable();
if (setup.GetAudioSetup() != null)
{
rtc_audio_setup audio;
audio.audioCodecType = (int)setup.GetAudioSetup().GetAudioCodecType();
IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(audio));
Marshal.StructureToPtr(audio, ptr, true);
cobject.audioSetup = ptr;
}
else
{
cobject.audioSetup = IntPtr.Zero;
}
//other paramers...
//调用方法
rtc_engine = NativeIOS.rcrtc_create_engine_with_setup(im_client, ref cobject);
if (cobject.audioSetup != IntPtr.Zero)
{
Marshal.FreeHGlobal(cobject.audioSetup);
}
}
2.3 其他情况
- 对象有继承关系
c#为ios_class_warpper
对象 , type
保存类名, obj
存真正的对象指针
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
internal struct ios_class_warpper
{
public IntPtr obj;
public string type;
}
internal static ios_class_warpper toMessageWapper(RCIMMessage message)
{
ios_class_warpper message_wapper;
if (message is RCIMUnknownMessage)
{
im_unknown_message cmessage = toUnknownMessage((RCIMUnknownMessage)message);
IntPtr message_ptr = Marshal.AllocHGlobal(Marshal.SizeOf(cmessage));
Marshal.StructureToPtr(cmessage, message_ptr, false);
message_wapper.obj = message_ptr;
message_wapper.type = "RCIMIWUnknownMessage";
}
else if (message is RCIMCustomMessage)
{
//...
}
}
oc转换则是根据type初始化对应的对象
+ (RCIMIWMessage *)fromMessageWapper:(struct ios_class_warpper *)cmessage {
if (cmessage == NULL || cmessage->obj == NULL || cmessage->type == NULL) {
return nil;
}
RCIMIWMessage *message_wapper = nil;
NSString *type = [NSString stringWithUTF8String:cmessage->type];
if ([type isEqualToString:@"RCIMIWUnknownMessage"]) {
message_wapper =
[self fromUnknownMessage:(im_unknown_message *)cmessage->obj];
}else if //...
}
转换回来的过程则是相反的
- 数组
c#转为 ios_c_list对象,list存数组指针,count存数组数量
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
internal struct ios_c_list
{
public IntPtr list;
public long count;
}
//转到ios
internal static IntPtr GetStructMapPointer<StructType>(ref List<StructType> clist)
{
int size = Marshal.SizeOf(typeof(StructType));
IntPtr list_ptr = Marshal.AllocHGlobal(clist.Count * size);
for (int i = 0; i < clist.Count; i++)
{
Marshal.StructureToPtr(clist[i], list_ptr+size*i, false);
}
ios_c_list map;
map.list = list_ptr;
map.count = clist.Count;
IntPtr map_ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ios_c_list)));
Marshal.StructureToPtr(map, map_ptr, false);
return map_ptr;
}
//从ios转回
internal static List<StructType> GetObjectListByPtr<StructType>(IntPtr ptr)
{
if (ptr == IntPtr.Zero)
{
return new List<StructType>();
}
ios_c_list clist = Marshal.PtrToStructure<ios_c_list>(ptr);
StructType[] cobjects = new StructType[clist.count];
for (uint i = 0; i < clist.count; ++i)
{
IntPtr item_ptr = new IntPtr(ptr.ToInt64() + Marshal.SizeOf(typeof(StructType)) * i);
if ((item_ptr != null))
{
cobjects.Add((StructType)Marshal.PtrToStructure(item_ptr, typeof(StructType)));
}
}
return new List<StructType>(cobjects);
}
oc根据count遍历
typedef struct ios_c_list {
void *list;
unsigned long count;
} ios_c_list;
//从c#
ios_class_warpper *messages_iter = c_list->list;
for (int i = 0; i < c_list->count; i++) {
// messages_iter ...
messages_iter++;
}
//转到c#
//NSArray<RCIMIWMessage *> *messages = ...
ios_c_list *clist = (ios_c_list *)malloc(sizeof(ios_c_list));
memset(clist, 0, sizeof(ios_c_list));
ios_class_warpper *cmessages = (ios_class_warpper *)malloc(
sizeof(ios_class_warpper) * messages.count);
memset(cmessages, 0, sizeof(ios_class_warpper) * messages.count);
ios_class_warpper *messages_iter = cmessages;
for (int i = 0; i < messages.count; i++) {
[RongUnityConvert makeMessageWapper:messages[i] to:messages_iter];
messages_iter++;
}
clist->list = cmessages;
clist->count = messages.count;
return clist;
- 字符串数组
和数组不同的点是字符串需要转换为指针,则结构就是指针数组
//转到ios
internal static IntPtr GetStringListPointer(List<string> clist)
{
IntPtr[] ptrArray = new IntPtr[clist.Count];
for (int i = 0; i < clist.Count; i++)
{
ptrArray[i] = Marshal.StringToHGlobalAnsi(clist[i]);
}
IntPtr list_ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(IntPtr)) * clist.Count);
for (int j = 0; j < clist.Count; j++)
{
IntPtr item_ptr = new IntPtr(list_ptr.ToInt64() + Marshal.SizeOf(typeof(IntPtr)) * j);
Marshal.StructureToPtr(ptrArray[j], item_ptr, false);
}
ios_c_list map;
map.list = list_ptr;
map.count = clist.Count;
IntPtr result_ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ios_c_list)));
Marshal.StructureToPtr(map, result_ptr, false);
return result_ptr;
}
//从ios转回
internal static List<string> GetStringListByPtr(IntPtr ptr)
{
if (ptr == IntPtr.Zero)
{
return new List<string>();
}
ios_c_list clist = Marshal.PtrToStructure<ios_c_list>(ptr);
List<string> cobjects = new List<string>();
for (int i = 0; i < clist.count; i++)
{
IntPtr item_ptr = new IntPtr(clist.list.ToInt64() + Marshal.SizeOf(typeof(IntPtr)) * i);
IntPtr str_ptr = Marshal.PtrToStructure<IntPtr>(item_ptr);
cobjects.Add(PtrToString(str_ptr));
}
return cobjects;
}
//需要注意释放
internal static void FreeStringListByPtr(IntPtr ptr)
{
if (ptr == IntPtr.Zero)
{
return;
}
ios_c_list clist = Marshal.PtrToStructure<ios_c_list>(ptr);
if (clist.list != IntPtr.Zero)
{
for (int i = 0; i < clist.count; i++)
{
IntPtr item_ptr = new IntPtr(clist.list.ToInt64() + Marshal.SizeOf(typeof(IntPtr)) * i);
IntPtr str_ptr = Marshal.PtrToStructure<IntPtr>(item_ptr);
Marshal.FreeHGlobal(str_ptr);
}
Marshal.FreeHGlobal(clist.list);
}
}
iOS接收采用char*
数组接收
//从c#
ios_c_list *clist = (ios_c_list *)cinfo->userIdList;
NSMutableArray<NSString *> *list =
[[NSMutableArray alloc] initWithCapacity:clist->count];
char **iter = (char **)clist->list;
for (int i = 0; i < clist->count; i++) {
if (*iter != NULL) {
[list addObject:[NSString stringWithUTF8String:*iter]];
}
iter++;
}
//从ios转回
//NSArray<NSString *> *keys = ...
ios_c_list *clist = (ios_c_list *)malloc(sizeof(ios_c_list));
memset(clist, 0, sizeof(ios_c_list));
char **cstrings = (char **)malloc(keys.count * sizeof(char *));
char **keys_iter = cstrings;
for (NSUInteger i = 0; i < keys.count; i++) {
NSString *string = keys[i];
char *cstring = (char *)malloc(
[string lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1);
strncpy(cstring, [string UTF8String],
[string lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1);
*keys_iter = cstring;
keys_iter++;
}
clist->list = cstrings;
clist->count = keys.count;
return clist;
- 字典
转换为以ios_c_map_item
为元素的数组
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
internal struct ios_c_map_item
{
public string key;
public string value;
}
2.4 内存管理
调用完成后
c# 非托管的需要手动释放
C malloc的内存需要free