UPL5-6——本机-托管的桥接
UPL5-6——本机-托管的桥接
本机-托管的桥接
本机-托管的桥接(Native-Managed Bridge)指的是 C# 的托管代码与 C/C++ 的本机代码之间的相互调用或通信机制
Unity 本身就是由 C++ 实现的引擎内核
而我们日常写的是 C# 脚本,它们之间必须有桥梁来通信
-
托管(Managed):
主要指的是 C# 代码,它的执行环境是 CLR、Mono、IL2CPP
-
本机(Native):
主要指的是 C/C++ 代码,它的执行环境为直接运行于 CPU(无虚拟机概念)
桥接的产生
Unity 在执行 C# 脚本时,需要让 C# 能调用到底层引擎的 C++ 函数(比如渲染、物理),这就产生了两个方向的桥接
-
托管 到 本机
即 C# 调用 Unity 引擎内部的 C++ 函数
比如访问 Unity 提供的GameObject、MonoBehaviour类中的各种属性和方法
这是通过 IL2CPP 或 Mono 的内部调用机制来桥接到 Unity 的 C++ -
本机 到 托管
即 C++ 层主动调用 C# 方法,比如原生插件调用 Unity 脚本中注册的事件,Android / iOS 的原生代码调用 Unity 脚本方法等
总结:
Unity 中的本机-托管的桥接就是 C++ 与 C# 之间相互调用的通道机制
我们在对 Unity 的 GameObject、Transform、Component 等进行操作时
有很多操作都会触发桥接的调用,即便你在 C# 层写的是简单的属性访问,很可能发生了隐形的跨域调用
比如:
-
访问
transform 中的position1
2Vector3 p = this.transform.position;
this.transform.position = p; -
添加组件
gameObject.AddComponent<T>() -
获取组件
GetComponent<T>() -
获取子物体
transform.GetChild -
激活/失活物体
gameObject.SetActive() -
查找物体
GameObject.Find
等等
本机-托管的桥接带来的性能消耗
-
上下文切换
C# 调 C++ 或反过来,都需要堆栈转换、线程状态保存
-
数据封送
如
Vector3等结构体需要从 C++ 拷贝到 C# 层,或反之 -
GC 压力
封送中生成临时对象可能在托管堆上分配新的内存,产生垃圾
-
调试困难
Profiler 只能显示 C# 或 C++ 的一侧,桥接代码不易追踪
等等
优化建议
-
不要频繁操作
GameObject、Transform、Component相关属性例如:同一帧对
transform.position 的多次修改应先缓存,再统一写回
例如:Vector3 v = transform.position;
先进行相关的位置计算,最终计算完毕后再赋值回去transform.position = v; -
不要在
Update 频繁调用GetComponent<T>()等组件查找方法推荐在初始化阶段缓存引用,避免在
Update中重复获取 -
不要每帧遍历
transform.GetChild()获取子物体子物体应在初始化阶段获取并缓存
-
逻辑和表现分离,弱化桥接路径
避免游戏逻辑直接操作
GameObject,转而使用纯数据结构(位置、状态等),最后集中批量同步回 Unity -
自定义托管对象池,降低桥接生命周期成本
桥接调用也和生命周期有关,例如频繁调用
AddComponent、Instantiate 会从本机层动态分配新对象,开销很大,使用对象池复用GameObject和组件
等等
总之,就是要想方设法的减少 本机-托管桥接 的成本
频繁访问建议 缓存引用,减少跨域访问,需要做大量位置、方向运算时,应优先在托管代码中处理后统一回写
