• JSBinding + SharpKit / 原理篇:内存管理与垃圾回收


    C# 和 JS 都有垃圾回收机制,需要保证 2 者能够分工协作。

     

    类对象

    类在C#中是引用类型。我们在 C# 中维护了2个map,保存 C# 对象和 JS 对象的一一对应关系

    举一个例子,看以下代码

    1 // C#
    2 List<int> lst = new List<int>();
    3 
    4 // JS
    5 var lst = new System.Collections.Generic.List$1.ctor(System.Int32.ctor);

     

    从C#返回一个对象到 JS 以及后续的过程是这样的:

    1) C# 获得一个表示JS对象的ID。

    1 // 文件:System_Collections_Generic_List77.javascript
    2 _jstype.definition.ctor = function(t0) { CS.Call(5, 249, 0, true, this, t0.getNativeType()); } // 1) 这里的 this 传到 C#后,C#得到一个对象ID

    2) C# 创建一个 List csObj 对象。

    3) 保存2种对象的一一对应关系。

    1 // 存储对应关系,伪代码,具体可看 JSMgr.addJSCSRel 函数!
    2 map1[jsObjID] = csObj;
    3 map2[csObj.hashCode] = jsObjID;

    注意,由于我们保存了 csObj 对象,那么我们必须在某个时刻就把他从map中移除,否则这个对象永远不会被C# GC 给回收。

    4) 给 JS 对象注册 finalizer 回调函数,当他被 JS GC 回收时,会通知我们的回调。

    在回调中做的事情就是从map中移除对 csObj 的引用,这样我们就没有主动握住  csObj 了,那他到某个时刻 自然就被回收了。

     1 // 文件:System_Collections_Generic_List77Generated.cs
     2 
     3 static bool ListA1_ListA11(JSVCall vc, int argc)
     4 {
     5     int _this = JSApi.getObject((int)JSApi.GetType.Arg); // 1) 获取JS对象ID
     6     JSApi.attachFinalizerObject(_this); // 4) 给 JS 对象注册 finalizer 函数,当他被垃圾回收时,会通知我们的回调
     7     --argc;
     8 
     9     ConstructorInfo constructor = JSDataExchangeMgr.makeGenericConstructor(typeof(System.Collections.Generic.List<>), constructorID0); 
    10     if (constructor == null) return true;
    11 
    12     int len = argc - 1;
    13     if (len == 0)
    14     {
    15         JSMgr.addJSCSRel(_this, 
    constructor.Invoke(null, new object[]{}));// 2), 3) 创建 C# 对象,保存一一对应关系 16 } 17 18 return true; 19 }

    5) 任何时刻,当要从C#返回一个对象到JS时,都会查找是否已经有对应的 JS 对象了。如果有,就返回已经保存的那个。如果没有,才创建一个对应的 JS 对象返回给 JS。

    小结:对于类对象,我们存储了JS对象和CS对象的一一对应关系。JS对象仍然是完全受 JS GC 管理,什么该被回收就什么时候被回收。而对于CS对象,通过往JS对象注册垃圾回收事件,当JS对象被回收时我们将得到通知,那时候我们就可以主动去除对CS对象的引用了。

     

    结构体对象

    结构体在C#中是值类型的。不可能存储一一对应关系。从C#返回结构体对象到JS时,每次都重新创建一个对象出来,返回给JS。只能通过JS对象找到对应的C#对象,反过来是行不通的

    存储关系变成:

    1 // 存储对应关系,伪代码,具体可看 JSMgr.addJSCSRel 函数!
    2 map1[jsObjID] = csObj;
    3 // map2[csObj.hashCode] = jsObjID; // 没有这个了!

    垃圾回收控制跟类对象是一样的。

     

    注意:目前代码里 Vector2,Vector3 是用JS写了一遍(以后还会增加)。他们和上面说到的结构体对象的存储方法没有什么关系。

    每当 从 JS->CS 时,根据JS对象的xyz值构造一个C#对象来使用。

    每个从 CS->JS 时,根据CS对象的xyz值构造一个 JS 对象。

    (不好理解?想象一下 int 如何 JS->CS,CS->JS? 目前V2, V3和 int 的传递是类似的,在 JS 和 CS 端都各有各自的数据结构)

    (还是不好理解?群里问吧,或者问我)

     

    纯JS对象在C#中的存储

    上面所说的类对象和结构体对象的存储有一个共同点:JS和CS两端都知道要存储的对象类型是什么。比如说 List,GameObject,2边都很了解这2个类,都能对他们的对象进行处理。

    纯JS对象是指,只有JS有类定义,C#并不清楚这个类。

     1 // JS 代码
     2 
     3 JsTypes.push( {
     4     fullname: "DebugMessages.Message",
     5     Kind: "Class",
     6     definition: {
     7         ctor: function (txt){
     8             this.txt = txt;
     9         }
    10     }
    11 });
    12 
    13 var l = new System.Collections.Generic.List$1.ctor(DebugMessages.Message.ctor);
    14 l.Add(new DebugMessages.Message("hello");
    15 l.Add(new DebugMessages.Message("world");

    Line13,创建一个 List,C#端构造 List 时会发现类型名是 "DebugMessage.Message",根据这个名字查找不到 System.Type(请看 JSDataExchange.GetTypeByName 函数)。

    怎么办呢?可不可以这样,当C#找不到类型的时候,就直接用 List<int>,int表示JS对象ID。这样不是挺好吗?

    是的,一开始就是这么做的。请看 Line14,new一个JS对象并传递给C#,C#存储了他的ID,看起来很美好。

    但!是!这个JS对象接下去就没有人引用他了,如果发生GC,那个对象就要被回收。而C#那边还存着一个int,等一下要用的时候就找不着JS对象了……

    当你把一个JS函数设置给C# Delegate 时,也存在相同的问题。

    所以,在C#中存储这个JS对象时,要增加JS对象的引用!

     

    我增加了一个 CSRepresentedObject 类,来完成这个功能。

    (这里面还多了个小细节:使用了 WeakReference 类,具体可以看下代码,缺他好像不行)

     1 public class CSRepresentedObject
     2 {
     3     public static int s_objCount = 0;
     4     public static int s_funCount = 0;
     5 
     6     //不要直接创建这个对象,应该调用 JSDataExchangeMgr.getObject
     7     public CSRepresentedObject(int jsObjID, bool bFunction = false)
     8     {
     9         this.jsEngineRound = JSMgr.jsEngineRound;
    10         this.jsObjID = jsObjID;
    11         this.bFunction = bFunction;
    12         JSMgr.addJSCSRel(jsObjID, this, true);
    13 
    14         if (bFunction) 
    15             s_funCount++;
    16         else 
    17             s_objCount++;
    18 
    19         // 通常是1,不会加的
    20         int refCount = JSApi.incRefCount(jsObjID);
    21         //Debug.Log(new StringBuilder().AppendFormat("+ CSRepresentedObject {0} Ref[{1}] Fun[{1}]", jsObjID, refCount, bFunction ? 1 : 0));
    22     }
    23     ~CSRepresentedObject()
    24     {
    25         if (bFunction)
    26             s_funCount--;
    27         else 
    28             s_objCount--;
    29 
    30         int refCount = JSApi.decRefCount(jsObjID);
    31         if (refCount <= 0)
    32         {
    33             JSMgr.removeJSCSRel(jsObjID, this.jsEngineRound);
    34             if (bFunction)
    35             {
    36                 JSMgr.removeJSFunCSDelegateRel(jsObjID);
    37             }
    38         }
    39         else
    40         {
    41             Debug.LogError(";;;//IIL.x&");
    42         }
    43         //Debug.Log(new StringBuilder().AppendFormat("- CSRepresentedObject {0} Ref[{1}] Fun[{1}]", jsObjID, refCount, bFunction ? 1 : 0));
    44     }
    45     public int jsObjID;
    46     public bool bFunction;
    47     int jsEngineRound;
    48 }

     

    纯C#对象在JS中的存储

    目前已知这种情况只发生在,当传递一个C#函数到JS时。(例子:TestEasingFunctions.cs)

    在JS中也定义了一个叫做 JSRepresentedObject 的类来处理。(myclrhandler.javascript)

    不过比上一种情况要简单得多。这种情况处理后就变成和 “类对象” 一毛一样了。

     

    注意:C#对象存储在JS中,JS根本无法使用拿到的C#对象,他只能起到一个过渡作用而已。

    JS对象存储在C#中也是一样的。

     

    返回:Unity代码热更新方案 JSBinding + SharpKit 首页

  • 相关阅读:
    erlang中变量作用域
    erlang数字转字符串
    gen_server笔记
    Using Eredis, Redis With Erlang
    erlang lists模块函数使用大全
    erlang抽象码与basho的protobuf
    Erlang Module and Function
    Erlang中频繁发送远程消息要注意的问题
    Erlang中的record与宏
    数据结构之数羊问题
  • 原文地址:https://www.cnblogs.com/answerwinner/p/4574462.html
Copyright © 2020-2023  润新知