您的位置:首页 > 互联网

那些年我们趟过的坑,环信IM Unity SDK 2.0正式发布

发布时间:2022-07-17 05:30:35  来源:互联网     背景:

  引言

  Untiy作为游戏引擎和内容开发平台,吸引了众多游戏开发者,基于其开发的游戏更是不胜其数。具体请参见1。

  环信作为领先的即时通讯云服务商,在游戏行业也进行了持续的探索和研发投入。在产品发布的早期(2015年)就推出了Unity SDK,帮助游戏开发者快速实现游戏场景下诸如世界频道,游戏公会、组队群聊,1对1私聊等功能,安全稳定的服务也为游戏玩家带来了极佳的实时沟通体验。

  2021年第二季度,环信IM Unity SDK进行了重构改版,环信IM Unity SDK 2.0正式发布,主要改进包括如下:

    1、迭代更新,更加实用的API接口

    2、IM+Push增强功能的补全

    3、C#语言层面引入了版本7.0 – 9.0之后的一些新语法改进

    4、特别的,增加了PC端Unity Editor环境下编译调试支持,大大提升了开发效率

  在过去的一段时间里,笔者也参与了相应的研发工作。在整个过程中,为了解决各种问题,不仅要到处翻阅资料,还要尝试各种方法和参数组合。其间也经历了各种程序崩溃甚至系统崩溃,诡异的程序表现一次次让开发人员束手无策,四处碰壁,当真像深夜里行走在迷宫之中,手里还拿着一个待破解的魔方。“此路不通,请绕行!”,是在一次次的尝试后无奈的慨叹和难舍的放弃。而一旦问题最后得到圆满解决,又宛如飞入云端,以上帝视角俯瞰一片片迷宫,一切又显得那么理所当然,繁复琐细但又丝丝入扣,这样的苦尽甘来也算是做程序员能享受到的巨大喜悦和满足。

  不敢独享,特记录下一些心得供大家参考,也欢迎.NET平台资深玩家批评指正。以下,Enjoy!

  开发概览:非托管插件开发(Native/Unmanaged Plugin)

  Unity是基于Microsoft .Net Framework开发的游戏引擎2,它采用了开源的.NET Platform,并依赖此框架来实现跨硬件设备和运行时(操作系统)的目标,也是所谓的”Write once, run anywhere”。在语言方面,Unity选择C#作为主要的脚本编程语言,虽然.NET平台本身支持的语言有很多种。

  进一步,Unity支持Mono和ILC2PP两种脚本框架(Scripting Backends)。特别的,Unity Editor采用的是Mono脚本框架。

  一般的,游戏类库开发者可以选择直接用C#语言开发,目标类库可以实现基于.NET Framework基础功能之上的高级功能,这类插件称之为Managed Plugin(托管插件)。由于环信IM核心SDK已经基于C++开发,因此我们选择另一种Native Plugin(本地插件)的方式,正是它把我们引向了迷宫之旅。两种类型的Plugin介绍,参见3。

  不幸的是,Unity网站上关于Native Plugin的相关介绍少只又少,想要了解它的具体细节还要去参考Microsoft MSDN文档。作为中规中矩的文档介绍,微软的文档是合格的,但是,当你真正上手编程时就会发现,这些远远不够:下面记录的一些坑点就很难在相应的文档中得到直接的提示;而要通过Google大法,结合其他程序员留下的蛛丝马迹,再加上自己不断的调试来最终确认。

  在微软文档上下文中,Unity Native Plugin有个另外的名字:Unmanaged Plugin,即非托管插件。简单来讲,Managed Plugin生存在.NET Framework的运行时环境(类似于Java的JVM),而Unmanaged Plugin则生存在这个运行时环境之外,也即和运行时环境是兄弟的关系。如果你原本的类库实现满足微软的COM(Component Object Model)规范,那自然最好是使用COM Interop4的互操作方式;而环信IM SDK本身是纯C++实现,因此采用了Platform Invoke5(简称P/Invoke)方式,本文剩下的内容均是基于P/Invoke。

  下图则概要描述了Managed和Unmanaged区域代码之间互相操作的方式:

  更具体的,为了实现对于Unmanaged DLL function的调用,只需要简单的4步6:

  1、确认DLL类库中需要被操作的函数;

  2、创建一个C#类来关联被操作的这些函数(给函数穿上一个马甲,以便集中管理和反复调用);

  3、使用DllImport标志在受管侧(C#)定义函数原型;

  4、在受管侧随意调用相关非托管区域函数。

  上图中,Standard marshalling service即负责将数据在两个区域进行封装/解封装传送(marshall/unmarshall),它主要定义了数据在两个不同内存区域进行拷贝(Copy)和引用(Reference)的规则7,而迷宫中的坑主要是和这些具体规则有关。

  坑王驾到之封送(Marshall/Unmarshall)中的那些坑

  坑一:sizeof(bool) = ?

  绝大多数的基本类型属于Blittable Types8:如System.Byte, System.Single等。System.Boolean虽然不属于Blittable types,但是Standard Marshalling Service默认将其转换为1,2,4字节的内存存储,当其值为true时,其对应的值为1。如果你想当然的直接将System.Boolean映射到Unmanaged侧的bool类型而不做特别处理的话,你并一定会理解碰到编译或者运行时错误,但是如果你严格的测试每个字段是,会惊讶的发现这些bool值跟你想象的不尽相同:有时正确,有时错误。

  经过调试跟踪,动态打印sizeof(bool)来确认Unmanaged侧bool类型数据长度后,你会发现System.Boolean默认会被保存为4个字节长度,而在macOS环境下(对于其它环境,需要自行认证),C++定义的bool其实只有一个字节。因此当你在Unmanaged侧取bool值的时候,其实只读取了System.Boolean的1/4个字节而已。而当你声明了多个连续的System.Boolean/bool值时,可能在Unmanaged侧读取的这几个bool值仅仅是第一个System.Boolean值的不同偏移字节而已。

  知道了原因,解决方案自然就出来了,在Managed侧强制声明System.Boolean字段封送到Unmanaged侧时仅使用一个字节:

  [MarshallAs(UnmanagedType.U1)]public bool TrueOrFalse;

  坑二:字节对齐

  对于C++开发者来说,可能知道当一个数据结构(class or struct)中的各字段在内存中进行排列时,会按照一个设定的装箱长度进行字节对齐,例如:

  struct MyStruct {

  int one;

  short two;

  int three;

  bool four;

  }

  假设在我们的平台上,sizeof(int)=4, sizeof(short)=2, sizeof(bool)=1, 如果问你sizeof(MyStruct)=?,你可能会马上做个加法得到答案,但是答案不一定对。It depends! 假设我们是按照4个字节对齐,这上面的结构体在内存中实际排列如下图:

那些年我们趟过的坑,环信IM Unity SDK 2.0正式发布

  了解这个对于我们编码有两个意义:

  1、通过合理排列字段声明顺序来优化存储效率,内存布局中不留空洞;

  2、MarshalAsAttribute支持Layout.Explicit来进行绝对定位,懂得了字节对齐可以配合Unmanaged侧的内存排列规则以保证字段长度映射正确,不然同样会发生字段长度不一致带来的困扰。

  坑三:如何避免Double Free

  Standard Marshalling Service/Interop marshaller总是试图释放Unmanaged侧代码分配的内存9,这会带来Double Free的问题,如果碰到这种问题,程序就会直接崩溃。

  引用资料中举了以下例子:

  BSTR MethodOne (BSTR b) {

  return b;

  }

  如果这段代码直接从Unmanaged侧DLL中直接执行,不会发生任何额外的内存释放;但是当你从Managed侧调用这个方法时,b会被释放两次。

  而更让人抓狂的是,并没有相应的信息提示究竟是哪个指针,哪个字段被Double Free了,你唯一能做的就是一点点加代码来验证自己猜测。所以,严格来说,并没有一个万无一失的方案来避免Double Free,你唯一能做的就是通过测试来验证结果(有点盲拧魔方的味道了)。

  有两个基本的方法来解决Double Free的问题:

  1、按照官方文档建议,在Unmanaged侧通过使用CoTaskMemAlloc来分配内存,通过此种方法分配的内存,除非显式调用了CoTaskMemFree方法(在Unmanaged侧或者Managed侧均可以调用),Interop Marshaller会严格保证不去释放该内存。使用这种方法可以灵活的在任意一侧分配内存,并在合适的时候在另一侧释放内存。

  2、但上面这种方法貌似仅适用于Windows平台,在macOS下没有办法使用(需要引用win32base.dll相关实现)。在macOS下仅能通过在Mananged侧调用Marshal.AllocCoTaskMem()方法分配内存,并通过Marshal.FreeCoTaskMem()来在同一侧进行释放(按照此方法分配的内存指针传入Unmanaged侧后,不要进行任何释放即可)。另外有一个不太可靠的workaround是:在Unmanaged一侧创建的内存指针尽量通过IntPtr传递,并在可能的时候将对象中一些指针类型的属性值置空,以避免Double Free的发生。

  坑四:virtual函数带来的内存布局变化

  vptr和vtable是C++的一个概念:当你定义的类型中有虚函数存在时,内存对象的第一个位置会存放一个vptr指针,该指针指向vtable(虚函数表)。因此当你开始创建的自定义类型一开始没有虚函数时(包括虚析构函数virtual ~MyClass()),一切运行正常。有一天你重构此类型,增加了一些虚函数:DUANG,一切都崩塌了!原因就在于Unmanaged侧内存对象的排列规则变了,原有的对象字段都被新加入的vptr往后面移位了。此时可能你唯一能做的就是通过Layout.Explicit来手工对齐每一个字段新的位置。

  其它坑

  坑一:针对M1芯片编译

  对于M1芯片的macOS系统,编译环信IM Unity SDK时候需要注意几个问题:

  1、XCode编译时需要Excluded Architecture中排除arm64架构(很奇葩的设置,不是应该排除x86吗?)

  2、类库的依赖解决:通过otool -L命令来确认相应的plugin依赖的类库位置都正确(文件路径下文件确实存在),如果相应文件不存在要手工拷贝文件到指定目录:而新的macOS安全架构限制了往系统目录下(如/usr/lib)进行任何改动,一个临时的解决方法是通过install_name_tool工具主动修改类库依赖路径到另一个可以放置新文件的位置(如home目录)。

  坑二:Delegate的正确使用姿势

  如果Managed侧的编程语言是C#,则Delegate是实现回调的重要手段。在Unmanaged侧完成期望工作时回调一个FunctionPtr即可实现通用的回调模式,而此FunctionPtr正是对应到Managed侧的Delegate。当你的Delegate绑定到一个类对象上时,你有两种选择:

  namespace ChatSDK {

  //delegate definition

  public void delegate OnMessageReceived(EMMessage message);

  public class MyDelegate {

  //Option 1: field

  public OnMessageReceived MyMessageReceived;

  //Option 2: instance method

  public void OnMessageReceived(EMMessage message)

  {

  ...

  }

  }

  //send delegate method to unmanaged side

  MyDelegate md = new();

  NativeMethods.SetOnMessageReceivedCallback(md.MyMessageReceived); //option 1

  NativeMethods.SetOnMessageReceivedCallback(md.OnMessageReceived); //option 2

  }

  看起来两个方式都没有问题,并且第二个方式看起来更顺眼。但是这里隐藏着一个很深的坑,就是你选择第二个方式的时候,如果你在回调方法实现中采用this.xxx方式引用时,你会发现this = null!这是因为当你使用这种方式传递一个对象的方法作为回调方法指针时,其实已经丢失了Delegate.Target(也就是this)属性。而通过第一种方式传递的是一个对象的属性/字段,它和对象本身的绑定是不会在传递过程中丢失的。

  至于该Delegate字段的定义可以在此类的构造函数中通过以下方式实现:

  ...

  public MyDelegate() {

  MyMessageReceived = (EMMessage message) => { ... }

  }

  ...

  参考资料

  1、List of Unity Games: https://en.wikipedia.org/wiki/List_of_Unity_games

  2、Unity and .NET: https://docs.unity3d.com/Manual/overview-of-dot-net-in-unity.html

  3、Unity Scripting-Plugins: https://docs.unity3d.com/Manual/Plugins.html

  4、COM Interop: https://docs.microsoft.com/en-us/dotnet/standard/native-interop/cominterop

  5、Platform Invoke: https://docs.microsoft.com/en-us/dotnet/standard/native-interop/pinvoke

  6、如何调用Unmanaged DLL Functions:https://docs.microsoft.com/en-us/dotnet/framework/interop/consuming-unmanaged-dll-functions

  7、Interop Marshalling:https://docs.microsoft.com/en-us/dotnet/framework/interop/interop-marshaling

  8、Blittable Types: https://docs.microsoft.com/en-us/dotnet/framework/interop/blittable-and-non-blittable-types

  9、Double Free: https://docs.microsoft.com/en-us/dotnet/framework/interop/default-marshaling-behavior

台铃超能系列600公里

孩子睡觉磨牙是什么原因引起的

今日头条怎么加灵犬

玩转极米H3


返回网站首页

大疆稳定器rsc2使用教程


五菱跑车标

腾讯吃鸡手游全球吸金51亿美元,中国玩家日均玩两小时贡献一半收入
爆料:苹果 Apple Watch Series 7 拥有更大 41mm、45mm 表盘版本

哈啰出行车主换车


返回网站首页

本文评论
工信部将适时开展智能网联汽车准入试点「智能网联汽车生产企业及产品准入管理指南(试行)」
IT之家 9 月 4 日消息,据央视新闻,工业和信息化部装备工业一司副司长郭守刚在中国汽车产业发展国际论坛上表示,下一步我国将发布汽车产业绿色低碳发展路线图;加快制定《道路机动...
日期:09-09
区块链是存储技术吗_为什么区块链公司都要做存储,CWV2.0告诉你
  今天,全世界有近三分之二的人口连接上网。通过Web 2.0过去二十年互联网实现云计算、移动化、社交化的趋势,无数丰富的互联网应用和服务通过各种计算机设备将我们的数字生...
日期:07-14
百度输入法再同大热游戏“组CP”,游戏键盘嘉年华第二季上线
  就算关了游戏也能有所爱角色的“时刻相伴”是种什么体验?   最近百度输入法推出了游戏键盘嘉年华第二季活动,就要帮一众游戏玩家们时时“武装到指尖”。在第一季联合...
日期:01-15
SpaceX正在大肆招聘 准备把加州发射火箭的数量增加一倍「美国火箭spacex发射」
SpaceX正计划大幅提高其从美国西海岸发射火箭的频率,这一信息来自于SpaceX的猎鹰9号运营经理为该公司在西海岸发射设施招募新员工的广告。SpaceX从三个地点发射任务,其中两个...
日期:09-14
Google首款可折叠设备Pixel Fold有望明年第一季度发布
在上周召开的硬件发布会上,Google 推出了 Pixel 7 和 Pixel 7 Pro 两款手机、Pixel Watch 智能手表以及备受期待的 Pixel Tablet。现在有爆料者称 Google 计划在 2023 年年初...
日期:10-12
NASA定于9月3日重新发射“阿尔忒弥斯1号”火箭「阿尔忒弥斯1号发射时间」
当地时间8月30日,美国国家航空航天局(NASA)“阿尔忒弥斯1号”任务经理迈克·萨拉芬表示,将于9月3日重新发射“阿尔忒弥斯1号”火箭。苏宁新年活动潮玩泡泡玛特公司2021年抖音20...
日期:09-06
驱动人生提醒:夏日高温预警!驱动还能这么玩?!
  作者:驱动人生   大暑来临!你还是那个热爱105°c的你吗?这个夏天,驱动人生将开启不一样的消暑模式,这可能是国内首家这么玩的公司——驱动人生请大家喝奶茶啦!   驱动...
日期:12-25
知乎第二季度营收8.36亿元,同比增长31%「知乎二季度财报」
查看最新行情   讯 8月30日晚间消息,知乎(NYSE:ZH)发布截至2022年6月30日的第二季度财报。财报显示,知乎第二季度总营收为8.360亿元(约合1.248亿美元),较202...
日期:08-31
专家解读九樱VINUX商业“消化”系统
  九樱率先开发完成的全球首个基于大规模协作与分享模式的组件化商业平台VINUX,彻底打破了传统商业链条上各经营者之间你死我活的“零和博弈”竞争观念,代之以共生多赢的“...
日期:07-23
上门消毒杀菌服务扩充至32城!顺丰丰修专业服务与安全防护并进
  近日,各中小企业陆续复工复产,人员密集度、流动性大大提升。未来一个月是复工高峰期,防疫工作仍不能松懈。应市场所需,顺丰丰修提供专业上门消毒杀菌服务,凭借科学消毒、安...
日期:10-20
易观称今年一季度中国无线搜索日均达3.66亿次
  易观国际分析师任洋辉今早发表署名报告称,2010年第一季度中国日均搜索量达3.66亿次,其中百度仍然以29.08%的绝对优势处于行业领先地位,而谷歌与宜搜则分别以17.21%、17.58...
日期:07-29
网易严选消费者特征_互联网20年 网易严选引领模式创新 品质零售成为消费主流
  中国互联网经过20年的探索与发展,已成为推动中国数字经济的重要引擎。近日,艾媒咨询发布《中国互联网发展20年盘点专题报告》,网易严选作为品质零售的开拓者,凭借“严选模...
日期:10-05
黑鱼被遗忘洗手间存活11个月掉色 超强生命力引围观:准备放生「黑鱼适合放生到哪里」
近日,湖北武汉王女士在主卧卫生间发现过年时养的鱼还活着,这也是吸引了众多网友的围观。苹果14值得入手吗王女士介绍,这条鱼一直在桶里,倒出来发现竟然还活着,便转移到了洗脸盆里...
日期:11-06
5G速度冲上万兆!iPhone 14 Pro/Max基带确认:高通骁龙X65「iphone 11pro max支持5g吗」
iPhone 14系列正式开售,今天你拿到首批新机了吗?华为全场景智慧生活新品发布会活动描述在消费者尝鲜iPhone 14 Pro系列“灵动岛”的同时,不少博主也对新机开启了拆解。今日@微...
日期:09-20
威马w6新能源汽车价格「威马W6宣布涨价:3款车未来3月每月上调2000元」
10月1日消息,因受上游原材料价格上涨等综合因素,威马汽车将在近期针对在售车型威马W6的价格进行上调。win11检查兼容性科学探索奖获得者名单iphone13在美国销量此次价格调整覆...
日期:10-02
iPhone14 Plus刚开售就破发:降价449元也没人买!「iphone13卖爆了」
10月7日,也就是昨天,iPhone14系列手机的最后一款机型iPhone 14 Plus正式全网开售,今天已经有首批预订者拿到了新机。然而如同预料的一样,这个一开始就被全网唱衰的iPhone 14 Plu...
日期:10-09
AMD B650 系列主板 10 月上市「b250主板什么时候上市的」
IT之家 8 月 30 日消息,在今天的 AMD 发布会上,AMD 宣布旗舰的 X670 系列主板将随锐龙 7000 处理器在 9 月上市,10 月份,性价比更高的 B650 系列也将上市,而且还包括一款B650E “...
日期:09-26
常熟网红直播基地「快手首届常熟直播节:未来将扶持100个百万GMV常熟新主播」
10 月 28 日消息,常熟市人民政府主办,常熟市商务局和快手承办的第七届中国·常熟昆承湖互联网创新创业大会今日在江苏常熟举行。快手第一届常熟直播节也在会上同步启动,会上,快...
日期:10-28
金山软件董事长 雷军「金山软件:腾讯总裁刘炽平辞任公司非执行董事,姚磊文接任」
查看最新行情 ict行业的现状和发展趋势  讯 8月23日晚间消息,金山软件发布公告称,董事会宣布,腾讯公司总裁刘炽平因其他工作安排而辞任公司...
日期:09-06
英特尔三季度净利润下滑85%,未来三年将削减100亿美元成本「英特尔第三季度财报」
  作者:彭新;新手怎么玩游戏  个人电脑和服务器芯片销售疲软拖累业绩,英特尔业绩承压,将采取裁员等措施削减成本。10月27日美股盘后,英特尔公布2022财年第三季度财报。报告...
日期:10-28