diff --git a/404.html b/404.html index 10570aed8..962ef52f5 100644 --- a/404.html +++ b/404.html @@ -9,13 +9,13 @@ - +
跳到主要内容

找不到页面

我们找不到您要找的页面。

请联系原始链接来源网站的所有者,并告知他们链接已损坏。

- + \ No newline at end of file diff --git a/assets/js/099d81ac.5b82ef6b.js b/assets/js/099d81ac.5b82ef6b.js new file mode 100644 index 000000000..d2b9a467f --- /dev/null +++ b/assets/js/099d81ac.5b82ef6b.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[1977],{3905:(e,t,r)=>{r.d(t,{Zo:()=>p,kt:()=>b});var n=r(7294);function l(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function a(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function i(e){for(var t=1;t=0||(l[r]=e[r]);return l}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(l[r]=e[r])}return l}var s=n.createContext({}),u=function(e){var t=n.useContext(s),r=t;return e&&(r="function"==typeof e?e(t):i(i({},t),e)),r},p=function(e){var t=u(e.components);return n.createElement(s.Provider,{value:t},e.children)},m="mdxType",c={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},d=n.forwardRef((function(e,t){var r=e.components,l=e.mdxType,a=e.originalType,s=e.parentName,p=o(e,["components","mdxType","originalType","parentName"]),m=u(r),d=l,b=m["".concat(s,".").concat(d)]||m[d]||c[d]||a;return r?n.createElement(b,i(i({ref:t},p),{},{components:r})):n.createElement(b,i({ref:t},p))}));function b(e,t){var r=arguments,l=t&&t.mdxType;if("string"==typeof e||l){var a=r.length,i=new Array(a);i[0]=d;var o={};for(var s in t)hasOwnProperty.call(t,s)&&(o[s]=t[s]);o.originalType=e,o[m]="string"==typeof e?e:l,i[1]=o;for(var u=2;u{r.r(t),r.d(t,{assets:()=>s,contentTitle:()=>i,default:()=>c,frontMatter:()=>a,metadata:()=>o,toc:()=>u});var n=r(7462),l=(r(7294),r(3905));const a={},i="\u70ed\u91cd\u8f7d\u6280\u672f",o={unversionedId:"business/reload/hotreloadassembly",id:"business/reload/hotreloadassembly",title:"\u70ed\u91cd\u8f7d\u6280\u672f",description:"\u70ed\u91cd\u8f7d\u6280\u672f\u7528\u4e8e\u5b8c\u5168\u5378\u8f7d\u6216\u8005\u91cd\u65b0\u52a0\u8f7d\u4e00\u4e2aassembly\uff0c\u9002\u7528\u4e8e\u5c0f\u6e38\u620f\u5408\u96c6\u7c7b\u578b\u7684\u6e38\u620f\u3002\u8be5\u65b9\u6848\u53ea\u63d0\u4f9b\u5546\u4e1a\u5316\u7248\u672c\u3002",source:"@site/docs/business/reload/hotreloadassembly.md",sourceDirName:"business/reload",slug:"/business/reload/hotreloadassembly",permalink:"/docs/business/reload/hotreloadassembly",draft:!1,tags:[],version:"current",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"\u5feb\u901f\u4e0a\u624b",permalink:"/docs/business/reload/quickstart"},next:{title:"\u514d\u8d39\u8bd5\u7528",permalink:"/docs/business/reload/freetrial"}},s={},u=[{value:"\u652f\u6301\u7684\u7279\u6027",id:"\u652f\u6301\u7684\u7279\u6027",level:2},{value:"\u4e0d\u652f\u6301\u7279\u6027\u53ca\u7279\u6b8a\u8981\u6c42",id:"\u4e0d\u652f\u6301\u7279\u6027\u53ca\u7279\u6b8a\u8981\u6c42",level:2},{value:"\u4e00\u4e9b\u4e0d\u517c\u5bb9\u7684\u5e93",id:"\u4e00\u4e9b\u4e0d\u517c\u5bb9\u7684\u5e93",level:2},{value:"\u89e3\u51b3\u88ab\u5378\u8f7d\u5bf9\u8c61\u7684\u5f15\u7528\u95ee\u9898",id:"\u89e3\u51b3\u88ab\u5378\u8f7d\u5bf9\u8c61\u7684\u5f15\u7528\u95ee\u9898",level:2}],p={toc:u},m="wrapper";function c(e){let{components:t,...r}=e;return(0,l.kt)(m,(0,n.Z)({},p,r,{components:t,mdxType:"MDXLayout"}),(0,l.kt)("h1",{id:"\u70ed\u91cd\u8f7d\u6280\u672f"},"\u70ed\u91cd\u8f7d\u6280\u672f"),(0,l.kt)("p",null,"\u70ed\u91cd\u8f7d\u6280\u672f\u7528\u4e8e\u5b8c\u5168\u5378\u8f7d\u6216\u8005\u91cd\u65b0\u52a0\u8f7d\u4e00\u4e2aassembly\uff0c\u9002\u7528\u4e8e\u5c0f\u6e38\u620f\u5408\u96c6\u7c7b\u578b\u7684\u6e38\u620f\u3002\u8be5\u65b9\u6848\u53ea\u63d0\u4f9b",(0,l.kt)("strong",{parentName:"p"},"\u5546\u4e1a\u5316\u7248\u672c"),"\u3002"),(0,l.kt)("h2",{id:"\u652f\u6301\u7684\u7279\u6027"},"\u652f\u6301\u7684\u7279\u6027"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"\u652f\u6301\u5378\u8f7dassembly\uff0c\u5378\u8f7d100%\u7684assembly\u6240\u5360\u7528\u7684\u5185\u5b58"),(0,l.kt)("li",{parentName:"ul"},"\u652f\u6301\u91cd\u65b0\u52a0\u8f7dassembly\uff0c\u4ee3\u7801\u53ef\u4ee5\u4efb\u610f\u53d8\u5316\u751a\u81f3\u5b8c\u5168\u4e0d\u540c\uff08MonoBehaviour\u548cScriptable\u6709\u4e00\u5b9a\u7684\u9650\u5236\uff09"),(0,l.kt)("li",{parentName:"ul"},"\u652f\u6301",(0,l.kt)("strong",{parentName:"li"},"\u9650\u5b9a\u70ed\u66f4\u65b0assembly\u4e2d\u80fd\u8bbf\u95ee\u7684\u51fd\u6570\u7684\u96c6\u5408"),"\uff0c\u9002\u5408UGC\u6e38\u620f\u4e2d\u521b\u5efa\u6c99\u76d2\u73af\u5883\uff0c\u907f\u514d\u6076\u610f\u73a9\u5bb6\u4ee3\u7801\u9020\u6210\u7834\u574f\u3002")),(0,l.kt)("h2",{id:"\u4e0d\u652f\u6301\u7279\u6027\u53ca\u7279\u6b8a\u8981\u6c42"},"\u4e0d\u652f\u6301\u7279\u6027\u53ca\u7279\u6b8a\u8981\u6c42"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"\u8981\u6c42\u4e1a\u52a1\u4ee3\u7801\u4e0d\u4f1a\u518d\u4f7f\u7528\u88ab\u5378\u8f7d\u7684Assembly\u4e2d\u7684\u5bf9\u8c61\u6216\u8005\u51fd\u6570\uff0c\u5e76\u4e14\u9000\u51fa\u6240\u6709\u5728\u6267\u884c\u7684\u65e7\u903b\u8f91"),(0,l.kt)("li",{parentName:"ul"},"\u4e0d\u80fd\u76f4\u63a5\u5378\u8f7d\u88ab\u4f9d\u8d56\u7684Assembly\uff0c\u5fc5\u987b\u6309\u7167\u9006\u4f9d\u8d56\u987a\u5e8f\u5148\u5378\u8f7d\u4f9d\u8d56\u8005\uff0c\u518d\u5378\u8f7d\u88ab\u4f9d\u8d56\u8005\u3002\u4f8b\u5982A.dll\u4f9d\u8d56B.dll\uff0c\u5219\u9700\u8981\u5148\u5378\u8f7dA.dll\uff0c\u518d\u5378\u8f7dB.dll"),(0,l.kt)("li",{parentName:"ul"},"MonoBehaviour\u548cScriptableObject\u76f8\u5173",(0,l.kt)("ul",{parentName:"li"},(0,l.kt)("li",{parentName:"ul"},"\u8981\u6c42\u91cd\u8f7d\u7684MonoBehaviour\u4e2d\u7684\u4e8b\u4ef6\u6216\u6d88\u606f\u51fd\u6570\u5982Awake\u3001OnEable\u4e4b\u7c7b\u4e0d\u53d1\u751f\u589e\u5220\uff08\u4f46\u51fd\u6570\u4f53\u53ef\u4ee5\u53d8\u5316\uff09"),(0,l.kt)("li",{parentName:"ul"},"\u8981\u6c42\u91cd\u8f7d\u540e\u5728\u65e7Assembly\u4e2d\u5b58\u5728\u540c\u540d\u811a\u672c\u7c7b\u7684\u5e8f\u5217\u5316\u5b57\u6bb5\u540d\u4e0d\u53d1\u751f\u53d8\u5316\uff08\u7c7b\u578b\u53ef\u4ee5\u53d8\uff09"),(0,l.kt)("li",{parentName:"ul"},"\u5982\u679c\u5b57\u6bb5\u7c7b\u578b\u4e3a\u53ef\u5378\u8f7d\u7a0b\u5e8f\u96c6\u4e2d\u7684\u81ea\u5b9a\u4e49\u7c7b\u578bA\uff08class\u6216struct\u6216enum\uff09\uff0c\u5fc5\u987b\u7ed9\u5b83\u52a0\u4e0a",(0,l.kt)("inlineCode",{parentName:"li"},"[Serializable]"),"\u7279\u6027"),(0,l.kt)("li",{parentName:"ul"},"\u4e0d\u652f\u6301\u5b57\u6bb5\u7c7b\u578b\u4e3a",(0,l.kt)("inlineCode",{parentName:"li"},"List"),"\u5176\u4e2dA\u4e3a\u53ef\u5378\u8f7d\u7684\u7a0b\u5e8f\u96c6\u4e2d\u7684\u7c7b\u578b\uff0c\u8bf7\u66ff\u6362\u4e3a",(0,l.kt)("inlineCode",{parentName:"li"},"A[]")),(0,l.kt)("li",{parentName:"ul"},"\u4e0d\u80fd\u7ee7\u627f\u6cdb\u578b\u7c7b\u578b\uff0c\u4f8b\u5982",(0,l.kt)("inlineCode",{parentName:"li"},"class MyScript : CommonScript")))),(0,l.kt)("li",{parentName:"ul"},"\u4e00\u4e9b\u4f1a\u7f13\u5b58\u53cd\u5c04\u4fe1\u606f\u7684\u5e93\uff08\u8fd9\u79cd\u884c\u4e3a\u5728\u5e8f\u5217\u5316\u76f8\u5173\u7684\u5e93\u4e2d\u6700\u4e3a\u666e\u904d\uff0c\u5982LitJson\uff09\uff0c\u5728\u70ed\u91cd\u8f7d\u540e\u9700\u8981\u6e05\u7406\u6389\u7f13\u5b58\u7684\u53cd\u5c04\u4fe1\u606f"),(0,l.kt)("li",{parentName:"ul"},"\u4e0d\u652f\u6301\u6790\u6784\u51fd\u6570\uff0c~XXX()\u3002\u4e5f\u4e0d\u5141\u8bb8\u5b9e\u4f8b\u5316\u6cdb\u578b\u53c2\u6570\u5e26\u672c\u7a0b\u5e8f\u96c6\u7c7b\u578b\u7684\u5e26\u6790\u6784\u51fd\u6570\u7684\u6cdb\u578b\u7c7b"),(0,l.kt)("li",{parentName:"ul"},"\u4e0edots\u4e0d\u517c\u5bb9\u3002\u7531\u4e8edots\u5927\u91cf\u7f13\u5b58\u7684\u7c7b\u578b\u4fe1\u606f\uff0c\u5b9e\u73b0\u590d\u6742\uff0c\u5f88\u96be\u5355\u72ec\u6e05\u7406\u6389\u7f13\u5b58\u4fe1\u606f\u3002")),(0,l.kt)("h2",{id:"\u4e00\u4e9b\u4e0d\u517c\u5bb9\u7684\u5e93"},"\u4e00\u4e9b\u4e0d\u517c\u5bb9\u7684\u5e93"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"2022\u7684Jobs\u4f1a\u7f13\u5b58\u7c7b\u578b\u76f8\u5173\u4fe1\u606f\uff0c\u9700\u8981\u81ea\u884c\u5c0f\u5e45",(0,l.kt)("a",{parentName:"li",href:"/docs/business/reload/modifydll"},"\u4fee\u6539UnityEngine.CoreModule.dll"),"\u7684\u4ee3\u7801\u3002 \u4f4e\u4e8e2022\u7684\u7248\u672c\u4e0d\u9700\u8981\u4fee\u6539"),(0,l.kt)("li",{parentName:"ul"},"LitJson\u4e4b\u7c7b\u7684\u53cd\u5e8f\u5217\u5316\u5e93\u4f1a\u7f13\u5b58\u53cd\u5c04\u4fe1\u606f\uff0c\u9700\u8981\u5728\u70ed\u91cd\u8f7d\u540e\u6e05\u7406\u6389\u5e93\u4e2d\u7f13\u5b58\u7684\u53cd\u5c04\u4fe1\u606f\uff0c\u5177\u4f53\u64cd\u4f5c\u8ddf\u5e93\u7684\u5b9e\u73b0\u76f8\u5173")),(0,l.kt)("h2",{id:"\u89e3\u51b3\u88ab\u5378\u8f7d\u5bf9\u8c61\u7684\u5f15\u7528\u95ee\u9898"},"\u89e3\u51b3\u88ab\u5378\u8f7d\u5bf9\u8c61\u7684\u5f15\u7528\u95ee\u9898"),(0,l.kt)("p",null,"\u70ed\u91cd\u8f7d\u6280\u672f\u8981\u6c42\u5728\u672a\u5378\u8f7d\u7684\u7a0b\u5e8f\u96c6\u6216\u8005\u5168\u5c40\u5185\u5b58\u4e2d\u4e0d\u80fd\u6301\u6709\u5df2\u5378\u8f7d\u7684\u7a0b\u5e8f\u96c6U\u7684\u5143\u6570\u636e\u3002\u5305\u62ec\u4f46\u4e0d\u9650\u4e8e\uff1a"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"\u88ab\u5378\u8f7d\u7a0b\u5e8f\u96c6\u7684\u7c7b\u578b\u7684\u5b9e\u4f8b"),(0,l.kt)("li",{parentName:"ul"},"\u6cdb\u578b\u7c7b\u6216\u8005\u51fd\u6570\u7684\u6cdb\u578b\u53c2\u6570\u4e2d\u5305\u542b\u88ab\u5378\u8f7d\u7a0b\u5e8f\u96c6\u7684\u7c7b\u578b"),(0,l.kt)("li",{parentName:"ul"},"\u88ab\u5378\u8f7d\u7a0b\u5e8f\u96c6\u76f8\u5173\u7684\u53cd\u5c04\u4fe1\u606f\uff0c\u5982Assembly\u3001Type\u3001MethodInfo\u3001PropertyInfo\u7b49\u7b49"),(0,l.kt)("li",{parentName:"ul"},"\u6307\u5411\u88ab\u5378\u8f7d\u7a0b\u5e8f\u96c6\u4e2d\u67d0\u51fd\u6570\u7684delegate"),(0,l.kt)("li",{parentName:"ul"},"\u88ab\u5378\u8f7d\u7a0b\u5e8f\u96c6\u4e2d\u5b9a\u4e49\u7684\u5f02\u6b65Task"),(0,l.kt)("li",{parentName:"ul"},"\u5176\u4ed6")),(0,l.kt)("p",null,"\u5b9e\u9645\u5de5\u7a0b\u53ef\u80fd\u5f88\u590d\u6742\uff0c\u5f00\u53d1\u8005\u627e\u51fa\u6240\u6709\u975e\u6cd5\u5f15\u7528\u662f\u5f88\u56f0\u96be\u548c\u4e0d\u5207\u5b9e\u9645\u7684\u3002\u6211\u4eec\u5df2\u7ecf\u5b9e\u73b0\u4e86\u975e\u6cd5\u5f15\u7528\u68c0\u67e5\uff0c\u5378\u8f7d\u8fc7\u7a0b\u4e2d\u4f1a\u6253\u5370\u51fa\u6240\u6709\u975e\u6cd5\u5f15\u7528\u7684\u65e5\u5fd7\u3002\u5f00\u53d1\u8005\u6839\u636e\u6253\u5370\u7684\u65e5\u5fd7\u6e05\u9664\u6240\u6709\u975e\u6cd5\u5f15\u7528\u5373\u53ef\u3002"))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/099d81ac.a04c15f6.js b/assets/js/099d81ac.a04c15f6.js deleted file mode 100644 index e1ce2d92b..000000000 --- a/assets/js/099d81ac.a04c15f6.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[1977],{3905:(e,t,r)=>{r.d(t,{Zo:()=>p,kt:()=>b});var l=r(7294);function n(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function a(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);t&&(l=l.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,l)}return r}function i(e){for(var t=1;t=0||(n[r]=e[r]);return n}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(l=0;l=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}var s=l.createContext({}),u=function(e){var t=l.useContext(s),r=t;return e&&(r="function"==typeof e?e(t):i(i({},t),e)),r},p=function(e){var t=u(e.components);return l.createElement(s.Provider,{value:t},e.children)},m="mdxType",c={inlineCode:"code",wrapper:function(e){var t=e.children;return l.createElement(l.Fragment,{},t)}},d=l.forwardRef((function(e,t){var r=e.components,n=e.mdxType,a=e.originalType,s=e.parentName,p=o(e,["components","mdxType","originalType","parentName"]),m=u(r),d=n,b=m["".concat(s,".").concat(d)]||m[d]||c[d]||a;return r?l.createElement(b,i(i({ref:t},p),{},{components:r})):l.createElement(b,i({ref:t},p))}));function b(e,t){var r=arguments,n=t&&t.mdxType;if("string"==typeof e||n){var a=r.length,i=new Array(a);i[0]=d;var o={};for(var s in t)hasOwnProperty.call(t,s)&&(o[s]=t[s]);o.originalType=e,o[m]="string"==typeof e?e:n,i[1]=o;for(var u=2;u{r.r(t),r.d(t,{assets:()=>s,contentTitle:()=>i,default:()=>c,frontMatter:()=>a,metadata:()=>o,toc:()=>u});var l=r(7462),n=(r(7294),r(3905));const a={},i="\u70ed\u91cd\u8f7d\u6280\u672f",o={unversionedId:"business/reload/hotreloadassembly",id:"business/reload/hotreloadassembly",title:"\u70ed\u91cd\u8f7d\u6280\u672f",description:"\u70ed\u91cd\u8f7d\u6280\u672f\u7528\u4e8e\u5b8c\u5168\u5378\u8f7d\u6216\u8005\u91cd\u65b0\u52a0\u8f7d\u4e00\u4e2aassembly\uff0c\u9002\u7528\u4e8e\u5c0f\u6e38\u620f\u5408\u96c6\u7c7b\u578b\u7684\u6e38\u620f\u3002\u8be5\u65b9\u6848\u53ea\u63d0\u4f9b\u5546\u4e1a\u5316\u7248\u672c\u3002",source:"@site/docs/business/reload/hotreloadassembly.md",sourceDirName:"business/reload",slug:"/business/reload/hotreloadassembly",permalink:"/docs/business/reload/hotreloadassembly",draft:!1,tags:[],version:"current",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"\u5feb\u901f\u4e0a\u624b",permalink:"/docs/business/reload/quickstart"},next:{title:"\u514d\u8d39\u8bd5\u7528",permalink:"/docs/business/reload/freetrial"}},s={},u=[{value:"\u652f\u6301\u7684\u7279\u6027",id:"\u652f\u6301\u7684\u7279\u6027",level:2},{value:"\u4e0d\u652f\u6301\u7279\u6027\u53ca\u7279\u6b8a\u8981\u6c42",id:"\u4e0d\u652f\u6301\u7279\u6027\u53ca\u7279\u6b8a\u8981\u6c42",level:2},{value:"\u4e00\u4e9b\u4e0d\u517c\u5bb9\u7684\u5e93",id:"\u4e00\u4e9b\u4e0d\u517c\u5bb9\u7684\u5e93",level:2},{value:"\u89e3\u51b3\u88ab\u5378\u8f7d\u5bf9\u8c61\u7684\u5f15\u7528\u95ee\u9898",id:"\u89e3\u51b3\u88ab\u5378\u8f7d\u5bf9\u8c61\u7684\u5f15\u7528\u95ee\u9898",level:2}],p={toc:u},m="wrapper";function c(e){let{components:t,...r}=e;return(0,n.kt)(m,(0,l.Z)({},p,r,{components:t,mdxType:"MDXLayout"}),(0,n.kt)("h1",{id:"\u70ed\u91cd\u8f7d\u6280\u672f"},"\u70ed\u91cd\u8f7d\u6280\u672f"),(0,n.kt)("p",null,"\u70ed\u91cd\u8f7d\u6280\u672f\u7528\u4e8e\u5b8c\u5168\u5378\u8f7d\u6216\u8005\u91cd\u65b0\u52a0\u8f7d\u4e00\u4e2aassembly\uff0c\u9002\u7528\u4e8e\u5c0f\u6e38\u620f\u5408\u96c6\u7c7b\u578b\u7684\u6e38\u620f\u3002\u8be5\u65b9\u6848\u53ea\u63d0\u4f9b",(0,n.kt)("strong",{parentName:"p"},"\u5546\u4e1a\u5316\u7248\u672c"),"\u3002"),(0,n.kt)("h2",{id:"\u652f\u6301\u7684\u7279\u6027"},"\u652f\u6301\u7684\u7279\u6027"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"\u652f\u6301\u5378\u8f7dassembly\uff0c\u5378\u8f7d100%\u7684assembly\u6240\u5360\u7528\u7684\u5185\u5b58"),(0,n.kt)("li",{parentName:"ul"},"\u652f\u6301\u91cd\u65b0\u52a0\u8f7dassembly\uff0c\u4ee3\u7801\u53ef\u4ee5\u4efb\u610f\u53d8\u5316\u751a\u81f3\u5b8c\u5168\u4e0d\u540c\uff08MonoBehaviour\u548cScriptable\u6709\u4e00\u5b9a\u7684\u9650\u5236\uff09"),(0,n.kt)("li",{parentName:"ul"},"\u652f\u6301",(0,n.kt)("strong",{parentName:"li"},"\u9650\u5b9a\u70ed\u66f4\u65b0assembly\u4e2d\u80fd\u8bbf\u95ee\u7684\u51fd\u6570\u7684\u96c6\u5408"),"\uff0c\u9002\u5408UGC\u6e38\u620f\u4e2d\u521b\u5efa\u6c99\u76d2\u73af\u5883\uff0c\u907f\u514d\u6076\u610f\u73a9\u5bb6\u4ee3\u7801\u9020\u6210\u7834\u574f\u3002")),(0,n.kt)("h2",{id:"\u4e0d\u652f\u6301\u7279\u6027\u53ca\u7279\u6b8a\u8981\u6c42"},"\u4e0d\u652f\u6301\u7279\u6027\u53ca\u7279\u6b8a\u8981\u6c42"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"\u8981\u6c42\u4e1a\u52a1\u4ee3\u7801\u4e0d\u4f1a\u518d\u4f7f\u7528\u88ab\u5378\u8f7d\u7684Assembly\u4e2d\u7684\u5bf9\u8c61\u6216\u8005\u51fd\u6570\uff0c\u5e76\u4e14\u9000\u51fa\u6240\u6709\u5728\u6267\u884c\u7684\u65e7\u903b\u8f91"),(0,n.kt)("li",{parentName:"ul"},"\u4e0d\u80fd\u76f4\u63a5\u5378\u8f7d\u88ab\u4f9d\u8d56\u7684Assembly\uff0c\u5fc5\u987b\u6309\u7167\u9006\u4f9d\u8d56\u987a\u5e8f\u5148\u5378\u8f7d\u4f9d\u8d56\u8005\uff0c\u518d\u5378\u8f7d\u88ab\u4f9d\u8d56\u8005\u3002\u4f8b\u5982A.dll\u4f9d\u8d56B.dll\uff0c\u5219\u9700\u8981\u5148\u5378\u8f7dA.dll\uff0c\u518d\u5378\u8f7dB.dll"),(0,n.kt)("li",{parentName:"ul"},"MonoBehaviour\u548cScriptableObject\u76f8\u5173",(0,n.kt)("ul",{parentName:"li"},(0,n.kt)("li",{parentName:"ul"},"\u8981\u6c42\u91cd\u8f7d\u7684MonoBehaviour\u4e2d\u7684\u4e8b\u4ef6\u6216\u6d88\u606f\u51fd\u6570\u5982Awake\u3001OnEable\u4e4b\u7c7b\u4e0d\u53d1\u751f\u589e\u5220\uff08\u4f46\u51fd\u6570\u4f53\u53ef\u4ee5\u53d8\u5316\uff09"),(0,n.kt)("li",{parentName:"ul"},"\u8981\u6c42\u91cd\u8f7d\u540e\u5728\u65e7Assembly\u4e2d\u5b58\u5728\u540c\u540d\u811a\u672c\u7c7b\u7684\u5e8f\u5217\u5316\u5b57\u6bb5\u540d\u4e0d\u53d1\u751f\u53d8\u5316\uff08\u7c7b\u578b\u53ef\u4ee5\u53d8\uff09"),(0,n.kt)("li",{parentName:"ul"},"\u5982\u679c\u5b57\u6bb5\u7c7b\u578b\u4e3a\u53ef\u5378\u8f7d\u7a0b\u5e8f\u96c6\u4e2d\u7684\u81ea\u5b9a\u4e49\u7c7b\u578bA\uff08class\u6216struct\u6216enum\uff09\uff0c\u5fc5\u987b\u7ed9\u5b83\u52a0\u4e0a",(0,n.kt)("inlineCode",{parentName:"li"},"[Serializable]"),"\u7279\u6027"),(0,n.kt)("li",{parentName:"ul"},"\u4e0d\u652f\u6301\u5b57\u6bb5\u7c7b\u578b\u4e3a",(0,n.kt)("inlineCode",{parentName:"li"},"List<A>"),"\u5176\u4e2dA\u4e3a\u53ef\u5378\u8f7d\u7684\u7a0b\u5e8f\u96c6\u4e2d\u7684\u7c7b\u578b\uff0c\u8bf7\u66ff\u6362\u4e3a",(0,n.kt)("inlineCode",{parentName:"li"},"A[]")),(0,n.kt)("li",{parentName:"ul"},"\u4e0d\u80fd\u7ee7\u627f\u6cdb\u578b\u7c7b\u578b\uff0c\u4f8b\u5982",(0,n.kt)("inlineCode",{parentName:"li"},"class MyScript : CommonScript")))),(0,n.kt)("li",{parentName:"ul"},"\u4e00\u4e9b\u4f1a\u7f13\u5b58\u53cd\u5c04\u4fe1\u606f\u7684\u5e93\uff08\u8fd9\u79cd\u884c\u4e3a\u5728\u5e8f\u5217\u5316\u76f8\u5173\u7684\u5e93\u4e2d\u6700\u4e3a\u666e\u904d\uff0c\u5982LitJson\uff09\uff0c\u5728\u70ed\u91cd\u8f7d\u540e\u9700\u8981\u6e05\u7406\u6389\u7f13\u5b58\u7684\u53cd\u5c04\u4fe1\u606f"),(0,n.kt)("li",{parentName:"ul"},"\u4e0d\u652f\u6301\u6790\u6784\u51fd\u6570\uff0c~XXX()\u3002\u4e5f\u4e0d\u5141\u8bb8\u5b9e\u4f8b\u5316\u6cdb\u578b\u53c2\u6570\u5e26\u672c\u7a0b\u5e8f\u96c6\u7c7b\u578b\u7684\u5e26\u6790\u6784\u51fd\u6570\u7684\u6cdb\u578b\u7c7b"),(0,n.kt)("li",{parentName:"ul"},"\u4e0edots\u4e0d\u517c\u5bb9\u3002\u7531\u4e8edots\u5927\u91cf\u7f13\u5b58\u7684\u7c7b\u578b\u4fe1\u606f\uff0c\u5b9e\u73b0\u590d\u6742\uff0c\u5f88\u96be\u5355\u72ec\u6e05\u7406\u6389\u7f13\u5b58\u4fe1\u606f\u3002")),(0,n.kt)("h2",{id:"\u4e00\u4e9b\u4e0d\u517c\u5bb9\u7684\u5e93"},"\u4e00\u4e9b\u4e0d\u517c\u5bb9\u7684\u5e93"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"2022\u7684Jobs\u4f1a\u7f13\u5b58\u7c7b\u578b\u76f8\u5173\u4fe1\u606f\uff0c\u9700\u8981\u81ea\u884c\u5c0f\u5e45",(0,n.kt)("a",{parentName:"li",href:"/docs/business/reload/modifydll"},"\u4fee\u6539UnityEngine.CoreModule.dll"),"\u7684\u4ee3\u7801\u3002 \u4f4e\u4e8e2022\u7684\u7248\u672c\u4e0d\u9700\u8981\u4fee\u6539"),(0,n.kt)("li",{parentName:"ul"},"LitJson\u4e4b\u7c7b\u7684\u53cd\u5e8f\u5217\u5316\u5e93\u4f1a\u7f13\u5b58\u53cd\u5c04\u4fe1\u606f\uff0c\u9700\u8981\u5728\u70ed\u91cd\u8f7d\u540e\u6e05\u7406\u6389\u5e93\u4e2d\u7f13\u5b58\u7684\u53cd\u5c04\u4fe1\u606f\uff0c\u5177\u4f53\u64cd\u4f5c\u8ddf\u5e93\u7684\u5b9e\u73b0\u76f8\u5173")),(0,n.kt)("h2",{id:"\u89e3\u51b3\u88ab\u5378\u8f7d\u5bf9\u8c61\u7684\u5f15\u7528\u95ee\u9898"},"\u89e3\u51b3\u88ab\u5378\u8f7d\u5bf9\u8c61\u7684\u5f15\u7528\u95ee\u9898"),(0,n.kt)("p",null,"\u70ed\u91cd\u8f7d\u6280\u672f\u8981\u6c42\u5728\u672a\u5378\u8f7d\u7684\u7a0b\u5e8f\u96c6\u6216\u8005\u5168\u5c40\u5185\u5b58\u4e2d\u4e0d\u80fd\u6301\u6709\u5df2\u5378\u8f7d\u7684\u7a0b\u5e8f\u96c6U\u7684\u5143\u6570\u636e\u3002\u5305\u62ec\u4f46\u4e0d\u9650\u4e8e\uff1a"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"\u88ab\u5378\u8f7d\u7a0b\u5e8f\u96c6\u7684\u7c7b\u578b\u7684\u5b9e\u4f8b"),(0,n.kt)("li",{parentName:"ul"},"\u6cdb\u578b\u7c7b\u6216\u8005\u51fd\u6570\u7684\u6cdb\u578b\u53c2\u6570\u4e2d\u5305\u542b\u88ab\u5378\u8f7d\u7a0b\u5e8f\u96c6\u7684\u7c7b\u578b"),(0,n.kt)("li",{parentName:"ul"},"\u88ab\u5378\u8f7d\u7a0b\u5e8f\u96c6\u76f8\u5173\u7684\u53cd\u5c04\u4fe1\u606f\uff0c\u5982Assembly\u3001Type\u3001MethodInfo\u3001PropertyInfo\u7b49\u7b49"),(0,n.kt)("li",{parentName:"ul"},"\u6307\u5411\u88ab\u5378\u8f7d\u7a0b\u5e8f\u96c6\u4e2d\u67d0\u51fd\u6570\u7684delegate"),(0,n.kt)("li",{parentName:"ul"},"\u88ab\u5378\u8f7d\u7a0b\u5e8f\u96c6\u4e2d\u5b9a\u4e49\u7684\u5f02\u6b65Task"),(0,n.kt)("li",{parentName:"ul"},"\u5176\u4ed6")),(0,n.kt)("p",null,"\u5b9e\u9645\u5de5\u7a0b\u53ef\u80fd\u5f88\u590d\u6742\uff0c\u5f00\u53d1\u8005\u627e\u51fa\u6240\u6709\u975e\u6cd5\u5f15\u7528\u662f\u5f88\u56f0\u96be\u548c\u4e0d\u5207\u5b9e\u9645\u7684\u3002\u6211\u4eec\u5df2\u7ecf\u5b9e\u73b0\u4e86\u975e\u6cd5\u5f15\u7528\u68c0\u67e5\uff0c\u5378\u8f7d\u8fc7\u7a0b\u4e2d\u4f1a\u6253\u5370\u51fa\u6240\u6709\u975e\u6cd5\u5f15\u7528\u7684\u65e5\u5fd7\u3002\u5f00\u53d1\u8005\u6839\u636e\u6253\u5370\u7684\u65e5\u5fd7\u6e05\u9664\u6240\u6709\u975e\u6cd5\u5f15\u7528\u5373\u53ef\u3002"))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/2e1b2baa.0ad87c0e.js b/assets/js/2e1b2baa.0ad87c0e.js deleted file mode 100644 index cdd4e53b0..000000000 --- a/assets/js/2e1b2baa.0ad87c0e.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[7972],{3905:(e,t,r)=>{r.d(t,{Zo:()=>p,kt:()=>f});var n=r(7294);function o(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function i(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function a(e){for(var t=1;t=0||(o[r]=e[r]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(o[r]=e[r])}return o}var s=n.createContext({}),c=function(e){var t=n.useContext(s),r=t;return e&&(r="function"==typeof e?e(t):a(a({},t),e)),r},p=function(e){var t=c(e.components);return n.createElement(s.Provider,{value:t},e.children)},u="mdxType",m={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},d=n.forwardRef((function(e,t){var r=e.components,o=e.mdxType,i=e.originalType,s=e.parentName,p=l(e,["components","mdxType","originalType","parentName"]),u=c(r),d=o,f=u["".concat(s,".").concat(d)]||u[d]||m[d]||i;return r?n.createElement(f,a(a({ref:t},p),{},{components:r})):n.createElement(f,a({ref:t},p))}));function f(e,t){var r=arguments,o=t&&t.mdxType;if("string"==typeof e||o){var i=r.length,a=new Array(i);a[0]=d;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[u]="string"==typeof e?e:o,a[1]=l;for(var c=2;c{r.r(t),r.d(t,{assets:()=>s,contentTitle:()=>a,default:()=>m,frontMatter:()=>i,metadata:()=>l,toc:()=>c});var n=r(7462),o=(r(7294),r(3905));const i={},a="\u5e38\u89c1\u95ee\u9898",l={unversionedId:"business/reload/commonerrors",id:"business/reload/commonerrors",title:"\u5e38\u89c1\u95ee\u9898",description:"Json\u5e8f\u5217\u5316\u7684\u95ee\u9898",source:"@site/docs/business/reload/commonerrors.md",sourceDirName:"business/reload",slug:"/business/reload/commonerrors",permalink:"/docs/business/reload/commonerrors",draft:!1,tags:[],version:"current",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"\u514d\u8d39\u8bd5\u7528",permalink:"/docs/business/reload/freetrial"},next:{title:"\u5e2e\u52a9",permalink:"/docs/help"}},s={},c=[{value:"Json\u5e8f\u5217\u5316\u7684\u95ee\u9898",id:"json\u5e8f\u5217\u5316\u7684\u95ee\u9898",level:2}],p={toc:c},u="wrapper";function m(e){let{components:t,...r}=e;return(0,o.kt)(u,(0,n.Z)({},p,r,{components:t,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"\u5e38\u89c1\u95ee\u9898"},"\u5e38\u89c1\u95ee\u9898"),(0,o.kt)("h2",{id:"json\u5e8f\u5217\u5316\u7684\u95ee\u9898"},"Json\u5e8f\u5217\u5316\u7684\u95ee\u9898"),(0,o.kt)("p",null," \u7a0b\u5e8f\u96c6\u5378\u8f7d\u540e\uff0c\u4f1a\u5378\u8f7d\u6240\u6709\u7c7b\u578b\u5143\u6570\u636e\u3002\u800c\u51e0\u4e4e\u6240\u6709\u5e38\u7528\u7684\u5e8f\u5217\u5316\u5e93\u90fd\u4f1a\u7f13\u5b58\u7c7b\u578b\u7684\u53cd\u5c04\u4fe1\u606f\uff0c\u8fd9\u610f\u5473\u7740\u5982\u679c\u4f60\u5728\u4ee3\u7801\u4e2d\u4f7f\u7528\u4e86Unity\u7684JsonUtility\u6216\u8005LitJson\u4e4b\u7c7b\u7684\n\u5e8f\u5217\u5e93\uff0c\u5b83\u4eec\u4f1a\u9519\u8bef\u5730\u7f13\u5b58\u53cd\u5c04\u4fe1\u606f\uff0c\u5bfc\u81f4\u4f60\u7b2c\u4e8c\u6b21\uff08\u6216\u8005\u7b2c\u4e09\u6b21\uff09\u91cd\u65b0\u52a0\u8f7d\uff0c\u5e76\u4e14\u53cd\u5e8f\u5217\u5316\u65f6\uff0c\u4f1a\u51fa\u9519\u3002"),(0,o.kt)("p",null,"\u89e3\u51b3\u529e\u6cd5\u6709\u51e0\u79cd\uff1a"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"\u7ed9\u88ab\u5e8f\u5217\u5316\u6216\u8005\u53cd\u5e8f\u5217\u5316\u7684\u7c7b\u578b\u52a0\u4e0a",(0,o.kt)("inlineCode",{parentName:"li"},"[Serializable]"),"\u7279\u6027\uff0c\u5982\u679c\u8fd9\u4e9b\u7c7b\u578b\u4e2d\u6210\u5458\u5b57\u6bb5\u7684\u7c7b\u578b\u4e5f\u662fclass\u7c7b\u578b",(0,o.kt)("inlineCode",{parentName:"li"},"A"),"\u6216\u8005",(0,o.kt)("inlineCode",{parentName:"li"},"List<A>"),"\uff0c\u5219\u4e5f\u8981\u7ed9",(0,o.kt)("inlineCode",{parentName:"li"},"A"),"\u4e5f\u52a0\u4e0a",(0,o.kt)("inlineCode",{parentName:"li"},"[Serializable]")),(0,o.kt)("li",{parentName:"ul"},"\u4fee\u6539\u8fd9\u4e9b\u53cd\u5e8f\u5217\u5316\u5e93\u7684\u4ee3\u7801\uff0c\u5728\u5378\u8f7d\u7a0b\u5e8f\u96c6\u540e\uff0c\u6e05\u7a7a\u5b83\u4eec\u7684\u53cd\u5c04\u7f13\u5b58\u3002\u50cfUnity\u7684JsonUtility\u662fnative\u5b9e\u73b0\uff0c\u65e0\u6cd5\u6e05\u7a7a\u7f13\u5b58\uff0c\u53ea\u80fd\u66f4\u6362\u4e3a\u5176\u4ed6Json\u5e93\u3002")))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/2e1b2baa.add9dc57.js b/assets/js/2e1b2baa.add9dc57.js new file mode 100644 index 000000000..3bb49007d --- /dev/null +++ b/assets/js/2e1b2baa.add9dc57.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[7972],{3905:(e,r,t)=>{t.d(r,{Zo:()=>p,kt:()=>f});var n=t(7294);function o(e,r,t){return r in e?Object.defineProperty(e,r,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[r]=t,e}function i(e,r){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);r&&(n=n.filter((function(r){return Object.getOwnPropertyDescriptor(e,r).enumerable}))),t.push.apply(t,n)}return t}function a(e){for(var r=1;r=0||(o[t]=e[t]);return o}(e,r);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(o[t]=e[t])}return o}var s=n.createContext({}),c=function(e){var r=n.useContext(s),t=r;return e&&(t="function"==typeof e?e(r):a(a({},r),e)),t},p=function(e){var r=c(e.components);return n.createElement(s.Provider,{value:r},e.children)},u="mdxType",m={inlineCode:"code",wrapper:function(e){var r=e.children;return n.createElement(n.Fragment,{},r)}},d=n.forwardRef((function(e,r){var t=e.components,o=e.mdxType,i=e.originalType,s=e.parentName,p=l(e,["components","mdxType","originalType","parentName"]),u=c(t),d=o,f=u["".concat(s,".").concat(d)]||u[d]||m[d]||i;return t?n.createElement(f,a(a({ref:r},p),{},{components:t})):n.createElement(f,a({ref:r},p))}));function f(e,r){var t=arguments,o=r&&r.mdxType;if("string"==typeof e||o){var i=t.length,a=new Array(i);a[0]=d;var l={};for(var s in r)hasOwnProperty.call(r,s)&&(l[s]=r[s]);l.originalType=e,l[u]="string"==typeof e?e:o,a[1]=l;for(var c=2;c{t.r(r),t.d(r,{assets:()=>s,contentTitle:()=>a,default:()=>m,frontMatter:()=>i,metadata:()=>l,toc:()=>c});var n=t(7462),o=(t(7294),t(3905));const i={},a="\u5e38\u89c1\u95ee\u9898",l={unversionedId:"business/reload/commonerrors",id:"business/reload/commonerrors",title:"\u5e38\u89c1\u95ee\u9898",description:"Json\u5e8f\u5217\u5316\u7684\u95ee\u9898",source:"@site/docs/business/reload/commonerrors.md",sourceDirName:"business/reload",slug:"/business/reload/commonerrors",permalink:"/docs/business/reload/commonerrors",draft:!1,tags:[],version:"current",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"\u514d\u8d39\u8bd5\u7528",permalink:"/docs/business/reload/freetrial"},next:{title:"\u5e2e\u52a9",permalink:"/docs/help"}},s={},c=[{value:"Json\u5e8f\u5217\u5316\u7684\u95ee\u9898",id:"json\u5e8f\u5217\u5316\u7684\u95ee\u9898",level:2}],p={toc:c},u="wrapper";function m(e){let{components:r,...t}=e;return(0,o.kt)(u,(0,n.Z)({},p,t,{components:r,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"\u5e38\u89c1\u95ee\u9898"},"\u5e38\u89c1\u95ee\u9898"),(0,o.kt)("h2",{id:"json\u5e8f\u5217\u5316\u7684\u95ee\u9898"},"Json\u5e8f\u5217\u5316\u7684\u95ee\u9898"),(0,o.kt)("p",null," \u7a0b\u5e8f\u96c6\u5378\u8f7d\u540e\uff0c\u4f1a\u5378\u8f7d\u6240\u6709\u7c7b\u578b\u5143\u6570\u636e\u3002\u800c\u51e0\u4e4e\u6240\u6709\u5e38\u7528\u7684\u5e8f\u5217\u5316\u5e93\u90fd\u4f1a\u7f13\u5b58\u7c7b\u578b\u7684\u53cd\u5c04\u4fe1\u606f\uff0c\u8fd9\u610f\u5473\u7740\u5982\u679c\u4f60\u5728\u4ee3\u7801\u4e2d\u4f7f\u7528\u4e86Unity\u7684JsonUtility\u6216\u8005LitJson\u4e4b\u7c7b\u7684\n\u5e8f\u5217\u5e93\uff0c\u5b83\u4eec\u4f1a\u9519\u8bef\u5730\u7f13\u5b58\u53cd\u5c04\u4fe1\u606f\uff0c\u5bfc\u81f4\u4f60\u7b2c\u4e8c\u6b21\uff08\u6216\u8005\u7b2c\u4e09\u6b21\uff09\u91cd\u65b0\u52a0\u8f7d\uff0c\u5e76\u4e14\u53cd\u5e8f\u5217\u5316\u65f6\uff0c\u4f1a\u51fa\u9519\u3002"),(0,o.kt)("p",null,"\u89e3\u51b3\u529e\u6cd5\u6709\u51e0\u79cd\uff1a"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"\u7ed9\u88ab\u5e8f\u5217\u5316\u6216\u8005\u53cd\u5e8f\u5217\u5316\u7684\u7c7b\u578b\u52a0\u4e0a",(0,o.kt)("inlineCode",{parentName:"li"},"[Serializable]"),"\u7279\u6027\uff0c\u5982\u679c\u8fd9\u4e9b\u7c7b\u578b\u4e2d\u6210\u5458\u5b57\u6bb5\u7684\u7c7b\u578b\u4e5f\u662fclass\u7c7b\u578b",(0,o.kt)("inlineCode",{parentName:"li"},"A"),"\u6216\u8005",(0,o.kt)("inlineCode",{parentName:"li"},"A[]"),"\uff0c\u5219\u4e5f\u8981\u7ed9",(0,o.kt)("inlineCode",{parentName:"li"},"A"),"\u4e5f\u52a0\u4e0a",(0,o.kt)("inlineCode",{parentName:"li"},"[Serializable]")),(0,o.kt)("li",{parentName:"ul"},"\u4fee\u6539\u8fd9\u4e9b\u53cd\u5e8f\u5217\u5316\u5e93\u7684\u4ee3\u7801\uff0c\u5728\u5378\u8f7d\u7a0b\u5e8f\u96c6\u540e\uff0c\u6e05\u7a7a\u5b83\u4eec\u7684\u53cd\u5c04\u7f13\u5b58\u3002\u50cfUnity\u7684JsonUtility\u662fnative\u5b9e\u73b0\uff0c\u65e0\u6cd5\u6e05\u7a7a\u7f13\u5b58\uff0c\u53ea\u80fd\u66f4\u6362\u4e3a\u5176\u4ed6Json\u5e93\u3002")))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/3d345fd1.16561613.js b/assets/js/3d345fd1.3cf57c5c.js similarity index 79% rename from assets/js/3d345fd1.16561613.js rename to assets/js/3d345fd1.3cf57c5c.js index 9a4e87b74..bbba7d469 100644 --- a/assets/js/3d345fd1.16561613.js +++ b/assets/js/3d345fd1.3cf57c5c.js @@ -1 +1 @@ -"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[9106],{3905:(e,t,n)=>{n.d(t,{Zo:()=>c,kt:()=>b});var r=n(7294);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function l(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var s=r.createContext({}),p=function(e){var t=r.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):l(l({},t),e)),n},c=function(e){var t=p(e.components);return r.createElement(s.Provider,{value:t},e.children)},m="mdxType",u={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},d=r.forwardRef((function(e,t){var n=e.components,a=e.mdxType,o=e.originalType,s=e.parentName,c=i(e,["components","mdxType","originalType","parentName"]),m=p(n),d=a,b=m["".concat(s,".").concat(d)]||m[d]||u[d]||o;return n?r.createElement(b,l(l({ref:t},c),{},{components:n})):r.createElement(b,l({ref:t},c))}));function b(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var o=n.length,l=new Array(o);l[0]=d;var i={};for(var s in t)hasOwnProperty.call(t,s)&&(i[s]=t[s]);i.originalType=e,i[m]="string"==typeof e?e:a,l[1]=i;for(var p=2;p{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>l,default:()=>u,frontMatter:()=>o,metadata:()=>i,toc:()=>p});var r=n(7462),a=(n(7294),n(3905));const o={},l="MonoBehaviour\u652f\u6301",i={unversionedId:"basic/monobehaviour",id:"basic/monobehaviour",title:"MonoBehaviour\u652f\u6301",description:"HybridCLR\u5b8c\u5168\u652f\u6301\u70ed\u66f4\u65b0MonoBehaviour\u548cScriptableObject\u5de5\u4f5c\u6d41\uff0c\u5373\u53ef\u4ee5\u5728\u4ee3\u7801\u91cc\u5728GameObject\u4e0aAdd\u70ed\u66f4\u65b0\u811a\u672c\u6216\u8005\u5728\u8d44\u6e90\u4e0a\u76f4\u63a5\u6302\u8f7d",source:"@site/docs/basic/monobehaviour.md",sourceDirName:"basic",slug:"/basic/monobehaviour",permalink:"/docs/basic/monobehaviour",draft:!1,tags:[],version:"current",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"\u4ee3\u7801\u88c1\u526a",permalink:"/docs/basic/codestriping"},next:{title:"AOT\u6cdb\u578b",permalink:"/docs/basic/aotgeneric"}},s={},p=[{value:"\u901a\u8fc7\u4ee3\u7801\u4f7f\u7528",id:"\u901a\u8fc7\u4ee3\u7801\u4f7f\u7528",level:2},{value:"\u5728\u8d44\u6e90\u4e0a\u6302\u8f7dMonoBehaviour\u6216\u8005\u521b\u5efaScriptableObject\u7c7b\u578b\u8d44\u6e90",id:"\u5728\u8d44\u6e90\u4e0a\u6302\u8f7dmonobehaviour\u6216\u8005\u521b\u5efascriptableobject\u7c7b\u578b\u8d44\u6e90",level:2},{value:"assembly\u5217\u8868\u6587\u4ef6",id:"assembly\u5217\u8868\u6587\u4ef6",level:2},{value:"\u5df2\u77e5\u95ee\u9898",id:"\u5df2\u77e5\u95ee\u9898",level:2},{value:"\u4e3b\u7ebf\u7a0bAddComponent\u53ca\u5176\u4ed6\u8d44\u6e90\u52a0\u8f7d\u7ebf\u7a0b\u52a0\u8f7d\u5305\u542b\u70ed\u66f4\u65b0\u811a\u672c\u7684\u8d44\u6e90\u540c\u65f6\u8fdb\u884c\u65f6\u5076\u53d1\u7684\u5d29\u6e83\u95ee\u9898",id:"\u4e3b\u7ebf\u7a0baddcomponent\u53ca\u5176\u4ed6\u8d44\u6e90\u52a0\u8f7d\u7ebf\u7a0b\u52a0\u8f7d\u5305\u542b\u70ed\u66f4\u65b0\u811a\u672c\u7684\u8d44\u6e90\u540c\u65f6\u8fdb\u884c\u65f6\u5076\u53d1\u7684\u5d29\u6e83\u95ee\u9898",level:3},{value:"GameObject.GetComponent(string name) \u63a5\u53e3\u65e0\u6cd5\u83b7\u5f97\u7ec4\u4ef6",id:"gameobjectgetcomponentstring-name-\u63a5\u53e3\u65e0\u6cd5\u83b7\u5f97\u7ec4\u4ef6",level:3},{value:"\u5176\u5b83",id:"\u5176\u5b83",level:2}],c={toc:p},m="wrapper";function u(e){let{components:t,...n}=e;return(0,a.kt)(m,(0,r.Z)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("h1",{id:"monobehaviour\u652f\u6301"},"MonoBehaviour\u652f\u6301"),(0,a.kt)("p",null,"HybridCLR\u5b8c\u5168\u652f\u6301\u70ed\u66f4\u65b0MonoBehaviour\u548cScriptableObject\u5de5\u4f5c\u6d41\uff0c\u5373\u53ef\u4ee5\u5728\u4ee3\u7801\u91cc\u5728GameObject\u4e0aAdd\u70ed\u66f4\u65b0\u811a\u672c\u6216\u8005\u5728\u8d44\u6e90\u4e0a\u76f4\u63a5\u6302\u8f7d\n\u70ed\u66f4\u65b0\u811a\u672c\u3002\u4f46\u7531\u4e8eUnity\u8d44\u6e90\u7ba1\u7406\u673a\u5236\u7684\u7279\u6b8a\u6027\uff0c\u5bf9\u4e8e\u8d44\u6e90\u4e0a\u6302\u8f7d\u70ed\u66f4\u65b0\u811a\u672c\uff0c\u9700\u8981\u6253\u5305\u5de5\u4f5c\u6d41\u4e0a\u4f5c\u4e00\u4e9b\u7279\u6b8a\u5904\u7406\u3002"),(0,a.kt)("h2",{id:"\u901a\u8fc7\u4ee3\u7801\u4f7f\u7528"},"\u901a\u8fc7\u4ee3\u7801\u4f7f\u7528"),(0,a.kt)("p",null,(0,a.kt)("inlineCode",{parentName:"p"},"AddComponent()"),"\u6216\u8005",(0,a.kt)("inlineCode",{parentName:"p"},"AddComponent(Type type)"),"\u4efb\u4f55\u65f6\u5019\u90fd\u662f\u5b8c\u7f8e\u652f\u6301\u7684\u3002\u53ea\u9700\u8981\u63d0\u524d\u901a\u8fc7Assembly.Load\u5c06\u70ed\u66f4\u65b0dll\u52a0\u8f7d\u5230\u8fd0\u884c\u65f6\n\u5185\u5373\u53ef\u3002"),(0,a.kt)("h2",{id:"\u5728\u8d44\u6e90\u4e0a\u6302\u8f7dmonobehaviour\u6216\u8005\u521b\u5efascriptableobject\u7c7b\u578b\u8d44\u6e90"},"\u5728\u8d44\u6e90\u4e0a\u6302\u8f7dMonoBehaviour\u6216\u8005\u521b\u5efaScriptableObject\u7c7b\u578b\u8d44\u6e90"),(0,a.kt)("p",null,"Unity\u8d44\u6e90\u7ba1\u7406\u7cfb\u7edf\u5728\u53cd\u5e8f\u5217\u5316\u8d44\u6e90\u4e2d\u7684\u70ed\u66f4\u65b0\u811a\u672c\u65f6\uff0c\u9700\u8981\u6ee1\u8db3\u4ee5\u4e0b\u6761\u4ef6\uff1a"),(0,a.kt)("ol",null,(0,a.kt)("li",{parentName:"ol"},"\u811a\u672c\u6240\u5728\u7684dll\u5df2\u7ecf\u52a0\u8f7d\u5230\u8fd0\u884c\u65f6\u4e2d"),(0,a.kt)("li",{parentName:"ol"},"\u5fc5\u987b\u662f\u4f7f\u7528AssetBundle\u6253\u5305\u7684\u8d44\u6e90\uff08",(0,a.kt)("strong",{parentName:"li"},"addressable\u4e4b\u7c7b\u95f4\u63a5\u4f7f\u7528\u4e86ab\u7684\u6846\u67b6\u4e5f\u53ef\u4ee5"),"\uff09"),(0,a.kt)("li",{parentName:"ol"},"\u811a\u672c\u6240\u5728\u7684dll\u5fc5\u987b\u6dfb\u52a0\u5230\u6253\u5305\u65f6\u751f\u6210\u7684assembly\u5217\u8868\u6587\u4ef6\u3002\u8fd9\u4e2a\u5217\u8868\u6587\u4ef6\u662funity\u542f\u52a8\u65f6\u5373\u52a0\u8f7d\u7684\uff0c\u4e0d\u53ef\u53d8\u6570\u636e\u3002\u4e0d\u540c\u7248\u672c\u7684Unity\u7684\u5217\u8868\u6587\u4ef6\u540d\u548c\u683c\u5f0f\u4e0d\u76f8\u540c\u3002")),(0,a.kt)("p",null,"\u5982\u679c\u672a\u5bf9\u6253\u5305\u6d41\u7a0b\u4f5c\u4efb\u4f55\u5904\u7406\uff0c\u7531\u4e8e\u70ed\u66f4\u65b0dll\u5df2\u7ecf\u5728",(0,a.kt)("inlineCode",{parentName:"p"},"IFilterBuildAssemblies"),"\u56de\u8c03\u4e2d\u88ab\u79fb\u9664\uff0c\u80af\u5b9a\u4e0d\u4f1a\u51fa\u73b0\u5728assembly\u5217\u8868\u6587\u4ef6\u4e2d\u3002\n\u7531\u4e8e\u4e0d\u6ee1\u8db3\u6761\u4ef63\uff0c\u6302\u8f7d\u5728\u70ed\u66f4\u65b0\u8d44\u6e90\u4e2d\u7684\u70ed\u66f4\u65b0\u811a\u672c\u65e0\u6cd5\u88ab\u8fd8\u539f\uff0c\u8fd0\u884c\u65f6\u4f1a\u51fa\u73b0 ",(0,a.kt)("inlineCode",{parentName:"p"},"Scripting Missing"),"\u7684\u9519\u8bef\u3002"),(0,a.kt)("p",null,"\u56e0\u6b64\u6211\u4eec\u5728",(0,a.kt)("inlineCode",{parentName:"p"},"Editor/BuildProcessors/PatchScriptingAssemblyList.cs")," \u811a\u672c\u4e2d\u4f5c\u4e86\u7279\u6b8a\u5904\u7406\uff0c\u628a\u70ed\u66f4\u65b0dll\u52a0\u5165\u5230assembly\u5217\u8868\u6587\u4ef6\u4e2d\u3002\n\u4f60\u9700\u8981\u628a\u9879\u76ee\u4e2d\u7684\u70ed\u66f4\u65b0assembly\u6dfb\u52a0\u5230",(0,a.kt)("inlineCode",{parentName:"p"},"HybridCLRSettings\u914d\u7f6e\u7684HotUpdateAssemblyDefinitions\u6216HotUpdateAssemblies \u5b57\u6bb5"),"\u4e2d\u3002"),(0,a.kt)("p",null,"\u53ea\u9650\u5236\u4e86\u70ed\u66f4\u65b0\u8d44\u6e90\u4ee5ab\u5305\u5f62\u5f0f\u6253\u5305\uff0c\u70ed\u66f4\u65b0dll\u6253\u5305\u65b9\u5f0f\u6ca1\u6709\u9650\u5236\u3002\u4f60\u53ef\u4ee5\u6309\u7167\u9879\u76ee\u9700\u6c42",(0,a.kt)("strong",{parentName:"p"},"\u81ea\u7531\u9009\u62e9\u70ed\u66f4\u65b0\u65b9\u5f0f"),"\uff0c\u53ef\u4ee5\u5c06dll\u6253\u5305\u5230ab\u4e2d\uff0c\u6216\u8005\u88f8\u6570\u636e\n\u6587\u4ef6\uff0c\u6216\u8005\u52a0\u5bc6\u538b\u7f29\u7b49\u7b49\u3002\u53ea\u8981\u80fd\u4fdd\u8bc1\u5728\u52a0\u8f7d\u70ed\u66f4\u65b0\u8d44\u6e90\u524d\u4f7f\u7528Assembly.Load\u5c06\u5176\u52a0\u8f7d\u5373\u53ef\u3002"),(0,a.kt)("admonition",{type:"warning"},(0,a.kt)("p",{parentName:"admonition"},(0,a.kt)("strong",{parentName:"p"},"\u5982\u679c\u5c06\u70ed\u66f4\u65b0\u811a\u672c\u6302\u8f7d\u5230Resources\u7b49\u968f\u4e3b\u5305\u7684\u8d44\u6e90\u4e0a\uff0c\u4f1a\u53d1\u751fscripting missing\u7684\u9519\u8bef\uff01"),"\u4f46\u5982\u679c\u5148\u6253\u6210assetbundle\u5305\uff0c\u518d\u653e\u5230Resources\u4e0b\uff0c\u8fd0\u884c\u65f6\u52a0\u8f7d\u8be5\u968f\u5305assetbundle\u5219\u6ca1\u6709\u95ee\u9898\u3002")),(0,a.kt)("h2",{id:"assembly\u5217\u8868\u6587\u4ef6"},"assembly\u5217\u8868\u6587\u4ef6"),(0,a.kt)("p",null,"\u4e0d\u540cUnity\u7248\u672c\u4e0bassembly\u5217\u8868\u6587\u4ef6\u7684\u540d\u79f0\u548c\u683c\u5f0f\u90fd\u4e0d\u4e00\u6837\u3002"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"2019\u7248\u672c\u3002 \u975e\u538b\u7f29\u6253\u5305\u65f6\u4e3aglobalgamemanagers\u6587\u4ef6\uff0c\u538b\u7f29\u6253\u5305\u65f6\u5148\u4fdd\u5b58\u5230globalgamemanagers\u6587\u4ef6\uff0c\u518d\u4ee5BundleFile\u683c\u5f0f\u548c\u5176\u4ed6\u6587\u4ef6\u6253\u5305\u5230data.unity3d\u6587\u4ef6\u3002"),(0,a.kt)("li",{parentName:"ul"},"2020-2021\u7248\u672c\u3002 \u4fdd\u5b58\u5728ScriptingAssembles.json\u6587\u4ef6\u4e2d\u3002")),(0,a.kt)("h2",{id:"\u5df2\u77e5\u95ee\u9898"},"\u5df2\u77e5\u95ee\u9898"),(0,a.kt)("h3",{id:"\u4e3b\u7ebf\u7a0baddcomponent\u53ca\u5176\u4ed6\u8d44\u6e90\u52a0\u8f7d\u7ebf\u7a0b\u52a0\u8f7d\u5305\u542b\u70ed\u66f4\u65b0\u811a\u672c\u7684\u8d44\u6e90\u540c\u65f6\u8fdb\u884c\u65f6\u5076\u53d1\u7684\u5d29\u6e83\u95ee\u9898"},"\u4e3b\u7ebf\u7a0bAddComponent\u53ca\u5176\u4ed6\u8d44\u6e90\u52a0\u8f7d\u7ebf\u7a0b\u52a0\u8f7d\u5305\u542b\u70ed\u66f4\u65b0\u811a\u672c\u7684\u8d44\u6e90\u540c\u65f6\u8fdb\u884c\u65f6\u5076\u53d1\u7684\u5d29\u6e83\u95ee\u9898"),(0,a.kt)("p",null,"\u6b64\u95ee\u9898\u6765\u81ea",(0,a.kt)("a",{parentName:"p",href:"https://github.com/focus-creative-games/hybridclr/issues/96"},"issue"),"\u62a5\u544a\u3002"),(0,a.kt)("p",null,"\u5728\u7b2c\u4e00\u6b21\u4f7f\u7528\u67d0\u70ed\u66f4\u65b0\u7c7b\u578b\u65f6\uff08\u4e3b\u7ebf\u7a0bAddComponent\u6216\u8005\u8d44\u6e90\u7ebf\u7a0b\u52a0\u8f7d\u542b\u811a\u672c\u7684\u8d44\u6e90\uff09\u4f1a\u89e6\u53d1\u5f15\u64ce\u521b\u5efaMonoScript\u6570\u636e\uff0c\u7136\u800c\u6b64\u64cd\u4f5c\u5e76\u975e\u7ebf\u7a0b\u5b89\u5168\u3002\u7531\u4e8e\u672a\u63a5\u5165hybridclr\u65f6\uff0c\u6240\u6709\u811a\u672c\u90fd\u5728\u542f\u52a8\u65f6\u5df2\u7ecf\u521d\u59cb\u5316\uff0c\u56e0\u6b64\u4e0d\u4f1a\u6709\u7ebf\u7a0b\u5b89\u5168\u95ee\u9898\u3002\u5f53\u63a5\u5165hybridclr\u540e\uff0c\u5728\u5076\u7136\u60c5\u51b5\u4e0b\uff08\u5c24\u5176\u662f\u52a0\u8f7d\u5305\u542b\u5927\u91cf\u811a\u672c\u7684\u8d44\u6e90\uff09\u4f1a\u89e6\u53d1\u8fd9\u4e2a\u95ee\u9898\u3002"),(0,a.kt)("p",null,"\u89e3\u51b3\u529e\u6cd5\uff1a"),(0,a.kt)("p",null,"\u52a0\u8f7d\u5b8c\u70ed\u66f4\u65b0\u7a0b\u5e8f\u96c6\u540e\uff0c\u901a\u8fc7\u4e34\u65f6\u521b\u5efa\u7684GameObject,\u628a\u6240\u6709\u70ed\u66f4\u65b0\u811a\u672c\u90fd\u6dfb\u52a0\u4e00\u904d\uff0c\u7c7b\u4f3c\u8fd9\u6837\uff1a"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-csharp"}," var go = new GameObject();\n foreach (var type in hotUpdateAss.GetTypes())\n {\n if (type.IsAssignTo(typeof(MonoBehaviour)))\n {\n go.AddComponent(type);\n }\n }\n GameObject.Destroy(go);\n\n")),(0,a.kt)("h3",{id:"gameobjectgetcomponentstring-name-\u63a5\u53e3\u65e0\u6cd5\u83b7\u5f97\u7ec4\u4ef6"},"GameObject.GetComponent(string name) \u63a5\u53e3\u65e0\u6cd5\u83b7\u5f97\u7ec4\u4ef6"),(0,a.kt)("p",null,"\u8fd9\u662f\u5df2\u77e5bug,\u8ddfunity\u7684\u4ee3\u7801\u5b9e\u73b0\u6709\u5173\uff0c\u53ea\u6709\u6302\u8f7d\u5728\u70ed\u66f4\u65b0\u8d44\u6e90\u4e0a\u70ed\u66f4\u65b0\u811a\u672c\u624d\u4f1a\u6709\u8fd9\u4e2a\u95ee\u9898\uff0c\u901a\u8fc7\u4ee3\u7801\u4e2dAddComponent\u6dfb\u52a0\u7684\u70ed\u66f4\u65b0\u811a\u672c\u662f\u53ef\u4ee5\u7528\u8fd9\u4e2a\u65b9\u6cd5\u67e5\u627e\u5230\u3002\u5982\u679c\u9047\u5230\u8fd9\u4e2a\u95ee\u9898\u8bf7\u6539\u7528 ",(0,a.kt)("inlineCode",{parentName:"p"},"GameObject.GetComponent()")," \u6216 ",(0,a.kt)("inlineCode",{parentName:"p"},"GameObject.GetComponent(typeof(T))")),(0,a.kt)("h2",{id:"\u5176\u5b83"},"\u5176\u5b83"),(0,a.kt)("p",null,"\u9700\u8981\u88ab\u6302\u5230\u8d44\u6e90\u4e0a\u7684\u811a\u672c\u6240\u5728dll\u540d\u79f0\u4e0a\u7ebf\u540e\u52ff\u4fee\u6539\uff0c\u56e0\u4e3aassembly\u5217\u8868\u6587\u4ef6\u6253\u5305\u540e\u65e0\u6cd5\u4fee\u6539\u3002"),(0,a.kt)("p",null,"\u5efa\u8bae\u6253AB\u65f6\u4e0d\u8981\u7981\u7528TypeTree\uff0c\u5426\u5219\u666e\u901a\u7684AB\u52a0\u8f7d\u65b9\u5f0f\u4f1a\u5931\u8d25\u3002\uff08\u539f\u56e0\u662f\u5bf9\u4e8e\u7981\u7528TypeTree\u7684\u811a\u672c\uff0cUnity\u4e3a\u4e86\u9632\u6b62\u4e8c\u8fdb\u5236\u4e0d\u5339\u914d\u5bfc\u81f4\u53cd\u5e8f\u5217\u5316MonoBehaviour\u8fc7\u7a0b\u4e2d\u8fdb\u7a0bCrash\uff0c\u4f1a\u5bf9\u811a\u672c\u7684\u7b7e\u540d\u8fdb\u884c\u6821\u9a8c\uff0c\u7b7e\u540d\u7684\u5185\u5bb9\u662f\u811a\u672cFullName\u53caTypeTree\u6570\u636e\u751f\u6210\u7684Hash, \u4f46\u7531\u4e8e\u6211\u4eec\u7684\u70ed\u66f4\u811a\u672c\u4fe1\u606f\u4e0d\u5b58\u5728\u4e8e\u6253\u5305\u540e\u7684\u5b89\u88c5\u5305\u4e2d\uff0c\u56e0\u6b64\u6821\u9a8c\u5fc5\u5b9a\u4f1a\u5931\u8d25\uff09"),(0,a.kt)("p",null,"\u5982\u679c\u5fc5\u987b\u8981\u7981\u7528TypeTree\uff0c\u4e00\u4e2a\u53d8\u901a\u7684\u65b9\u6cd5\u662f\u7981\u6b62\u811a\u672c\u7684Hash\u6821\u9a8c, \u6b64\u79cd\u60c5\u51b5\u4e0b\u7528\u6237\u5fc5\u987b\u4fdd\u8bc1\u6253\u5305\u65f6\u4ee3\u7801\u4e0e\u8d44\u6e90\u7248\u672c\u4e00\u81f4\uff0c\u5426\u5219\u53ef\u80fd\u4f1a\u5bfc\u81f4Crash\uff0c\u793a\u4f8b\u4ee3\u7801"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-csharp"}," AssetBundleCreateRequest req = AssetBundle.LoadFromFileAsync(path);\n req.SetEnableCompatibilityChecks(false); // \u975epublic\uff0c\u9700\u8981\u901a\u8fc7\u53cd\u5c04\u8c03\u7528\n")))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[9106],{3905:(e,t,n)=>{n.d(t,{Zo:()=>c,kt:()=>b});var r=n(7294);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function l(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var s=r.createContext({}),p=function(e){var t=r.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):l(l({},t),e)),n},c=function(e){var t=p(e.components);return r.createElement(s.Provider,{value:t},e.children)},m="mdxType",u={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},d=r.forwardRef((function(e,t){var n=e.components,a=e.mdxType,o=e.originalType,s=e.parentName,c=i(e,["components","mdxType","originalType","parentName"]),m=p(n),d=a,b=m["".concat(s,".").concat(d)]||m[d]||u[d]||o;return n?r.createElement(b,l(l({ref:t},c),{},{components:n})):r.createElement(b,l({ref:t},c))}));function b(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var o=n.length,l=new Array(o);l[0]=d;var i={};for(var s in t)hasOwnProperty.call(t,s)&&(i[s]=t[s]);i.originalType=e,i[m]="string"==typeof e?e:a,l[1]=i;for(var p=2;p{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>l,default:()=>u,frontMatter:()=>o,metadata:()=>i,toc:()=>p});var r=n(7462),a=(n(7294),n(3905));const o={},l="MonoBehaviour\u652f\u6301",i={unversionedId:"basic/monobehaviour",id:"basic/monobehaviour",title:"MonoBehaviour\u652f\u6301",description:"HybridCLR\u5b8c\u5168\u652f\u6301\u70ed\u66f4\u65b0MonoBehaviour\u548cScriptableObject\u5de5\u4f5c\u6d41\uff0c\u5373\u53ef\u4ee5\u5728\u4ee3\u7801\u91cc\u5728GameObject\u4e0aAdd\u70ed\u66f4\u65b0\u811a\u672c\u6216\u8005\u5728\u8d44\u6e90\u4e0a\u76f4\u63a5\u6302\u8f7d",source:"@site/docs/basic/monobehaviour.md",sourceDirName:"basic",slug:"/basic/monobehaviour",permalink:"/docs/basic/monobehaviour",draft:!1,tags:[],version:"current",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"\u4ee3\u7801\u88c1\u526a",permalink:"/docs/basic/codestriping"},next:{title:"AOT\u6cdb\u578b",permalink:"/docs/basic/aotgeneric"}},s={},p=[{value:"\u901a\u8fc7\u4ee3\u7801\u4f7f\u7528",id:"\u901a\u8fc7\u4ee3\u7801\u4f7f\u7528",level:2},{value:"\u5728\u8d44\u6e90\u4e0a\u6302\u8f7dMonoBehaviour\u6216\u8005\u521b\u5efaScriptableObject\u7c7b\u578b\u8d44\u6e90",id:"\u5728\u8d44\u6e90\u4e0a\u6302\u8f7dmonobehaviour\u6216\u8005\u521b\u5efascriptableobject\u7c7b\u578b\u8d44\u6e90",level:2},{value:"assembly\u5217\u8868\u6587\u4ef6",id:"assembly\u5217\u8868\u6587\u4ef6",level:2},{value:"\u5df2\u77e5\u95ee\u9898",id:"\u5df2\u77e5\u95ee\u9898",level:2},{value:"\u4e3b\u7ebf\u7a0bAddComponent\u53ca\u5176\u4ed6\u8d44\u6e90\u52a0\u8f7d\u7ebf\u7a0b\u52a0\u8f7d\u5305\u542b\u70ed\u66f4\u65b0\u811a\u672c\u7684\u8d44\u6e90\u540c\u65f6\u8fdb\u884c\u65f6\u5076\u53d1\u7684\u5d29\u6e83\u95ee\u9898",id:"\u4e3b\u7ebf\u7a0baddcomponent\u53ca\u5176\u4ed6\u8d44\u6e90\u52a0\u8f7d\u7ebf\u7a0b\u52a0\u8f7d\u5305\u542b\u70ed\u66f4\u65b0\u811a\u672c\u7684\u8d44\u6e90\u540c\u65f6\u8fdb\u884c\u65f6\u5076\u53d1\u7684\u5d29\u6e83\u95ee\u9898",level:3},{value:"GameObject.GetComponent(string name) \u63a5\u53e3\u65e0\u6cd5\u83b7\u5f97\u7ec4\u4ef6",id:"gameobjectgetcomponentstring-name-\u63a5\u53e3\u65e0\u6cd5\u83b7\u5f97\u7ec4\u4ef6",level:3},{value:"\u5176\u5b83",id:"\u5176\u5b83",level:2}],c={toc:p},m="wrapper";function u(e){let{components:t,...n}=e;return(0,a.kt)(m,(0,r.Z)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("h1",{id:"monobehaviour\u652f\u6301"},"MonoBehaviour\u652f\u6301"),(0,a.kt)("p",null,"HybridCLR\u5b8c\u5168\u652f\u6301\u70ed\u66f4\u65b0MonoBehaviour\u548cScriptableObject\u5de5\u4f5c\u6d41\uff0c\u5373\u53ef\u4ee5\u5728\u4ee3\u7801\u91cc\u5728GameObject\u4e0aAdd\u70ed\u66f4\u65b0\u811a\u672c\u6216\u8005\u5728\u8d44\u6e90\u4e0a\u76f4\u63a5\u6302\u8f7d\n\u70ed\u66f4\u65b0\u811a\u672c\u3002\u4f46\u7531\u4e8eUnity\u8d44\u6e90\u7ba1\u7406\u673a\u5236\u7684\u7279\u6b8a\u6027\uff0c\u5bf9\u4e8e\u8d44\u6e90\u4e0a\u6302\u8f7d\u70ed\u66f4\u65b0\u811a\u672c\uff0c\u9700\u8981\u6253\u5305\u5de5\u4f5c\u6d41\u4e0a\u4f5c\u4e00\u4e9b\u7279\u6b8a\u5904\u7406\u3002"),(0,a.kt)("h2",{id:"\u901a\u8fc7\u4ee3\u7801\u4f7f\u7528"},"\u901a\u8fc7\u4ee3\u7801\u4f7f\u7528"),(0,a.kt)("p",null,(0,a.kt)("inlineCode",{parentName:"p"},"AddComponent()"),"\u6216\u8005",(0,a.kt)("inlineCode",{parentName:"p"},"AddComponent(Type type)"),"\u4efb\u4f55\u65f6\u5019\u90fd\u662f\u5b8c\u7f8e\u652f\u6301\u7684\u3002\u53ea\u9700\u8981\u63d0\u524d\u901a\u8fc7Assembly.Load\u5c06\u70ed\u66f4\u65b0dll\u52a0\u8f7d\u5230\u8fd0\u884c\u65f6\n\u5185\u5373\u53ef\u3002"),(0,a.kt)("h2",{id:"\u5728\u8d44\u6e90\u4e0a\u6302\u8f7dmonobehaviour\u6216\u8005\u521b\u5efascriptableobject\u7c7b\u578b\u8d44\u6e90"},"\u5728\u8d44\u6e90\u4e0a\u6302\u8f7dMonoBehaviour\u6216\u8005\u521b\u5efaScriptableObject\u7c7b\u578b\u8d44\u6e90"),(0,a.kt)("p",null,"Unity\u8d44\u6e90\u7ba1\u7406\u7cfb\u7edf\u5728\u53cd\u5e8f\u5217\u5316\u8d44\u6e90\u4e2d\u7684\u70ed\u66f4\u65b0\u811a\u672c\u65f6\uff0c\u9700\u8981\u6ee1\u8db3\u4ee5\u4e0b\u6761\u4ef6\uff1a"),(0,a.kt)("ol",null,(0,a.kt)("li",{parentName:"ol"},"\u811a\u672c\u6240\u5728\u7684dll\u5df2\u7ecf\u52a0\u8f7d\u5230\u8fd0\u884c\u65f6\u4e2d"),(0,a.kt)("li",{parentName:"ol"},"\u5fc5\u987b\u662f\u4f7f\u7528AssetBundle\u6253\u5305\u7684\u8d44\u6e90\uff08",(0,a.kt)("strong",{parentName:"li"},"addressable\u4e4b\u7c7b\u95f4\u63a5\u4f7f\u7528\u4e86ab\u7684\u6846\u67b6\u4e5f\u53ef\u4ee5"),"\uff09"),(0,a.kt)("li",{parentName:"ol"},"\u811a\u672c\u6240\u5728\u7684dll\u5fc5\u987b\u6dfb\u52a0\u5230\u6253\u5305\u65f6\u751f\u6210\u7684assembly\u5217\u8868\u6587\u4ef6\u3002\u8fd9\u4e2a\u5217\u8868\u6587\u4ef6\u662funity\u542f\u52a8\u65f6\u5373\u52a0\u8f7d\u7684\uff0c\u4e0d\u53ef\u53d8\u6570\u636e\u3002\u4e0d\u540c\u7248\u672c\u7684Unity\u7684\u5217\u8868\u6587\u4ef6\u540d\u548c\u683c\u5f0f\u4e0d\u76f8\u540c\u3002")),(0,a.kt)("p",null,"\u5982\u679c\u672a\u5bf9\u6253\u5305\u6d41\u7a0b\u4f5c\u4efb\u4f55\u5904\u7406\uff0c\u7531\u4e8e\u70ed\u66f4\u65b0dll\u5df2\u7ecf\u5728",(0,a.kt)("inlineCode",{parentName:"p"},"IFilterBuildAssemblies"),"\u56de\u8c03\u4e2d\u88ab\u79fb\u9664\uff0c\u80af\u5b9a\u4e0d\u4f1a\u51fa\u73b0\u5728assembly\u5217\u8868\u6587\u4ef6\u4e2d\u3002\n\u7531\u4e8e\u4e0d\u6ee1\u8db3\u6761\u4ef63\uff0c\u6302\u8f7d\u5728\u70ed\u66f4\u65b0\u8d44\u6e90\u4e2d\u7684\u70ed\u66f4\u65b0\u811a\u672c\u65e0\u6cd5\u88ab\u8fd8\u539f\uff0c\u8fd0\u884c\u65f6\u4f1a\u51fa\u73b0 ",(0,a.kt)("inlineCode",{parentName:"p"},"Scripting Missing"),"\u7684\u9519\u8bef\u3002"),(0,a.kt)("p",null,"\u56e0\u6b64\u6211\u4eec\u5728",(0,a.kt)("inlineCode",{parentName:"p"},"Editor/BuildProcessors/PatchScriptingAssemblyList.cs")," \u811a\u672c\u4e2d\u4f5c\u4e86\u7279\u6b8a\u5904\u7406\uff0c\u628a\u70ed\u66f4\u65b0dll\u52a0\u5165\u5230assembly\u5217\u8868\u6587\u4ef6\u4e2d\u3002\n\u4f60\u9700\u8981\u628a\u9879\u76ee\u4e2d\u7684\u70ed\u66f4\u65b0assembly\u6dfb\u52a0\u5230",(0,a.kt)("inlineCode",{parentName:"p"},"HybridCLRSettings\u914d\u7f6e\u7684HotUpdateAssemblyDefinitions\u6216HotUpdateAssemblies \u5b57\u6bb5"),"\u4e2d\u3002"),(0,a.kt)("p",null,"\u53ea\u9650\u5236\u4e86\u70ed\u66f4\u65b0\u8d44\u6e90\u4ee5ab\u5305\u5f62\u5f0f\u6253\u5305\uff0c\u70ed\u66f4\u65b0dll\u6253\u5305\u65b9\u5f0f\u6ca1\u6709\u9650\u5236\u3002\u4f60\u53ef\u4ee5\u6309\u7167\u9879\u76ee\u9700\u6c42",(0,a.kt)("strong",{parentName:"p"},"\u81ea\u7531\u9009\u62e9\u70ed\u66f4\u65b0\u65b9\u5f0f"),"\uff0c\u53ef\u4ee5\u5c06dll\u6253\u5305\u5230ab\u4e2d\uff0c\u6216\u8005\u88f8\u6570\u636e\n\u6587\u4ef6\uff0c\u6216\u8005\u52a0\u5bc6\u538b\u7f29\u7b49\u7b49\u3002\u53ea\u8981\u80fd\u4fdd\u8bc1\u5728\u52a0\u8f7d\u70ed\u66f4\u65b0\u8d44\u6e90\u524d\u4f7f\u7528Assembly.Load\u5c06\u5176\u52a0\u8f7d\u5373\u53ef\u3002"),(0,a.kt)("admonition",{type:"warning"},(0,a.kt)("p",{parentName:"admonition"},(0,a.kt)("strong",{parentName:"p"},"\u5982\u679c\u5c06\u70ed\u66f4\u65b0\u811a\u672c\u6302\u8f7d\u5230Resources\u7b49\u968f\u4e3b\u5305\u7684\u8d44\u6e90\u4e0a\uff0c\u4f1a\u53d1\u751fscripting missing\u7684\u9519\u8bef\uff01"),"\u4f46\u5982\u679c\u5148\u6253\u6210assetbundle\u5305\uff0c\u518d\u653e\u5230Resources\u4e0b\uff0c\u8fd0\u884c\u65f6\u52a0\u8f7d\u8be5\u968f\u5305assetbundle\u5219\u6ca1\u6709\u95ee\u9898\u3002")),(0,a.kt)("h2",{id:"assembly\u5217\u8868\u6587\u4ef6"},"assembly\u5217\u8868\u6587\u4ef6"),(0,a.kt)("p",null,"\u4e0d\u540cUnity\u7248\u672c\u4e0bassembly\u5217\u8868\u6587\u4ef6\u7684\u540d\u79f0\u548c\u683c\u5f0f\u90fd\u4e0d\u4e00\u6837\u3002"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"2019\u7248\u672c\u3002 \u975e\u538b\u7f29\u6253\u5305\u65f6\u4e3aglobalgamemanagers\u6587\u4ef6\uff0c\u538b\u7f29\u6253\u5305\u65f6\u5148\u4fdd\u5b58\u5230globalgamemanagers\u6587\u4ef6\uff0c\u518d\u4ee5BundleFile\u683c\u5f0f\u548c\u5176\u4ed6\u6587\u4ef6\u6253\u5305\u5230data.unity3d\u6587\u4ef6\u3002"),(0,a.kt)("li",{parentName:"ul"},"2020-2021\u7248\u672c\u3002 \u4fdd\u5b58\u5728ScriptingAssembles.json\u6587\u4ef6\u4e2d\u3002")),(0,a.kt)("h2",{id:"\u5df2\u77e5\u95ee\u9898"},"\u5df2\u77e5\u95ee\u9898"),(0,a.kt)("h3",{id:"\u4e3b\u7ebf\u7a0baddcomponent\u53ca\u5176\u4ed6\u8d44\u6e90\u52a0\u8f7d\u7ebf\u7a0b\u52a0\u8f7d\u5305\u542b\u70ed\u66f4\u65b0\u811a\u672c\u7684\u8d44\u6e90\u540c\u65f6\u8fdb\u884c\u65f6\u5076\u53d1\u7684\u5d29\u6e83\u95ee\u9898"},"\u4e3b\u7ebf\u7a0bAddComponent\u53ca\u5176\u4ed6\u8d44\u6e90\u52a0\u8f7d\u7ebf\u7a0b\u52a0\u8f7d\u5305\u542b\u70ed\u66f4\u65b0\u811a\u672c\u7684\u8d44\u6e90\u540c\u65f6\u8fdb\u884c\u65f6\u5076\u53d1\u7684\u5d29\u6e83\u95ee\u9898"),(0,a.kt)("p",null,"\u6b64\u95ee\u9898\u6765\u81ea",(0,a.kt)("a",{parentName:"p",href:"https://github.com/focus-creative-games/hybridclr/issues/96"},"issue"),"\u62a5\u544a\u3002"),(0,a.kt)("p",null,"\u5728\u7b2c\u4e00\u6b21\u4f7f\u7528\u67d0\u70ed\u66f4\u65b0\u7c7b\u578b\u65f6\uff08\u4e3b\u7ebf\u7a0bAddComponent\u6216\u8005\u8d44\u6e90\u7ebf\u7a0b\u52a0\u8f7d\u542b\u811a\u672c\u7684\u8d44\u6e90\uff09\u4f1a\u89e6\u53d1\u5f15\u64ce\u521b\u5efaMonoScript\u6570\u636e\uff0c\u7136\u800c\u6b64\u64cd\u4f5c\u5e76\u975e\u7ebf\u7a0b\u5b89\u5168\u3002\u7531\u4e8e\u672a\u63a5\u5165hybridclr\u65f6\uff0c\u6240\u6709\u811a\u672c\u90fd\u5728\u542f\u52a8\u65f6\u5df2\u7ecf\u521d\u59cb\u5316\uff0c\u56e0\u6b64\u4e0d\u4f1a\u6709\u7ebf\u7a0b\u5b89\u5168\u95ee\u9898\u3002\u5f53\u63a5\u5165hybridclr\u540e\uff0c\u5728\u5076\u7136\u60c5\u51b5\u4e0b\uff08\u5c24\u5176\u662f\u52a0\u8f7d\u5305\u542b\u5927\u91cf\u811a\u672c\u7684\u8d44\u6e90\uff09\u4f1a\u89e6\u53d1\u8fd9\u4e2a\u95ee\u9898\u3002"),(0,a.kt)("p",null,"\u89e3\u51b3\u529e\u6cd5\uff1a"),(0,a.kt)("p",null,"\u52a0\u8f7d\u5b8c\u70ed\u66f4\u65b0\u7a0b\u5e8f\u96c6\u540e\uff0c\u901a\u8fc7\u4e34\u65f6\u521b\u5efa\u7684GameObject,\u628a\u6240\u6709\u70ed\u66f4\u65b0\u811a\u672c\u90fd\u6dfb\u52a0\u4e00\u904d\uff0c\u7c7b\u4f3c\u8fd9\u6837\uff1a"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-csharp"}," var go = new GameObject();\n // \u6211\u4eec\u4e0d\u5e0c\u671b\u6302\u8f7d\u5230\u8fd9\u4e2aGameObject\u4e0a\u7684\u811a\u672c\u6267\u884c\n go.Active = false;\n foreach (var type in hotUpdateAss.GetTypes())\n {\n if (typeof(MonoBehaviour).IsAssignFrom(type))\n {\n go.AddComponent(type);\n }\n }\n GameObject.Destroy(go);\n\n")),(0,a.kt)("h3",{id:"gameobjectgetcomponentstring-name-\u63a5\u53e3\u65e0\u6cd5\u83b7\u5f97\u7ec4\u4ef6"},"GameObject.GetComponent(string name) \u63a5\u53e3\u65e0\u6cd5\u83b7\u5f97\u7ec4\u4ef6"),(0,a.kt)("p",null,"\u8fd9\u662f\u5df2\u77e5bug,\u8ddfunity\u7684\u4ee3\u7801\u5b9e\u73b0\u6709\u5173\uff0c\u53ea\u6709\u6302\u8f7d\u5728\u70ed\u66f4\u65b0\u8d44\u6e90\u4e0a\u70ed\u66f4\u65b0\u811a\u672c\u624d\u4f1a\u6709\u8fd9\u4e2a\u95ee\u9898\uff0c\u901a\u8fc7\u4ee3\u7801\u4e2dAddComponent\u6dfb\u52a0\u7684\u70ed\u66f4\u65b0\u811a\u672c\u662f\u53ef\u4ee5\u7528\u8fd9\u4e2a\u65b9\u6cd5\u67e5\u627e\u5230\u3002\u5982\u679c\u9047\u5230\u8fd9\u4e2a\u95ee\u9898\u8bf7\u6539\u7528 ",(0,a.kt)("inlineCode",{parentName:"p"},"GameObject.GetComponent()")," \u6216 ",(0,a.kt)("inlineCode",{parentName:"p"},"GameObject.GetComponent(typeof(T))")),(0,a.kt)("h2",{id:"\u5176\u5b83"},"\u5176\u5b83"),(0,a.kt)("p",null,"\u9700\u8981\u88ab\u6302\u5230\u8d44\u6e90\u4e0a\u7684\u811a\u672c\u6240\u5728dll\u540d\u79f0\u4e0a\u7ebf\u540e\u52ff\u4fee\u6539\uff0c\u56e0\u4e3aassembly\u5217\u8868\u6587\u4ef6\u6253\u5305\u540e\u65e0\u6cd5\u4fee\u6539\u3002"),(0,a.kt)("p",null,"\u5efa\u8bae\u6253AB\u65f6\u4e0d\u8981\u7981\u7528TypeTree\uff0c\u5426\u5219\u666e\u901a\u7684AB\u52a0\u8f7d\u65b9\u5f0f\u4f1a\u5931\u8d25\u3002\uff08\u539f\u56e0\u662f\u5bf9\u4e8e\u7981\u7528TypeTree\u7684\u811a\u672c\uff0cUnity\u4e3a\u4e86\u9632\u6b62\u4e8c\u8fdb\u5236\u4e0d\u5339\u914d\u5bfc\u81f4\u53cd\u5e8f\u5217\u5316MonoBehaviour\u8fc7\u7a0b\u4e2d\u8fdb\u7a0bCrash\uff0c\u4f1a\u5bf9\u811a\u672c\u7684\u7b7e\u540d\u8fdb\u884c\u6821\u9a8c\uff0c\u7b7e\u540d\u7684\u5185\u5bb9\u662f\u811a\u672cFullName\u53caTypeTree\u6570\u636e\u751f\u6210\u7684Hash, \u4f46\u7531\u4e8e\u6211\u4eec\u7684\u70ed\u66f4\u811a\u672c\u4fe1\u606f\u4e0d\u5b58\u5728\u4e8e\u6253\u5305\u540e\u7684\u5b89\u88c5\u5305\u4e2d\uff0c\u56e0\u6b64\u6821\u9a8c\u5fc5\u5b9a\u4f1a\u5931\u8d25\uff09"),(0,a.kt)("p",null,"\u5982\u679c\u5fc5\u987b\u8981\u7981\u7528TypeTree\uff0c\u4e00\u4e2a\u53d8\u901a\u7684\u65b9\u6cd5\u662f\u7981\u6b62\u811a\u672c\u7684Hash\u6821\u9a8c, \u6b64\u79cd\u60c5\u51b5\u4e0b\u7528\u6237\u5fc5\u987b\u4fdd\u8bc1\u6253\u5305\u65f6\u4ee3\u7801\u4e0e\u8d44\u6e90\u7248\u672c\u4e00\u81f4\uff0c\u5426\u5219\u53ef\u80fd\u4f1a\u5bfc\u81f4Crash\uff0c\u793a\u4f8b\u4ee3\u7801"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-csharp"}," AssetBundleCreateRequest req = AssetBundle.LoadFromFileAsync(path);\n req.SetEnableCompatibilityChecks(false); // \u975epublic\uff0c\u9700\u8981\u901a\u8fc7\u53cd\u5c04\u8c03\u7528\n")))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/runtime~main.6d86bebc.js b/assets/js/runtime~main.821a6c90.js similarity index 97% rename from assets/js/runtime~main.6d86bebc.js rename to assets/js/runtime~main.821a6c90.js index 5ae44d812..a8075524f 100644 --- a/assets/js/runtime~main.6d86bebc.js +++ b/assets/js/runtime~main.821a6c90.js @@ -1 +1 @@ -(()=>{"use strict";var e,a,d,b,f,c={},t={};function r(e){var a=t[e];if(void 0!==a)return a.exports;var d=t[e]={exports:{}};return c[e].call(d.exports,d,d.exports,r),d.exports}r.m=c,e=[],r.O=(a,d,b,f)=>{if(!d){var c=1/0;for(i=0;i=f)&&Object.keys(r.O).every((e=>r.O[e](d[o])))?d.splice(o--,1):(t=!1,f0&&e[i-1][2]>f;i--)e[i]=e[i-1];e[i]=[d,b,f]},r.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return r.d(a,{a:a}),a},d=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,r.t=function(e,b){if(1&b&&(e=this(e)),8&b)return e;if("object"==typeof e&&e){if(4&b&&e.__esModule)return e;if(16&b&&"function"==typeof e.then)return e}var f=Object.create(null);r.r(f);var c={};a=a||[null,d({}),d([]),d(d)];for(var t=2&b&&e;"object"==typeof t&&!~a.indexOf(t);t=d(t))Object.getOwnPropertyNames(t).forEach((a=>c[a]=()=>e[a]));return c.default=()=>e,r.d(f,c),f},r.d=(e,a)=>{for(var d in a)r.o(a,d)&&!r.o(e,d)&&Object.defineProperty(e,d,{enumerable:!0,get:a[d]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce(((a,d)=>(r.f[d](e,a),a)),[])),r.u=e=>"assets/js/"+({12:"f160c361",53:"935f2afb",90:"2efe1410",220:"6ec19757",533:"b2b675dd",685:"05f46752",688:"e044ccdf",852:"6704bb9d",964:"733c4d41",1176:"84b73551",1198:"72413e93",1286:"5edba3ff",1433:"ff8c06e1",1477:"b2f554cd",1634:"e6335e6f",1744:"7bef7309",1752:"dd53d751",1876:"2bead8bc",1977:"099d81ac",1998:"6d0a6812",2e3:"90e3b8d9",2034:"21ad55e6",2182:"f739fd9f",2243:"6a812547",2288:"cfa9d267",2306:"48d46c19",2365:"a7626ec9",2505:"0a31fa0b",2535:"814f3328",2616:"e9748e8f",2815:"918ca7cd",2828:"b7eeea20",2838:"635e1cda",2857:"cab0a0b1",2965:"c9dac562",3089:"a6aa9e1f",3131:"fe886eaa",3170:"b74f6ad3",3423:"7d20b2b1",3503:"744de10c",3608:"9e4087bc",3764:"7618167c",3775:"6ecda459",3777:"303a7ab0",3892:"0f4b3ece",4140:"5aff3be2",4195:"c4f5d8e4",4369:"9e92f087",4475:"bacda3a9",4569:"39b1bd06",5041:"ebee79fe",5048:"bbd26a74",5080:"88236a13",5133:"3d63e4cd",5153:"c9aab52f",5183:"032c34c3",5367:"26b576d2",5649:"5dd67a5f",5650:"5148d8fe",5659:"27b4bb7f",5746:"5a96aca1",5936:"1566bc1f",6103:"ccc49370",6191:"04fadddf",6290:"1d92ca72",6333:"41bb1898",6468:"4dfc0651",6695:"1c517ff1",6729:"bdd7c4d4",6848:"f33e1a49",6946:"2b2937ed",7020:"ba76a366",7040:"fbd8196d",7065:"80680481",7087:"1b21ecc3",7203:"f4f82255",7589:"0ccd1bc3",7681:"a99908d5",7884:"c71319a4",7918:"17896441",7920:"1a4e3797",7972:"2e1b2baa",7991:"7faaab83",8052:"b7e34b9a",8063:"f93d3a31",8787:"c55163c5",9057:"33811787",9106:"3d345fd1",9124:"c4ad3b7e",9451:"355d470d",9462:"9b588bbf",9514:"1be78505",9650:"e8c40ffe",9671:"0e384e19",9817:"14eb3368",9822:"3d291b3d",9888:"026413ce"}[e]||e)+"."+{12:"496fad6d",53:"5df054a1",90:"7aa895fb",220:"0f1d1daf",533:"d838adc3",685:"18f6713d",688:"c9c5c633",852:"68a790ba",964:"ef6332f7",1176:"02df715b",1198:"3edf80c7",1286:"c8fd6b3e",1426:"de2b7f72",1433:"4c296390",1477:"65b4b0f1",1634:"c0a5ed97",1744:"c4d83df4",1752:"dfbaf82d",1876:"7ff24eed",1977:"a04c15f6",1998:"67d24a1d",2e3:"b985c852",2034:"f05e7d12",2182:"ad078e06",2243:"3e49ddbd",2288:"23488626",2306:"532d567d",2365:"1a7ed67a",2505:"4483d521",2535:"9d9ea10e",2616:"8ff4f04a",2815:"0acead41",2828:"cbb0c6af",2838:"db36e186",2857:"e6128cda",2965:"122778b2",3089:"d1467cbe",3131:"c03d0775",3170:"c111c296",3423:"dfcebd24",3503:"8579c521",3608:"e989768d",3764:"2b7401cb",3775:"6eafc553",3777:"f657297a",3892:"c9ab43bc",4140:"10930a36",4195:"e054211d",4369:"ff5a2159",4475:"a5874148",4569:"e57052c7",4972:"3d0f496c",5041:"4b8329c2",5048:"6790acbd",5080:"69a05ad9",5133:"ce7e4d23",5153:"06d401cc",5183:"2f2bf67e",5367:"866585e9",5649:"54b51878",5650:"dc4901be",5659:"f30530ec",5746:"f49f1414",5936:"4a9652f1",6048:"779f8c90",6103:"1d3911bc",6186:"170d1bc9",6191:"d8c75018",6290:"3e88330e",6333:"5094bbc9",6468:"34a19723",6695:"58bed521",6729:"a2018f49",6848:"ab097966",6945:"94f4a660",6946:"3c504ec0",7020:"2330e1fc",7040:"ac772000",7065:"62a4c757",7087:"608a04ff",7203:"5479f5ff",7589:"3ae65c80",7681:"ee00bf9a",7884:"31e49daa",7918:"f596931a",7920:"275f830e",7972:"0ad87c0e",7991:"63c96c2c",8052:"022da9e7",8063:"2193695e",8787:"00243471",8894:"91734414",9057:"4ab0a8b8",9106:"16561613",9124:"b8f62a18",9451:"594f8519",9462:"f5a0ad34",9514:"209895ce",9650:"d61cea8b",9671:"3ef214ac",9817:"3f1657f1",9822:"9139634b",9888:"b74cd375"}[e]+".js",r.miniCssF=e=>{},r.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),r.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),b={},f="my-website:",r.l=(e,a,d,c)=>{if(b[e])b[e].push(a);else{var t,o;if(void 0!==d)for(var n=document.getElementsByTagName("script"),i=0;i{t.onerror=t.onload=null,clearTimeout(s);var f=b[e];if(delete b[e],t.parentNode&&t.parentNode.removeChild(t),f&&f.forEach((e=>e(d))),a)return a(d)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:t}),12e4);t.onerror=l.bind(null,t.onerror),t.onload=l.bind(null,t.onload),o&&document.head.appendChild(t)}},r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.p="/",r.gca=function(e){return e={17896441:"7918",33811787:"9057",80680481:"7065",f160c361:"12","935f2afb":"53","2efe1410":"90","6ec19757":"220",b2b675dd:"533","05f46752":"685",e044ccdf:"688","6704bb9d":"852","733c4d41":"964","84b73551":"1176","72413e93":"1198","5edba3ff":"1286",ff8c06e1:"1433",b2f554cd:"1477",e6335e6f:"1634","7bef7309":"1744",dd53d751:"1752","2bead8bc":"1876","099d81ac":"1977","6d0a6812":"1998","90e3b8d9":"2000","21ad55e6":"2034",f739fd9f:"2182","6a812547":"2243",cfa9d267:"2288","48d46c19":"2306",a7626ec9:"2365","0a31fa0b":"2505","814f3328":"2535",e9748e8f:"2616","918ca7cd":"2815",b7eeea20:"2828","635e1cda":"2838",cab0a0b1:"2857",c9dac562:"2965",a6aa9e1f:"3089",fe886eaa:"3131",b74f6ad3:"3170","7d20b2b1":"3423","744de10c":"3503","9e4087bc":"3608","7618167c":"3764","6ecda459":"3775","303a7ab0":"3777","0f4b3ece":"3892","5aff3be2":"4140",c4f5d8e4:"4195","9e92f087":"4369",bacda3a9:"4475","39b1bd06":"4569",ebee79fe:"5041",bbd26a74:"5048","88236a13":"5080","3d63e4cd":"5133",c9aab52f:"5153","032c34c3":"5183","26b576d2":"5367","5dd67a5f":"5649","5148d8fe":"5650","27b4bb7f":"5659","5a96aca1":"5746","1566bc1f":"5936",ccc49370:"6103","04fadddf":"6191","1d92ca72":"6290","41bb1898":"6333","4dfc0651":"6468","1c517ff1":"6695",bdd7c4d4:"6729",f33e1a49:"6848","2b2937ed":"6946",ba76a366:"7020",fbd8196d:"7040","1b21ecc3":"7087",f4f82255:"7203","0ccd1bc3":"7589",a99908d5:"7681",c71319a4:"7884","1a4e3797":"7920","2e1b2baa":"7972","7faaab83":"7991",b7e34b9a:"8052",f93d3a31:"8063",c55163c5:"8787","3d345fd1":"9106",c4ad3b7e:"9124","355d470d":"9451","9b588bbf":"9462","1be78505":"9514",e8c40ffe:"9650","0e384e19":"9671","14eb3368":"9817","3d291b3d":"9822","026413ce":"9888"}[e]||e,r.p+r.u(e)},(()=>{var e={1303:0,532:0};r.f.j=(a,d)=>{var b=r.o(e,a)?e[a]:void 0;if(0!==b)if(b)d.push(b[2]);else if(/^(1303|532)$/.test(a))e[a]=0;else{var f=new Promise(((d,f)=>b=e[a]=[d,f]));d.push(b[2]=f);var c=r.p+r.u(a),t=new Error;r.l(c,(d=>{if(r.o(e,a)&&(0!==(b=e[a])&&(e[a]=void 0),b)){var f=d&&("load"===d.type?"missing":d.type),c=d&&d.target&&d.target.src;t.message="Loading chunk "+a+" failed.\n("+f+": "+c+")",t.name="ChunkLoadError",t.type=f,t.request=c,b[1](t)}}),"chunk-"+a,a)}},r.O.j=a=>0===e[a];var a=(a,d)=>{var b,f,c=d[0],t=d[1],o=d[2],n=0;if(c.some((a=>0!==e[a]))){for(b in t)r.o(t,b)&&(r.m[b]=t[b]);if(o)var i=o(r)}for(a&&a(d);n{"use strict";var e,a,d,b,f,c={},t={};function r(e){var a=t[e];if(void 0!==a)return a.exports;var d=t[e]={exports:{}};return c[e].call(d.exports,d,d.exports,r),d.exports}r.m=c,e=[],r.O=(a,d,b,f)=>{if(!d){var c=1/0;for(i=0;i=f)&&Object.keys(r.O).every((e=>r.O[e](d[o])))?d.splice(o--,1):(t=!1,f0&&e[i-1][2]>f;i--)e[i]=e[i-1];e[i]=[d,b,f]},r.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return r.d(a,{a:a}),a},d=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,r.t=function(e,b){if(1&b&&(e=this(e)),8&b)return e;if("object"==typeof e&&e){if(4&b&&e.__esModule)return e;if(16&b&&"function"==typeof e.then)return e}var f=Object.create(null);r.r(f);var c={};a=a||[null,d({}),d([]),d(d)];for(var t=2&b&&e;"object"==typeof t&&!~a.indexOf(t);t=d(t))Object.getOwnPropertyNames(t).forEach((a=>c[a]=()=>e[a]));return c.default=()=>e,r.d(f,c),f},r.d=(e,a)=>{for(var d in a)r.o(a,d)&&!r.o(e,d)&&Object.defineProperty(e,d,{enumerable:!0,get:a[d]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce(((a,d)=>(r.f[d](e,a),a)),[])),r.u=e=>"assets/js/"+({12:"f160c361",53:"935f2afb",90:"2efe1410",220:"6ec19757",533:"b2b675dd",685:"05f46752",688:"e044ccdf",852:"6704bb9d",964:"733c4d41",1176:"84b73551",1198:"72413e93",1286:"5edba3ff",1433:"ff8c06e1",1477:"b2f554cd",1634:"e6335e6f",1744:"7bef7309",1752:"dd53d751",1876:"2bead8bc",1977:"099d81ac",1998:"6d0a6812",2e3:"90e3b8d9",2034:"21ad55e6",2182:"f739fd9f",2243:"6a812547",2288:"cfa9d267",2306:"48d46c19",2365:"a7626ec9",2505:"0a31fa0b",2535:"814f3328",2616:"e9748e8f",2815:"918ca7cd",2828:"b7eeea20",2838:"635e1cda",2857:"cab0a0b1",2965:"c9dac562",3089:"a6aa9e1f",3131:"fe886eaa",3170:"b74f6ad3",3423:"7d20b2b1",3503:"744de10c",3608:"9e4087bc",3764:"7618167c",3775:"6ecda459",3777:"303a7ab0",3892:"0f4b3ece",4140:"5aff3be2",4195:"c4f5d8e4",4369:"9e92f087",4475:"bacda3a9",4569:"39b1bd06",5041:"ebee79fe",5048:"bbd26a74",5080:"88236a13",5133:"3d63e4cd",5153:"c9aab52f",5183:"032c34c3",5367:"26b576d2",5649:"5dd67a5f",5650:"5148d8fe",5659:"27b4bb7f",5746:"5a96aca1",5936:"1566bc1f",6103:"ccc49370",6191:"04fadddf",6290:"1d92ca72",6333:"41bb1898",6468:"4dfc0651",6695:"1c517ff1",6729:"bdd7c4d4",6848:"f33e1a49",6946:"2b2937ed",7020:"ba76a366",7040:"fbd8196d",7065:"80680481",7087:"1b21ecc3",7203:"f4f82255",7589:"0ccd1bc3",7681:"a99908d5",7884:"c71319a4",7918:"17896441",7920:"1a4e3797",7972:"2e1b2baa",7991:"7faaab83",8052:"b7e34b9a",8063:"f93d3a31",8787:"c55163c5",9057:"33811787",9106:"3d345fd1",9124:"c4ad3b7e",9451:"355d470d",9462:"9b588bbf",9514:"1be78505",9650:"e8c40ffe",9671:"0e384e19",9817:"14eb3368",9822:"3d291b3d",9888:"026413ce"}[e]||e)+"."+{12:"496fad6d",53:"5df054a1",90:"7aa895fb",220:"0f1d1daf",533:"d838adc3",685:"18f6713d",688:"c9c5c633",852:"68a790ba",964:"ef6332f7",1176:"02df715b",1198:"3edf80c7",1286:"c8fd6b3e",1426:"de2b7f72",1433:"4c296390",1477:"65b4b0f1",1634:"c0a5ed97",1744:"c4d83df4",1752:"dfbaf82d",1876:"7ff24eed",1977:"5b82ef6b",1998:"67d24a1d",2e3:"b985c852",2034:"f05e7d12",2182:"ad078e06",2243:"3e49ddbd",2288:"23488626",2306:"532d567d",2365:"1a7ed67a",2505:"4483d521",2535:"9d9ea10e",2616:"8ff4f04a",2815:"0acead41",2828:"cbb0c6af",2838:"db36e186",2857:"e6128cda",2965:"122778b2",3089:"d1467cbe",3131:"c03d0775",3170:"c111c296",3423:"dfcebd24",3503:"8579c521",3608:"e989768d",3764:"2b7401cb",3775:"6eafc553",3777:"f657297a",3892:"c9ab43bc",4140:"10930a36",4195:"e054211d",4369:"ff5a2159",4475:"a5874148",4569:"e57052c7",4972:"3d0f496c",5041:"4b8329c2",5048:"6790acbd",5080:"69a05ad9",5133:"ce7e4d23",5153:"06d401cc",5183:"2f2bf67e",5367:"866585e9",5649:"54b51878",5650:"dc4901be",5659:"f30530ec",5746:"f49f1414",5936:"4a9652f1",6048:"779f8c90",6103:"1d3911bc",6186:"170d1bc9",6191:"d8c75018",6290:"3e88330e",6333:"5094bbc9",6468:"34a19723",6695:"58bed521",6729:"a2018f49",6848:"ab097966",6945:"94f4a660",6946:"3c504ec0",7020:"2330e1fc",7040:"ac772000",7065:"62a4c757",7087:"608a04ff",7203:"5479f5ff",7589:"3ae65c80",7681:"ee00bf9a",7884:"31e49daa",7918:"f596931a",7920:"275f830e",7972:"add9dc57",7991:"63c96c2c",8052:"022da9e7",8063:"2193695e",8787:"00243471",8894:"91734414",9057:"4ab0a8b8",9106:"3cf57c5c",9124:"b8f62a18",9451:"594f8519",9462:"f5a0ad34",9514:"209895ce",9650:"d61cea8b",9671:"3ef214ac",9817:"3f1657f1",9822:"9139634b",9888:"b74cd375"}[e]+".js",r.miniCssF=e=>{},r.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),r.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),b={},f="my-website:",r.l=(e,a,d,c)=>{if(b[e])b[e].push(a);else{var t,o;if(void 0!==d)for(var n=document.getElementsByTagName("script"),i=0;i{t.onerror=t.onload=null,clearTimeout(s);var f=b[e];if(delete b[e],t.parentNode&&t.parentNode.removeChild(t),f&&f.forEach((e=>e(d))),a)return a(d)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:t}),12e4);t.onerror=l.bind(null,t.onerror),t.onload=l.bind(null,t.onload),o&&document.head.appendChild(t)}},r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.p="/",r.gca=function(e){return e={17896441:"7918",33811787:"9057",80680481:"7065",f160c361:"12","935f2afb":"53","2efe1410":"90","6ec19757":"220",b2b675dd:"533","05f46752":"685",e044ccdf:"688","6704bb9d":"852","733c4d41":"964","84b73551":"1176","72413e93":"1198","5edba3ff":"1286",ff8c06e1:"1433",b2f554cd:"1477",e6335e6f:"1634","7bef7309":"1744",dd53d751:"1752","2bead8bc":"1876","099d81ac":"1977","6d0a6812":"1998","90e3b8d9":"2000","21ad55e6":"2034",f739fd9f:"2182","6a812547":"2243",cfa9d267:"2288","48d46c19":"2306",a7626ec9:"2365","0a31fa0b":"2505","814f3328":"2535",e9748e8f:"2616","918ca7cd":"2815",b7eeea20:"2828","635e1cda":"2838",cab0a0b1:"2857",c9dac562:"2965",a6aa9e1f:"3089",fe886eaa:"3131",b74f6ad3:"3170","7d20b2b1":"3423","744de10c":"3503","9e4087bc":"3608","7618167c":"3764","6ecda459":"3775","303a7ab0":"3777","0f4b3ece":"3892","5aff3be2":"4140",c4f5d8e4:"4195","9e92f087":"4369",bacda3a9:"4475","39b1bd06":"4569",ebee79fe:"5041",bbd26a74:"5048","88236a13":"5080","3d63e4cd":"5133",c9aab52f:"5153","032c34c3":"5183","26b576d2":"5367","5dd67a5f":"5649","5148d8fe":"5650","27b4bb7f":"5659","5a96aca1":"5746","1566bc1f":"5936",ccc49370:"6103","04fadddf":"6191","1d92ca72":"6290","41bb1898":"6333","4dfc0651":"6468","1c517ff1":"6695",bdd7c4d4:"6729",f33e1a49:"6848","2b2937ed":"6946",ba76a366:"7020",fbd8196d:"7040","1b21ecc3":"7087",f4f82255:"7203","0ccd1bc3":"7589",a99908d5:"7681",c71319a4:"7884","1a4e3797":"7920","2e1b2baa":"7972","7faaab83":"7991",b7e34b9a:"8052",f93d3a31:"8063",c55163c5:"8787","3d345fd1":"9106",c4ad3b7e:"9124","355d470d":"9451","9b588bbf":"9462","1be78505":"9514",e8c40ffe:"9650","0e384e19":"9671","14eb3368":"9817","3d291b3d":"9822","026413ce":"9888"}[e]||e,r.p+r.u(e)},(()=>{var e={1303:0,532:0};r.f.j=(a,d)=>{var b=r.o(e,a)?e[a]:void 0;if(0!==b)if(b)d.push(b[2]);else if(/^(1303|532)$/.test(a))e[a]=0;else{var f=new Promise(((d,f)=>b=e[a]=[d,f]));d.push(b[2]=f);var c=r.p+r.u(a),t=new Error;r.l(c,(d=>{if(r.o(e,a)&&(0!==(b=e[a])&&(e[a]=void 0),b)){var f=d&&("load"===d.type?"missing":d.type),c=d&&d.target&&d.target.src;t.message="Loading chunk "+a+" failed.\n("+f+": "+c+")",t.name="ChunkLoadError",t.type=f,t.request=c,b[1](t)}}),"chunk-"+a,a)}},r.O.j=a=>0===e[a];var a=(a,d)=>{var b,f,c=d[0],t=d[1],o=d[2],n=0;if(c.some((a=>0!==e[a]))){for(b in t)r.o(t,b)&&(r.m[b]=t[b]);if(o)var i=o(r)}for(a&&a(d);n

数组访问相关指令

比较常规直接,不过有个特殊点:根据规范index变量可以是i4或者native int类型。由于数组访问是非常频繁的操作,我们不想插入运行时数据类型类型及转换,因为我们根据index变量的size为每条数组相关指令设计了2条hybridclr指令。

以ldelem.i4 指令的index是i4类型的情形为例

struct IRGetArrayElementVarVar_i4_4 : IRCommon
{
uint16_t dst;
uint16_t arr;
uint16_t index;
};

// 对应解释执行代码
case HiOpcodeEnum::GetArrayElementVarVar_i4_4:
{
uint16_t __dst = *(uint16_t*)(ip + 2);
uint16_t __arr = *(uint16_t*)(ip + 4);
uint16_t __index = *(uint16_t*)(ip + 6);
Il2CppArray* arr = (*(Il2CppArray**)(localVarBase + __arr));
CHECK_NOT_NULL_AND_ARRAY_BOUNDARY(arr, (*(int32_t*)(localVarBase + __index)));
(*(int32_t*)(localVarBase + __dst)) = il2cpp_array_get(arr, int32_t, (*(int32_t*)(localVarBase + __index)));
ip += 8;
continue;
}

函数调用指令

目前调用AOT函数和调用Interpreter函数使用不同的指令,因为Interpreter函数可以直接复用已经压到栈顶的数据,可以完全优化掉 Manged2Native -> Native2Managed 这个过程,提升性能。

调用解释器函数时可以复用当前 InterpreterModule::Execute函数帧,也节省了函数调用开销,同时也避免了解释器嵌套调用过深导致native栈overflow的问题。

对于带返回值的函数,由于多了一个返回值地址参数ret,与返回void的函数分别设计了不同指令。

如果调用的是AOT函数,由于每条函数的参数不定,我们将参数信息记录到resolvedDatas,然后argIdxs中保存这个间接索引。另外还需要通过桥接函数完成解释器函数参数到native abi函数参数的转换,为了避免运行时查找的开销,也提前计算了这个桥接函数,记录到resolvedDatas中,然后在managed2NativeMethod中保存了这个间接索引。

以call指令为例,为它设计了5条指令

  • IRCallNative_void
  • IRCallNative_ret
  • IRCallNative_ret_expand
  • IRCallInterp_void
  • IRCallInterp_ret

以IRCallNative_ret的实现为例,介绍调用AOT函数的指令:


struct IRCallNative_ret : IRCommon
{
uint16_t ret;
uint32_t managed2NativeMethod;
uint32_t methodInfo;
uint32_t argIdxs;
};

// 对应解释执行代码
case HiOpcodeEnum::CallNative_ret:
{
uint32_t __managed2NativeMethod = *(uint32_t*)(ip + 4);
uint32_t __methodInfo = *(uint32_t*)(ip + 8);
uint32_t __argIdxs = *(uint32_t*)(ip + 12);
uint16_t __ret = *(uint16_t*)(ip + 2);
void* _ret = (void*)(localVarBase + __ret);
((Managed2NativeCallMethod)imi->resolveDatas[__managed2NativeMethod])(((MethodInfo*)imi->resolveDatas[__methodInfo]), ((uint16_t*)&imi->resolveDatas[__argIdxs]), localVarBase, _ret);
ip += 16;
continue;
}

如果调用Interpreter函数,由于函数参数已经按顺序压到栈上,只需要一个argBase参数指定arg0逻辑地址即可,不需要借助resolvedDatas,也不需要managed2NativeMethod桥接函数指针。 这也是解释器函数不受桥接函数影响的原因。

以IRCallInterp_ret为例,介绍调用Interpreter函数的指令:

struct IRCallInterp_ret : IRCommon
{
uint16_t argBase;
uint16_t ret;
uint8_t __pad6;
uint8_t __pad7;
uint32_t methodInfo;
};

// 对应解释执行代码
case HiOpcodeEnum::CallInterp_ret:
{
MethodInfo* __methodInfo = *(MethodInfo**)(ip + 8);
uint16_t __argBase = *(uint16_t*)(ip + 2);
uint16_t __ret = *(uint16_t*)(ip + 4);
CALL_INTERP_RET((ip + 16), __methodInfo, (StackObject*)(void*)(localVarBase + __argBase), (void*)(localVarBase + __ret));
continue;
}

异常机制相关指令

异常机制相关指令本身不复杂,但异常处理机制非常复杂。

异常这种特殊的流程控制指令,跟分支跳转指令相似,原始指令里包含了相对offset,为了简单起见,指令转换时我们改成int32_t类型的绝对offset。

以leave指令为例

struct IRLeaveEx : IRCommon
{
uint8_t __pad2;
uint8_t __pad3;
int32_t offset;
};

// 对应解释执行代码
case HiOpcodeEnum::LeaveEx:
{
int32_t __offset = *(int32_t*)(ip + 4);
LEAVE_EX(__offset);
continue;
}

一些额外的instinct 指令

对于一些特别常见的函数,为了优化性能,hybridclr直接内置了相应的指令,例如 new Vector{2,3,4},如可空变量相关操作。这些instinct指令的执行性能基本与AOT持平。

以 new Vector3() 为例


struct IRNewVector3_3 : IRCommon
{
uint16_t obj;
uint16_t x;
uint16_t y;
uint16_t z;
uint8_t __pad10;
uint8_t __pad11;
uint8_t __pad12;
uint8_t __pad13;
uint8_t __pad14;
uint8_t __pad15;
};

// 对应解释执行代码
case HiOpcodeEnum::NewVector3_3:
{
uint16_t __obj = *(uint16_t*)(ip + 2);
uint16_t __x = *(uint16_t*)(ip + 4);
uint16_t __y = *(uint16_t*)(ip + 6);
uint16_t __z = *(uint16_t*)(ip + 8);
*(HtVector3f*)(*(void**)(localVarBase + __obj)) = {(*(float*)(localVarBase + __x)), (*(float*)(localVarBase + __y)), (*(float*)(localVarBase + __z))};
ip += 16;
continue;
}

InitOnce 指令

有一些指令(如ldsfld)第一次执行的时候需要进行初始化操作,但后续再次执行时,不需要再执行初始化操作。但即使这样,免不了一个检查是否已经初始化的操作,我们希望完全优化掉这个检查行为。InitOnce动态JIT技术用于解决这个问题。

InitOnce是hybridclr的专利技术,暂未在代码中实现,这儿不详细介绍。

其他技术相关指令

限于篇幅,对于这些指令,会在单独的文章中介绍

总结

至此我们完成hybridclr指令集实现相关介绍。

· 阅读需 23 分钟

我们在上一节完成了hybridclr可行性分析。由于hybridclr内容极多,限于篇幅本篇文章主要概述性介绍hybridclr的技术实现。

CLR和il2cpp基础

给纯AOT的il2cpp运行时添加一个原生interpreter模块,最终实现hybrid mode execution,这看起来是非常复杂的事情。

其实不然,程序不外乎代码+数据。CLR运行中做的事情,综合起来主要就几种:

  1. 执行简单的内存操作或者计算或者逻辑跳转。这部分与CLI的Base指令集大致对应
  2. 执行一个依赖于元数据信息的基础操作。例如 a.x, arr[3] 这种,依赖于元数据信息才能正确工作的代码。对应部分CLI的Object Model指令集。
  3. 执行一个依赖元数据的较复杂的操作。如 typeof(object),a is string、(object)5 这种依赖于运行时提供的函数及相应元数据才正确工作的代码。对应部分CLI的Object Model指令集。
  4. 函数调用。包括且不限于被AOT函数调用及调用AOT函数,及interpreter之间的函数调用。对应CLI指令集中的 call、callvir、newobj 等Object Model指令。

如果对CLR有深入的了解和透彻的分析,为了实现hybrid mode execution,hybridclr核心要完成的就以下两件事,其他则是无碍全局的细节:

  • assembly信息能够加载和注册。 在此基础可以实现 1-3
  • 确保interpreter函数能被找到并且被调用,并且能执行出正确的结果。则可以实现 4

由于彻底理解以上内容需要较丰富的对CLR的认知以及较强的洞察力,我们不再费口舌解释,不能理解的开发者不必深究,继续看后续章节。

核心模块

从功能来看包含以下核心部分:

  • metadata初级解析
  • metadata高级元数据结构解析
  • metadata动态注册
  • 寄存器指令集设计
  • IL指令集到hybridclr寄存器指令集的转换
  • 解释执行hybridclr指令集
  • 其他如GC、多线程相关处理

从代码结构来看包含三个目录:

  • metadata 元数据相关
  • transform 指令集转换相关
  • interpreter 解释器相关

metadata 初级解析

这部分内容技术门槛不高,但比较琐碎和辛苦,忠实地按照 ECMA-335规范 的文档实现即可。对于少量有疑惑的地方,可以网上的资料或者借鉴mono的代码。

相关代码在hybridclr\metadata目录,主要在RawImage.h和RawImage.cpp中实现。如果再细分,相关实现分为以下几个部分。

PE 文件结构解析

managed dll扩展了PE文件结构,增加了CLI相关metadata部分。这环节的主要工作有:

  • 解析PE headers
  • 解析 section headers,找出CLI header,定位出cli数据段
  • 解析出所有stream。Stream是CLI中最底层的数据结构之一,CLI将元数据根据特性分为几个大类
    • #~ 流。包含所有tables定义,是最核心的元数据结构
    • #Strings 流。包括代码中非文档类型的字符串,如类型名、字段名等等
    • #GUID 流
    • #Blob 流。一些元数据类型过于复杂,以blob格式保存。还有一些数据如数组初始化数据列表,也常常保存到Blob流。
    • #- 流
    • #Pdb 流。用于调试

解析PE文件和代码在RawImage::Load,解析stream对应的代码在RawImage::LoadStreams。

tables metadata 解析

CLI中大多数metadata被为几十种类型,每个类型的数据组织成一个table。对于每个table,每行记录都是相同大小。

初级解析中不解析table中每行记录,只解析table的每行记录大小和每个字段偏移。有一大类字段为Coded Index类型,有可能是2或4字节,并不固定,需要根据其他表的Row Count来决定table中这一列的字段大小。由于table很多,这个计算过程比较琐碎易错。

对应代码在RawImage::LoadTables,截取部分代码如下

void RawImage::BuildTableRowMetas()
{
{
auto& table = _tableRowMetas[(int)TableType::MODULE];
table.push_back({ 2 });
table.push_back({ ComputStringIndexByte() });
table.push_back({ ComputGUIDIndexByte() });
table.push_back({ ComputGUIDIndexByte() });
table.push_back({ ComputGUIDIndexByte() });
}
{
auto& table = _tableRowMetas[(int)TableType::TYPEREF];
table.push_back({ ComputTableIndexByte(TableType::MODULE, TableType::MODULEREF, TableType::ASSEMBLYREF, TableType::TYPEREF, TagBits::ResoulutionScope) });
table.push_back({ ComputStringIndexByte() });
table.push_back({ ComputStringIndexByte() });
}

// ... 其他
}

table 解析

上一节已经解析出每个table的起始数据位置、row count、表中每个字段的偏移和大小,有足够的信息可以解析出每个table中任意row的数据。table中row的id从1开始。

每个table的row的解析方式根据ECMA规范实现即可。每个table的row定义在 metadata\Coff.h文件,Row解析代码在 RawImage.h。这些解析代码都非常相似,为了避免错误,使用了大量的宏,截取部分代码如下:

TABLE2(GenericParamConstraint, TableType::GENERICPARAMCONSTRAINT, owner, constraint)
TABLE3(MemberRef, TableType::MEMBERREF, classIdx, name, signature)
TABLE1(StandAloneSig, TableType::STANDALONESIG, signature)
TABLE3(MethodImpl, TableType::METHODIMPL, classIdx, methodBody, methodDeclaration)
TABLE2(FieldRVA, TableType::FIELDRVA, rva, field)
TABLE2(FieldLayout, TableType::FIELDLAYOUT, offset, field)
TABLE3(Constant, TableType::CONSTANT, type, parent, value)
TABLE2(MethodSpec, TableType::METHODSPEC, method, instantiation)
TABLE3(CustomAttribute, TableType::CUSTOMATTRIBUTE, parent, type, value)

metadata高级元数据结构解析

从tables里直接读出来的都是持久化的初始metadata,而运行时需要的不只是这些简单原始数据,经常需要进一步resolve后的数据。例如

  • Il2CppType 。即可以是简单的 int,也可以是比较复杂的List<int>,甚至是特别复杂的List<(int,int)>&
  • MethodInfo 。 即可以是简单的object.ToString,也有复杂的泛型 IEnumerator<int>.Count

CLI的泛型机制导致元数据变得极其复杂,典型的是TypeSpec,MethodSpec,MemberSpec相关元数据的运行时解析。核心实现代码在Image.cpp中实现,剩余一部分在 InterpreterImage.cpp及AOTHomologousImage.cpp中实现。后面会有专门介绍。

metadata动态注册

根据粒度从大到小,主要分为以下几类

  • Assembly 注册。即将加载的assembly注册到il2cpp的元数据管理中。
  • TypeDefinition 注册。 这一步会生成基础运行时类型 Il2CppClass。
  • VTable虚表计算。 由于il2cpp的虚表计算是个黑盒,内部相当复杂,我们费了很多功夫才研究明白它的计算机制。后面会有专门章节介绍VTable计算,这儿不再赘述。
  • 其他元数据,如CustomAttribute计算等等。

Assembly 注册

Assembly加载的关键函数在 il2cpp::vm::MetadataCache::LoadAssemblyFromBytes 。由于il2cpp是AOT运行时,原始实现只是简单地抛出异常。我们修改和完善了实现,在其中调用了hybridclr::metadata::Assembly::LoadFromBytes,完成了Assembly的创建,然后再注册到全局Assemblies列表。相关代码实现如下:

const Il2CppAssembly* il2cpp::vm::MetadataCache::LoadAssemblyFromBytes(const char* assemblyBytes, size_t length)
{
il2cpp::os::FastAutoLock lock(&il2cpp::vm::g_MetadataLock);

Il2CppAssembly* newAssembly = hybridclr::metadata::Assembly::LoadFromBytes(assemblyBytes, length, true);
if (newAssembly)
{
// avoid register placeholder assembly twicely.
for (Il2CppAssembly* ass : s_cliAssemblies)
{
if (ass == newAssembly)
{
return ass;
}
}
il2cpp::vm::Assembly::Register(newAssembly);
s_cliAssemblies.push_back(newAssembly);
return newAssembly;
}

return nullptr;
}

TypeDefinition 注册

Assembly使用了延迟初始化方式,注册后Assembly中的类型信息并未创建相应的运行时metadata Il2CppClass,只有当第一次访问到该类型时才进行初始化。

由于交叉依赖以及为了优化性能,Il2Class的创建是个分步过程

  • Il2CppClass 基础创建
  • Il2CppClass的子元数据延迟初始化
  • 运行时Class初始化

Il2CppClass基础创建

在上一节加载Assembly时已经创建好所有类型对应的定义数据Il2CppTypeDefinition,在 il2cpp::vm::GlobalMetadata::FromTypeDefinition 中完成Il2CppClass创建工作。代码如下:

Il2CppClass* il2cpp::vm::GlobalMetadata::FromTypeDefinition(TypeDefinitionIndex index)
{
/// ... 省略其他
Il2CppClass* typeInfo = (Il2CppClass*)IL2CPP_CALLOC(1, sizeof(Il2CppClass) + (sizeof(VirtualInvokeData) * typeDefinition->vtable_count));
typeInfo->klass = typeInfo;
typeInfo->image = GetImageForTypeDefinitionIndex(index);
typeInfo->name = il2cpp::vm::GlobalMetadata::GetStringFromIndex(typeDefinition->nameIndex);
typeInfo->namespaze = il2cpp::vm::GlobalMetadata::GetStringFromIndex(typeDefinition->namespaceIndex);
typeInfo->byval_arg = *il2cpp::vm::GlobalMetadata::GetIl2CppTypeFromIndex(typeDefinition->byvalTypeIndex);
typeInfo->this_arg = typeInfo->byval_arg;
typeInfo->this_arg.byref = true;
typeInfo->typeMetadataHandle = reinterpret_cast<const Il2CppMetadataTypeHandle>(typeDefinition);
typeInfo->genericContainerHandle = GetGenericContainerFromIndex(typeDefinition->genericContainerIndex);
typeInfo->instance_size = typeDefinitionSizes->instance_size;
typeInfo->actualSize = typeDefinitionSizes->instance_size; // actualySize is instance_size for compiler generated values
typeInfo->native_size = typeDefinitionSizes->native_size;
typeInfo->static_fields_size = typeDefinitionSizes->static_fields_size;
typeInfo->thread_static_fields_size = typeDefinitionSizes->thread_static_fields_size;
typeInfo->thread_static_fields_offset = -1;
typeInfo->flags = typeDefinition->flags;
typeInfo->valuetype = (typeDefinition->bitfield >> (kBitIsValueType - 1)) & 0x1;
typeInfo->enumtype = (typeDefinition->bitfield >> (kBitIsEnum - 1)) & 0x1;
typeInfo->is_generic = typeDefinition->genericContainerIndex != kGenericContainerIndexInvalid; // generic if we have a generic container
typeInfo->has_finalize = (typeDefinition->bitfield >> (kBitHasFinalizer - 1)) & 0x1;
typeInfo->has_cctor = (typeDefinition->bitfield >> (kBitHasStaticConstructor - 1)) & 0x1;
typeInfo->is_blittable = (typeDefinition->bitfield >> (kBitIsBlittable - 1)) & 0x1;
typeInfo->is_import_or_windows_runtime = (typeDefinition->bitfield >> (kBitIsImportOrWindowsRuntime - 1)) & 0x1;
typeInfo->packingSize = ConvertPackingSizeEnumToValue(static_cast<PackingSize>((typeDefinition->bitfield >> (kPackingSize - 1)) & 0xF));
typeInfo->method_count = typeDefinition->method_count;
typeInfo->property_count = typeDefinition->property_count;
typeInfo->field_count = typeDefinition->field_count;
typeInfo->event_count = typeDefinition->event_count;
typeInfo->nested_type_count = typeDefinition->nested_type_count;
typeInfo->vtable_count = typeDefinition->vtable_count;
typeInfo->interfaces_count = typeDefinition->interfaces_count;
typeInfo->interface_offsets_count = typeDefinition->interface_offsets_count;
typeInfo->token = typeDefinition->token;
typeInfo->interopData = il2cpp::vm::MetadataCache::GetInteropDataForType(&typeInfo->byval_arg);

// 省略其他

return typeInfo;
}

可以看到TypeDefinition中字段相当多,这些都是在Assembly加载环节计算好的。

Il2CppClass的子metadata延迟初始化

由于交互依赖以及为了优化性能,Il2Class的子metadata数据使用了延迟初始化策略,分步进行,在第一次使用时才初始化。以下代码截取自 Class.h 文件:

class Class
{
// ... 其他代码
static bool Init(Il2CppClass *klass);

static void SetupEvents(Il2CppClass *klass);
static void SetupFields(Il2CppClass *klass);
static void SetupMethods(Il2CppClass *klass);
static void SetupNestedTypes(Il2CppClass *klass);
static void SetupProperties(Il2CppClass *klass);
static void SetupTypeHierarchy(Il2CppClass *klass);
static void SetupInterfaces(Il2CppClass *klass);
// ... 其他代码
};

重点来了!!!函数metadata的执行指针的绑定在SetupMethods函数中完成,其中关键代码片段如下:

void SetupMethodsLocked(Il2CppClass *klass, const il2cpp::os::FastAutoLock& lock)
{
/// ... 其他忽略的代码
for (MethodIndex index = 0; index < end; ++index)
{
Il2CppMetadataMethodInfo methodInfo = MetadataCache::GetMethodInfo(klass, index);

newMethod->name = methodInfo.name;

if (klass->valuetype)
{
Il2CppMethodPointer adjustorThunk = MetadataCache::GetAdjustorThunk(klass->image, methodInfo.token);
if (adjustorThunk != NULL)
newMethod->methodPointer = adjustorThunk;
}

// We did not find an adjustor thunk, or maybe did not need to look for one. Let's get the real method pointer.
if (newMethod->methodPointer == NULL)
newMethod->methodPointer = MetadataCache::GetMethodPointer(klass->image, methodInfo.token);

newMethod->invoker_method = MetadataCache::GetMethodInvoker(klass->image, methodInfo.token);
}
/// ... 其他忽略的代码
}

函数运行时元数据结构为 MethodInfo,定义如下,

typedef struct MethodInfo
{
Il2CppMethodPointer methodPointer;
InvokerMethod invoker_method;
const char* name;
Il2CppClass *klass;
const Il2CppType *return_type;
const ParameterInfo* parameters;

// ... 省略其他
} MethodInfo;

其中我们比较关心的是methodPointer和invoker_method这两个字段。 methodPointer指向普通执行函数,invoker_method指向反射执行函数。

我们以 methodPointer为例,进一步跟踪它的设置过程, il2cpp::vm::MetadataCache::GetMethodPointer 的实现如下:

Il2CppMethodPointer il2cpp::vm::MetadataCache::GetMethodPointer(const Il2CppImage* image, uint32_t token)
{
uint32_t rid = GetTokenRowId(token);
uint32_t table = GetTokenType(token);
if (rid == 0)
return NULL;

// ==={{ hybridclr
if (hybridclr::metadata::IsInterpreterImage(image))
{
return hybridclr::metadata::MetadataModule::GetMethodPointer(image, token);
}
// ===}} hybridclr

IL2CPP_ASSERT(rid <= image->codeGenModule->methodPointerCount);

return image->codeGenModule->methodPointers[rid - 1];
}

可以看出,如果是解释器assembly,就跳转到解释器元数据模块获得对应的MethodPointer指针。 继续跟踪,相关代码如下:


Il2CppMethodPointer InterpreterImage::GetMethodPointer(uint32_t token)
{
uint32_t methodIndex = DecodeTokenRowIndex(token) - 1;
IL2CPP_ASSERT(methodIndex < (uint32_t)_methodDefines.size());
const Il2CppMethodDefinition* methodDef = &_methodDefines[methodIndex];
return hybridclr::interpreter::InterpreterModule::GetMethodPointer(methodDef);
}

Il2CppMethodPointer InterpreterModule::GetMethodPointer(const Il2CppMethodDefinition* method)
{
const NativeCallMethod* ncm = GetNativeCallMethod(method, false);
if (ncm)
{
return ncm->method;
}
//RaiseMethodNotSupportException(method, "GetMethodPointer");
return (Il2CppMethodPointer)NotSupportNative2Managed;
}

// interpreter/InterpreterModule.cpp
template<typename T>
const NativeCallMethod* GetNativeCallMethod(const T* method, bool forceStatic)
{
char sigName[1000];
ComputeSignature(method, !forceStatic, sigName, sizeof(sigName) - 1);
auto it = s_calls.find(sigName);
return (it != s_calls.end()) ? &it->second : nullptr;
}

// s_calls 定义
static std::unordered_map<const char*, NativeCallMethod, CStringHash, CStringEqualTo> s_calls;

void InterpreterModule::Initialize()
{
for (size_t i = 0; ; i++)
{
NativeCallMethod& method = g_callStub[i];
if (!method.signature)
{
break;
}
s_calls.insert({ method.signature, method });
}

for (size_t i = 0; ; i++)
{
NativeInvokeMethod& method = g_invokeStub[i];
if (!method.signature)
{
break;
}
s_invokes.insert({ method.signature, method });
}
}

这儿根据函数定义计算其签名并且返回了一个函数指针,这个函数指针是什么呢? s_calls在InterpreterModule::Initialize中使用g_callStub初始化。那g_calStub又是什么呢?它在 interpreter/MethodBridge_xxx.cpp 中定义,原来是桥接函数相关的数据结构!

为什么要返回一个这样的函数,而不是直接将methodPointer指向 InterpreterModule::Execute 函数呢? 以 int Foo::Sum(int,int) 函数为例,这个函数的实际的签名为 int32_t (int32_t, int32_t, MethodInfo*),在调用这个methodPointer函数时,调用方一定会传递这三个参数。这些参数每个函数都不一样,如果直接指向 InterpreterModule::Execute 函数,由于ABI调用无法自省(就算可以,性能也比较差),Execute函数既无法提取出普通参数,也无法提取出MethodInfo*参数,因而无法正确运行。因此需要对每个函数,适当地将ABI调用中的这些参数传递给Execute函数。

桥接函数如其名,承担了native ABI函数参数和interpreter函数之间双向的参数的转换作用。截取一段示例代码:


/// AOT 到 interpreter 的调用参数转换
static int64_t __Native2ManagedCall_i8srr8sr(void* __arg0, double __arg1, void* __arg2, const MethodInfo* method)
{
StackObject args[4] = {*(void**)&__arg0, *(void**)&__arg1, *(void**)&__arg2 };
StackObject* ret = args + 3;
Interpreter::Execute(method, args, ret);
return *(int64_t*)ret;
}

// interpreter 到 AOT 的调用参数转换
static void __Managed2NativeCall_i8srr8sr(const MethodInfo* method, uint16_t* argVarIndexs, StackObject* localVarBase, void* ret)
{
if (hybridclr::metadata::IsInstanceMethod(method) && !localVarBase[argVarIndexs[0]].obj)
{
il2cpp::vm::Exception::RaiseNullReferenceException();
}
Interpreter::RuntimeClassCCtorInit(method);
typedef int64_t (*NativeMethod)(void* __arg0, double __arg1, void* __arg2, const MethodInfo* method);
*(int64_t*)ret = ((NativeMethod)(method->methodPointer))((void*)(localVarBase+argVarIndexs[0]), *(double*)(localVarBase+argVarIndexs[1]), (void*)(localVarBase+argVarIndexs[2]), method);
}

运行时Class初始化

即程序运行过程中第一次访问类的静态字段或者函数时或者创建对象时触发的类型初始化。在il2cpp::vm::Runtime::ClassInit(klass)中完成。不是特别关键,我们后面在单独文章中介绍。

VTable虚表计算

虚表是多态的核心。CLI的虚表计算非常复杂,但不理解它的实现并不影响开发者理解hybridclr的核心运行流程,我们后面在单独文章中介绍。

其他元数据

CustomAttribute使用延迟初始化方式,计算也很复杂,我们后面单独文章介绍。

寄存器指令集设计

直接解释原始IL指令有几个问题:

  • IL是基于栈的指令,运行时维护执行栈是个无谓的开销
  • IL有大量单指令多功能的指令,如add指令可以用于计算int、long、float、double类型的和,导致运行时需要根据上文判断到底该执行哪种计算。不仅增加了运行时判定的开销,还增加了运行时维护执行栈数据类型的开销
  • IL指令包含一些需要运行时resolve的数据,如newobj指令第一个参数是method token。token resolve是一个开销很大的操作,每次执行都进行resolve会极大拖慢执行性能
  • IL是基于栈的指令,压栈退栈相关指令数较多。像a=b+c这样的指令需要4条指令完成,而如果采用基于寄存器的指令,完全可以一条指令完成。
  • IL不适合做其他优化操作,如我们的InitOnce JIT技术。
  • 其他

因此我们需要将原始IL指令转换为更高效的寄存器指令。由于指令很多,这儿不介绍寄存器指令集的详细设计。以add指令举例


// 包含type字段,即指令ID。
struct IRCommon
{
HiOpcodeEnum type;
};

// add int, int -> int 对应的寄存器指令
struct IRBinOpVarVarVar_Add_i4 : IRCommon
{
uint16_t ret; // 计算结果对应的 栈位置
uint16_t op1; // 操作数1对应的栈位置
uint16_t op2; // 操作数2对应的栈位置
};

指令集的转换

理解这节需要初步的编译原理相关知识,我们使用了非常朴素的转换算法,并且基本没有做指令优化。转换过程分为几步:

  • BasicBlock 划分。 将IL指令块切成一段段不包含任何跳转指令的代码块,称之为BasicBlock。
  • 模拟指令执行流程,同时使用广度优先遍历算法遍历所有BasicBlock,将每个BasicBlock转换为IRBasicBlock。

BasicBlock到IRBasicBlock转换采用了最朴素的一对一指令转换算法,转换相关代码在transform::HiTransform::Transform。我们以add指令为例:


case OpcodeValue::ADD:
{
IL2CPP_ASSERT(evalStackTop >= 2);
EvalStackVarInfo& op1 = evalStack[evalStackTop - 2];
EvalStackVarInfo& op2 = evalStack[evalStackTop - 1];

CreateIR(ir, BinOpVarVarVar_Add_i4);
ir->op1 = op1.locOffset;
ir->op2 = op2.locOffset;
ir->ret = op1.locOffset;

EvalStackReduceDataType resultType;
switch (op1.reduceType)
{
case EvalStackReduceDataType::I4:
{
switch (op2.reduceType)
{
case EvalStackReduceDataType::I4:
{
resultType = EvalStackReduceDataType::I4;
ir->type = HiOpcodeEnum::BinOpVarVarVar_Add_i4;
break;
}
case EvalStackReduceDataType::I:
case EvalStackReduceDataType::Ref:
{
CreateAddIR(irConv, ConvertVarVar_i4_i8);
irConv->dst = irConv->src = op1.locOffset;

resultType = op2.reduceType;
ir->type = HiOpcodeEnum::BinOpVarVarVar_Add_i8;
break;
}
default:
{
IL2CPP_ASSERT(false);
break;
}
}
break;
}
case EvalStackReduceDataType::I8:
{
switch (op2.reduceType)
{
case EvalStackReduceDataType::I8:
case EvalStackReduceDataType::I: // not support i8 + i ! but we support
{
resultType = EvalStackReduceDataType::I8;
ir->type = HiOpcodeEnum::BinOpVarVarVar_Add_i8;
break;
}
default:
{
IL2CPP_ASSERT(false);
break;
}
}
break;
}
case EvalStackReduceDataType::I:
case EvalStackReduceDataType::Ref:
{
switch (op2.reduceType)
{
case EvalStackReduceDataType::I4:
{
CreateAddIR(irConv, ConvertVarVar_i4_i8);
irConv->dst = irConv->src = op2.locOffset;

resultType = op1.reduceType;
ir->type = HiOpcodeEnum::BinOpVarVarVar_Add_i8;
break;
}
case EvalStackReduceDataType::I:
case EvalStackReduceDataType::I8:
{
resultType = op1.reduceType;
ir->type = HiOpcodeEnum::BinOpVarVarVar_Add_i8;
break;
}
default:
{
IL2CPP_ASSERT(false);
break;
}
}
break;
}
case EvalStackReduceDataType::R4:
{
switch (op2.reduceType)
{
case EvalStackReduceDataType::R4:
{
resultType = op2.reduceType;
ir->type = HiOpcodeEnum::BinOpVarVarVar_Add_f4;
break;
}
default:
{
IL2CPP_ASSERT(false);
break;
}
}
break;
}
case EvalStackReduceDataType::R8:
{
switch (op2.reduceType)
{
case EvalStackReduceDataType::R8:
{
resultType = op2.reduceType;
ir->type = HiOpcodeEnum::BinOpVarVarVar_Add_f8;
break;
}
default:
{
IL2CPP_ASSERT(false);
break;
}
}
break;
}
default:
{
IL2CPP_ASSERT(false);
break;
}
}

PopStack();
op1.reduceType = resultType;
op1.byteSize = GetSizeByReduceType(resultType);
AddInst(ir);
ip++;
continue;
}

从代码可以看出,其实转换算法非常简单,就是根据add指令的参数类型,决定转换为哪条寄存器指令,同时正确设置指令的字段值。

解释执行hybridclr指令集

解释执行在代码 interpreter::InterpreterModule::Execute 函数中完成。涉及到几部分:

  • 函数帧构建,参数、局部变量、执行栈的初始化
  • 执行普通指令
  • 调用子函数
  • 异常处理

这块内容也很多,我们会在多篇文章中详细介绍实现,这里简单摘取 BinOpVarVarVar_Add_i4 指令的实现代码:

case HiOpcodeEnum::BinOpVarVarVar_Add_i4:
{
uint16_t __ret = *(uint16_t*)(ip + 2);
uint16_t __op1 = *(uint16_t*)(ip + 4);
uint16_t __op2 = *(uint16_t*)(ip + 6);
(*(int32_t*)(localVarBase + __ret)) = (*(int32_t*)(localVarBase + __op1)) + (*(int32_t*)(localVarBase + __op2));
ip += 8;
continue;
}

相信这段代码还是比较好理解的。指令集转换和指令解释相关代码是hybridclr的核心,但复杂度却不高,这得感谢il2cpp运行时帮我们承担了绝大多数复杂的元数据相关操作的支持。

其他如GC、多线程相关处理

我们在hybridclr可行性的思维实验中分析过这两部分实现。

GC

对于对象分配,我们使用il2cpp::vm::Object::New函数分配对象即可。还有一些其他涉及到GC的部分如ldstr指令中Il2CppString对象的缓存,利用了一些其他il2cpp运行时提供的GC机制。

多线程相关处理

  • volatile 。对于指令中包含volatile前缀指令,我们简单在执行代码前后插入MemoryBarrier。
  • ThreadStatic 。 使用il2cpp内置的Class的ThreadStatic变量机制即可。
  • Thread。 我们对于每个托管线程,都创建了一个对应的解释器栈。
  • async 相关。由于异步相关只是语法糖,由编译器和标准库完成了所有内容。hybridclr只需要解决其中产生的AOT泛型实例化的问题即可。

总结

概括地说,hybridclr的实现为:

  • MetadataCache::LoadAssemblyFromBytes (c#层调用Assembly.Load时触发)时加载并注册interpreter Assembly
  • il2cpp运行过程中延迟初始化类型相关元数据,其中关键为正确设置了MethodInfo元数据中methodPointer指针
  • il2cpp运行时通过methodPointer或者methodInvoke指针,再经过桥接函数跳转,最终执行了Interpreter::Execute函数。
    • Execute函数在第一次执行某interpreter函数时触发HiTransform::Transform操作,将原始IL指令翻译为hybridclr的寄存器指令。
    • 然后执行该函数对应的hybridclr寄存器指令。

至此完成hybridclr的技术原理介绍。

· 阅读需 10 分钟

在确定目标,动手实现hybridclr前,有一个必须考虑的问题——我们如何确定hybridclr的可行性?

il2cpp虽然不是一个极其完整的运行时,但代码仍高达12w行,复杂度相当高,想要短期内深入了解它的实现是非常困难的。除了官方几个介绍il2cpp的博客外,几乎找不到其他文档, 而且Hybrid mode execution 的实现复杂度也很高。磨刀不误砍柴工,在动手前从理论上确信这套方案有极高可行性,是完全必要的。

以我们对CLR运行时的认识,要实现 hybrid mode execution 机制,至少要解决以下几个问题

  • 能够动态注册元数据,这些动态注册的元数据必须在运行时中跟AOT元数据完全等价。
  • 所有调用动态加载的assembly中函数的路径,都能定向到正确的解释器实现。包括虚函数override、delegate回调、反射调用等等。
  • 解释器中的gc,必须能够与AOT部分的gc统一处理。
  • 多线程相关能正常工作。包括且不限于创建Thread、async、volatile、ThreadStatic等等。

我们下面一一分析解决这些问题。

动态注册元数据

我们大略地分析了il2cpp元数据初始化相关代码,得出以下结论。

首先,动态修改globalmetadata.dat这个方式不可行。因为globalmetadata.dat保存了持久化的元数据,元数据之间关系大量使用id来相互引用,添加新的数据很容易引入错误,变成极难检测的bug。另外,globalmetadata里有不少数据项由于没有文档,无法分析实际用途,也不得而知如何设置正确的值。另外,运行时会动态加载新的dll,重新计算globalmetadata.dat是成本高昂的事情。而且il2cpp中元数据管理并不支持二次加载,重复加载globalmetadata.dat会产生相当大的代码改动。

一个较可行办法,修改所有元数据访问的底层函数,检查被访问的元数据的类型,如果是AOT元数据,则保持之前的调用,如果来自动态加载,则跳转到hybridclr的元数据管理模块,返回一个恰当的值。但这儿又遇到一个问题,其次globalmetadata为了优化性能,所有dll中的元数据在统一的id命名空间下。很多元数据查询操作仅仅使用一个id参数,如何根据id区别出到底是AOT还是interpreter的元数据?

我们发现实际项目生成的globalmetadata.dat中这些元数据id的值都较小,最大也不过几十万级别。思考后用一个技巧:我们将id分成两部分: 高位为image id,低位为实际上的id,将image id=0保留给AOT元数据使用。我们为每个动态加载的dll分配一个image id,这个image中解析出的所有元数据id的高位为相应的image id。

我们通过这个技巧,hook了所有底层访问元数据的方法。大约修改了几十处,基本都是如下这样的代码,尽量不修改原始逻辑,很容易保证正确性。

const char* il2cpp::vm::GlobalMetadata::GetStringFromIndex(StringIndex index)
{
// ==={{ hybridclr
if (hybridclr::metadata::IsInterpreterIndex(index))
{
return hybridclr::metadata::MetadataModule::GetStringFromEncodeIndex(index);
}
// ===}} hybridclr
IL2CPP_ASSERT(index <= s_GlobalMetadataHeader->stringSize);
const char* strings = MetadataOffset<const char*>(s_GlobalMetadata, s_GlobalMetadataHeader->stringOffset, index);
#if __ENABLE_UNITY_PLUGIN__
if (g_get_string != NULL)
{
g_get_string((char*)strings, index);
}
#endif // __ENABLE_UNITY_PLUGIN__
return strings;
}

我们在动手前检查了多个相关函数,基本没有问题。虽然不敢确定这一定是可行的,但元数据加载是hybridclr第一阶段的开发任务,万一发现问题,及时中止hybridclr开发损失不大。于是我们认为算是解决了第一个问题。

所有调用动态加载的assembly中函数的路径,都能定向到正确的解释器实现

我们分析了il2cpp中关于Method元数据的管理方式,发现MethodInfo结构中保存了运行时实际执行逻辑的函数指针。如果我们简单地设置动态加载的函数元数据的MethodInfo结构的指针为正确的解释器函数,能否保证所有流程对该函数的调用,都能正确定向到解释器函数呢?

严谨思考后的结论是肯定的。首先AOT部分不可能直接调用动态加载的dll中的函数。其次,运行时并没有其他地方保存了函数指针。意味着,如果想调用动态加载的函数,必须获得MethodInfo中的函数指针,才能正确执行到目标函数。意味着我们运行过程中所有对该函数的调用一定会调用到正确的解释器函数。

至于我们解决了第二个问题。

解释器中的gc,必须能够与AOT部分的gc统一处理

很容易观察到,通过il2cpp::vm::Object::New可以分配托管对象,通过gc模块的函数可以分配一些能够被gc自动管理的内存。但我们如何保证,使用这种方式就一定能保存正确性呢,会不会有特殊的使用规则 ,hybridclr的解释器代码无法与之配合工作呢?

考虑到AOT代码中也有很多gc相关的操作,我们检查了一些il2cpp为这些操作生成的c++代码,都是简简单单直接调用 il2cpp::vm::Object::New 之类的函数,并无特殊之处。 可以这么分析:il2cpp生成的代码是普通的c++代码,hybridclr解释器代码也是c++代码,既然生成的代码的内存使用方式能够正确工作,那么hybridclr解释器中gc相关代码,肯定也能正确工作。

至此,我们解决了第三个问题。

多线程相关代码能正常工作

与上一个问题相似。我们检查了il2cpp生成的c++代码,发现并无特殊之处也能在多线程环境下正常运行,那我们也可以非常确信,hybridclr解释器的代码只要符合常规的多线程的要求,也能在多线程环境下正常运行。

至此,我们解决了第四个问题。

总结

我们通过少量的对实际il2cpp代码的观察,以及对CLR运行时原理的了解,再配合思维实验,可以99.9%以上确定,既然il2cpp生成的代码都能在运行时正确运行,那hybridclr解释模式下执行的代码,也能正确运行。

我们在完成思维实验的那一刻,难掩内心激动的心情。作为一名物理专业的IT人,脑海里第一时间浮现出爱因斯坦在思考广义相对论时的,使用电梯思维实验得出引力使时空弯曲这一惊人结论。我们不敢比肩这种伟大的科学家,但我们确实在使用类似的思维技巧。可以说,hybridclr不是简单的经验总结,是深刻洞察力与分析能力孕育的结果。

· 阅读需 5 分钟

我们在实现hybridclr过程中,深入研究了CLI规范与il2cpp实现,积累了大量宝贵的经验。考虑到国内游戏行业对clr及il2cpp相关的资料不多,我们希望将这些知识系统性地整理出来,帮助那些渴望深入研究Unity下CLR Runtime实现的开发者们,更好了掌握相关知识。

Inspect il2cpp 目录

  • il2cpp 序章
    • il2cpp 介绍
    • il2cpp il2cpp 架构及源码结构介绍
    • il2cpp 安装、编译及调试
  • il2cpp 运行时实现
    • il2cpp Runtime 初始化流程剖析
    • il2cpp metadata (此节内容极其庞大)
      • CLI metadata 简略介绍
      • il2cpp metadata 初始化流程剖析
      • persistent metadata 即 global-metadata.dat 介绍
      • runtime metadata 介绍
    • il2cpp IL to c++ 代码的转换
      • 基础指令集
      • 对象模型相关指令 (内容极其庞大)
      • 异常机制
      • 泛型共享机制
      • PInvoke 与 MonoPInvokeCallbackAttribute相关。(一个有趣的问题:il2cpp中lua回调c#函数相比与回调普通c函数,多了哪些开销?)
      • icalls
      • delegate
      • 反射相关支持
      • 跨平台相关
    • 类型初始化 Class::Init 流程剖析
    • 泛型类实现
    • 泛型函数实现
    • 泛型共享机制
    • 异常机制
    • 反射相关实现
    • 值类型相关机制
    • box与unbox相关机制
    • object、string、Array、TypedReference等一些基础BCL类型的探究
    • icalls 实现
    • il2cpp及mono的bug介绍
    • il2cpp gc管理
    • il2cpp 多线程及内存模型处理
  • 2018-2022中il2cpp实现的演化

Inspect hybridclr 目录

  • 1 导论
    • 手游热更新技术的发展史
    • 当前主流热更新技术的缺陷
    • 下一代热更新技术探索——unity引擎下的原生c#热更新技术
  • 2 hybridclr概览
    • 1 hybridclr介绍
    • 2 关于hybridclr可行性的思维实验
    • 3 hybridclr技术原理剖析
  • 3 metadata 加载
    • 1 coff文件解析
    • 2 stream 解析
    • 3 原始tables解析
    • 4 复杂元数据解析
  • 4 metadata 注册
    • 1 assembly 注册
    • 2 TypeDefinition 注册(复杂)
    • 3 generic class
    • 4 generic method
    • 5 桥接函数
  • 5 寄存器指令集设计
    • IL指令集介绍
    • 基于栈的指令集的缺陷
    • 寄存器指令集
      • 基础转换规则
      • 指令静态特例化
      • resolve data
      • 其他特殊处理
    • 一些用于解释器JIT技术
      • InitOnce JIT优化技术
  • 6 指令集transform实现
    • 基础思路介绍
    • transform算法
      • basic block划分
      • 基于basic block的指令流遍历及转换
      • 普通指令
      • 函数调用指令
      • branch相关指令
      • 异常相关指令
    • 指令集优化
      • 指令合并
      • ValueType相关指令优化
      • 函数inline
      • instinct函数替换
    • virtual Execution System
      • Thread Interpreter Stack
      • Interpreter Frame实现与优化
      • localloc 与 Local Memory Pool
      • 桥接函数
      • 指令实现
      • instinct函数
      • reflection相关实现
      • extern函数实现
    • 跨平台兼容性处理
      • 32位与64位
      • 内存对齐访问
      • x86与arm系列区别
        • float与int之间转换
        • abi
      • 虚拟地址空间差异
      • 一些行为不定的函数
        • memcpy
    • AOT泛型 (基于补充元数据的泛型实例化技术)
    • AOT hotfix实现
  • misc
    • 解决Unity资源上挂载interpreter脚本
    • gc 处理
    • 多线程相关处理
  • test框架
    • 测试用例项目
      • bootstrap cpp测试集
      • .net c#测试集
      • 生成测试报告
    • 测试工具
      • 创建多版本多平台的测试项目
      • 运行测试用例,收集测试报告
      • 生成最终测试报告
    • 自动化测试DevOps框架
- + \ No newline at end of file diff --git a/blog/archive.html b/blog/archive.html index 543325830..981a9abd8 100644 --- a/blog/archive.html +++ b/blog/archive.html @@ -9,13 +9,13 @@ - + - + \ No newline at end of file diff --git a/blog/catelog.html b/blog/catelog.html index cba8cdf95..dfd573e0e 100644 --- a/blog/catelog.html +++ b/blog/catelog.html @@ -9,13 +9,13 @@ - +
跳到主要内容

深入探究hybridclr 目录

· 阅读需 5 分钟

我们在实现hybridclr过程中,深入研究了CLI规范与il2cpp实现,积累了大量宝贵的经验。考虑到国内游戏行业对clr及il2cpp相关的资料不多,我们希望将这些知识系统性地整理出来,帮助那些渴望深入研究Unity下CLR Runtime实现的开发者们,更好了掌握相关知识。

Inspect il2cpp 目录

  • il2cpp 序章
    • il2cpp 介绍
    • il2cpp il2cpp 架构及源码结构介绍
    • il2cpp 安装、编译及调试
  • il2cpp 运行时实现
    • il2cpp Runtime 初始化流程剖析
    • il2cpp metadata (此节内容极其庞大)
      • CLI metadata 简略介绍
      • il2cpp metadata 初始化流程剖析
      • persistent metadata 即 global-metadata.dat 介绍
      • runtime metadata 介绍
    • il2cpp IL to c++ 代码的转换
      • 基础指令集
      • 对象模型相关指令 (内容极其庞大)
      • 异常机制
      • 泛型共享机制
      • PInvoke 与 MonoPInvokeCallbackAttribute相关。(一个有趣的问题:il2cpp中lua回调c#函数相比与回调普通c函数,多了哪些开销?)
      • icalls
      • delegate
      • 反射相关支持
      • 跨平台相关
    • 类型初始化 Class::Init 流程剖析
    • 泛型类实现
    • 泛型函数实现
    • 泛型共享机制
    • 异常机制
    • 反射相关实现
    • 值类型相关机制
    • box与unbox相关机制
    • object、string、Array、TypedReference等一些基础BCL类型的探究
    • icalls 实现
    • il2cpp及mono的bug介绍
    • il2cpp gc管理
    • il2cpp 多线程及内存模型处理
  • 2018-2022中il2cpp实现的演化

Inspect hybridclr 目录

  • 1 导论
    • 手游热更新技术的发展史
    • 当前主流热更新技术的缺陷
    • 下一代热更新技术探索——unity引擎下的原生c#热更新技术
  • 2 hybridclr概览
    • 1 hybridclr介绍
    • 2 关于hybridclr可行性的思维实验
    • 3 hybridclr技术原理剖析
  • 3 metadata 加载
    • 1 coff文件解析
    • 2 stream 解析
    • 3 原始tables解析
    • 4 复杂元数据解析
  • 4 metadata 注册
    • 1 assembly 注册
    • 2 TypeDefinition 注册(复杂)
    • 3 generic class
    • 4 generic method
    • 5 桥接函数
  • 5 寄存器指令集设计
    • IL指令集介绍
    • 基于栈的指令集的缺陷
    • 寄存器指令集
      • 基础转换规则
      • 指令静态特例化
      • resolve data
      • 其他特殊处理
    • 一些用于解释器JIT技术
      • InitOnce JIT优化技术
  • 6 指令集transform实现
    • 基础思路介绍
    • transform算法
      • basic block划分
      • 基于basic block的指令流遍历及转换
      • 普通指令
      • 函数调用指令
      • branch相关指令
      • 异常相关指令
    • 指令集优化
      • 指令合并
      • ValueType相关指令优化
      • 函数inline
      • instinct函数替换
    • virtual Execution System
      • Thread Interpreter Stack
      • Interpreter Frame实现与优化
      • localloc 与 Local Memory Pool
      • 桥接函数
      • 指令实现
      • instinct函数
      • reflection相关实现
      • extern函数实现
    • 跨平台兼容性处理
      • 32位与64位
      • 内存对齐访问
      • x86与arm系列区别
        • float与int之间转换
        • abi
      • 虚拟地址空间差异
      • 一些行为不定的函数
        • memcpy
    • AOT泛型 (基于补充元数据的泛型实例化技术)
    • AOT hotfix实现
  • misc
    • 解决Unity资源上挂载interpreter脚本
    • gc 处理
    • 多线程相关处理
  • test框架
    • 测试用例项目
      • bootstrap cpp测试集
      • .net c#测试集
      • 生成测试报告
    • 测试工具
      • 创建多版本多平台的测试项目
      • 运行测试用例,收集测试报告
      • 生成最终测试报告
    • 自动化测试DevOps框架
- + \ No newline at end of file diff --git a/blog/instructions.html b/blog/instructions.html index c84eda82e..c305151ae 100644 --- a/blog/instructions.html +++ b/blog/instructions.html @@ -9,7 +9,7 @@ - + @@ -19,7 +19,7 @@ 尽管可以针对64位和32位设计两套完全不同的指令,但出于方便维护考虑,hybridclr还是统一使用了一套指令集。

hybridclr指令的一些设计约束:

  • 每条指令的前2字节必须为opcode
  • 满足内存对齐。指令param的size可能是1、2、4、8。为了满足内存对齐的要求,我们在param之间插入一些uint8_t类型的无用padding数据。

padding优化

为了最大程度减少浪费的padding数据空间,我们将所有param排序,从小到大排列,同时插入padding以满足内存对齐。经过不太复杂的推理,我们可以知道,每条指令最多浪费7字节的padding空间。

指令实现

由于IL指令众多,我们无法一一介绍所有指令对应的hybridclr指令集设计,我们分为几大类详细介绍。

空指令

如nop、pop指令,直接在transform阶段就被消除,完全不产生对应的hybridclr指令。

简单数据复制指令

典型有

  • 操作函数参数的指令。如 ldarg、starg、ldarga
  • 操作函数局部变量的指令。如 ldloc、stloc、ldloca
  • 隐含操作eval stack栈顶数据的指令。如add、dup

对于操作函数帧栈的指令,一般要做以下几类处理

  • 为源数据和目标数据添加对应的逻辑地址字段
  • 对于源数据或者目标数据有多个变种的指令,统一为带逻辑地址字段的指令。如ldarg.0 - ldarg.3、ldarg、ldarg.s 都统一为一条指令。

以典型的ldarg指令为例。如果被操作函数参数的类型为int时,对应的hybridclr指令为

struct IRCommon
{
uint16_t opcode;
}

struct IRLdlocVarVar : IRCommon
{
uint16_t dst;
uint16_t src;
uint8_t __pad6;
uint8_t __pad7;
};

// 对应解释执行代码
case HiOpcodeEnum::LdlocVarVar:
{
uint16_t __dst = *(uint16_t*)(ip + 2);
uint16_t __src = *(uint16_t*)(ip + 4);
(*(uint64_t*)(localVarBase + __dst)) = (*(uint64_t*)(localVarBase + __src));
ip += 8;
continue;
}

  • dst 指向当前执行栈顶的逻辑地址
  • src ldarg中要加载的变量的逻辑地址
  • __pad6 为了内存对齐而插入的
  • __pad7 同上

需要expand目标数据的指令

根据CLI规范,像byte、sbyte、short、ushort这种size小于4的primitive类型,以及underlying type为这些primitive类型的枚举,它们被加载到evaluate stack时,需要符号扩展为int32_t类型数据。我们不想执行ldarg指令时作运行时判断,因为这样会降低性能。因此为这些size小于4的操作,单独设计了对应的指令。

以byte类型为例,对应的hybridclr指令为

struct IRLdlocExpandVarVar_u1 : IRCommon
{
uint16_t dst;
uint16_t src;
uint8_t __pad6;
uint8_t __pad7;
};

// 对应解释执行代码
case HiOpcodeEnum::LdlocExpandVarVar_u1:
{
uint16_t __dst = *(uint16_t*)(ip + 2);
uint16_t __src = *(uint16_t*)(ip + 4);
(*(int32_t*)(localVarBase + __dst)) = (*(uint8_t*)(localVarBase + __src));
ip += 8;
continue;
}

静态特例化的指令

有一类指令的实际执行方式跟它的参数类型有关,如add。当操作的数是int、long、float、double时,执行对应类型的数据相加操作。但实际上由于IL程序的静态性,每条指令操作的数据类型肯定是固定的,并不需要运行时维护数据类型,并且根据数据类型决定执行什么操作。我们使用一种叫静态特例化的技术,为这种指令设计了多条hybridclr指令,在transform时,根据具体的操作数据类型,生成相应的指令。

以add 对两个int32_t类型数据相加为例

struct IRBinOpVarVarVar_Add_i4 : IRCommon
{
uint16_t ret;
uint16_t op1;
uint16_t op2;
};

// 对应解释执行代码
case HiOpcodeEnum::BinOpVarVarVar_Add_i4:
{
uint16_t __ret = *(uint16_t*)(ip + 2);
uint16_t __op1 = *(uint16_t*)(ip + 4);
uint16_t __op2 = *(uint16_t*)(ip + 6);
(*(int32_t*)(localVarBase + __ret)) = (*(int32_t*)(localVarBase + __op1)) + (*(int32_t*)(localVarBase + __op2));
ip += 8;
continue;
}

直接包含常量的指令

有一些指令包含普通字面常量,如ldc指令。相应的寄存器指令只是简单地添加了相应大小的字段。

以ldc int32_t类型数据为例

struct IRLdcVarConst_4 : IRCommon
{
uint16_t dst;
uint32_t src;
};

// 对应解释执行代码
case HiOpcodeEnum::LdcVarConst_4:
{
uint16_t __dst = *(uint16_t*)(ip + 2);
uint32_t __src = *(uint32_t*)(ip + 4);
(*(int32_t*)(localVarBase + __dst)) = __src;
ip += 8;
continue;
}

隐含常量的指令

有一些指令隐含了所操作的常量,如 ldnull、ldc.i4.0 - ldc.i4.8 等等。对于这类指令,如果有对应的直接包含常量的指令的实现,则简单转换为 上一节中介绍的 直接包含常量的指令。后续可能会进一步优化。

以ldnull为例

struct IRLdnullVar : IRCommon
{
uint16_t dst;
uint8_t __pad4;
uint8_t __pad5;
uint8_t __pad6;
uint8_t __pad7;
};

// 对应解释执行代码
case HiOpcodeEnum::LdnullVar:
{
uint16_t __dst = *(uint16_t*)(ip + 2);
(*(void**)(localVarBase + __dst)) = nullptr;
ip += 8;
continue;
}

指令共享

为了减少指令数量,操作相同size常量的ldc指令会被合并为同一个。如ldloc.r4 指令就被合并到ldloc.i4指令的实现。

包含resolved后数据的指令

有一些指令包含metadata token,如sizeof、ldstr、newobj。为了避免巨大的运行时resolve开销,hybridclr在transform这些指令时就已经将包含token数据resolve为对应的runtime metadata。

更细致一些,又分为两类。

直接包含resolved后数据的指令

以sizeof为例,原始指令token为类型信息,transform时,直接计算了对应ValueType的size,甚至都不需要专门为sizeof设计对应的指令,直接使用现成的LdcVarConst_4指令。

case OpcodeValue::SIZEOF:
{
uint32_t token = (uint32_t)GetI4LittleEndian(ip + 2);
Il2CppClass* objKlass = image->GetClassFromToken(token, klassContainer, methodContainer, genericContext);
IL2CPP_ASSERT(objKlass);
int32_t typeSize = GetTypeValueSize(objKlass);
CI_ldc4(typeSize, EvalStackReduceDataType::I4);
ip += 6;
continue;
}

间接包含resolved后数据的指令

像ldstr、newobj这些指令包含的token经过resolve后,变成对应runtime metadata的指针,考虑到指针在不同平台大小不一,因此不直接将这个指针放到指令中,而是换成一个uint32_t类型的指向InterpMethodInfo::resolvedData字段的index param。执行过程中需要一次向resolvedData的查询操作,时间复杂度为O(1)。

以newobj指令为例

struct IRLdstrVar : IRCommon
{
uint16_t dst;
uint32_t str;
};

// 对应解释执行代码
case HiOpcodeEnum::LdstrVar:
{
uint16_t __dst = *(uint16_t*)(ip + 2);
uint32_t __str = *(uint32_t*)(ip + 4);
(*(Il2CppString**)(localVarBase + __dst)) = ((Il2CppString*)imi->resolveDatas[__str]);
ip += 8;
continue;
}

分支跳转指令

原始IL字节码使用了相对offset的跳转目标,并且几乎为每条跳转相关指令都设计了near和far offset 两条指令,hybridclr为了简单起见,直接使用4字节的绝对跳转地址。

以br无条件跳转指令为例


struct IRBranchUncondition_4 : IRCommon
{
uint8_t __pad2;
uint8_t __pad3;
int32_t offset;
};

// 对应解释执行代码
case HiOpcodeEnum::BranchUncondition_4:
{
int32_t __offset = *(int32_t*)(ip + 4);
ip = ipBase + __offset;
continue;
}

offset为转换后的指令地址的绝对偏移。

对象成员访问指令

由于字段在对象中的偏移已经完全确定,transform时计算出字段在对象中的偏移,保存为指令的offset param, 执行时根据对象大小,使用this指针和偏移,直接访问字段数据。

以ldfld 读取int类型字段为例


struct IRLdfldVarVar_i4 : IRCommon
{
uint16_t dst;
uint16_t obj;
uint16_t offset;
};

// 对应解释执行代码
case HiOpcodeEnum::LdfldVarVar_i4:
{
uint16_t __dst = *(uint16_t*)(ip + 2);
uint16_t __obj = *(uint16_t*)(ip + 4);
uint16_t __offset = *(uint16_t*)(ip + 6);
CHECK_NOT_NULL_THROW((*(Il2CppObject**)(localVarBase + __obj)));
(*(int32_t*)(localVarBase + __dst)) = *(int32_t*)((uint8_t*)(*(Il2CppObject**)(localVarBase + __obj)) + __offset);
ip += 8;
continue;
}

ThreadStatic 成员访问指令

在初始化Il2CppClass时,如果它包含ThreadStatic属性标记的静态成员变量,则为它分配一个可以放下这个类型所有ThreadStatic变量的ThreadLocalStorage的连续空间。 借助于il2cpp运行时对ThreadStatic的支持,相关指令实现相当简单直接。

以ldsfld指令为例


struct IRLdthreadlocalVarVar_i4 : IRCommon
{
uint16_t dst;
int32_t offset;
int32_t klass;
};

// 对应解释执行代码
case HiOpcodeEnum::LdthreadlocalVarVar_i4:
{
uint16_t __dst = *(uint16_t*)(ip + 2);
uint32_t __klass = *(uint32_t*)(ip + 8);
int32_t __offset = *(int32_t*)(ip + 4);

Il2CppClass* _klass = (Il2CppClass*)imi->resolveDatas[__class];
Interpreter::RuntimeClassCCtorInit(_klass);
(*(int32_t*)(localVarBase + __dst)) = *(int32_t*)((byte*)il2cpp::vm::Thread::GetThreadStaticData(_klass->thread_static_fields_offset) + __offset);
ip += 16;
continue;
}

数组访问相关指令

比较常规直接,不过有个特殊点:根据规范index变量可以是i4或者native int类型。由于数组访问是非常频繁的操作,我们不想插入运行时数据类型类型及转换,因为我们根据index变量的size为每条数组相关指令设计了2条hybridclr指令。

以ldelem.i4 指令的index是i4类型的情形为例

struct IRGetArrayElementVarVar_i4_4 : IRCommon
{
uint16_t dst;
uint16_t arr;
uint16_t index;
};

// 对应解释执行代码
case HiOpcodeEnum::GetArrayElementVarVar_i4_4:
{
uint16_t __dst = *(uint16_t*)(ip + 2);
uint16_t __arr = *(uint16_t*)(ip + 4);
uint16_t __index = *(uint16_t*)(ip + 6);
Il2CppArray* arr = (*(Il2CppArray**)(localVarBase + __arr));
CHECK_NOT_NULL_AND_ARRAY_BOUNDARY(arr, (*(int32_t*)(localVarBase + __index)));
(*(int32_t*)(localVarBase + __dst)) = il2cpp_array_get(arr, int32_t, (*(int32_t*)(localVarBase + __index)));
ip += 8;
continue;
}

函数调用指令

目前调用AOT函数和调用Interpreter函数使用不同的指令,因为Interpreter函数可以直接复用已经压到栈顶的数据,可以完全优化掉 Manged2Native -> Native2Managed 这个过程,提升性能。

调用解释器函数时可以复用当前 InterpreterModule::Execute函数帧,也节省了函数调用开销,同时也避免了解释器嵌套调用过深导致native栈overflow的问题。

对于带返回值的函数,由于多了一个返回值地址参数ret,与返回void的函数分别设计了不同指令。

如果调用的是AOT函数,由于每条函数的参数不定,我们将参数信息记录到resolvedDatas,然后argIdxs中保存这个间接索引。另外还需要通过桥接函数完成解释器函数参数到native abi函数参数的转换,为了避免运行时查找的开销,也提前计算了这个桥接函数,记录到resolvedDatas中,然后在managed2NativeMethod中保存了这个间接索引。

以call指令为例,为它设计了5条指令

  • IRCallNative_void
  • IRCallNative_ret
  • IRCallNative_ret_expand
  • IRCallInterp_void
  • IRCallInterp_ret

以IRCallNative_ret的实现为例,介绍调用AOT函数的指令:


struct IRCallNative_ret : IRCommon
{
uint16_t ret;
uint32_t managed2NativeMethod;
uint32_t methodInfo;
uint32_t argIdxs;
};

// 对应解释执行代码
case HiOpcodeEnum::CallNative_ret:
{
uint32_t __managed2NativeMethod = *(uint32_t*)(ip + 4);
uint32_t __methodInfo = *(uint32_t*)(ip + 8);
uint32_t __argIdxs = *(uint32_t*)(ip + 12);
uint16_t __ret = *(uint16_t*)(ip + 2);
void* _ret = (void*)(localVarBase + __ret);
((Managed2NativeCallMethod)imi->resolveDatas[__managed2NativeMethod])(((MethodInfo*)imi->resolveDatas[__methodInfo]), ((uint16_t*)&imi->resolveDatas[__argIdxs]), localVarBase, _ret);
ip += 16;
continue;
}

如果调用Interpreter函数,由于函数参数已经按顺序压到栈上,只需要一个argBase参数指定arg0逻辑地址即可,不需要借助resolvedDatas,也不需要managed2NativeMethod桥接函数指针。 这也是解释器函数不受桥接函数影响的原因。

以IRCallInterp_ret为例,介绍调用Interpreter函数的指令:

struct IRCallInterp_ret : IRCommon
{
uint16_t argBase;
uint16_t ret;
uint8_t __pad6;
uint8_t __pad7;
uint32_t methodInfo;
};

// 对应解释执行代码
case HiOpcodeEnum::CallInterp_ret:
{
MethodInfo* __methodInfo = *(MethodInfo**)(ip + 8);
uint16_t __argBase = *(uint16_t*)(ip + 2);
uint16_t __ret = *(uint16_t*)(ip + 4);
CALL_INTERP_RET((ip + 16), __methodInfo, (StackObject*)(void*)(localVarBase + __argBase), (void*)(localVarBase + __ret));
continue;
}

异常机制相关指令

异常机制相关指令本身不复杂,但异常处理机制非常复杂。

异常这种特殊的流程控制指令,跟分支跳转指令相似,原始指令里包含了相对offset,为了简单起见,指令转换时我们改成int32_t类型的绝对offset。

以leave指令为例

struct IRLeaveEx : IRCommon
{
uint8_t __pad2;
uint8_t __pad3;
int32_t offset;
};

// 对应解释执行代码
case HiOpcodeEnum::LeaveEx:
{
int32_t __offset = *(int32_t*)(ip + 4);
LEAVE_EX(__offset);
continue;
}

一些额外的instinct 指令

对于一些特别常见的函数,为了优化性能,hybridclr直接内置了相应的指令,例如 new Vector{2,3,4},如可空变量相关操作。这些instinct指令的执行性能基本与AOT持平。

以 new Vector3() 为例


struct IRNewVector3_3 : IRCommon
{
uint16_t obj;
uint16_t x;
uint16_t y;
uint16_t z;
uint8_t __pad10;
uint8_t __pad11;
uint8_t __pad12;
uint8_t __pad13;
uint8_t __pad14;
uint8_t __pad15;
};

// 对应解释执行代码
case HiOpcodeEnum::NewVector3_3:
{
uint16_t __obj = *(uint16_t*)(ip + 2);
uint16_t __x = *(uint16_t*)(ip + 4);
uint16_t __y = *(uint16_t*)(ip + 6);
uint16_t __z = *(uint16_t*)(ip + 8);
*(HtVector3f*)(*(void**)(localVarBase + __obj)) = {(*(float*)(localVarBase + __x)), (*(float*)(localVarBase + __y)), (*(float*)(localVarBase + __z))};
ip += 16;
continue;
}

InitOnce 指令

有一些指令(如ldsfld)第一次执行的时候需要进行初始化操作,但后续再次执行时,不需要再执行初始化操作。但即使这样,免不了一个检查是否已经初始化的操作,我们希望完全优化掉这个检查行为。InitOnce动态JIT技术用于解决这个问题。

InitOnce是hybridclr的专利技术,暂未在代码中实现,这儿不详细介绍。

其他技术相关指令

限于篇幅,对于这些指令,会在单独的文章中介绍

总结

至此我们完成hybridclr指令集实现相关介绍。

- + \ No newline at end of file diff --git a/blog/mindexperiment.html b/blog/mindexperiment.html index 31e5eb06e..20299424b 100644 --- a/blog/mindexperiment.html +++ b/blog/mindexperiment.html @@ -9,14 +9,14 @@ - +
跳到主要内容

关于hybridclr可行性的思维实验

· 阅读需 10 分钟

在确定目标,动手实现hybridclr前,有一个必须考虑的问题——我们如何确定hybridclr的可行性?

il2cpp虽然不是一个极其完整的运行时,但代码仍高达12w行,复杂度相当高,想要短期内深入了解它的实现是非常困难的。除了官方几个介绍il2cpp的博客外,几乎找不到其他文档, 而且Hybrid mode execution 的实现复杂度也很高。磨刀不误砍柴工,在动手前从理论上确信这套方案有极高可行性,是完全必要的。

以我们对CLR运行时的认识,要实现 hybrid mode execution 机制,至少要解决以下几个问题

  • 能够动态注册元数据,这些动态注册的元数据必须在运行时中跟AOT元数据完全等价。
  • 所有调用动态加载的assembly中函数的路径,都能定向到正确的解释器实现。包括虚函数override、delegate回调、反射调用等等。
  • 解释器中的gc,必须能够与AOT部分的gc统一处理。
  • 多线程相关能正常工作。包括且不限于创建Thread、async、volatile、ThreadStatic等等。

我们下面一一分析解决这些问题。

动态注册元数据

我们大略地分析了il2cpp元数据初始化相关代码,得出以下结论。

首先,动态修改globalmetadata.dat这个方式不可行。因为globalmetadata.dat保存了持久化的元数据,元数据之间关系大量使用id来相互引用,添加新的数据很容易引入错误,变成极难检测的bug。另外,globalmetadata里有不少数据项由于没有文档,无法分析实际用途,也不得而知如何设置正确的值。另外,运行时会动态加载新的dll,重新计算globalmetadata.dat是成本高昂的事情。而且il2cpp中元数据管理并不支持二次加载,重复加载globalmetadata.dat会产生相当大的代码改动。

一个较可行办法,修改所有元数据访问的底层函数,检查被访问的元数据的类型,如果是AOT元数据,则保持之前的调用,如果来自动态加载,则跳转到hybridclr的元数据管理模块,返回一个恰当的值。但这儿又遇到一个问题,其次globalmetadata为了优化性能,所有dll中的元数据在统一的id命名空间下。很多元数据查询操作仅仅使用一个id参数,如何根据id区别出到底是AOT还是interpreter的元数据?

我们发现实际项目生成的globalmetadata.dat中这些元数据id的值都较小,最大也不过几十万级别。思考后用一个技巧:我们将id分成两部分: 高位为image id,低位为实际上的id,将image id=0保留给AOT元数据使用。我们为每个动态加载的dll分配一个image id,这个image中解析出的所有元数据id的高位为相应的image id。

我们通过这个技巧,hook了所有底层访问元数据的方法。大约修改了几十处,基本都是如下这样的代码,尽量不修改原始逻辑,很容易保证正确性。

const char* il2cpp::vm::GlobalMetadata::GetStringFromIndex(StringIndex index)
{
// ==={{ hybridclr
if (hybridclr::metadata::IsInterpreterIndex(index))
{
return hybridclr::metadata::MetadataModule::GetStringFromEncodeIndex(index);
}
// ===}} hybridclr
IL2CPP_ASSERT(index <= s_GlobalMetadataHeader->stringSize);
const char* strings = MetadataOffset<const char*>(s_GlobalMetadata, s_GlobalMetadataHeader->stringOffset, index);
#if __ENABLE_UNITY_PLUGIN__
if (g_get_string != NULL)
{
g_get_string((char*)strings, index);
}
#endif // __ENABLE_UNITY_PLUGIN__
return strings;
}

我们在动手前检查了多个相关函数,基本没有问题。虽然不敢确定这一定是可行的,但元数据加载是hybridclr第一阶段的开发任务,万一发现问题,及时中止hybridclr开发损失不大。于是我们认为算是解决了第一个问题。

所有调用动态加载的assembly中函数的路径,都能定向到正确的解释器实现

我们分析了il2cpp中关于Method元数据的管理方式,发现MethodInfo结构中保存了运行时实际执行逻辑的函数指针。如果我们简单地设置动态加载的函数元数据的MethodInfo结构的指针为正确的解释器函数,能否保证所有流程对该函数的调用,都能正确定向到解释器函数呢?

严谨思考后的结论是肯定的。首先AOT部分不可能直接调用动态加载的dll中的函数。其次,运行时并没有其他地方保存了函数指针。意味着,如果想调用动态加载的函数,必须获得MethodInfo中的函数指针,才能正确执行到目标函数。意味着我们运行过程中所有对该函数的调用一定会调用到正确的解释器函数。

至于我们解决了第二个问题。

解释器中的gc,必须能够与AOT部分的gc统一处理

很容易观察到,通过il2cpp::vm::Object::New可以分配托管对象,通过gc模块的函数可以分配一些能够被gc自动管理的内存。但我们如何保证,使用这种方式就一定能保存正确性呢,会不会有特殊的使用规则 ,hybridclr的解释器代码无法与之配合工作呢?

考虑到AOT代码中也有很多gc相关的操作,我们检查了一些il2cpp为这些操作生成的c++代码,都是简简单单直接调用 il2cpp::vm::Object::New 之类的函数,并无特殊之处。 可以这么分析:il2cpp生成的代码是普通的c++代码,hybridclr解释器代码也是c++代码,既然生成的代码的内存使用方式能够正确工作,那么hybridclr解释器中gc相关代码,肯定也能正确工作。

至此,我们解决了第三个问题。

多线程相关代码能正常工作

与上一个问题相似。我们检查了il2cpp生成的c++代码,发现并无特殊之处也能在多线程环境下正常运行,那我们也可以非常确信,hybridclr解释器的代码只要符合常规的多线程的要求,也能在多线程环境下正常运行。

至此,我们解决了第四个问题。

总结

我们通过少量的对实际il2cpp代码的观察,以及对CLR运行时原理的了解,再配合思维实验,可以99.9%以上确定,既然il2cpp生成的代码都能在运行时正确运行,那hybridclr解释模式下执行的代码,也能正确运行。

我们在完成思维实验的那一刻,难掩内心激动的心情。作为一名物理专业的IT人,脑海里第一时间浮现出爱因斯坦在思考广义相对论时的,使用电梯思维实验得出引力使时空弯曲这一惊人结论。我们不敢比肩这种伟大的科学家,但我们确实在使用类似的思维技巧。可以说,hybridclr不是简单的经验总结,是深刻洞察力与分析能力孕育的结果。

- + \ No newline at end of file diff --git a/blog/principle.html b/blog/principle.html index cae45a0a4..74aa5d807 100644 --- a/blog/principle.html +++ b/blog/principle.html @@ -9,13 +9,13 @@ - +
跳到主要内容

hybridclr技术原理剖析

· 阅读需 23 分钟

我们在上一节完成了hybridclr可行性分析。由于hybridclr内容极多,限于篇幅本篇文章主要概述性介绍hybridclr的技术实现。

CLR和il2cpp基础

给纯AOT的il2cpp运行时添加一个原生interpreter模块,最终实现hybrid mode execution,这看起来是非常复杂的事情。

其实不然,程序不外乎代码+数据。CLR运行中做的事情,综合起来主要就几种:

  1. 执行简单的内存操作或者计算或者逻辑跳转。这部分与CLI的Base指令集大致对应
  2. 执行一个依赖于元数据信息的基础操作。例如 a.x, arr[3] 这种,依赖于元数据信息才能正确工作的代码。对应部分CLI的Object Model指令集。
  3. 执行一个依赖元数据的较复杂的操作。如 typeof(object),a is string、(object)5 这种依赖于运行时提供的函数及相应元数据才正确工作的代码。对应部分CLI的Object Model指令集。
  4. 函数调用。包括且不限于被AOT函数调用及调用AOT函数,及interpreter之间的函数调用。对应CLI指令集中的 call、callvir、newobj 等Object Model指令。

如果对CLR有深入的了解和透彻的分析,为了实现hybrid mode execution,hybridclr核心要完成的就以下两件事,其他则是无碍全局的细节:

  • assembly信息能够加载和注册。 在此基础可以实现 1-3
  • 确保interpreter函数能被找到并且被调用,并且能执行出正确的结果。则可以实现 4

由于彻底理解以上内容需要较丰富的对CLR的认知以及较强的洞察力,我们不再费口舌解释,不能理解的开发者不必深究,继续看后续章节。

核心模块

从功能来看包含以下核心部分:

  • metadata初级解析
  • metadata高级元数据结构解析
  • metadata动态注册
  • 寄存器指令集设计
  • IL指令集到hybridclr寄存器指令集的转换
  • 解释执行hybridclr指令集
  • 其他如GC、多线程相关处理

从代码结构来看包含三个目录:

  • metadata 元数据相关
  • transform 指令集转换相关
  • interpreter 解释器相关

metadata 初级解析

这部分内容技术门槛不高,但比较琐碎和辛苦,忠实地按照 ECMA-335规范 的文档实现即可。对于少量有疑惑的地方,可以网上的资料或者借鉴mono的代码。

相关代码在hybridclr\metadata目录,主要在RawImage.h和RawImage.cpp中实现。如果再细分,相关实现分为以下几个部分。

PE 文件结构解析

managed dll扩展了PE文件结构,增加了CLI相关metadata部分。这环节的主要工作有:

  • 解析PE headers
  • 解析 section headers,找出CLI header,定位出cli数据段
  • 解析出所有stream。Stream是CLI中最底层的数据结构之一,CLI将元数据根据特性分为几个大类
    • #~ 流。包含所有tables定义,是最核心的元数据结构
    • #Strings 流。包括代码中非文档类型的字符串,如类型名、字段名等等
    • #GUID 流
    • #Blob 流。一些元数据类型过于复杂,以blob格式保存。还有一些数据如数组初始化数据列表,也常常保存到Blob流。
    • #- 流
    • #Pdb 流。用于调试

解析PE文件和代码在RawImage::Load,解析stream对应的代码在RawImage::LoadStreams。

tables metadata 解析

CLI中大多数metadata被为几十种类型,每个类型的数据组织成一个table。对于每个table,每行记录都是相同大小。

初级解析中不解析table中每行记录,只解析table的每行记录大小和每个字段偏移。有一大类字段为Coded Index类型,有可能是2或4字节,并不固定,需要根据其他表的Row Count来决定table中这一列的字段大小。由于table很多,这个计算过程比较琐碎易错。

对应代码在RawImage::LoadTables,截取部分代码如下

void RawImage::BuildTableRowMetas()
{
{
auto& table = _tableRowMetas[(int)TableType::MODULE];
table.push_back({ 2 });
table.push_back({ ComputStringIndexByte() });
table.push_back({ ComputGUIDIndexByte() });
table.push_back({ ComputGUIDIndexByte() });
table.push_back({ ComputGUIDIndexByte() });
}
{
auto& table = _tableRowMetas[(int)TableType::TYPEREF];
table.push_back({ ComputTableIndexByte(TableType::MODULE, TableType::MODULEREF, TableType::ASSEMBLYREF, TableType::TYPEREF, TagBits::ResoulutionScope) });
table.push_back({ ComputStringIndexByte() });
table.push_back({ ComputStringIndexByte() });
}

// ... 其他
}

table 解析

上一节已经解析出每个table的起始数据位置、row count、表中每个字段的偏移和大小,有足够的信息可以解析出每个table中任意row的数据。table中row的id从1开始。

每个table的row的解析方式根据ECMA规范实现即可。每个table的row定义在 metadata\Coff.h文件,Row解析代码在 RawImage.h。这些解析代码都非常相似,为了避免错误,使用了大量的宏,截取部分代码如下:

TABLE2(GenericParamConstraint, TableType::GENERICPARAMCONSTRAINT, owner, constraint)
TABLE3(MemberRef, TableType::MEMBERREF, classIdx, name, signature)
TABLE1(StandAloneSig, TableType::STANDALONESIG, signature)
TABLE3(MethodImpl, TableType::METHODIMPL, classIdx, methodBody, methodDeclaration)
TABLE2(FieldRVA, TableType::FIELDRVA, rva, field)
TABLE2(FieldLayout, TableType::FIELDLAYOUT, offset, field)
TABLE3(Constant, TableType::CONSTANT, type, parent, value)
TABLE2(MethodSpec, TableType::METHODSPEC, method, instantiation)
TABLE3(CustomAttribute, TableType::CUSTOMATTRIBUTE, parent, type, value)

metadata高级元数据结构解析

从tables里直接读出来的都是持久化的初始metadata,而运行时需要的不只是这些简单原始数据,经常需要进一步resolve后的数据。例如

  • Il2CppType 。即可以是简单的 int,也可以是比较复杂的List<int>,甚至是特别复杂的List<(int,int)>&
  • MethodInfo 。 即可以是简单的object.ToString,也有复杂的泛型 IEnumerator<int>.Count

CLI的泛型机制导致元数据变得极其复杂,典型的是TypeSpec,MethodSpec,MemberSpec相关元数据的运行时解析。核心实现代码在Image.cpp中实现,剩余一部分在 InterpreterImage.cpp及AOTHomologousImage.cpp中实现。后面会有专门介绍。

metadata动态注册

根据粒度从大到小,主要分为以下几类

  • Assembly 注册。即将加载的assembly注册到il2cpp的元数据管理中。
  • TypeDefinition 注册。 这一步会生成基础运行时类型 Il2CppClass。
  • VTable虚表计算。 由于il2cpp的虚表计算是个黑盒,内部相当复杂,我们费了很多功夫才研究明白它的计算机制。后面会有专门章节介绍VTable计算,这儿不再赘述。
  • 其他元数据,如CustomAttribute计算等等。

Assembly 注册

Assembly加载的关键函数在 il2cpp::vm::MetadataCache::LoadAssemblyFromBytes 。由于il2cpp是AOT运行时,原始实现只是简单地抛出异常。我们修改和完善了实现,在其中调用了hybridclr::metadata::Assembly::LoadFromBytes,完成了Assembly的创建,然后再注册到全局Assemblies列表。相关代码实现如下:

const Il2CppAssembly* il2cpp::vm::MetadataCache::LoadAssemblyFromBytes(const char* assemblyBytes, size_t length)
{
il2cpp::os::FastAutoLock lock(&il2cpp::vm::g_MetadataLock);

Il2CppAssembly* newAssembly = hybridclr::metadata::Assembly::LoadFromBytes(assemblyBytes, length, true);
if (newAssembly)
{
// avoid register placeholder assembly twicely.
for (Il2CppAssembly* ass : s_cliAssemblies)
{
if (ass == newAssembly)
{
return ass;
}
}
il2cpp::vm::Assembly::Register(newAssembly);
s_cliAssemblies.push_back(newAssembly);
return newAssembly;
}

return nullptr;
}

TypeDefinition 注册

Assembly使用了延迟初始化方式,注册后Assembly中的类型信息并未创建相应的运行时metadata Il2CppClass,只有当第一次访问到该类型时才进行初始化。

由于交叉依赖以及为了优化性能,Il2Class的创建是个分步过程

  • Il2CppClass 基础创建
  • Il2CppClass的子元数据延迟初始化
  • 运行时Class初始化

Il2CppClass基础创建

在上一节加载Assembly时已经创建好所有类型对应的定义数据Il2CppTypeDefinition,在 il2cpp::vm::GlobalMetadata::FromTypeDefinition 中完成Il2CppClass创建工作。代码如下:

Il2CppClass* il2cpp::vm::GlobalMetadata::FromTypeDefinition(TypeDefinitionIndex index)
{
/// ... 省略其他
Il2CppClass* typeInfo = (Il2CppClass*)IL2CPP_CALLOC(1, sizeof(Il2CppClass) + (sizeof(VirtualInvokeData) * typeDefinition->vtable_count));
typeInfo->klass = typeInfo;
typeInfo->image = GetImageForTypeDefinitionIndex(index);
typeInfo->name = il2cpp::vm::GlobalMetadata::GetStringFromIndex(typeDefinition->nameIndex);
typeInfo->namespaze = il2cpp::vm::GlobalMetadata::GetStringFromIndex(typeDefinition->namespaceIndex);
typeInfo->byval_arg = *il2cpp::vm::GlobalMetadata::GetIl2CppTypeFromIndex(typeDefinition->byvalTypeIndex);
typeInfo->this_arg = typeInfo->byval_arg;
typeInfo->this_arg.byref = true;
typeInfo->typeMetadataHandle = reinterpret_cast<const Il2CppMetadataTypeHandle>(typeDefinition);
typeInfo->genericContainerHandle = GetGenericContainerFromIndex(typeDefinition->genericContainerIndex);
typeInfo->instance_size = typeDefinitionSizes->instance_size;
typeInfo->actualSize = typeDefinitionSizes->instance_size; // actualySize is instance_size for compiler generated values
typeInfo->native_size = typeDefinitionSizes->native_size;
typeInfo->static_fields_size = typeDefinitionSizes->static_fields_size;
typeInfo->thread_static_fields_size = typeDefinitionSizes->thread_static_fields_size;
typeInfo->thread_static_fields_offset = -1;
typeInfo->flags = typeDefinition->flags;
typeInfo->valuetype = (typeDefinition->bitfield >> (kBitIsValueType - 1)) & 0x1;
typeInfo->enumtype = (typeDefinition->bitfield >> (kBitIsEnum - 1)) & 0x1;
typeInfo->is_generic = typeDefinition->genericContainerIndex != kGenericContainerIndexInvalid; // generic if we have a generic container
typeInfo->has_finalize = (typeDefinition->bitfield >> (kBitHasFinalizer - 1)) & 0x1;
typeInfo->has_cctor = (typeDefinition->bitfield >> (kBitHasStaticConstructor - 1)) & 0x1;
typeInfo->is_blittable = (typeDefinition->bitfield >> (kBitIsBlittable - 1)) & 0x1;
typeInfo->is_import_or_windows_runtime = (typeDefinition->bitfield >> (kBitIsImportOrWindowsRuntime - 1)) & 0x1;
typeInfo->packingSize = ConvertPackingSizeEnumToValue(static_cast<PackingSize>((typeDefinition->bitfield >> (kPackingSize - 1)) & 0xF));
typeInfo->method_count = typeDefinition->method_count;
typeInfo->property_count = typeDefinition->property_count;
typeInfo->field_count = typeDefinition->field_count;
typeInfo->event_count = typeDefinition->event_count;
typeInfo->nested_type_count = typeDefinition->nested_type_count;
typeInfo->vtable_count = typeDefinition->vtable_count;
typeInfo->interfaces_count = typeDefinition->interfaces_count;
typeInfo->interface_offsets_count = typeDefinition->interface_offsets_count;
typeInfo->token = typeDefinition->token;
typeInfo->interopData = il2cpp::vm::MetadataCache::GetInteropDataForType(&typeInfo->byval_arg);

// 省略其他

return typeInfo;
}

可以看到TypeDefinition中字段相当多,这些都是在Assembly加载环节计算好的。

Il2CppClass的子metadata延迟初始化

由于交互依赖以及为了优化性能,Il2Class的子metadata数据使用了延迟初始化策略,分步进行,在第一次使用时才初始化。以下代码截取自 Class.h 文件:

class Class
{
// ... 其他代码
static bool Init(Il2CppClass *klass);

static void SetupEvents(Il2CppClass *klass);
static void SetupFields(Il2CppClass *klass);
static void SetupMethods(Il2CppClass *klass);
static void SetupNestedTypes(Il2CppClass *klass);
static void SetupProperties(Il2CppClass *klass);
static void SetupTypeHierarchy(Il2CppClass *klass);
static void SetupInterfaces(Il2CppClass *klass);
// ... 其他代码
};

重点来了!!!函数metadata的执行指针的绑定在SetupMethods函数中完成,其中关键代码片段如下:

void SetupMethodsLocked(Il2CppClass *klass, const il2cpp::os::FastAutoLock& lock)
{
/// ... 其他忽略的代码
for (MethodIndex index = 0; index < end; ++index)
{
Il2CppMetadataMethodInfo methodInfo = MetadataCache::GetMethodInfo(klass, index);

newMethod->name = methodInfo.name;

if (klass->valuetype)
{
Il2CppMethodPointer adjustorThunk = MetadataCache::GetAdjustorThunk(klass->image, methodInfo.token);
if (adjustorThunk != NULL)
newMethod->methodPointer = adjustorThunk;
}

// We did not find an adjustor thunk, or maybe did not need to look for one. Let's get the real method pointer.
if (newMethod->methodPointer == NULL)
newMethod->methodPointer = MetadataCache::GetMethodPointer(klass->image, methodInfo.token);

newMethod->invoker_method = MetadataCache::GetMethodInvoker(klass->image, methodInfo.token);
}
/// ... 其他忽略的代码
}

函数运行时元数据结构为 MethodInfo,定义如下,

typedef struct MethodInfo
{
Il2CppMethodPointer methodPointer;
InvokerMethod invoker_method;
const char* name;
Il2CppClass *klass;
const Il2CppType *return_type;
const ParameterInfo* parameters;

// ... 省略其他
} MethodInfo;

其中我们比较关心的是methodPointer和invoker_method这两个字段。 methodPointer指向普通执行函数,invoker_method指向反射执行函数。

我们以 methodPointer为例,进一步跟踪它的设置过程, il2cpp::vm::MetadataCache::GetMethodPointer 的实现如下:

Il2CppMethodPointer il2cpp::vm::MetadataCache::GetMethodPointer(const Il2CppImage* image, uint32_t token)
{
uint32_t rid = GetTokenRowId(token);
uint32_t table = GetTokenType(token);
if (rid == 0)
return NULL;

// ==={{ hybridclr
if (hybridclr::metadata::IsInterpreterImage(image))
{
return hybridclr::metadata::MetadataModule::GetMethodPointer(image, token);
}
// ===}} hybridclr

IL2CPP_ASSERT(rid <= image->codeGenModule->methodPointerCount);

return image->codeGenModule->methodPointers[rid - 1];
}

可以看出,如果是解释器assembly,就跳转到解释器元数据模块获得对应的MethodPointer指针。 继续跟踪,相关代码如下:


Il2CppMethodPointer InterpreterImage::GetMethodPointer(uint32_t token)
{
uint32_t methodIndex = DecodeTokenRowIndex(token) - 1;
IL2CPP_ASSERT(methodIndex < (uint32_t)_methodDefines.size());
const Il2CppMethodDefinition* methodDef = &_methodDefines[methodIndex];
return hybridclr::interpreter::InterpreterModule::GetMethodPointer(methodDef);
}

Il2CppMethodPointer InterpreterModule::GetMethodPointer(const Il2CppMethodDefinition* method)
{
const NativeCallMethod* ncm = GetNativeCallMethod(method, false);
if (ncm)
{
return ncm->method;
}
//RaiseMethodNotSupportException(method, "GetMethodPointer");
return (Il2CppMethodPointer)NotSupportNative2Managed;
}

// interpreter/InterpreterModule.cpp
template<typename T>
const NativeCallMethod* GetNativeCallMethod(const T* method, bool forceStatic)
{
char sigName[1000];
ComputeSignature(method, !forceStatic, sigName, sizeof(sigName) - 1);
auto it = s_calls.find(sigName);
return (it != s_calls.end()) ? &it->second : nullptr;
}

// s_calls 定义
static std::unordered_map<const char*, NativeCallMethod, CStringHash, CStringEqualTo> s_calls;

void InterpreterModule::Initialize()
{
for (size_t i = 0; ; i++)
{
NativeCallMethod& method = g_callStub[i];
if (!method.signature)
{
break;
}
s_calls.insert({ method.signature, method });
}

for (size_t i = 0; ; i++)
{
NativeInvokeMethod& method = g_invokeStub[i];
if (!method.signature)
{
break;
}
s_invokes.insert({ method.signature, method });
}
}

这儿根据函数定义计算其签名并且返回了一个函数指针,这个函数指针是什么呢? s_calls在InterpreterModule::Initialize中使用g_callStub初始化。那g_calStub又是什么呢?它在 interpreter/MethodBridge_xxx.cpp 中定义,原来是桥接函数相关的数据结构!

为什么要返回一个这样的函数,而不是直接将methodPointer指向 InterpreterModule::Execute 函数呢? 以 int Foo::Sum(int,int) 函数为例,这个函数的实际的签名为 int32_t (int32_t, int32_t, MethodInfo*),在调用这个methodPointer函数时,调用方一定会传递这三个参数。这些参数每个函数都不一样,如果直接指向 InterpreterModule::Execute 函数,由于ABI调用无法自省(就算可以,性能也比较差),Execute函数既无法提取出普通参数,也无法提取出MethodInfo*参数,因而无法正确运行。因此需要对每个函数,适当地将ABI调用中的这些参数传递给Execute函数。

桥接函数如其名,承担了native ABI函数参数和interpreter函数之间双向的参数的转换作用。截取一段示例代码:


/// AOT 到 interpreter 的调用参数转换
static int64_t __Native2ManagedCall_i8srr8sr(void* __arg0, double __arg1, void* __arg2, const MethodInfo* method)
{
StackObject args[4] = {*(void**)&__arg0, *(void**)&__arg1, *(void**)&__arg2 };
StackObject* ret = args + 3;
Interpreter::Execute(method, args, ret);
return *(int64_t*)ret;
}

// interpreter 到 AOT 的调用参数转换
static void __Managed2NativeCall_i8srr8sr(const MethodInfo* method, uint16_t* argVarIndexs, StackObject* localVarBase, void* ret)
{
if (hybridclr::metadata::IsInstanceMethod(method) && !localVarBase[argVarIndexs[0]].obj)
{
il2cpp::vm::Exception::RaiseNullReferenceException();
}
Interpreter::RuntimeClassCCtorInit(method);
typedef int64_t (*NativeMethod)(void* __arg0, double __arg1, void* __arg2, const MethodInfo* method);
*(int64_t*)ret = ((NativeMethod)(method->methodPointer))((void*)(localVarBase+argVarIndexs[0]), *(double*)(localVarBase+argVarIndexs[1]), (void*)(localVarBase+argVarIndexs[2]), method);
}

运行时Class初始化

即程序运行过程中第一次访问类的静态字段或者函数时或者创建对象时触发的类型初始化。在il2cpp::vm::Runtime::ClassInit(klass)中完成。不是特别关键,我们后面在单独文章中介绍。

VTable虚表计算

虚表是多态的核心。CLI的虚表计算非常复杂,但不理解它的实现并不影响开发者理解hybridclr的核心运行流程,我们后面在单独文章中介绍。

其他元数据

CustomAttribute使用延迟初始化方式,计算也很复杂,我们后面单独文章介绍。

寄存器指令集设计

直接解释原始IL指令有几个问题:

  • IL是基于栈的指令,运行时维护执行栈是个无谓的开销
  • IL有大量单指令多功能的指令,如add指令可以用于计算int、long、float、double类型的和,导致运行时需要根据上文判断到底该执行哪种计算。不仅增加了运行时判定的开销,还增加了运行时维护执行栈数据类型的开销
  • IL指令包含一些需要运行时resolve的数据,如newobj指令第一个参数是method token。token resolve是一个开销很大的操作,每次执行都进行resolve会极大拖慢执行性能
  • IL是基于栈的指令,压栈退栈相关指令数较多。像a=b+c这样的指令需要4条指令完成,而如果采用基于寄存器的指令,完全可以一条指令完成。
  • IL不适合做其他优化操作,如我们的InitOnce JIT技术。
  • 其他

因此我们需要将原始IL指令转换为更高效的寄存器指令。由于指令很多,这儿不介绍寄存器指令集的详细设计。以add指令举例


// 包含type字段,即指令ID。
struct IRCommon
{
HiOpcodeEnum type;
};

// add int, int -> int 对应的寄存器指令
struct IRBinOpVarVarVar_Add_i4 : IRCommon
{
uint16_t ret; // 计算结果对应的 栈位置
uint16_t op1; // 操作数1对应的栈位置
uint16_t op2; // 操作数2对应的栈位置
};

指令集的转换

理解这节需要初步的编译原理相关知识,我们使用了非常朴素的转换算法,并且基本没有做指令优化。转换过程分为几步:

  • BasicBlock 划分。 将IL指令块切成一段段不包含任何跳转指令的代码块,称之为BasicBlock。
  • 模拟指令执行流程,同时使用广度优先遍历算法遍历所有BasicBlock,将每个BasicBlock转换为IRBasicBlock。

BasicBlock到IRBasicBlock转换采用了最朴素的一对一指令转换算法,转换相关代码在transform::HiTransform::Transform。我们以add指令为例:


case OpcodeValue::ADD:
{
IL2CPP_ASSERT(evalStackTop >= 2);
EvalStackVarInfo& op1 = evalStack[evalStackTop - 2];
EvalStackVarInfo& op2 = evalStack[evalStackTop - 1];

CreateIR(ir, BinOpVarVarVar_Add_i4);
ir->op1 = op1.locOffset;
ir->op2 = op2.locOffset;
ir->ret = op1.locOffset;

EvalStackReduceDataType resultType;
switch (op1.reduceType)
{
case EvalStackReduceDataType::I4:
{
switch (op2.reduceType)
{
case EvalStackReduceDataType::I4:
{
resultType = EvalStackReduceDataType::I4;
ir->type = HiOpcodeEnum::BinOpVarVarVar_Add_i4;
break;
}
case EvalStackReduceDataType::I:
case EvalStackReduceDataType::Ref:
{
CreateAddIR(irConv, ConvertVarVar_i4_i8);
irConv->dst = irConv->src = op1.locOffset;

resultType = op2.reduceType;
ir->type = HiOpcodeEnum::BinOpVarVarVar_Add_i8;
break;
}
default:
{
IL2CPP_ASSERT(false);
break;
}
}
break;
}
case EvalStackReduceDataType::I8:
{
switch (op2.reduceType)
{
case EvalStackReduceDataType::I8:
case EvalStackReduceDataType::I: // not support i8 + i ! but we support
{
resultType = EvalStackReduceDataType::I8;
ir->type = HiOpcodeEnum::BinOpVarVarVar_Add_i8;
break;
}
default:
{
IL2CPP_ASSERT(false);
break;
}
}
break;
}
case EvalStackReduceDataType::I:
case EvalStackReduceDataType::Ref:
{
switch (op2.reduceType)
{
case EvalStackReduceDataType::I4:
{
CreateAddIR(irConv, ConvertVarVar_i4_i8);
irConv->dst = irConv->src = op2.locOffset;

resultType = op1.reduceType;
ir->type = HiOpcodeEnum::BinOpVarVarVar_Add_i8;
break;
}
case EvalStackReduceDataType::I:
case EvalStackReduceDataType::I8:
{
resultType = op1.reduceType;
ir->type = HiOpcodeEnum::BinOpVarVarVar_Add_i8;
break;
}
default:
{
IL2CPP_ASSERT(false);
break;
}
}
break;
}
case EvalStackReduceDataType::R4:
{
switch (op2.reduceType)
{
case EvalStackReduceDataType::R4:
{
resultType = op2.reduceType;
ir->type = HiOpcodeEnum::BinOpVarVarVar_Add_f4;
break;
}
default:
{
IL2CPP_ASSERT(false);
break;
}
}
break;
}
case EvalStackReduceDataType::R8:
{
switch (op2.reduceType)
{
case EvalStackReduceDataType::R8:
{
resultType = op2.reduceType;
ir->type = HiOpcodeEnum::BinOpVarVarVar_Add_f8;
break;
}
default:
{
IL2CPP_ASSERT(false);
break;
}
}
break;
}
default:
{
IL2CPP_ASSERT(false);
break;
}
}

PopStack();
op1.reduceType = resultType;
op1.byteSize = GetSizeByReduceType(resultType);
AddInst(ir);
ip++;
continue;
}

从代码可以看出,其实转换算法非常简单,就是根据add指令的参数类型,决定转换为哪条寄存器指令,同时正确设置指令的字段值。

解释执行hybridclr指令集

解释执行在代码 interpreter::InterpreterModule::Execute 函数中完成。涉及到几部分:

  • 函数帧构建,参数、局部变量、执行栈的初始化
  • 执行普通指令
  • 调用子函数
  • 异常处理

这块内容也很多,我们会在多篇文章中详细介绍实现,这里简单摘取 BinOpVarVarVar_Add_i4 指令的实现代码:

case HiOpcodeEnum::BinOpVarVarVar_Add_i4:
{
uint16_t __ret = *(uint16_t*)(ip + 2);
uint16_t __op1 = *(uint16_t*)(ip + 4);
uint16_t __op2 = *(uint16_t*)(ip + 6);
(*(int32_t*)(localVarBase + __ret)) = (*(int32_t*)(localVarBase + __op1)) + (*(int32_t*)(localVarBase + __op2));
ip += 8;
continue;
}

相信这段代码还是比较好理解的。指令集转换和指令解释相关代码是hybridclr的核心,但复杂度却不高,这得感谢il2cpp运行时帮我们承担了绝大多数复杂的元数据相关操作的支持。

其他如GC、多线程相关处理

我们在hybridclr可行性的思维实验中分析过这两部分实现。

GC

对于对象分配,我们使用il2cpp::vm::Object::New函数分配对象即可。还有一些其他涉及到GC的部分如ldstr指令中Il2CppString对象的缓存,利用了一些其他il2cpp运行时提供的GC机制。

多线程相关处理

  • volatile 。对于指令中包含volatile前缀指令,我们简单在执行代码前后插入MemoryBarrier。
  • ThreadStatic 。 使用il2cpp内置的Class的ThreadStatic变量机制即可。
  • Thread。 我们对于每个托管线程,都创建了一个对应的解释器栈。
  • async 相关。由于异步相关只是语法糖,由编译器和标准库完成了所有内容。hybridclr只需要解决其中产生的AOT泛型实例化的问题即可。

总结

概括地说,hybridclr的实现为:

  • MetadataCache::LoadAssemblyFromBytes (c#层调用Assembly.Load时触发)时加载并注册interpreter Assembly
  • il2cpp运行过程中延迟初始化类型相关元数据,其中关键为正确设置了MethodInfo元数据中methodPointer指针
  • il2cpp运行时通过methodPointer或者methodInvoke指针,再经过桥接函数跳转,最终执行了Interpreter::Execute函数。
    • Execute函数在第一次执行某interpreter函数时触发HiTransform::Transform操作,将原始IL指令翻译为hybridclr的寄存器指令。
    • 然后执行该函数对应的hybridclr寄存器指令。

至此完成hybridclr的技术原理介绍。

- + \ No newline at end of file diff --git a/docs/basic.html b/docs/basic.html index 6be3c312f..673f32b9c 100644 --- a/docs/basic.html +++ b/docs/basic.html @@ -9,13 +9,13 @@ - +
跳到主要内容

使用指南

- + \ No newline at end of file diff --git a/docs/basic/aotgeneric.html b/docs/basic/aotgeneric.html index 2f0a838c4..1281239ec 100644 --- a/docs/basic/aotgeneric.html +++ b/docs/basic/aotgeneric.html @@ -9,7 +9,7 @@ - + @@ -34,7 +34,7 @@ 加载补充元数据不仅导致内存占用明显增加(一般为3-4倍补充元数据dll大小),还增加了启动时间。对于微信小游戏这些对包体和内存要求严苛的场合,这是一个影响较大的问题。 被补充的泛型函数以解释方式执行,还降低了运行性能。

HybridCLR支持full genric sharing后,不再需要补充元数据,简化了工作流,以原生方式运行AOT泛型,性能大幅提升,彻底解决了补充元数据的以上缺点。 详细文档见完全泛型共享

附录:AOT泛型的共享泛型实例化示例

警告

HybridCLR性能非常优异,除非确实遇到到性能问题,否则绝大多数情况下你应该使用补充元技术或者full generic sharing技术来解决AOT泛型问题。

示例1

错误日志

MissingMethodException: AOT generic method not instantiated in aot module 
void System.Collections.Generic.List<System.String>.ctor()

你在主工程中随便找个地方(比如在RefTypes.cs)加上 List<string>.ctor() 的调用,即 new List<string>()。由于泛型共享机制,你调用 new List<object>() 即可。

class RefTypes
{
public void MyAOTRefs()
{
new List<object>(); // 也可以用 new List<string>()
}
}

示例2

错误日志

MissingMethodException: AOT generic method not instantiated in aot module 
void System.ValueType<System.Int32, System.String>.ctor()
提示

值类型的空构造函数没有调用相应的构造函数,而是对应 initobj指令。实际上你无法直接引用它,但你只要强制实例化这个类型就行了,preserve这个类的所有函数,自然就会包含.ctor函数了。

实际中你可以用强制装箱 (object)(default(ValueTuple<int, object>))

class RefTypes
{
public void MyAOTRefs()
{
// 以下两种写法都是可以的
_ = (object)(new ValueTuple<int, object>());
_ = (object)(default(ValueTuple<int, object>));
}
}

示例3

错误日志

MissingMethodException: AOT generic method not instantiated in aot module 
System.Void System.Runtime.CompilerService.AsyncVoidMethodBuilder::Start<UIMgr+ShowUId__2>(UIMgr+<ShowUI>d__2&)
class RefTypes
{
public void MyAOTRefs()
{
System.Runtime.CompilerService.AsyncVoidMethodBuilder builder = default;
IAsyncStateMachine asm = default;
builder.Start(ref asm);
}
}
- + \ No newline at end of file diff --git a/docs/basic/architecture.html b/docs/basic/architecture.html index 76c8e8bb8..b35d7270e 100644 --- a/docs/basic/architecture.html +++ b/docs/basic/architecture.html @@ -9,7 +9,7 @@ - + @@ -17,7 +17,7 @@
跳到主要内容

代码结构及版本

完整的HybridCLR代码由三个仓库构成:

  • il2cpp_plus
  • hybridclr
  • com.code-philosophy.hybridclr

这三个仓库有独立的版本号,因此谈到HybridCLR版本时,一般包含这三个版本号。

il2cpp_plus

仓库地址 github gitee

HybridCLR扩展il2cpp运行时,需要对原始il2cpp代码作少量调整,以支持混合运行模式。这部分代码对应了 il2cpp_plus 仓库。由于il2cpp每个年度大版本变化较大,需要对每个Unity大版本单独进行适配。

每个年度版本都对应一个 {version}-main主分支,如 2021-main

当前每个年度版本还有一个老的1.0分支 {version}-1.0,如 2019-1.0

hybridclr

仓库地址 github gitee

hybridclr仓库中包含了解释器的核心代码,所有il2cpp_plus共享同一套hybridclr代码,不区分Unity大版本。当前有两个分支:

  • main
  • 3.x
  • 2.x
  • 1.0

com.code-philosophy.hybridclr

仓库地址 github gitee

com.code-philosophy.hybridclr是Unity Package,包含一些使用HybridCLR所需的运行时代码及编辑器工作流工具。

com.code-philosophy.hybridclr也不区分Unity大版本,因此像hybridclr一样,当前有两个分支:

  • main
  • 3.x
  • 2.x
  • 1.0

在早期版本中(如1.0分支),需要在Installer中指定你要安装的il2cpp_plus和hybridclr的分支。这两个仓库的分支必须匹配, 即 il2cpp_plus 的{version}-main与hybridclr的main匹配, {version}-1.01.0匹配。

v2.0.0-rc版本(属于main分支)起,com.code-philosophy.hybridclr中直接配置了与它兼容的 il2cpp_plus及hybridclr仓库的版本号。对于开发者来说, 只需要安装合适的com.code-philosophy.hybridclr版本即可。

- + \ No newline at end of file diff --git a/docs/basic/bestpractice.html b/docs/basic/bestpractice.html index 56bff69b8..063f07ba4 100644 --- a/docs/basic/bestpractice.html +++ b/docs/basic/bestpractice.html @@ -9,14 +9,14 @@ - +
跳到主要内容

最佳实践

unity版本推荐

推荐使用 2020.3.x(x >= 21) 系列及 2021.3.x 系列,最稳定。

Assembly.Load之后不要保存 assemblyBytes

assembly的byte[]数据在调用完Assembly.Load后不要保存起来,因为在Assembly.Load中会自动复制一份。

推荐启动脚本挂载到热更新完成后首个加载的热更新场景

推荐将启动脚本挂载到启动热更新场景,这样可以零改动将非热更新工程改造成热更新工程,还不需要任何反射操作。

RuntimeApi.LoadMetadataForAOTAssembly 调用的时机

你只要在使用AOT泛型前调用即可(只需要调用一次),理论上越早加载越好。实践中比较合理的时机是热更新完成后,或者热更新dll加载后但还未执行任何何代码前。如果补充元数据的dll作为额外数据文件也打入了主包,则主工程启动时加载更优。可参考HybridCLR_trial项目

Assembly.Load或者RuntimeApi.LoadMetadataForAOTAssembly执行时间过长,导致游戏卡顿。

可以把它们放到其他线程异步加载。

原生与解释器部分性能敏感的场合不要用反射来交互,应该通过Delegate或虚函数

以Update函数为例,大多数人会想到主工程跟热更部分的交互像这样:

var klass = ass.GetType("App");
var method = klass.GetMethod("Update");
method.Invoke(null, new object[] {deltaTime});

这种方式的缺点是反射成本高,万一带参数,还有额外gc,其实完全有更高效的办法。主要有两种方式:

热更新层返回一个 Delegate

// Hotfix.asmdf 热更新部分 
class App
{
public static Action<float> GetUpdateDelegate()
{
return Update;
}

public static void Update(float deltaTime)
{
}
}

// Main.asmdf 主工程
var klass = ass.GetType("App");
var method = klass.GetMethod("GetUpdateDelegate");
var updateDel = (Action<float>)method.Invoke(null, null);

updateDel(deltaTime);

通过 Delegate.Create,根据MethodInfo创建相应的Delegate

var klass = ass.GetType("App");
var method = klass.GetMethod("Update");
updateDel = (Action<float>)System.Delegate.CreateDelegate(typeof(Action<float>), null, method);
updateDel(deltaTime);

2021 版本不要使用 faster(smaller) builds 选项

自2021.3.x LTS版本起,il2cpp已经完全支持full generic sharing技术,当 Build Settings中 Il2Cpp Code Generation 选项为 faster runtime时为标准泛型共享机制,为 faster(smaller) builds 时开启 full generic sharing 机制。

当开启full generic sharing后每个泛型函数(无论泛型参数是值类型还是class类型)都会完全共享一份代码,优点是节约包体大小,缺点是极大地伤害了泛型函数的性能。完全泛型共享的代码相比于标准泛型共享代码有时候会慢几倍到十几倍,甚至比不上纯解释版本。因此强烈推荐不要开启 faster(smaller) builds 选项。

- + \ No newline at end of file diff --git a/docs/basic/buildpipeline.html b/docs/basic/buildpipeline.html index 32ed12e80..9642c3460 100644 --- a/docs/basic/buildpipeline.html +++ b/docs/basic/buildpipeline.html @@ -9,7 +9,7 @@ - + @@ -17,7 +17,7 @@
跳到主要内容

打包工作流

由于热更新本身的要求以及Unity资源管理的一些限制,对打包工作流需要一些特殊处理,主要分为几部分:

  • 设置UNITY_IL2CPP_PATH环境变量
  • 打包时自动排除热更新assembly
  • 打包时将热更新dll名添加到assembly列表
  • 将打包过程中生成的裁剪后的aot dll拷贝出来,供补充元数据使用
  • 编译热更新dll
  • 生成一些打包需要的文件和代码
  • iOS平台的特殊处理

手动操作这些是烦琐易错的,com.code-philosophy.hybridclr package包含了打包工作流相关的标准工具脚本,将这些复杂流程简化为一键操作。 详细实现请看源码或者com.code-philosophy.hybridclr介绍

打包流程

  1. 运行菜单 HybridCLR/Generate/All 一键执行必要的生成操作
  2. HybridCLRData/HotUpdateDlls下的热更新dll添加到项目的热更新资源管理系统
  3. HybridCLRData/AssembliesPostIl2CppStrip下的补充元数据 dll添加到项目的热更新资源管理系统
  4. 根据你项目原来的打包流程打包

优化的打包流程

提示

Unity 2019的Android平台,以及团结引擎的鸿蒙平台在第二步导出工程时会自动编译libil2cpp.a,此时桥接函数之类还未生成,因此这些平台无法使用优化的打包流程。

HybridCLR/Generate/All 命令运行过程中会执行一次导出工程,以生成裁剪后的AOT dll。这一步对于大型项目来说可能非常耗时,几乎将打包时间增加了一倍。如果需要优化打包时间,可以按照如下流程一次出包。

  • 运行 HybridCLR/Generate/LinkXml
  • 导出工程
  • 运行 HybridCLR/Generate/Il2cppDef
  • 运行 HybridCLR/Generate/MethodBridge生成桥接函数
  • 运行 HybridCLR/Generate/PReverseInvokeWrapper。 不需要与lua之类交互的项目可跳过此步。
  • {proj}\HybridCLRData\LocalIl2CppData-{platform}\il2cpp\libil2cpp\hybridclr\generated目录 替换导出工程中的此目录。
  • 在导出工程上执行build

iOS平台的特殊处理

当 com.code-philosophy.hybridclr 版本 v3.2.0

不需要任何处理,直接导出xcode工程,再打包即可。由于在build完成后才将libil2cpp源码加入xcode工程,因此只能先导出xcode,再手动或者命令行编译,试图直接Build And Run会出错。

危险

如果你的 com.code-philosophy.hybridclr 版本 < v3.3.0, 由于xcode工程里写死了libil2cpp相关代码的路径,如果你导出xcode工程,推送到其他电脑上打包,会出现代码文件找不到的错误!

当 com.code-philosophy.hybridclr 版本 < v3.2.0

除了iOS以外平台都是根据libil2cpp源码编译出目标程序,iOS平台使用提前编译好libil2cpp.a文件。Unity导出的xcode工程引用了提前生成好的libil2cpp.a,而不包含libil2cpp源码, 直接打包无法支持热更新。因此编译iOS程序时需要自己单独编译libil2cpp.a,再替换xcode工程的libil2cpp.a文件,接着再打包。

替换xcode工程中的libil2cpp.a文件请自行完成

com.code-philosophy.hybridclr/Data~/iOSBuild 目录包含了编译 libil2cpp.a 所需的脚本。使用HybridCLR/Installer...完成安装后,该iOSBuild目录会被复制到{project}/HybridCLRData/iOSBuild 目录。

编译 libil2cpp.a

  • 运行 HybridCLR/Generate/All 生成所有必要的文件
  • 打开命令控制台,切换到 {project}/HybridCLRData/iOSBuild 目录。请确保这个路径的绝对路径不包含空格!否则会出错。
  • bash ./build_libil2cpp.sh 编译libil2cpp.a 。运行结束后,如果在iOSBuild/build目录下能找到libil2cpp.a文件并且size大于60M,表示编译成功

常见错误

  • 未在HybridCLR/Installer...中完成安装
  • 未运行HybridCLR/Generate/All
  • 未安装使用较新的macOS(12以上)及最新xcode
  • 未安装cmake
  • 由于git设置的原因,拉下来的build_libil2cpp.sh及build_lump.sh包含不正确的文件结束符,导致脚本运行前几行代码就出错。 错误信息也很明显,如 /bin/bash^M 文件不存在。运行命令 cat -v build_libil2cpp.sh 检查确认换行符的正确性。 运行 git config --global core.autocrlf input,然后再重新拉取这这两个脚本文件即可。详情可 参见git换行符设置
  • {project}/HybridCLRData/iOSBuild的绝对路径包含空格,导致gen_lump.sh脚本生成错误的结果
- + \ No newline at end of file diff --git a/docs/basic/buildwebgl.html b/docs/basic/buildwebgl.html index 415931b87..c4f15a66c 100644 --- a/docs/basic/buildwebgl.html +++ b/docs/basic/buildwebgl.html @@ -9,13 +9,13 @@ - +
跳到主要内容

发布WebGL平台

由于WebGL平台有较多特殊性,故特地单独文档介绍如何发布WebGL平台。本文档在 hybridclr_trial项目(github gitee )上演示发布过程。

提示

从Unity 2021.3.4+、2022.3.0+版本起,不再需要全局安装,也就是webgl平台的构建过程与其他平台完全相同。

使用的版本

不同Unity版本及hybridclr package的发布流程都是相似的,不再赘述。

  • Unity 2021.3.1f1
  • com.code-philosophy.hybridclr v3.4.0

准备工作

提示

新手请至少阅读过快速上手文档,已经掌握Win或Android之类平台的发布流程。

  • 确保Unity Editor 安装了WebGL模块,如下图
  • 根据 install 文档完成HybridCLR安装及设置
  • 在HybridCLRSettings中,开启Use Global Il2cpp 选项,因为webgl平台只支持全局安装。从2021.3.4+、2022.3.0+起,不再需要开启此选项

select_il2cpp_module_webgl

建立 Editor目录的libil2cpp到本地libil2cpp目录的软(硬)引用

危险

注意:从Unity 2021.3.4+、2022.3.0+版本起,由于支持本地安装,不再需要建立此引用。

提示

升级hybridclr等情形需要重新install时,先恢复Editor安装目录的原始libil2cpp目录,再重新按照如下说明建立链接。

Win平台

不熟悉命令行的开发者请先掌握命令行的基础用法。

  • 以管理员权限打开命令行窗口,这个操作不同操作系统版本不一样,请酌情处理。在Win11下为在开始菜单上右键,选中终端管理员菜单项
  • 运行 cd /d {editor_install_dir}/Editor/Data/il2cpp, 切换目录到安装目录的il2cpp目录
  • 运行ren libil2cpp libil2cpp-origin 将原始libil2cpp改名为libil2cpp-origin
  • 运行 mklink /D libil2cpp "{project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp/libil2cpp" 建立Editor目录的libil2cpp到本地libil2cpp目录的符号引用

MacOS或者Linux平台

  • 打开命令行窗口
  • 运行 cd /d {editor_install_dir}/Editor/Data/il2cpp 切换目录到安装目录的il2cpp目录。具体目录可能因为操作系统而有所不同,请酌情处理
  • 运行mv libil2cpp libil2cpp-origin 将原始libil2cpp改名为libil2cpp-origin
  • 运行 ln -s "{project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp/libil2cpp" libil2cpp 建立Editor目录的libil2cpp到本地libil2cpp目录的符号引用

打包

  • 运行 HybridCLR/Generate/All
  • 运行 HybridCLR/Build/BuildAssetsAndCopyToStreamingAssets。注意!这个菜单是hybridclr_trial项目添加的,并不是hybridclr package自带的命令。
  • Build Player中运行Build And Run即可
- + \ No newline at end of file diff --git a/docs/basic/codestriping.html b/docs/basic/codestriping.html index 1a3381889..58e3d4db1 100644 --- a/docs/basic/codestriping.html +++ b/docs/basic/codestriping.html @@ -9,7 +9,7 @@ - + @@ -23,7 +23,7 @@ 类型。因此你仍然需要有规划地提前在 Assets/link.xml(注意!不是自动生成的那个link.xml)预留你将来 可能用到的类型。切记不要疏漏,免得出现上线后某次更新使用的类型被裁剪的尴尬状况!

检查热更新代码中是否引用了被裁剪的类型或函数

只要构建游戏时正确执行了HybridCLR/Generate/All,运行当时的热更新代码,不会出现类型或函数缺失的问题。但随着后面热更新代码不断迭代, 有可能访问了被裁剪的类型或函数。如果能提前在发布热更新代码时检查出来,可以及早发现和解决问题。

自v5.0.0版本起,提供HybridCLR.Editor.HotUpdate.MissingMetadataChecker类用于检查是否访问了被裁剪的类型和函数。示例代码如下:

        public static void CheckAccessMissingMetadata()
{
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
// aotDir指向 构建主包时生成的裁剪aot dll目录,而不是最新的SettingsUtil.GetAssembliesPostIl2CppStripDir(target)目录。
// 一般来说,发布热更新包时,由于中间可能调用过generate/all,SettingsUtil.GetAssembliesPostIl2CppStripDir(target)目录中包含了最新的aot dll,
// 肯定无法检查出类型或者函数裁剪的问题。
// 需要在构建完主包后,将当时的aot dll保存下来,供后面补充元数据或者裁剪检查。
string aotDir = "xxxx";

// 第2个参数excludeDllNames为要排除的aot dll。一般取空列表即可。对于旗舰版本用户,
// excludeDllNames需要为dhe程序集列表,因为dhe 程序集会进行热更新,热更新代码中
// 引用的dhe程序集中的类型或函数肯定存在。
var checker = new MissingMetadataChecker(aotDir, new List<string>());

string hotUpdateDir = SettingsUtil.GetHotUpdateDllsOutputDirByTarget(target);
foreach (var dll in SettingsUtil.HotUpdateAssemblyFilesExcludePreserved)
{
string dllPath = $"{hotUpdateDir}/{dll}";
bool notAnyMissing = checker.Check(dllPath);
if (!notAnyMissing)
{
// DO SOMETHING
}
}
}

- + \ No newline at end of file diff --git a/docs/basic/com.code-philosophy.hybridclr.html b/docs/basic/com.code-philosophy.hybridclr.html index fd197678d..bd062b299 100644 --- a/docs/basic/com.code-philosophy.hybridclr.html +++ b/docs/basic/com.code-philosophy.hybridclr.html @@ -9,7 +9,7 @@ - + @@ -41,7 +41,7 @@ 脚本提供了自动生成桩函数的功能。详细请见 MonoPInvokeCallback支持HybridCLR+lua/js/python 文档

每个带 [MonoPInvokeCallback] 特性的函数都需要一个唯一对应的wrapper函数。这些wrapper函数必须是打包时预先生成,不可变化。 因此如果后续热更新新增了 带 [MonoPInvokeCallback] 特性的函数,则会发生wrapper函数不足的情况。ReversePInvokeWrapperGenerationAttribute 用于为当前添加了 [MonoPInvokeCallback] 特性的函数预留指定数量的wrapper函数。在如下示例中,为LuaFunction签名的函数预留了10个wrapper函数。

    delegate int LuaFunction(IntPtr luaState);

public class MonoPInvokeWrapperPreserves
{
[ReversePInvokeWrapperGeneration(10)]
[MonoPInvokeCallback(typeof(LuaFunction))]
public static int LuaCallback(IntPtr luaState)
{
return 0;
}

[MonoPInvokeCallback(typeof(Func<int, int, int>))]
public static int Sum(int a, int b)
{
return a + b;
}

[MonoPInvokeCallback(typeof(Func<int, int, int>))]
public static int Sum2(int a, int b)
{
return a + b;
}

[MonoPInvokeCallback(typeof(Func<int>))]
public static int Sum3()
{
return 0;
}
}
- + \ No newline at end of file diff --git a/docs/basic/compileassembly.html b/docs/basic/compileassembly.html index 08581c4aa..84a48e5ac 100644 --- a/docs/basic/compileassembly.html +++ b/docs/basic/compileassembly.html @@ -9,7 +9,7 @@ - + @@ -20,7 +20,7 @@ 下的热更新dll。编译结果输出到{proj}/HybridCLRData/HotUpdateDlls/{target}目录下。

运行菜单HybridCLR/Compile/xxx命令直接编译出热更新dll。运行HybridCLR/Generate/All时会也隐含编译最新的热更新程序集。在调用该命令后可以直接复制热更新dll,不用再次运行HybridCLR/Compile/xxx。 由于该接口编译时并不区分AOT与热更新,将项目整体编译了,开发者只需要将输出的热更新dll加入项目的资源管理系统即可。

com.code-philosophy.hybridclr的HybridCLR.Editor程序集提供了HybridCLR.Editor.Commands.CompileDllCommand.CompileDll(BuildTarget target)接口, 方便开发者灵活地自行编译热更新dll。

发布主包后,每次热更新时只需要简单使用HybridCLR/Compile/xxx命令重新编译热更新dll,再发布热更新dll即可,不用运行HybridCLR/Generate/xxx命令。

- + \ No newline at end of file diff --git a/docs/basic/dots.html b/docs/basic/dots.html index 94391d51c..e14eccaa2 100644 --- a/docs/basic/dots.html +++ b/docs/basic/dots.html @@ -9,7 +9,7 @@ - + @@ -19,7 +19,7 @@ 在该Unity版本上正常运行,也能支持hybridclr。

有特殊DOTS版本需求的开发者,由于维护单独的DOTS版本成本较高,需要联系我们单独付费定制。

支持的特性

目前绝大多数DOTS特性都可以在hybridclr下正常运行,只有跟BurstCompile及资源序列化相关的特性支持较差。

1.0.16版本

特性社区版本专业版旗舰版热重载版
Jobs
Managed Component
Unmanaged Component
Managed System
Unmanaged System
Aspect
IJobEntity
BurstCompile
SubScene

0.51.1-preview.21版本

特性社区版本专业版旗舰版热重载版
Jobs
Managed Component
Unmanaged Component
Managed System
Unmanaged System
IJobEntity
BurstCompile
SubScene

安装

安装com.unity.entities

  • 在项目中移除 com.unity.entities包,退出Unity Editor,清空 Library\PackageCache 目录下该包对应的目录
  • 根据项目使用的版本,下载修改后的com.unity.entities,将对应目录下的com.unity.entities.7z 解压到 Packages目录。请确保解压后的目录名为com.unity.entities。

重新打开Unity Editor时可能会提示是否要进行Api升级,根据项目情况自行决定是否升级。

修改项目设置

为了避免DOTS运行过程中带动态注册Component或System可能引发的问题,需要调整World的初始化时机以确保运行所有World之前已经注册了所有热更新类型。

Player SettingsScripting Define Symbols中,添加编译宏 UNITY_DISABLE_AUTOMATIC_SYSTEM_BOOTSTRAP_RUNTIME_WORLD。详细介绍可以参见World的 自定义初始化文档

初始化

为了避免遇到问题,请在加载完热更新代码后,运行任何dots代码之前进行初始化。

初始化中主要包含两部分:

  • 注册热更新的dots类型
  • 初始化World

不同的com.unity.entities版本的初始化实现略有差别。

0.51.1版本初始化代码如下:

    private static void InitializeWorld()
{
#if !UNITY_EDITOR
var dotsAssemblies = new Assembly[] { ... };
var componentTypes = new HashSet<System.Type>();
TypeManager.CollectComponentTypes(dotsAssemblies, componentTypes);
TypeManager.AddNewComponentTypes(componentTypes.ToArray());
TypeManager.EarlyInitAssemblies(dotsAssemblies);
#endif


DefaultWorldInitialization.Initialize("Default World", false);

}

1.0.16版本的初始化代码如下:

    private static void InitializeWorld()
{
#if !UNITY_EDITOR
var dotsAssemblies = new Assembly[] { ... };
var componentTypes = new HashSet<Type>();
TypeManager.CollectComponentTypes(dotsAssemblies, componentTypes);
TypeManager.AddComponentTypes(dotsAssemblies, componentTypes);
TypeManager.RegisterSystemTypes(dotsAssemblies);
TypeManager.InitializeSharedStatics();
TypeManager.EarlyInitAssemblies(dotsAssemblies);
#endif


DefaultWorldInitialization.Initialize("Default World", false);
}

解决ReversePInvokeCallback的问题

DOTS系统初始化Unmanaged System时会尝试获得它的OnStart之类函数的Marshal指针。hybridclr需要为每个这种函数绑定一个运行时唯一的cpp函数指针, 否则运行过程中会出现GetReversePInvokeWrapper fail. exceed max wrapper num of method错误。详细介绍可见HybridCLR+lua/js/python文档。

简单来说,需要预留足够多的 SystemBaseRegistry.ForwardingFunc对应的 wrapper函数。在热更新模块(也可以在DHE程序集,但不能在AOT程序集)中添加如下代码:

public static class PreserveDOTSReversePInvokeWrapper
{
[ReversePInvokeWrapperGeneration(100)]
[MonoPInvokeCallback(typeof(SystemBaseRegistry.ForwardingFunc))]
public static void ForwordMethod(IntPtr system, IntPtr state)
{

}
}


将代码中的100改为一个适当的数字即可,推荐为Unmanaged System类型个数的5-10倍。

Burst相关

  • 包含[BurstCompile]的热更新函数发生变化,需要去掉[BurstCompile]特性,否则运行会报错。这个问题后面可能会优化
- + \ No newline at end of file diff --git a/docs/basic/hotupdateassemblysetting.html b/docs/basic/hotupdateassemblysetting.html index 807316cdc..4dd75af2a 100644 --- a/docs/basic/hotupdateassemblysetting.html +++ b/docs/basic/hotupdateassemblysetting.html @@ -9,7 +9,7 @@ - + @@ -17,7 +17,7 @@
跳到主要内容

配置程序集

一般来说,必须将热更新代码独立为assembly,才能方便地进行热更新。

程序集分类

Assembly Definition定义的程序集

这是Unity推荐的程序集方式。将一个大的Unity项目代码拆分为多个程序集模块,便于管理,缩短编译时间。

请阅读文档Assembly definitions了解如何创建程序集。

Assembly-CSharp 程序集

这是Unity的默认全局程序集。它可以像普通dll一样当作热更新程序集。

普通的dll程序集

一些代码被提前编译成dll文件,再移到项目中。

划分程序集

很显然,项目代码必须合理拆分为AOT(即编译到游戏主包内)程序集 和 热更新程序集,才能进行热更新。HybridCLR对于 怎么拆分程序集并无任何限制,甚至可以把第三方工程中的代码作为热更新程序集。一般来说,游戏刚启动时,至少需要一个AOT程序集来负责启动及热更新相关工作。

常见的拆分方式有几种:

  • Assembly-CSharp作为AOT程序集。剩余代码自己拆分为N个AOT程序集和M个热更新程序集。
  • Assembly-CSharp作为热更新程序集。剩余代码自己拆分为N个AOT程序集和M个热更新程序集。

无论哪种拆分方式,正确设置好程序集之间的引用关系即可。请不要在AOT程序集中引用热更新程序集,这会导致打包出错。如果 你们项目把Assembly-CSharp作为AOT程序集,强烈建议关闭热更新程序集的auto reference选项。因为Assembly-CSharp是最顶层assembly,它会自动引用剩余所有assembly,很容易就出现失误引用热更新程序集的情况。

- + \ No newline at end of file diff --git a/docs/basic/il2cppbugs.html b/docs/basic/il2cppbugs.html index 6dcbe5c1f..01e290ca3 100644 --- a/docs/basic/il2cppbugs.html +++ b/docs/basic/il2cppbugs.html @@ -9,13 +9,13 @@ - +
跳到主要内容

il2cpp bug记录

逆变协变泛型接口调用错误

查找obj的interface实现有误,按规范以下代码应该打出"Comput B",例如.net 6是这个结果,但mono和il2cpp下却打印出"Comput A"。


interface ITest<out T>
{
T Comput();
}

class A : ITest<object>
{
public object Comput()
{
return "Comput A";
}
}

class B : A, ITest<string>
{
public string Comput()
{
return "Comput B";
}
}

class App
{
public static void Main()
{
ITest<object> f = new B();
Debug.Log(f.Comput());
}
}

obj.Func() 非虚调用不符合规范

ECMA规范允许对null使用call指令进行非虚调用,但il2cpp却在调用前插入了NullCheck操作。导致以下代码在mono下会打印出 "hello",而在il2cpp下抛了NullReferenceException。


class TestNull
{
public void Show()
{
Debug.Log("hello");
}
}

class App
{
public void Main()
{
TestNull nu = null;
nu.Show();
}
}

当struct中包含class类型对象时,StructLayout的pack不会生效

    [StructLayout( LayoutKind.Sequential, Pack = 1)]
struct StructWithoutClass
{
byte a;
long b;
}

[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct StructWithClass
{
byte a;
object b;
}

x64下这两个struct计算的size都应该=9,运行.net 6程序测试也验证了这点。但在mono中,第一个结构计算值是9,第2个是16.

泛型数组函数未设置token

metadata/ArrayMetadata.cpp中

    static MethodInfo* ConstructGenericArrayMethod(const GenericArrayMethod& genericArrayMethod, Il2CppClass* klass, Il2CppGenericContext* context)
{
MethodInfo* inflatedMethod = (MethodInfo*)MetadataCalloc(1, sizeof(MethodInfo));
inflatedMethod->name = StringUtils::StringDuplicate(genericArrayMethod.name.c_str());
inflatedMethod->klass = klass;

const MethodInfo* methodToCopyDataFrom = genericArrayMethod.method;
if (genericArrayMethod.method->is_generic)
{
const Il2CppGenericMethod* genericMethod = MetadataCache::GetGenericMethod(genericArrayMethod.method, context->class_inst, context->method_inst);
methodToCopyDataFrom = GenericMethod::GetMethod(genericMethod);

inflatedMethod->is_inflated = true;
inflatedMethod->genericMethod = genericMethod;
inflatedMethod->rgctx_data = methodToCopyDataFrom->rgctx_data;
}
// ==={{ add by HybridCLR
inflatedMethod->token = methodToCopyDataFrom->token;
// ===}} add by HybridCLR
inflatedMethod->slot = methodToCopyDataFrom->slot;
inflatedMethod->parameters_count = methodToCopyDataFrom->parameters_count;
inflatedMethod->parameters = methodToCopyDataFrom->parameters;
inflatedMethod->return_type = methodToCopyDataFrom->return_type;

inflatedMethod->methodPointer = methodToCopyDataFrom->methodPointer;
inflatedMethod->invoker_method = methodToCopyDataFrom->invoker_method;

return inflatedMethod;
}

throw null 会导致崩溃

对于 c#代码 throw ex; 会生成如下代码,当ex = null时崩溃。

    IL2CPP_RAISE_MANAGED_EXCEPTION(L_107, TestCase_Run_m5B897FE9D1ABDC1AA114D3482A6613BAAE3243F6_RuntimeMethod_var);

close delegate 的this为null时,抛出的异常不合规范

Delegate.Create(XXInstanceMethod, null) ,调用时应该抛出 NullReferenceException异常,而unity2021版本抛出了ArgumentException。

2019 生成的delegate 调用代码,未正确处理open delegate,并且this为ValueType的情形

当使用open delegate,并且 ref ValueType作为this参数时,会错误地产生两次调用!

    if (targetThis == NULL && il2cpp_codegen_class_is_value_type(il2cpp_codegen_method_get_declaring_type(targetMethod)))
{
typedef int32_t (*FunctionPointerType) (RuntimeObject*, int32_t, const RuntimeMethod*);
result = ((FunctionPointerType)targetMethodPointer)((reinterpret_cast<RuntimeObject*>(___a0) - 1), ___b1, targetMethod);
}
if (targetThis == NULL)
{
typedef int32_t (*FunctionPointerType) (RuntimeObject*, int32_t, const RuntimeMethod*);
result = ((FunctionPointerType)targetMethodPointer)((RuntimeObject*)(reinterpret_cast<RuntimeObject*>(___a0) - 1), ___b1, targetMethod);
}
else
{
typedef int32_t (*FunctionPointerType) (void*, FT_AOT_ValueType_t851DF541610F2A3DE72568571355F3953F0063AF *, int32_t, const RuntimeMethod*);
result = ((FunctionPointerType)targetMethodPointer)(targetThis, ___a0, ___b1, targetMethod);
}

mono及il2cpp不支持 instance method的open delegate 上调用 InvokeDyanmic

会抛出 'Object does not match target type' 错误。

    public void void_class_intp_open_reflection()
{
var b = new FT_Class() { x = 1, y = 2f, z = "abc" };
var m = typeof(FT_Class).GetMethod("Run");
var del = (Action<FT_Class, int>)Delegate.CreateDelegate(typeof(Action<FT_Class, int>), null, m);
del.DynamicInvoke(b, 4);
Assert.Equal(5, b.x);

var dd = del + del;
dd.DynamicInvoke(b, 1);
Assert.Equal(7, b.x);

Assert.ExpectException<NullReferenceException>();
del.DynamicInvoke(null, 4);
Assert.Fail();
}

2019 WebGL平台生成的对象成员访问代码未检查空引用

取类成员字段时未检查是否空指针。目前发现只有WebGL平台才会这样。


//WebGL平台没有NullCheck
IL2CPP_EXTERN_C IL2CPP_METHOD_ATTR void FT_AOT_Class_Run2_m0451FFC153671CD294EB1178A01AB2D92202624C (FT_AOT_Class_t03C2F346FF0EA8694088FD3F901E6536935FB2BA * ___s0, int32_t ___b1, const RuntimeMethod* method)
{
{
// s.x += b;
FT_AOT_Class_t03C2F346FF0EA8694088FD3F901E6536935FB2BA * L_0 = ___s0;
FT_AOT_Class_t03C2F346FF0EA8694088FD3F901E6536935FB2BA * L_1 = L_0;
int32_t L_2 = L_1->get_x_0();
int32_t L_3 = ___b1;
L_1->set_x_0(((int32_t)il2cpp_codegen_add((int32_t)L_2, (int32_t)L_3)));
// }
return;
}
}

// 其他平台有NullCheck
IL2CPP_EXTERN_C IL2CPP_METHOD_ATTR void FT_AOT_Class_Run2_m0451FFC153671CD294EB1178A01AB2D92202624C (FT_AOT_Class_t03C2F346FF0EA8694088FD3F901E6536935FB2BA * ___s0, int32_t ___b1, const RuntimeMethod* method)
{
{
// s.x += b;
FT_AOT_Class_t03C2F346FF0EA8694088FD3F901E6536935FB2BA * L_0 = ___s0;
FT_AOT_Class_t03C2F346FF0EA8694088FD3F901E6536935FB2BA * L_1 = L_0;
NullCheck(L_1);
int32_t L_2 = L_1->get_x_0();
int32_t L_3 = ___b1;
NullCheck(L_1);
L_1->set_x_0(((int32_t)il2cpp_codegen_add((int32_t)L_2, (int32_t)L_3)));
// }
return;
}
}

Mono 对于ValueType的成员函数的open delegate调用复制而不是传递ref的bug

在il2cpp可以正常运行。

        public void void_valuetype_instance_open_interp()
{
var b = new FT_ValueType() { x = 1, y = 2f, z = "abc" };
var m = typeof(FT_ValueType).GetMethod("Run");
var invoke = typeof(ValueTypeRun).GetMethod("Invoke");
var del = (ValueTypeRun)Delegate.CreateDelegate(typeof(ValueTypeRun), null, m);

object c = b;
invoke.Invoke(del, new object[] { c, 1 });
// mono BUG!!! mono 会在此处断言失败, get value 1。
// 但il2cpp却是正确的!
Assert.Equal(2, ((FT_ValueType)c).x);

var dd = del + del;
invoke.Invoke(dd, new object[] { c, 1 });
Assert.Equal(4, ((FT_ValueType)c).x);
}
- + \ No newline at end of file diff --git a/docs/basic/install.html b/docs/basic/install.html index 7d99c658d..915866a4d 100644 --- a/docs/basic/install.html +++ b/docs/basic/install.html @@ -9,7 +9,7 @@ - + @@ -24,7 +24,7 @@ 发现失败时 HybridCLRData/hybridclr_repoHybridCLRData/il2cpp_plus_repo为空,请再次尝试。

最常见失败原因为git未安装,或者安装git后未重启UnityEditor和UnityHub。如果你确信安装了git,cmd中也确实能运行git,则尝试重启电脑。

如果因为各种特殊原因未能完成自动化安装,请参照下面的安装原理手动模拟整个安装过程。

安装后的特殊处理

WebGL平台

提示

从Unity 2021.3.4+、2022.3.0+版本起已经支持本地安装,WebGL平台构建过程与其他平台完全相同

由于Unity自身原因,如果使用的于Unity版本低于2021.3.4,构建WebGL平台必须全局安装。 请查阅下面章节的全局安装文档。

Unity 2019

为了支持2019,需要修改il2cpp生成的源码,因此我们修改了2019版本的il2cpp工具。故Installer的安装过程多了一个额外步骤:将 {package}/Data~/ModifiedUnityAssemblies/2019.4.40/Unity.IL2CPP.dll 复制到 {project}/HybridCLRData/LocalIl2CppData/il2cpp/build/deploy/net471/Unity.IL2CPP.dll

注意,该操作在Installer安装时自动完成,不需要手动操作。

提示

对于使用2019.4.0-2019.4.39版本的开发者,请先切换到2019.4.40版本完成安装,再切回你当前版本。

在非兼容的Unity版本中使用HybridCLR

由于我们没有完全测试所有Unity版本,实际上一些不在支持列表中的Unity版本,也有可能能正常使用HybridCLR。安装方式如下:

  • 找一个离你的版本最近的在支持列表中的版本,例如你的版本号为 2021.2.20,则离你最新的版本为2021.3.0。
  • 先将你的Unity工程切换到这个最近的受支持的版本,安装HybridCLR。
  • 切换回你的Unity版本。
  • 尝试打包,如果能顺利运行,则表明HybridCLR支持你这个版本,如果有问题,那还是升级版本吧。

如果你一定要使用该版本,可以联系我们提供商业技术支持

HybridCLR/Installer工作原理

本节只是介绍原理,安装libil2cpp的操作已由installer完成,并不需要你手动操作

HybridCLR安装过程主要包含这几部分:

  • 制作支持热更新的libil2cpp
  • 本地或者全局安装,使新版本libil2cpp生效
  • 对Unity Editor的少量改造

替换libil2cpp代码

原始的libil2cpp代码是AOT运行时,需要替换成改造后的libil2cpp才能支持热更新。改造后的libil2cpp由两部分构成

  • il2cpp_plus
  • hybridclr

il2cpp_plus仓库为对原始libil2cpp作了少量修改以支持动态register元数据的版本(改了几百行代码)。这个仓库与原始libil2cpp代码高度 相似。hybridclr为解释器部分的核心代码,包含元数据加载、代码transform(编译)、代码解释执行。

如下图所示,将il2cpp_plus/libil2cpp目录和hybridclr/hybridclr目录合并,制作出最终的支持热更新的libil2cpp。

merge_hybridclr_dir

本地安装

Unity允许使用环境变量UNITY_IL2CPP_PATH自定义il2cpp的位置,因此可以在项目本地创建il2cpp目录,用改造后的libil2cpp替换il2cpp目录下的libil2cpp目录, 再将UNITY_IL2CPP_PATH环境变量指向该目录。大致过程如下:

  • 从Editor安装目录复制il2cpp目录到{project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp
  • 从clone il2cpp_plus和hybridclr仓库,制作出最终的libil2cpp目录
  • 将最终的libil2cpp目录替换 {project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp/libil2cpp
  • 从Editor安装目录复制 MonoBleedingEdge 目录到 {project}/HybridCLRData/LocalIl2CppData-{platform}/MonoBleedingEdge
  • 其他处理。如2019版本将 {package}/Data~/ModifiedUnityAssemblies/2019.4.40/Unity.IL2CPP.dll 复制到 {project}/HybridCLRData/LocalIl2CppData/il2cpp/build/deploy/net471/Unity.IL2CPP.dll
提示

com.code-philosophy.hybridclr 包修改了本UnityEditor进程内的环境变量UNITY_IL2CPP_PATH,并不会影响其他Unity项目。

创建上层LocalIl2CppData-{platform}目录,而不是只创建il2cpp是因为实测发现仅仅指定il2cpp目录位置是不够的,打包时Unity隐含假设了il2cpp同级有一个MonoBleedingEdge目录,所以创建了上级目录,将il2cpp及MonoBleedingEdge目录都复制过来。

因为不同平台Editor自带的il2cpp目录略有不同,LocalIl2CppData要区分platform。

全局安装

全局安装需要替换(或链接)Editor安装目录的libil2cpp目录(Win下为{editor}/Data/il2cpp/libil2cpp,Mac类似)为改造后的libil2cpp,及额外替换一些修改的文件(如2019还需要修改Unity.IL2CPP.dll)。有几个缺陷:

  • 因为目录权限原因,可能无法自动完成
  • 会影响其他不使用hybridclr的项目
  • HybridCLR/Generate/xxxx操作需要修改libil2cpp目录下的文件,有可能目录权限的原因而失败。

使用HybridCLR/Installer完成安装后,在HybridCLR/Settings中开启 useGlobalIl2Cpp 选项来启动全局安装,此时会清除环境变量UNITY_IL2CPP_PATH

如果你使用替换目录的方式进行全局安装,并且你的com.code-philosophy.hybridclr版本 >= 2.1.0,则第一次覆盖libil2cpp前,请先运行HybridCLR/Generate/Il2cppDef(只此一次,后面不再需要,除非你切换了项目Unity版本)以生成正确的版本宏,再覆盖原始的libil2cpp目录。符号链接安装方式或者com.code-philosophy.hybridclr版本低于2.1.0不需要执行此操作,直接覆盖原始的libil2cpp目录即可

由于权限原因,即使是全局安装,Generate/xxx命令修改的是本地{project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp/libil2cpp下的文件。请每次generate后都将本地libil2cpp目录覆盖全局安装目录

每次替换libil2cpp目录非常麻烦,推荐使用链接安装目录的libil2cpp目录到本地libil2cpp目录方式。方法如下:

  • Win平台。以管理员权限打开命令行窗口,删除或者重命名原libil2cpp,然后运行 mklink /D "<Editor安装目录的libil2cpp目录路径>" "{project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp/libil2cpp"
  • Linux或者Mac平台。以管理员权限打开命令行窗口,删除或者重命名原libil2cpp,然后运行 ln -s "{project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp/libil2cpp" "<Editor安装目录的libil2cpp目录路径>"

对于2019版本替换 Unity.IL2CPP.dll,也使用类似上面的替换或者软链接的方式。

注意事项

由于 Unity 的缓存机制,更新 HybridCLR 后,一定要清除 Library\Il2cppBuildCache 目录,不然打包时不会使用最新的代码。如果你使用Installer来自动安装或者更新HybridCLR,它会自动清除这些目录,不需要你额外操作。

- + \ No newline at end of file diff --git a/docs/basic/memory.html b/docs/basic/memory.html index ccddc83a4..bbae114c2 100644 --- a/docs/basic/memory.html +++ b/docs/basic/memory.html @@ -9,7 +9,7 @@ - + @@ -18,7 +18,7 @@ 商业化版本开启完全泛型共享后由于不需要补充元数据,此项为0。商业化版本相比社区降低了67%(开启完全泛型共享时为100%)的内存。

详细数据

aot-metadata-data

消耗内存:

aot-metadata-memory

消耗内存/dll大小

aot-metadata-dll-rate

加载热更新程序集内存

我们测试了常见的插件以解释模式加载后消耗的内存。社区版本消耗的内存大约dll大小的4.7倍,商业化版本为2.9倍。商业化版本相比社区降低了39%的内存。

提示

此数据未包含运行时延迟数据化的 Il2CppClass、MethodInfo及翻译后指令占据的内存,此部分延迟初始化的内存大约为2.9-3.5倍dll大小。最终消耗的元数据内存,社区版本为7.6-8.2倍,商业化版本为5.8-6.4倍。 商业化版本相比社区版本降低了大约25%内存。

详细数据

aot-metadata-data

消耗内存:

aot-metadata-memory

消耗内存/dll大小

aot-metadata-dll-rate

解释执行过程中产生的内存

HybridCLR是CLR级别的实现,除了执行方式是以解释模式执行,其他方式跟AOT部分完全相同的。因此运行过程中创建的对象,无论在AOT还是热更新中,大小是完全相同的。

提示

同等对象在ILRuntime或者lua方案下消耗的内存是HybridCLR的4-88甚至更多,这个差距是惊人的!

另外经过精心优化,HybridCLR执行代码过程中消耗的内存跟il2cpp AOT部分完全相同。例如foreach 循环在il2cpp或mono下产生多少GC,在HybridCLR中解释执行时也产生完全相同的GC。

与lua、ILRuntime的对象内存大小对比

lua的计算规则略复杂,参见第三方文章。空table占56字节,每多一个字段至少多占32字节。

ILRuntime的类型除了enum外统一以IlTypeInstance表达,空类型占72字节,每多一个字段至少多用16字节。如果对象中包含引用类型数据,则整体又至少多24字节,并且每多一个object字段多8字节。

类型XluaILRuntimeHybridCLR/原生il2cpp
V188+881
V2120+1048
V3184+16824
C188+8824
C2120+10424
C3184+16840

以下是测试类型:


// V1 对象大小 1
struct V1
{
public byte a1;
}

// V2 对象大小 8
struct V2
{
public byte a1;
public int a2;
}

// V3 对象大小 24
struct V3
{
public int a1;
public int a2;
public object a3;
public byte a4;
}

// C1 对象大小 24
class C1
{
public byte a1;
}
// C2 对象大小 24
class C2
{
public byte a1;
public int a2;
}
// C3 对象大小 40
class C3
{
public int a1;
public int a2;
public object a3;
public byte a4;
}
- + \ No newline at end of file diff --git a/docs/basic/methodbridge.html b/docs/basic/methodbridge.html index 2c7f72b67..11ebcca36 100644 --- a/docs/basic/methodbridge.html +++ b/docs/basic/methodbridge.html @@ -9,7 +9,7 @@ - + @@ -17,7 +17,7 @@
跳到主要内容

桥接函数

HybridCLR的interpreter与AOT之间需要双向函数调用。比如interpreter调用AOT函数,或者AOT通过interface接口或者delegate回调interpreter。

AOT部分与解释器部分的参数传递和存储方式是不同的。解释器部分调用AOT函数,解释器的参数全在解释器栈上,必须借助合适的办法才能将解释器的函数参数传递给AOT函数。同样的,解释器无法直接获得AOT回调函数的参数。必须为每一种签名的函数生成对应的桥接函数,来实现解释器与aot部分的双向函数参数传递。interpreter -> AOT 方向的调用,虽然可以通过ffi之类的库来完成,但函数调用的成本过高,最合理的方式仍然是提前生成好这种双向桥接函数。解释器内部调用直接走解释器栈,不需要桥接函数。

提示

根据桥接函数的原理,对于固定的AOT部分,桥接函数集是确定的,后续无论进行任何热更新,都不会需要新的额外桥接函数。因此不用担心热更上线后突然出现桥接函数缺失的问题。

桥接函数签名

桥接函数必须提前在AOT部分生成,这点跟lua的wrapper函数原理相似。

为了给每个AOT <-> interpreter之间调用的函数找到对应的桥接函数,必须有一种计算函数签名的方式。另外,参数类型和返回值类型完全等效的函数可以共享同一个桥接函数,这极大减少了桥接函数的个数。如下示例,class类型共享相同的签名。因此它们都可以共享一个 object (object, long) 签名的桥接函数。

object Fun1(object a, long b);
string Fun2(string a, long b);
类型签名
sbytei1
byteu1
boolu1
charu2
shorti2
ushortu2
inti4
uintu4
longi8
ulongu8
IntPtri
UintPtru
floatr4
doubler8
class类型u
指针类型u
enum类型underlying 类型对应的签名,如enum Color:short {}的签名为i2
TypedReferencetypedbyref
struct全局唯一struct签名, 类似s{序号}这样

生成桥接函数

com.code-philosophy.hybridclr package中提供工具脚本,推荐使用菜单命令 HybridCLR/Generate/All 自动生成所有桥接函数。你也可以直接使用HybridCLR/Generate/MethodBridge 生成桥接函数,但该命令依赖裁剪后的AOT dll热更新dll,而裁剪后的AOT dll依赖于生成LinkXml生成Il2CppDef。因此如果没有使用HybridCLR/Generate/All命令,必须先依次运行:

  • HybridCLR/Generate/Il2CppDef
  • HybridCLR/Generate/LinkXml
  • HybridCLR/CompileDll/ActiveBuildTarget
  • HybridCLR/Generate/AotDlls
  • HybridCLR/Generate/MethodBridge

平台相关

桥接函数本身是平台无关的。对于同一个dll,所有平台生成的桥接函数文件完全相同。但由于编译宏开关及各平台的基础库(mscorlib之类)不同,导致不同平台生成的桥接函数也不同。因此不要复用桥接函数, 而是每个平台单独生成。

- + \ No newline at end of file diff --git a/docs/basic/migratefromnetstandard.html b/docs/basic/migratefromnetstandard.html index e0c2bec22..5bd7ff484 100644 --- a/docs/basic/migratefromnetstandard.html +++ b/docs/basic/migratefromnetstandard.html @@ -9,7 +9,7 @@ - + @@ -22,7 +22,7 @@ 所幸,这些工作都是一次性的。

迁移步骤

迁移主要包含两步:

  • 将项目内的预编译好的基于netstandard的dll转换为基于.net framework的dll
  • 项目的Api Level 切换为 .Net Framework

转换基于netstarndard的外部dll

如果能直接找到该外部dll的基于.Net Framework的版本,替换项目对应的dll即可。如果找不到, 则可以利用Unity的打包过程会生成最终基于.Net Framework的aot dll的特性,生成该dll的 .Net Framework的版本。具体操作如下:

  • 确保主工程已经有代码引用了此外部dll,而不仅仅是热更新代码中引用了该dll
  • 在任意一个link.xml中perserve这个dll,如在Assets/link.xml里添加<assembly fullname="xxx.dll" preserve="all"/>
  • 运行HybridCLR/Generate/AOTDlls
  • {project}/HybridCLRData/AssembliesPostIl2CppStrip/{target}裁剪目录中获得该dll对应的文件
  • 用该dll替换项目中的对应dll即可
- + \ No newline at end of file diff --git a/docs/basic/monobehaviour.html b/docs/basic/monobehaviour.html index 67864029d..3ed1a8b09 100644 --- a/docs/basic/monobehaviour.html +++ b/docs/basic/monobehaviour.html @@ -9,7 +9,7 @@ - + @@ -19,8 +19,8 @@ 内即可。

在资源上挂载MonoBehaviour或者创建ScriptableObject类型资源

Unity资源管理系统在反序列化资源中的热更新脚本时,需要满足以下条件:

  1. 脚本所在的dll已经加载到运行时中
  2. 必须是使用AssetBundle打包的资源(addressable之类间接使用了ab的框架也可以
  3. 脚本所在的dll必须添加到打包时生成的assembly列表文件。这个列表文件是unity启动时即加载的,不可变数据。不同版本的Unity的列表文件名和格式不相同。

如果未对打包流程作任何处理,由于热更新dll已经在IFilterBuildAssemblies回调中被移除,肯定不会出现在assembly列表文件中。 由于不满足条件3,挂载在热更新资源中的热更新脚本无法被还原,运行时会出现 Scripting Missing的错误。

因此我们在Editor/BuildProcessors/PatchScriptingAssemblyList.cs 脚本中作了特殊处理,把热更新dll加入到assembly列表文件中。 你需要把项目中的热更新assembly添加到HybridCLRSettings配置的HotUpdateAssemblyDefinitions或HotUpdateAssemblies 字段中。

只限制了热更新资源以ab包形式打包,热更新dll打包方式没有限制。你可以按照项目需求自由选择热更新方式,可以将dll打包到ab中,或者裸数据 -文件,或者加密压缩等等。只要能保证在加载热更新资源前使用Assembly.Load将其加载即可。

危险

如果将热更新脚本挂载到Resources等随主包的资源上,会发生scripting missing的错误!但如果先打成assetbundle包,再放到Resources下,运行时加载该随包assetbundle则没有问题。

assembly列表文件

不同Unity版本下assembly列表文件的名称和格式都不一样。

  • 2019版本。 非压缩打包时为globalgamemanagers文件,压缩打包时先保存到globalgamemanagers文件,再以BundleFile格式和其他文件打包到data.unity3d文件。
  • 2020-2021版本。 保存在ScriptingAssembles.json文件中。

已知问题

主线程AddComponent及其他资源加载线程加载包含热更新脚本的资源同时进行时偶发的崩溃问题

此问题来自issue报告。

在第一次使用某热更新类型时(主线程AddComponent或者资源线程加载含脚本的资源)会触发引擎创建MonoScript数据,然而此操作并非线程安全。由于未接入hybridclr时,所有脚本都在启动时已经初始化,因此不会有线程安全问题。当接入hybridclr后,在偶然情况下(尤其是加载包含大量脚本的资源)会触发这个问题。

解决办法:

加载完热更新程序集后,通过临时创建的GameObject,把所有热更新脚本都添加一遍,类似这样:

    var go = new GameObject();
foreach (var type in hotUpdateAss.GetTypes())
{
if (type.IsAssignTo(typeof(MonoBehaviour)))
{
go.AddComponent(type);
}
}
GameObject.Destroy(go);

GameObject.GetComponent(string name) 接口无法获得组件

这是已知bug,跟unity的代码实现有关,只有挂载在热更新资源上热更新脚本才会有这个问题,通过代码中AddComponent添加的热更新脚本是可以用这个方法查找到。如果遇到这个问题请改用 GameObject.GetComponent<T>()GameObject.GetComponent(typeof(T))

其它

需要被挂到资源上的脚本所在dll名称上线后勿修改,因为assembly列表文件打包后无法修改。

建议打AB时不要禁用TypeTree,否则普通的AB加载方式会失败。(原因是对于禁用TypeTree的脚本,Unity为了防止二进制不匹配导致反序列化MonoBehaviour过程中进程Crash,会对脚本的签名进行校验,签名的内容是脚本FullName及TypeTree数据生成的Hash, 但由于我们的热更脚本信息不存在于打包后的安装包中,因此校验必定会失败)

如果必须要禁用TypeTree,一个变通的方法是禁止脚本的Hash校验, 此种情况下用户必须保证打包时代码与资源版本一致,否则可能会导致Crash,示例代码

    AssetBundleCreateRequest req = AssetBundle.LoadFromFileAsync(path);
req.SetEnableCompatibilityChecks(false); // 非public,需要通过反射调用
- +文件,或者加密压缩等等。只要能保证在加载热更新资源前使用Assembly.Load将其加载即可。

危险

如果将热更新脚本挂载到Resources等随主包的资源上,会发生scripting missing的错误!但如果先打成assetbundle包,再放到Resources下,运行时加载该随包assetbundle则没有问题。

assembly列表文件

不同Unity版本下assembly列表文件的名称和格式都不一样。

  • 2019版本。 非压缩打包时为globalgamemanagers文件,压缩打包时先保存到globalgamemanagers文件,再以BundleFile格式和其他文件打包到data.unity3d文件。
  • 2020-2021版本。 保存在ScriptingAssembles.json文件中。

已知问题

主线程AddComponent及其他资源加载线程加载包含热更新脚本的资源同时进行时偶发的崩溃问题

此问题来自issue报告。

在第一次使用某热更新类型时(主线程AddComponent或者资源线程加载含脚本的资源)会触发引擎创建MonoScript数据,然而此操作并非线程安全。由于未接入hybridclr时,所有脚本都在启动时已经初始化,因此不会有线程安全问题。当接入hybridclr后,在偶然情况下(尤其是加载包含大量脚本的资源)会触发这个问题。

解决办法:

加载完热更新程序集后,通过临时创建的GameObject,把所有热更新脚本都添加一遍,类似这样:

    var go = new GameObject();
// 我们不希望挂载到这个GameObject上的脚本执行
go.Active = false;
foreach (var type in hotUpdateAss.GetTypes())
{
if (typeof(MonoBehaviour).IsAssignFrom(type))
{
go.AddComponent(type);
}
}
GameObject.Destroy(go);

GameObject.GetComponent(string name) 接口无法获得组件

这是已知bug,跟unity的代码实现有关,只有挂载在热更新资源上热更新脚本才会有这个问题,通过代码中AddComponent添加的热更新脚本是可以用这个方法查找到。如果遇到这个问题请改用 GameObject.GetComponent<T>()GameObject.GetComponent(typeof(T))

其它

需要被挂到资源上的脚本所在dll名称上线后勿修改,因为assembly列表文件打包后无法修改。

建议打AB时不要禁用TypeTree,否则普通的AB加载方式会失败。(原因是对于禁用TypeTree的脚本,Unity为了防止二进制不匹配导致反序列化MonoBehaviour过程中进程Crash,会对脚本的签名进行校验,签名的内容是脚本FullName及TypeTree数据生成的Hash, 但由于我们的热更脚本信息不存在于打包后的安装包中,因此校验必定会失败)

如果必须要禁用TypeTree,一个变通的方法是禁止脚本的Hash校验, 此种情况下用户必须保证打包时代码与资源版本一致,否则可能会导致Crash,示例代码

    AssetBundleCreateRequest req = AssetBundle.LoadFromFileAsync(path);
req.SetEnableCompatibilityChecks(false); // 非public,需要通过反射调用
+ \ No newline at end of file diff --git a/docs/basic/notsupportedfeatures.html b/docs/basic/notsupportedfeatures.html index 3f19080e0..c184e1a15 100644 --- a/docs/basic/notsupportedfeatures.html +++ b/docs/basic/notsupportedfeatures.html @@ -9,13 +9,13 @@ - +
跳到主要内容

不支持的特性

提示

不在限制事项中的特性HybridCLR都支持,请不要再问HybridCLR是否支持某个功能。

  • 暂时不支持在热更新脚本中定义extern函数,但可以调用AOT中extern函数。
  • 不支持System.Runtime.InteropServices.MarshalMarshal.StructureToPtr之类序列化结构的函数,但普通Marshal函数如Marshal.PtrToStringAnsi都是能正常工作的。
  • 不支持[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.xxx)]。纯粹是时机问题,Unity收集这些函数的时机很早,此时热更新dll还没加载。一个推荐的办法是你使用反射收集这些函数,在合适的时机主动调用它们。
  • 不支持对解释代码部分进行C#级别调试,因为没暂时没时间写调试器
  • RequireComponent(typeof(AAA)) 要求AAA必须已经在别处资源中实例化或者AddComponent过,否则Unity无法识别AAA为脚本而忽略处理。
- + \ No newline at end of file diff --git a/docs/basic/performance.html b/docs/basic/performance.html index 6ae9b212a..0ebb10fed 100644 --- a/docs/basic/performance.html +++ b/docs/basic/performance.html @@ -9,7 +9,7 @@ - + @@ -22,7 +22,7 @@ 对象的内存数据,通过提前计算字段在对象中的偏移,直接 *(int32_t*)(obj + offset) = b; 就能完成这个访问操作。

相比其他热更新方案数几十倍地提升了效率。

直接支持引用与指针操作,无需通过间接方法

由于CLI的规范限制,在C#中引用只能放到托管栈上,而不能存放到解释器栈上(因为是堆内存)。为了处理 ref int a = ref b; a = 5; 之类的代码,不得不使用非常复杂的 技巧间接地维护这个引用。而HybridCLR使用c++实现,可以直接保存和操作这些数据。

相比其他热更新方案效率极大提升。

元数据统一,创建对象更高效,内存占用也更小

由于元数据统一,可以直接调用il2cpp::vm::Object::New来创建对象,效率跟原生非常接近,而且内存完全相同。相比之下,其他热更新方案使用假类型, 对象臃肿,创建对象的过程更重度复杂。

相比其他热更新方案极大提升了效率。

元数据统一,函数调用方式统一,并且没有PInvoke和ReservePInvoke的额外开销

HybridCLR可以直接调用 由IL函数翻译后的c++函数,没有任何中间环节,而ILRuntime和xlua需要各种复杂的判定和参数转换以及与C#之间PInvoke和ReservePInvoke带来额外大量开销。

HybridCLR与il2cpp AOT部分交互极其轻量高效。不再有性能问题。

额外提供大量instinct函数

new Vector{2,3,4}new string()Nullable<T>.Value 等等的常用操作,我们直接提供了对应的指令,运行开销甚至低于AOT的实现。

相比其他热更新方案数几十倍地提升了效率。

严格遵循规范,不引入额外不必要成本

由于精心的设计和优化,HybridCLR尽量规避各种不必要的开销。例如执行过程的GC与原生il2cpp及mono完全相同。

其他指令优化技术

其他的优化技术

附录:测试用例代码

商业化版本 vs xlua


public class AOTForCallFunctions
{
public void Empty()
{

}

public int ReturnInt()
{
return 0;
}

public Vector3 ReturnVector3()
{
return default;
}

public void Func1(int a, int b, int c, int d, int e)
{
}

public void Func2(Vector3 a, Vector3 b, Vector3 c, Vector3 d)
{

}
}

public class BenchmarkTestCases
{
public const int kTestCount = 10000;

/// <summary>
/// 测试简单数值计算
/// </summary>
/// <param name="n"></param>
/// <returns></returns>
[Benchmark]
[Params(kTestCount * 100)]
public int BinOpAdd(int n)
{
int a = 1;
int b = n;
int c = 2;
int d = n;

for (int i = 0; i < n; i++)
{
a = b + c;
b = c + d;
c = d + a;
d = a + b;
a = b + c;
b = c + d;
c = d + a;
d = a + b;
a = b + c;
b = c + d;
c = d + a;
d = a + b;
a = b + c;
b = c + d;
c = d + a;
d = a + b;
a = b + c;
b = c + d;
c = d + a;
d = a + b;
}
return a + b + c + d;
}

/// <summary>
/// 测试复杂数值计算
/// </summary>
/// <param name="cnt"></param>
[Benchmark]
[Params(kTestCount * 100)]
public void BinOpComplex(int cnt)
{
int total = 0;
for (int i = 0; i < cnt; i++)
{
total = total + i - (i - 2) * (i + 3);
total = total + i - (i - 2) * (i + 3);
total = total + i - (i - 2) * (i + 3);
total = total + i - (i - 2) * (i + 3);
total = total + i - (i - 2) * (i + 3);

total = total + i - (i - 2) * (i + 3);
total = total + i - (i - 2) * (i + 3);
total = total + i - (i - 2) * (i + 3);
total = total + i - (i - 2) * (i + 3);
total = total + i - (i - 2) * (i + 3);
}
}

/// <summary>
/// 测试数组操作
/// </summary>
/// <param name="cnt"></param>
[Benchmark]
[Params(kTestCount * 100)]
public int ArrayOp(int cnt)
{
var arr = new int[100];
int k = cnt % 100 + 1;
for (int i = 0; i < cnt; i++)
{
arr[k] = arr[k] + i;
arr[k] = arr[k] + i;
arr[k] = arr[k] + i;
arr[k] = arr[k] + i;
arr[k] = arr[k] + i;
arr[k] = arr[k] + i;
arr[k] = arr[k] + i;
arr[k] = arr[k] + i;
arr[k] = arr[k] + i;
arr[k] = arr[k] + i;
}
return arr[0];
}

/// <summary>
/// 测试Vector3操作
/// </summary>
/// <param name="cnt"></param>
/// <returns></returns>
[Benchmark]
[Params(kTestCount * 10)]
public int VectorOp1(int cnt)
{
float m = 0f;
var v = Vector3.one;
for (var i = 0; i < cnt; i++)
{
m = v.sqrMagnitude;
m = v.sqrMagnitude;
m = v.sqrMagnitude;
m = v.sqrMagnitude;
m = v.sqrMagnitude;

m = v.sqrMagnitude;
m = v.sqrMagnitude;
m = v.sqrMagnitude;
m = v.sqrMagnitude;
m = v.sqrMagnitude;
}
return (int)m;
}

[Benchmark]
[Params(kTestCount * 10)]
public int VectorOp2(int cnt)
{
Vector3 c = default;
var a = new Vector3(1, 2, 3);
var b = new Vector3(4, 5, 6);
for (var i = 0; i < cnt; i++)
{
c = c + a;
c = c + b;
c = c + a;
c = c + b;
c = c + a;

c = c + b;
c = c + a;
c = c + b;
c = c + a;
c = c + b;
}
return (int)c.x;
}

/// <summary>
/// 测试Quaternion操作
/// </summary>
/// <param name="cnt"></param>
/// <returns></returns>
[Benchmark]
[Params(kTestCount * 10)]
public int QuaternionOp(int cnt)
{
for (var i = 0; i < cnt; i++)
{
var q1 = Quaternion.Euler(i, i, i);
var q2 = Quaternion.Slerp(Quaternion.identity, q1, 0.5f);
var q3 = q2.normalized;
var q4 = Quaternion.Lerp(q3, q2, 0.5f);
}
return 0;
}

/// <summary>
/// 测试调用AOT静态成员函数
/// </summary>
/// <param name="cnt"></param>
/// <returns></returns>
[Benchmark]
[Params(kTestCount * 10)]
public int CallAOTStaticMethod(int cnt)
{
float t = 0f;
for (var i = 0; i < cnt; i++)
{
t = Time.deltaTime;
t = Time.deltaTime;
t = Time.deltaTime;
t = Time.deltaTime;
t = Time.deltaTime;

t = Time.deltaTime;
t = Time.deltaTime;
t = Time.deltaTime;
t = Time.deltaTime;
t = Time.deltaTime;
}

return (int)t;
}

/// <summary>
/// 测试调用AOT实例成员函数传int类型参数
/// </summary>
/// <param name="cnt"></param>
/// <returns></returns>
[Benchmark]
[Params(kTestCount * 10)]
public int CallAOTInstanceMethodParamInt(int cnt)
{
var o = new AOTForCallFunctions();
int x = 0;
for (var i = 0; i < cnt; i++)
{
o.Func1(1, 2, 3, 4, 5);
o.Func1(1, 2, 3, 4, 5);
o.Func1(1, 2, 3, 4, 5);
o.Func1(1, 2, 3, 4, 5);
o.Func1(1, 2, 3, 4, 5);
o.Func1(1, 2, 3, 4, 5);
o.Func1(1, 2, 3, 4, 5);
o.Func1(1, 2, 3, 4, 5);
o.Func1(1, 2, 3, 4, 5);
o.Func1(1, 2, 3, 4, 5);
}
return x + 1;
}

/// <summary>
/// 测试调用AOT实例成员函数传Vector3类型参数
/// </summary>
/// <param name="cnt"></param>
/// <returns></returns>
[Benchmark]
[Params(kTestCount * 10)]
public int CallAOTInstanceMethodParamVector3(int cnt)
{
var o = new AOTForCallFunctions();
Vector3 a = Vector3.one;
Vector3 b = Vector3.one;
for (var i = 0; i < cnt; i++)
{
o.Func2(a, b, a, b);
o.Func2(a, b, a, b);
o.Func2(a, b, a, b);
o.Func2(a, b, a, b);
o.Func2(a, b, a, b);
o.Func2(a, b, a, b);
o.Func2(a, b, a, b);
o.Func2(a, b, a, b);
o.Func2(a, b, a, b);
o.Func2(a, b, a, b);
}
return 0;
}

/// <summary>
/// 测试调用AOT实例成员函数,返回值为int
/// </summary>
/// <param name="cnt"></param>
/// <returns></returns>
[Benchmark]
[Params(kTestCount * 10)]
public int CallAOTInstanceMethodReturn1(int cnt)
{
var o = new AOTForCallFunctions();
int x = 0;
for (var i = 0; i < cnt; i++)
{
x = o.ReturnInt();
x = o.ReturnInt();
x = o.ReturnInt();
x = o.ReturnInt();
x = o.ReturnInt();

x = o.ReturnInt();
x = o.ReturnInt();
x = o.ReturnInt();
x = o.ReturnInt();
x = o.ReturnInt();
}
return x + 1;
}

/// <summary>
/// 测试调用AOT实例成员函数,返回值为Vector3
/// </summary>
/// <param name="cnt"></param>
/// <returns></returns>
[Benchmark]
[Params(kTestCount * 10)]
public int CallAOTInstanceMethodReturnVector3(int cnt)
{
var o = new AOTForCallFunctions();
Vector3 x = default;
for (var i = 0; i < cnt; i++)
{
x = o.ReturnVector3();
x = o.ReturnVector3();
x = o.ReturnVector3();
x = o.ReturnVector3();
x = o.ReturnVector3();

x = o.ReturnVector3();
x = o.ReturnVector3();
x = o.ReturnVector3();
x = o.ReturnVector3();
x = o.ReturnVector3();
}
return (int)x.x;
}

/// <summary>
/// 测试GameObject创建和销毁操作
/// </summary>
/// <param name="cnt"></param>
[Benchmark]
[Params(kTestCount)]
public void GameObjectCreateAndDestroy(int cnt)
{
for (var i = 0; i < cnt; i++)
{
var go = new GameObject("t");
Object.Destroy(go);
}
}

/// <summary>
/// 测试 Transform操作
/// </summary>
/// <param name="cnt"></param>
[Benchmark]
[Params(kTestCount * 10)]
public void SetTransformPosition(int cnt)
{
var go = new GameObject();
Vector3 v = Vector3.one;
for (var i = 0; i < cnt; i++)
{
go.transform.position = v;
go.transform.position = v;
go.transform.position = v;
go.transform.position = v;
go.transform.position = v;

go.transform.position = v;
go.transform.position = v;
go.transform.position = v;
go.transform.position = v;
go.transform.position = v;
}
Object.Destroy(go);
}
}

AOT vs 社区版 vs 商业化版本 vs Mono

public class LongArithmetics
{
[Benchmark]
[Params(1000000)]
public long add_1(long n)
{
long a = 1;
long b = n;
long c = 2;
long d = n;

for(long i = 0; i < n; i++)
{
a = b + c;
b = c + d;
c = d + a;
d = a + b;
a = b + c;
b = c + d;
c = d + a;
d = a + b;
a = b + c;
b = c + d;
c = d + a;
d = a + b;
a = b + c;
b = c + d;
c = d + a;
d = a + b;
a = b + c;
b = c + d;
c = d + a;
d = a + b;
}
return a + b + c + d;
}


[Benchmark]
[Params(1000000)]
public long add_2(long n)
{
long a = 1;
long b = n;
long c = 2;
long d = n;
long e = 3;

for (long i = 0; i < n; i++)
{
a = b + c + d + e;
b = c + d + e + a;
c = d + e + a + b;
d = e + a + b + c;
e = a + b + c + d;
a = b + c + d + e;
b = c + d + e + a;
c = d + e + a + b;
d = e + a + b + c;
e = a + b + c + d;
a = b + c + d + e;
b = c + d + e + a;
c = d + e + a + b;
d = e + a + b + c;
e = a + b + c + d;
a = b + c + d + e;
b = c + d + e + a;
c = d + e + a + b;
d = e + a + b + c;
e = a + b + c + d;
}
return a + b + c + d;
}


[Benchmark]
[Params(1000000)]
public long mul_1(long n)
{
long a = 1;
long b = n;
long c = 2;
long d = n;

for (long i = 0; i < n; i++)
{
a = b * c;
b = c * d;
c = d * a;
d = a * b;
a = b * c;
b = c * d;
c = d * a;
d = a * b;
a = b * c;
b = c * d;
c = d * a;
d = a * b;
a = b * c;
b = c * d;
c = d * a;
d = a * b;
a = b * c;
b = c * d;
c = d * a;
d = a * b;
}
return a + b + c + d;
}


[Benchmark]
[Params(1000000)]
public long mul_2(long n)
{
long a = 1;
long b = n;
long c = 2;
long d = n;
long e = 3;

for (long i = 0; i < n; i++)
{
a = b * c * d * e;
b = c * d * e * a;
c = d * e * a * b;
d = e * a * b * c;
e = a * b * c * d;
a = b * c * d * e;
b = c * d * e * a;
c = d * e * a * b;
d = e * a * b * c;
e = a * b * c * d;
a = b * c * d * e;
b = c * d * e * a;
c = d * e * a * b;
d = e * a * b * c;
e = a * b * c * d;
a = b * c * d * e;
b = c * d * e * a;
c = d * e * a * b;
d = e * a * b * c;
e = a * b * c * d;
}
return a + b + c + d;
}


[Benchmark]
[Params(1000000)]
public long div_1(long n)
{
long a = 1;
long b = n;
long c = 2;
long d = n;

for (long i = 0; i < n; i++)
{
b = c / a;
c = d / a;
d = b / a;

b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
a = a / n + 1;
}
return a + b + c + d;
}


public class IntArithmetics
{
[Benchmark]
[Params(1000000)]
public int add_1(int n)
{
int a = 1;
int b = n;
int c = 2;
int d = n;

for(int i = 0; i < n; i++)
{
a = b + c;
b = c + d;
c = d + a;
d = a + b;
a = b + c;
b = c + d;
c = d + a;
d = a + b;
a = b + c;
b = c + d;
c = d + a;
d = a + b;
a = b + c;
b = c + d;
c = d + a;
d = a + b;
a = b + c;
b = c + d;
c = d + a;
d = a + b;
}
return a + b + c + d;
}


[Benchmark]
[Params(1000000)]
public int add_2(int n)
{
int a = 1;
int b = n;
int c = 2;
int d = n;
int e = 3;

for (int i = 0; i < n; i++)
{
a = b + c + d + e;
b = c + d + e + a;
c = d + e + a + b;
d = e + a + b + c;
e = a + b + c + d;
a = b + c + d + e;
b = c + d + e + a;
c = d + e + a + b;
d = e + a + b + c;
e = a + b + c + d;
a = b + c + d + e;
b = c + d + e + a;
c = d + e + a + b;
d = e + a + b + c;
e = a + b + c + d;
a = b + c + d + e;
b = c + d + e + a;
c = d + e + a + b;
d = e + a + b + c;
e = a + b + c + d;
}
return a + b + c + d;
}


[Benchmark]
[Params(1000000)]
public int mul_1(int n)
{
int a = 1;
int b = n;
int c = 2;
int d = n;

for (int i = 0; i < n; i++)
{
a = b * c;
b = c * d;
c = d * a;
d = a * b;
a = b * c;
b = c * d;
c = d * a;
d = a * b;
a = b * c;
b = c * d;
c = d * a;
d = a * b;
a = b * c;
b = c * d;
c = d * a;
d = a * b;
a = b * c;
b = c * d;
c = d * a;
d = a * b;
}
return a + b + c + d;
}


[Benchmark]
[Params(1000000)]
public int mul_2(int n)
{
int a = 1;
int b = n;
int c = 2;
int d = n;
int e = 3;

for (int i = 0; i < n; i++)
{
a = b * c * d * e;
b = c * d * e * a;
c = d * e * a * b;
d = e * a * b * c;
e = a * b * c * d;
a = b * c * d * e;
b = c * d * e * a;
c = d * e * a * b;
d = e * a * b * c;
e = a * b * c * d;
a = b * c * d * e;
b = c * d * e * a;
c = d * e * a * b;
d = e * a * b * c;
e = a * b * c * d;
a = b * c * d * e;
b = c * d * e * a;
c = d * e * a * b;
d = e * a * b * c;
e = a * b * c * d;
}
return a + b + c + d;
}


[Benchmark]
[Params(1000000)]
public int div_1(int n)
{
int a = 1;
int b = n;
int c = 2;
int d = n;

for (int i = 0; i < n; i++)
{
b = c / a;
c = d / a;
d = b / a;

b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
a = a / n + 1;
}
return a + b + c + d;
}
}

public class FloatArithmetics
{
[Benchmark]
[Params(1000000)]
public float add_1(int n)
{
float a = 1;
float b = n;
float c = 2;
float d = n;

for(int i = 0; i < n; i++)
{
a = b + c;
b = c + d;
c = d + a;
d = a + b;
a = b + c;
b = c + d;
c = d + a;
d = a + b;
a = b + c;
b = c + d;
c = d + a;
d = a + b;
a = b + c;
b = c + d;
c = d + a;
d = a + b;
a = b + c;
b = c + d;
c = d + a;
d = a + b;
}
return a + b + c + d;
}


[Benchmark]
[Params(1000000)]
public float add_2(int n)
{
float a = 1;
float b = n;
float c = 2;
float d = n;
float e = 3;

for (int i = 0; i < n; i++)
{
a = b + c + d + e;
b = c + d + e + a;
c = d + e + a + b;
d = e + a + b + c;
e = a + b + c + d;
a = b + c + d + e;
b = c + d + e + a;
c = d + e + a + b;
d = e + a + b + c;
e = a + b + c + d;
a = b + c + d + e;
b = c + d + e + a;
c = d + e + a + b;
d = e + a + b + c;
e = a + b + c + d;
a = b + c + d + e;
b = c + d + e + a;
c = d + e + a + b;
d = e + a + b + c;
e = a + b + c + d;
}
return a + b + c + d;
}


[Benchmark]
[Params(1000000)]
public float mul_1(int n)
{
float a = 1;
float b = n;
float c = 2;
float d = n;

for (int i = 0; i < n; i++)
{
a = b * c;
b = c * d;
c = d * a;
d = a * b;
a = b * c;
b = c * d;
c = d * a;
d = a * b;
a = b * c;
b = c * d;
c = d * a;
d = a * b;
a = b * c;
b = c * d;
c = d * a;
d = a * b;
a = b * c;
b = c * d;
c = d * a;
d = a * b;
}
return a + b + c + d;
}


[Benchmark]
[Params(1000000)]
public float mul_2(int n)
{
float a = 1;
float b = n;
float c = 2;
float d = n;
float e = 3;

for (int i = 0; i < n; i++)
{
a = b * c * d * e;
b = c * d * e * a;
c = d * e * a * b;
d = e * a * b * c;
e = a * b * c * d;
a = b * c * d * e;
b = c * d * e * a;
c = d * e * a * b;
d = e * a * b * c;
e = a * b * c * d;
a = b * c * d * e;
b = c * d * e * a;
c = d * e * a * b;
d = e * a * b * c;
e = a * b * c * d;
a = b * c * d * e;
b = c * d * e * a;
c = d * e * a * b;
d = e * a * b * c;
e = a * b * c * d;
}
return a + b + c + d;
}


[Benchmark]
[Params(1000000)]
public float div_1(int n)
{
float a = 1;
float b = n;
float c = 2;
float d = n;

for (int i = 0; i < n; i++)
{
b = c / a;
c = d / a;
d = b / a;

b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
a = a / n + 1;
}
return a + b + c + d;
}
}

public class DoubleArithmetics
{
[Benchmark]
[Params(1000000)]
public double add_1(int n)
{
double a = 1;
double b = n;
double c = 2;
double d = n;

for (int i = 0; i < n; i++)
{
a = b + c;
b = c + d;
c = d + a;
d = a + b;
a = b + c;
b = c + d;
c = d + a;
d = a + b;
a = b + c;
b = c + d;
c = d + a;
d = a + b;
a = b + c;
b = c + d;
c = d + a;
d = a + b;
a = b + c;
b = c + d;
c = d + a;
d = a + b;
}
return a + b + c + d;
}


[Benchmark]
[Params(1000000)]
public double add_2(int n)
{
double a = 1;
double b = n;
double c = 2;
double d = n;
double e = 3;

for (int i = 0; i < n; i++)
{
a = b + c + d + e;
b = c + d + e + a;
c = d + e + a + b;
d = e + a + b + c;
e = a + b + c + d;
a = b + c + d + e;
b = c + d + e + a;
c = d + e + a + b;
d = e + a + b + c;
e = a + b + c + d;
a = b + c + d + e;
b = c + d + e + a;
c = d + e + a + b;
d = e + a + b + c;
e = a + b + c + d;
a = b + c + d + e;
b = c + d + e + a;
c = d + e + a + b;
d = e + a + b + c;
e = a + b + c + d;
}
return a + b + c + d;
}


[Benchmark]
[Params(1000000)]
public double mul_1(int n)
{
double a = 1;
double b = n;
double c = 2;
double d = n;

for (int i = 0; i < n; i++)
{
a = b * c;
b = c * d;
c = d * a;
d = a * b;
a = b * c;
b = c * d;
c = d * a;
d = a * b;
a = b * c;
b = c * d;
c = d * a;
d = a * b;
a = b * c;
b = c * d;
c = d * a;
d = a * b;
a = b * c;
b = c * d;
c = d * a;
d = a * b;
}
return a + b + c + d;
}


[Benchmark]
[Params(1000000)]
public double mul_2(int n)
{
double a = 1;
double b = n;
double c = 2;
double d = n;
double e = 3;

for (int i = 0; i < n; i++)
{
a = b * c * d * e;
b = c * d * e * a;
c = d * e * a * b;
d = e * a * b * c;
e = a * b * c * d;
a = b * c * d * e;
b = c * d * e * a;
c = d * e * a * b;
d = e * a * b * c;
e = a * b * c * d;
a = b * c * d * e;
b = c * d * e * a;
c = d * e * a * b;
d = e * a * b * c;
e = a * b * c * d;
a = b * c * d * e;
b = c * d * e * a;
c = d * e * a * b;
d = e * a * b * c;
e = a * b * c * d;
}
return a + b + c + d;
}


[Benchmark]
[Params(1000000)]
public double div_1(int n)
{
double a = 1;
double b = n;
double c = 2;
double d = n;

for (int i = 0; i < n; i++)
{
b = c / a;
c = d / a;
d = b / a;

b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
a = a / n + 1;
}
return a + b + c + d;
}
}

- + \ No newline at end of file diff --git a/docs/basic/projectsettings.html b/docs/basic/projectsettings.html index 7ca7669bd..99b599c22 100644 --- a/docs/basic/projectsettings.html +++ b/docs/basic/projectsettings.html @@ -9,14 +9,14 @@ - +
跳到主要内容

配置

安装完com.code-philosophy.hybridclr包后,需要正确设置相关参数。配置相关详细文档可见 hybridclr_unity包介绍

配置PlayerSettings

  • 如果你的com.code-philosophy.hybridclr版本低于v4.0.0,需要关闭增量式GC(Use Incremental GC) 选项。自v4.0.0起已经支持增量式GC,可以开启这个选项。
  • Scripting Backend 切换为 il2cpp, WebGL平台不用设置此选项。v2.4.0起,会自动设置此选项,可以不用手动执行此操作
  • Api Compatability Level 切换为 .NetFramework 4(Unity 2019、2020) 或 .Net Framework(Unity 2021+)。

配置热更新程序集

很显然,对于需要热更新的代码应该拆分为独立的程序集,才能方便地热更新。如何创建和拆分热更新程序集,请看创建和配置热更新Assembly文档。

点击菜单 HybridCLR/Settings 打开配置界面。

  • 如果是Assembly Definition(asmdef)方式定义的程序集,加入hotUpdateAssemblyDefinitions
  • 如果是普通dll或者Assembly-CSharp.dll,则将程序集名字(不包含'.dll'后缀,如Main、Assembly-CSharp)加入hotUpdateAssemblies

hotUpdateAssemblyDefinitionshotUpdateAssemblies列表是等价的,不要重复添加,否则会报错。

警告

如果热更新程序集是已经编译好的dll(无论放在Assets下还是其他目录),必须同时在 HybridCLR/Settings外部dll搜索路径中配置它的搜索路径。 搜索路径为相对路径,相对于项目根目录(也就是Assets的父目录)。

其他参数

大多数参数保持默认值即可,一般开发者不用关心。详细请查看com.code-philosophy.hybridclr包介绍

- + \ No newline at end of file diff --git a/docs/basic/runhotupdatecodes.html b/docs/basic/runhotupdatecodes.html index fa5ab1fd0..4e882ee12 100644 --- a/docs/basic/runhotupdatecodes.html +++ b/docs/basic/runhotupdatecodes.html @@ -9,14 +9,14 @@ - +
跳到主要内容

加载和运行

加载更新assembly

根据你们项目资源管理的方式,获得热更新dll的bytes数据。然后再直接调用Assembly.Load(byte[] assemblyData)即可。代码类似 如下:

    // 从你的资源管理系统中获得热更新dll的数据
byte[] assemblyData = xxxx;
// Assembly.Load内部会自动复制assemblyData,调用完此函数可以释放assemblyData,没必要保存起来。
Assembly ass = Assembly.Load(assemblyData);

如果有多个热更新dll,请一定要按照依赖顺序加载,先加载被依赖的assembly。加载完热更新dll后,有多种方式运行热更新代码,这些技巧跟不考虑热更新时完全相同。

提示

如果Assembly.Load花费太多时间,造成卡顿,你可以在其他线程异步加载。

通过反射直接运行热更新函数

假设热更新集中有HotUpdateEntry类,主入口是静态的Main函数,代码类似:

class HotUpdateEntry
{
public static void Main()
{
UnityEngine.Debug.Log("hello, HybridCLR");
}
}

你用如下方式运行:

    // ass 为Assembly.Load返回的热更新assembly。
// 你也可以在Assembly.Load后通过类似如下代码查找获得。
// Assembly ass = AppDomain.CurrentDomain.GetAssemblies().First(assembly => assembly.GetName().Name == "Your-HotUpdate-Assembly");
Type entryType = ass.GetType("HotUpdateEntry");
MethodInfo method = entryType.GetMethod("Main");
method.Invoke(null, null);

通过反射创造出Delegate后运行

    Type entryType = ass.GetType("HotUpdateEntry");
MethodInfo method = entryType.GetMethod("Main");
Action mainFunc = (Action)Delegate.CreateDelegate(typeof(Action), method);
mainFunc();

通过反射创建出对象后,再调用接口函数

假设AOT中有这样的接口

public interface IEntry
{
void Start();
}

热更新中实现了这样的类

class HotUpdateEntry : IEntry
{
public void Start()
{
UnityEngine.Debug.Log("hello, HybridCLR");
}
}

你用如下方式运行:

    Type entryType = ass.GetType("HotUpdateEntry");
IEntry entry = (IEntry)Activator.CreateInstance(entryType);
entry.Start();

通过动态AddComponent运行脚本代码

假设热更新中有这样的代码:

class Rotate : MonoBehaviour
{
void Update()
{

}
}

你在AOT中运行如下代码:

    Type type = ass.GetType("Rotate");
GameObject go = new GameObject("Test");
go.AddComponent(type);

通过初始化从打包成assetbundle的prefab或者scene还原挂载的热更新脚本

假设热更新中有这样的入口脚本,这个脚本被挂到HotUpdatePrefab.prefab上。


public class HotUpdateMain : MonoBehaviour
{
void Start()
{
Debug.Log("hello, HybridCLR");
}
}

你通过实例化这个prefab,即可运行热更新逻辑。

        AssetBundle prefabAb = xxxxx; // 获得HotUpdatePrefab.prefab所在的AssetBundle
GameObject testPrefab = Instantiate(prefabAb.LoadAsset<GameObject>("HotUpdatePrefab.prefab"));

这种方法不需要借助任何反射,而且跟原生的启动流程相同,推荐使用这种方式初始化热更新入口代码!

- + \ No newline at end of file diff --git a/docs/basic/sourceinspect.html b/docs/basic/sourceinspect.html index 25cc49bb6..3aeff6ddf 100644 --- a/docs/basic/sourceinspect.html +++ b/docs/basic/sourceinspect.html @@ -9,13 +9,13 @@ - +
跳到主要内容

HybridCLR源码结构及调试

HybridCLR模块介绍

HybridCLR实现了以下功能:

  • c++实现的dll解析库
  • 元数据注册。由于il2cpp是静态AOT,原始代码并不支持动态注册,因为做了少量修改(几百行)
  • 指令集转换。将原始IL指令转成更高效的寄存器指令
  • 寄存器解释器。实现了一个高效的解释器。

目录结构上,与之对应:

  • HybridCLR 自身源码
    • interpreter 解释器模块
    • metadata 元数据解析与注册模块
    • transform 指令集转换模块
  • 对il2cpp源码的小幅修改。HybridCLR对il2cpp源码修改主要为支持动态注册元数据。大多数地方只是插入了hook处理,并未修改原始实现。例如:
const char* il2cpp::vm::GlobalMetadata::GetStringFromIndex(StringIndex index)
{
// ==={{ hybridclr
if (hybridclr::metadata::IsInterpreterIndex(index))
{
return hybridclr::metadata::MetadataModule::GetStringFromEncodeIndex(index);
}
// ===}} hybridclr

IL2CPP_ASSERT(index <= s_GlobalMetadataHeader->stringCount);
const char* strings = ((const char*)s_GlobalMetadata + s_GlobalMetadataHeader->stringOffset) + index;
return strings;
}

transform 实现简介

提示

核心代码在 hybridclr/transform/Transform.cppHiTransform::Transform函数。

跟常规的指令树分析非常相似。分为几部分

  • BasicBlock划分。将原始IL指令切分为多个BasicBlock,每个BasicBlock不包含任何跳转函数。这么做可以比较高效地避免意外的跨跳块的指令合并
  • 模拟执行所有逻辑分支,包括跳转和异常分支,将每个IL指令转换为对应寄存器指令。
  • 指令优化(待做)。预计于下个月版本开始开发。届时大多数指令可以获得100-300%的性能提升。

interpreter 实现简介

提示

核心代码在hybridclr/interpreter/Interpreter_Execute.cppInterpreter::Execute函数。

比较直接,就是一个巨大的switch语句,解释执行指令。

调试

HybridCLR解释器核心工作包括两部分:

  • 指令集转换。将基于栈的IL指令转换为基于寄存器的版本。在 HybridCLR/transform/transform.cpp 的 HiTransform::Transform函数。
  • 寄存器指令的解释执行。在 HybridCLR/interpreter/interpreter_Execute.cpp的 Interpreter::Execute函数。

只要断点到这两个函数,就很容易逐步跟踪IL函数的转换转换到解决执行的整个流程。

创建Win, Mac Standalone调试工程

  • Project Settings设置
    • 修改 C++ Compiler Configuration为Debug
  • Building Settings中选中 "Create VisualStudio Solution"

Build完成后,即产生一个可调试的工程。想了解更多,可参考Unity官方文档

创建Android调试工程

  • Project Settings设置
    • 修改 C++ Compiler Configuration为Debug
  • Building Settings选中Export Project
  • Build完成后,使用Android Studio打开工程。
  • 假设打包输出路径为build_android,在Android Studio中选择 Build->Make Module 'build_android.unityLibrary',编译unityLibrary,等待编译完成
  • 选择Run->Edit Configurations...,按下图所示进行设置。

android studio debug

  • 正常debug即可。

创建iOS调试工程

必须使用 com.code-philosophy.hybridclr v3.2.0及以上版本才可直接源码调试,低版本由于使用了独立编译的release版本libil2cpp.a,无法调试。

  • Project Settings设置
    • 修改 C++ Compiler Configuration为Debug
  • 点击Build生成xcode工程
  • 在xcode工程内调试即可
- + \ No newline at end of file diff --git a/docs/basic/supportedplatformanduniyversion.html b/docs/basic/supportedplatformanduniyversion.html index 75e629890..0a1990245 100644 --- a/docs/basic/supportedplatformanduniyversion.html +++ b/docs/basic/supportedplatformanduniyversion.html @@ -9,7 +9,7 @@ - + @@ -19,7 +19,7 @@ 如果某个小版本非我们标准支持版本,也可以联系我们提供商业化服务

支持的平台

自v4.0.0版本起,已经消除了所有已知的平台不兼容的代码,彻底支持了所有il2cpp能运行的所有平台。但对于一些不常见平台,有可能残留一些Editor或者Runtime的小bug, 如果有遇到问题,请联系我们商务解决。

以下平台是已经久经测试,非常稳定支持的平台:

  • Windows x86、x64
  • MacOS x86、x64
  • MacOS arm64(silicon)
  • Android armv7、armv8(arm64)
  • iOS arm64
  • tvOS
  • visionOS
  • WebGL 包括WebGL、MiniGame、微信小游戏
  • PS4、PS5
  • UWP
  • 华为Harmony(鸿蒙)平台(当前只有团结引擎支持鸿蒙)
  • 其他平台

不支持的平台

提示

团结引擎除了微信小游戏平台以外的平台与HybridCLR是兼容的!

  • 不支持团结引擎+微信小游戏平台。 因为团结引擎发布微信小游戏平台时对metadata index的优化(4字节改为2字节),与HybridCLR不兼容
  • 不支持抖音小游戏平台。因为抖音小游戏平台要求开发者提交il2cpp由dll生成的c++代码,他们来构建app,而他们使用了原始的libil2cpp

团结引擎

自v5.2.0版本起,社区版本正式支持团结引擎,使用方式与Unity官方版本完全相同。

特殊说明

微信小游戏

微信小游戏转换工具,默认会将IL2CPP Code Generation设置为Faster(Smaller) builds模式,如果未补充元数据,会导致无法访问AOT泛型函数。自2021.3.x版本起,所有商业化版本 支持完全泛型共享,可以不再需要补充元数据,减少了包体,明显降低内存占用,并且大幅提升了未在主工程实例化的AOT泛型函数的执行性能。

MiniGame

有关版本兼容性的补充说明:

  • MiniGame2019和2020版本的推荐版本与HybridCLR的兼容版本有交叉,尽量直接选择那些交叉版本(如2019.4.35、2020.3.33),因为已经被项目验证过,基本不会遇到问题。
  • MiniGame2021系列推荐版本为2021.2.5-2021.2.18,非HybridCLR支持的LTS版本,但这些版本已经被其他开发者验证过,也是可以正常使用HybridCLR的(可能需要少量代码调整)。如果有遇到问题,可以联系我们提供商业技术支持。
- + \ No newline at end of file diff --git a/docs/basic/workwithscriptlanguage.html b/docs/basic/workwithscriptlanguage.html index eb4aedbb1..50442d250 100644 --- a/docs/basic/workwithscriptlanguage.html +++ b/docs/basic/workwithscriptlanguage.html @@ -9,7 +9,7 @@ - + @@ -23,7 +23,7 @@ 一个wrapper函数。如果对多个相同签名的函数添加了[ReversePInvokeWrapperGeneration(xx)] 特性,则wrapper函数总数为 所有 preserveCount之和 + 不包含 ReversePInvokeWrapperGenerationAttribute 特性的函数个数

如下如示, LuaFunction 类型的wrapper有10个, Func<int, int, int> 类型的wrapper有101个,Func<int, int> 类型的wrapper有1个。


delegate int LuaFunction(IntPtr luaState);

public class MonoPInvokeWrapperPreserves
{
[ReversePInvokeWrapperGeneration(10)]
[MonoPInvokeCallback(typeof(LuaFunction))]
public static int LuaCallback(IntPtr luaState)
{
return 0;
}

[ReversePInvokeWrapperGeneration(100)]
[MonoPInvokeCallback(typeof(Func<int, int, int>))]
public static int Sum(int a, int b)
{
return a + b;
}

[MonoPInvokeCallback(typeof(Func<int, int, int>))]
public static int Sum2(int a, int b)
{
return a + b;
}

[MonoPInvokeCallback(typeof(Func<int, int>))]
public static int Inc(int a)
{
return a + 1;
}
}

限制

警告

请确保函数参数都是简单primitive类型如int、float之类。

目前没有对引用类型参数作marshal处理,诸如string之类引用类型的参数都是直接传参处理,使用后必然会导致崩溃! 如果实在有这种需求,可以将回调函数放到AOT中,在AOT中再回调热更新 函数。

- + \ No newline at end of file diff --git a/docs/beginner.html b/docs/beginner.html index 608205b5c..0071eac84 100644 --- a/docs/beginner.html +++ b/docs/beginner.html @@ -9,13 +9,13 @@ - + - + \ No newline at end of file diff --git a/docs/beginner/generic.html b/docs/beginner/generic.html index c1c3c933b..a42b4731f 100644 --- a/docs/beginner/generic.html +++ b/docs/beginner/generic.html @@ -9,7 +9,7 @@ - + @@ -17,7 +17,7 @@
跳到主要内容

使用泛型

HybridCLR完整支持泛型特性,没有任何限制。

使用热更新中定义的泛型类或函数

直接使用即可。

使用AOT中定义的泛型类或函数

关于AOT泛型问题的详细原理请阅读AOT泛型

如果AOT中已经有代码实例化过某个泛型类或者函数,则热更新中可以直接使用,例如:


// AOT 中已经用过List<float>泛型
class Foo
{
public void Run()
{
var arr = new List<float>();
}
}

// 热更新中可以使用 List<float>
class HotUpdateGenericDemos
{
public void Run()
{
var arr = new List<float>();
}
}

但如果AOT中没有实例化过某个AOT泛型类或者函数,解决办法有几种:

  1. 在AOT代码添加相应的实例化代码。
  2. 补充元数据技术。 这是HybridCLR的专利技术,社区版本也能使用。
  3. full generic sharing 完全泛型共享技术,相比补充元数据技术,工作流更简单,既不需要随包携带或者下载补充元数据dll,也不需要加载补充元数据dll,包体大小和内存都明显降低。该技术目前只在商业化版本提供。

对于方法1,有几个致命缺陷:

  • AOT代码中添加实例化代码需要重新打包,不仅开发期很麻烦,上线后短期内重新发主包是不现实的。
  • 泛型参数有可能是热更新类型,不可能在AOT中提前实例化。例如你在热更新代码中定义了 struct MyVector3 {int x, y, z;},你不可能在AOT中提前实例化List<MyVector3>

补充元数据技术彻底解决了这个问题。粗略地说,你补充AOT泛型类(或泛型函数)的原始元数据后,就可以任意实例化这个泛型类了。以上面List<MyVector3>为例,你补充了List类(而不是MyVector3)所在的mscorlib.dll元数据后,就可以在热更新代码中使用任意List<T>泛型类了。

补充元数据技术的缺陷是增大了包体或者需要额外下载补充元数据dll,导致工作流复杂一些,另外还多占用了内存。full generic sharing 又进一步解决补充元数据的这些缺陷。由于full generic sharing是商业化方案,这儿限于篇幅只介绍补充元数据的用法。

获得补充元数据dll

打包过程生成的裁剪后的AOT dll可以用于补充元数据。com.code-philosophy.hybridclr插件会自动把它们复制到{project}/HybridCLRData/AssembliesPostIl2CppStrip/{target}

危险

不同BuildTarget的裁剪AOT dll不可复用。

使用HybridCLR/Generate/AotDlls命令也可以立即生成裁剪后的AOT dll,它的工作原理是通过导出一个Temp工程来获得裁剪AOT dll。

{project}/HybridCLRData/AssembliesPostIl2CppStrip/{target}目录获得你所需要的补充元数据dll,加入项目的热更新资源管理系统。示例项目出于演示起见,将它们放到StreamingAssets目录下。 以List<T>类型为例,它需要补充mscorlib.dll。将{project}/HybridCLRData/AssembliesPostIl2CppStrip/{target}/mscorlib.dll复制到Assets/StreamingAssets/mscorlib.dll.bytes

执行补充元数据

使用com.code-philosophy.hybridclr包中的HybridCLR.RuntimeApi.LoadMetadataForAOTAssembly函数为AOT泛型补充元数据。 元数据只需要补充一次,推荐在执行任何热更新代码前。LoadDll.cs最终变成类似如下。


public class LoadDll : MonoBehaviour
{

void Start()
{
// 先补充元数据
LoadMetadataForAOTAssemblies();
// Editor环境下,HotUpdate.dll.bytes已经被自动加载,不需要加载,重复加载反而会出问题。
#if !UNITY_EDITOR
Assembly hotUpdateAss = Assembly.Load(File.ReadAllBytes($"{Application.streamingAssetsPath}/HotUpdate.dll.bytes"));
#else
// Editor下无需加载,直接查找获得HotUpdate程序集
Assembly hotUpdateAss = System.AppDomain.CurrentDomain.GetAssemblies().First(a => a.GetName().Name == "HotUpdate");
#endif

Type type = hotUpdateAss.GetType("Hello");
type.GetMethod("Run").Invoke(null, null);
}

private static void LoadMetadataForAOTAssemblies()
{
List<string> aotDllList = new List<string>
{
"mscorlib.dll",
"System.dll",
"System.Core.dll", // 如果使用了Linq,需要这个
// "Newtonsoft.Json.dll",
// "protobuf-net.dll",
};

foreach (var aotDllName in aotDllList)
{
byte[] dllBytes = File.ReadAllBytes($"{Application.streamingAssetsPath}/{aotDllName}.bytes");
int err = HybridCLR.RuntimeApi.LoadMetadataForAOTAssembly(dllBytes, HomologousImageMode.SuperSet);
Debug.Log($"LoadMetadataForAOTAssembly:{aotDllName}. ret:{err}");
}
}
}

现在你可以在热更新代码随意使用AOT泛型了。

优化补充元数据dll大小

默认生成的补充元数据dll中包含了大量补充元数据机制不需要的数据,自v4.0.16版本起支持对补充元数据dll进行剔除优化,详细文档见AOT泛型

- + \ No newline at end of file diff --git a/docs/beginner/monobehaviour.html b/docs/beginner/monobehaviour.html index 8ab60de0a..3a26fbb3b 100644 --- a/docs/beginner/monobehaviour.html +++ b/docs/beginner/monobehaviour.html @@ -9,13 +9,13 @@ - +
跳到主要内容

使用MonoBehaviour

HybridCLR完全支持MonoBehaviour工作流,你既可以通过AddComponent的方式在代码里动态挂载热更新脚本,也可以将热更新脚本挂到资源上,再通过加载资源的方式还原脚本。

基于快速上手文档的项目,我们演示如何使用热更新脚本。

创建 Print.cs 热更新脚本

创建 Assets/HotUpdate/Print.cs脚本,代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Print : MonoBehaviour
{
void Start()
{
Debug.Log($"[Print] GameObject:{name}");
}
}

代码中调用AddComponent来动态挂载热更新脚本

修改 Hello.Run函数,添加动态挂载Print脚本的代码,最终代码如下:

    public static void Run()
{
Debug.Log("Hello, World");

GameObject go = new GameObject("Test1");
go.AddComponent<Print>();
}

热更新后,屏幕上会新增一行日志 [Print] GameObject:Test1

将脚本挂载到热更新资源

由于Unity资源管理系统的限制,热更新脚本所挂载的资源(prefab、scene、ScriptableObject资源)必须打成assetbundle,从ab包中实例化资源,才能正确还原脚本。

危险

如果将热更新脚本挂载到Resources等随主包的资源上,会发生scripting missing的错误!但如果先打成assetbundle包,再放到Resources下,运行时加载该随包assetbundle则没有问题。

由于整个过程涉及到打ab包,比较冗长,这儿不详细说明。请直接体验 hybridclr_trial 项目(githubgitee)。

对于新手来说,你只需要记住:挂载热更新脚本的资源(场景或prefab)必须打包成ab,在实例化资源前先加载热更新dll即可(这个要求是显然的!)。

- + \ No newline at end of file diff --git a/docs/beginner/otherhelp.html b/docs/beginner/otherhelp.html index f5b8830ad..48e01cae4 100644 --- a/docs/beginner/otherhelp.html +++ b/docs/beginner/otherhelp.html @@ -9,13 +9,13 @@ - +
跳到主要内容

其他资料

官方相关仓库

视频教程

你可以可在B站搜索 HybridCLR相关视频,请尽量选择较新的视频,否则可能与当前HybridCLR相差较大,造成误导。

遇到问题

请先查阅 常见错误

如果没有解决,可以加入官方群求助:

如果确定是bug(HybridCLR已经非常稳定,新手遇到bug的概率极低),请按照Bug反馈模板反馈给我们。

- + \ No newline at end of file diff --git a/docs/beginner/quickstart.html b/docs/beginner/quickstart.html index 0dd8110c4..e770a37f8 100644 --- a/docs/beginner/quickstart.html +++ b/docs/beginner/quickstart.html @@ -9,13 +9,13 @@ - +
跳到主要内容

快速上手

本教程引导从空项目开始体验HybridCLR热更新。出于简化起见,只演示BuildTarget为WindowsMacOS Standalone平台的情况。

请在Standalone平台上正确跑通热更新流程后再自行尝试Android、iOS平台的热更新,它们的流程非常相似。

体验目标

  • 创建热更新程序集
  • 加载热更新程序集,并执行其中热更新代码,打印 Hello, HybridCLR
  • 修改热更新代码,打印 Hello, World

准备环境

安装Unity

警告

HybridCLR支持Unity 2019-2022的所有LTS版本,如果当前使用的版本不在以下推荐版本中,请参考安装HybridCLR文档。

  • 安装 2019.4.40、2020.3.26+、 2021.3.0+、2022.3.0+ 中任一版本
  • 根据你所用的操作系统,安装过程中选择模块时,必须选中 Windows Build Support(IL2CPP)Mac Build Support(IL2CPP)

select il2cpp modules

安装IDE及相关编译环境

  • Windows
    • Win下需要安装visual studio 2019或更高版本。安装时至少要包含 使用Unity的游戏开发使用c++的游戏开发 组件。
    • 安装git
  • Mac
    • 要求MacOS版本 >= 12,xcode版本 >= 13,例如xcode 13.4.1, macos 12.4
    • 安装 git

安装HybridCLR

安装 com.code-philosophy.hybridclr

主菜单中点击Windows/Package Manager打开包管理器。如下图所示点击Add package from git URL...,填入https://gitee.com/focus-creative-games/hybridclr_unity.githttps://github.com/focus-creative-games/hybridclr_unity.git

add package

不熟悉从url安装package的请看install from giturl

由于国内网络原因,在unity中可能遇到网络异常而无法安装。你可以先把 com.code-philosophy.hybridclr clone或者下载到本地,将文件夹改名为com.code-philosophy.hybridclr,直接移动到项目的Packages目录下即可。

初始化 com.code-philosophy.hybridclr

打开菜单HybridCLR/Installer..., 点击安装按钮进行安装。 耐心等待30s左右,安装完成后会在最后打印 安装成功日志。

初始化Unity热更新项目

从零开始构造热更新项目的过程较冗长,项目结构及资源及代码均可参考hybridclr_trial项目,其仓库地址为 githubgitee

创建项目

创建空的Unity项目。

创建ConsoleToScreen.cs脚本

这个脚本对于演示热更新没有直接作用。它可以打印日志到屏幕上,方便定位错误。

创建 Assets/ConsoleToScreen.cs 脚本类,代码如下:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ConsoleToScreen : MonoBehaviour
{
const int maxLines = 50;
const int maxLineLength = 120;
private string _logStr = "";

private readonly List<string> _lines = new List<string>();

public int fontSize = 15;

void OnEnable() { Application.logMessageReceived += Log; }
void OnDisable() { Application.logMessageReceived -= Log; }

public void Log(string logString, string stackTrace, LogType type)
{
foreach (var line in logString.Split('\n'))
{
if (line.Length <= maxLineLength)
{
_lines.Add(line);
continue;
}
var lineCount = line.Length / maxLineLength + 1;
for (int i = 0; i < lineCount; i++)
{
if ((i + 1) * maxLineLength <= line.Length)
{
_lines.Add(line.Substring(i * maxLineLength, maxLineLength));
}
else
{
_lines.Add(line.Substring(i * maxLineLength, line.Length - i * maxLineLength));
}
}
}
if (_lines.Count > maxLines)
{
_lines.RemoveRange(0, _lines.Count - maxLines);
}
_logStr = string.Join("\n", _lines);
}

void OnGUI()
{
GUI.matrix = Matrix4x4.TRS(Vector3.zero, Quaternion.identity,
new Vector3(Screen.width / 1200.0f, Screen.height / 800.0f, 1.0f));
GUI.Label(new Rect(10, 10, 800, 370), _logStr, new GUIStyle() { fontSize = Math.Max(10, fontSize) });
}
}


创建主场景

  • 创建默认初始场景 main.scene
  • 场景中创建一个空GameObject,将ConsoleToScreen挂到上面
  • Build Settings中添加main场景到打包场景列表

创建 HotUpdate 热更新模块

  • 创建 Assets/HotUpdate 目录
  • 在目录下 右键 Create/Assembly Definition,创建一个名为HotUpdate的程序集模块
    提示

    如果你们项目把Assembly-CSharp作为AOT程序集,强烈建议关闭热更新程序集的auto reference选项。因为Assembly-CSharp是最顶层assembly,开启此选项后会自动引用剩余所有assembly,包括热更新程序集,很容易就出现失误引用热更新程序集导致打包失败的情况。

配置HybridCLR

配置HybridCLR

打开菜单 HybridCLR/Settings, 在Hot Update Assemblies配置项中添加HotUpdate程序集,如下图:

settings

配置PlayerSettings

  • 如果你用的hybridclr包低于v4.0.0版本,需要关闭增量式GC(Use Incremental GC) 选项
  • Scripting Backend 切换为 IL2CPP
  • Api Compatability Level 切换为 .Net 4.x(Unity 2019-2020) 或 .Net Framework(Unity 2021+)

player settings

创建热更新脚本

创建 Assets/HotUpdate/Hello.cs 文件,代码内容如下

using System.Collections;
using UnityEngine;

public class Hello
{
public static void Run()
{
Debug.Log("Hello, HybridCLR");
}
}

你可能会关心热更新部分的代码会不会像其他方案那样对C#语法有限制。HybridCLR是近乎完备的实现,对热更新代码几乎没有限制。极少数的例外可以查看不支持的特性

加载热更新程序集

为了简化演示,我们不通过http服务器下载HotUpdate.dll,而是直接将HotUpdate.dll放到StreamingAssets目录下。

HybridCLR是原生运行时实现,因此调用Assembly Assembly.Load(byte[])即可加载热更新程序集。

创建Assets/LoadDll.cs脚本,然后在main场景中创建一个GameObject对象,挂载LoadDll脚本

using HybridCLR;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;

public class LoadDll : MonoBehaviour
{

void Start()
{
// Editor环境下,HotUpdate.dll.bytes已经被自动加载,不需要加载,重复加载反而会出问题。
#if !UNITY_EDITOR
Assembly hotUpdateAss = Assembly.Load(File.ReadAllBytes($"{Application.streamingAssetsPath}/HotUpdate.dll.bytes"));
#else
// Editor下无需加载,直接查找获得HotUpdate程序集
Assembly hotUpdateAss = System.AppDomain.CurrentDomain.GetAssemblies().First(a => a.GetName().Name == "HotUpdate");
#endif
}
}

调用热更新代码

显然,主工程不能直接引用热更新代码。有多种方式可以从主工程调用热更新程序集中的代码,这里通过反射来调用热更新代码。

LoadDll.Start函数后面添加反射调用代码,最终代码如下:

    void Start()
{
// Editor环境下,HotUpdate.dll.bytes已经被自动加载,不需要加载,重复加载反而会出问题。
#if !UNITY_EDITOR
Assembly hotUpdateAss = Assembly.Load(File.ReadAllBytes($"{Application.streamingAssetsPath}/HotUpdate.dll.bytes"));
#else
// Editor下无需加载,直接查找获得HotUpdate程序集
Assembly hotUpdateAss = System.AppDomain.CurrentDomain.GetAssemblies().First(a => a.GetName().Name == "HotUpdate");
#endif

Type type = hotUpdateAss.GetType("Hello");
type.GetMethod("Run").Invoke(null, null);
}

至此,完成整个热更新工程的创建工作!!!

Editor中试运行

运行main场景,屏幕上会显示 'Hello,HybridCLR',表示代码工作正常。

打包运行

  • 运行菜单 HybridCLR/Generate/All 进行必要的生成操作。这一步不可遗漏!!!
  • {proj}/HybridCLRData/HotUpdateDlls/StandaloneWindows64(MacOS下为StandaloneMacXxx)目录下的HotUpdate.dll复制到Assets/StreamingAssets/HotUpdate.dll.bytes注意,要加.bytes后缀!!!
  • 打开Build Settings对话框,点击Build And Run,打包并且运行热更新示例工程。

如果打包成功,并且屏幕上显示 'Hello,HybridCLR',表示热更新代码被顺利执行!

测试热更新

  • 修改Assets/HotUpdate/Hello.cs的Run函数中Debug.Log("Hello, HybridCLR");代码,改成Debug.Log("Hello, World");
  • 运行菜单命令HybridCLR/CompileDll/ActiveBulidTarget重新编译热更新代码。
  • {proj}/HybridCLRData/HotUpdateDlls/StandaloneWindows64(MacOS下为StandaloneMacXxx)目录下的HotUpdate.dll复制为刚才的打包输出目录的 XXX_Data/StreamingAssets/HotUpdate.dll.bytes
  • 重新运行程序,会发现屏幕中显示Hello, World,表示热更新代码生效了!

至此完成热更新体验!!!

- + \ No newline at end of file diff --git a/docs/business.html b/docs/business.html index dc3fb44f9..4e76c0af7 100644 --- a/docs/business.html +++ b/docs/business.html @@ -9,13 +9,13 @@ - +
跳到主要内容

商业化版本

- + \ No newline at end of file diff --git a/docs/business/accesspolicy.html b/docs/business/accesspolicy.html index 51ceb3821..1ea7b4421 100644 --- a/docs/business/accesspolicy.html +++ b/docs/business/accesspolicy.html @@ -9,7 +9,7 @@ - + @@ -22,7 +22,7 @@ 示例代码如下:

        [MenuItem("Test/ConvertXmlAccessPolicyToBinary")]
public static void ConvertXmlAccessPolicyToBinary()
{
string accessPolicyDir = Application.dataPath + "/AccessPolicy";
AccessPolicyUtil.ConvertXmlAccessPolicyToBinaryAccessPolicy($"{accessPolicyDir}/AccessPolicy.xml",
$"{accessPolicyDir}/AccessPolicy.bytes");
}

校验AccessPolicy配置合法性

实践中很容易错误地填写了assembly.fullnametype.fullname之类的名字,导致没有正确执行期望的访问控制策略。 HybridCLR.Editor.Security.AccessPolicyConfigValidator 用于检查 AccessPolicy的合法性,避免这种错误。

函数说明
ValidateRules检查Rule规则的合法性
ValidateTargets检查Target规则的合法性

示例代码如下:

    public static void ValidateAccessPolicy()
{
var reader = new XmlAccessPolicyReader();
reader.LoadXmlFile("Assets/AccessPolicy/AccessPolicy.xml");
List<string> hotUpdateDllNames = SettingsUtil.HotUpdateAssemblyNamesExcludePreserved;
var assemblyCache = new AssemblyCache(MetaUtil.CreateHotUpdateAndAOTAssemblyResolver(EditorUserBuildSettings.activeBuildTarget, hotUpdateDllNames));
var validator = new AccessPolicyConfigValidator(assemblyCache);

var accessPolicy = reader.GetAccessPolicy();
validator.ValidateRules(accessPolicy);
validator.ValidateTargets(accessPolicy, new List<string> { "Tests2" });
}

预校验程序集是否满足AccessPolicy

Assembly.Load加载程序集时不检查程序集中是否存在非法调用,运行过程中第一次调用某函数时才检查调用该函数是否符合AccessPolicy,这带来不便。 HybridCLR.Editor.Security.AssemblyValidator用于离线预校验assembly中所有调用是否符合AccessPolicy。

示例代码如下:

        public static void ValidateAssembly()
{
var reader = new XmlAccessPolicyReader();
reader.LoadXmlFile("Assets/AccessPolicy/AccessPolicy.xml");
var validator = new AssemblyValidator(reader.GetAccessPolicy());
string test2DllPath = $"{SettingsUtil.GetHotUpdateDllsOutputDirByTarget(EditorUserBuildSettings.activeBuildTarget)}/Tests2.dll";
var mod = ModuleDefMD.Load(test2DllPath);
validator.ValidateAssembly(mod);
}

运行时设置访问策略

RuntimeApi类中提供了LoadAccessPolicy函数用于设置访问策略。运行过程中可以多次调用该函数更新访问策略。

示例代码如下:


void LoadAccessPolicy()
{
byte[] accessPolicyData = File.ReadAllBytes($"{Application.streamingAssetsPath}/AccessPolicy.bin");
RuntimeApi.LoadAccessPolicy(accessPolicyData);
}

- + \ No newline at end of file diff --git a/docs/business/advancedencryption.html b/docs/business/advancedencryption.html index 54b33e4c1..7a17a01b1 100644 --- a/docs/business/advancedencryption.html +++ b/docs/business/advancedencryption.html @@ -9,14 +9,14 @@ - +
跳到主要内容

高级代码加固

高级代码加固使用自定义的程序集结构和自定义的指令,极大提升了App安全性。

原理

高级代码加固技术从以下几个方面提升了代码安全性:

  • 使用自定义的可随机化的程序集结构。程序集结构定义本身是可以随机化的,通过生成相应的专有代码 来解析相应结构,极大提高了破解难度
  • 对所有元数据结构进行自定义的转换,使得无法再被常规的IL反编译工具(如ILSpy)读取
  • 提前将IL指令不可逆地转换为自定义寄存器指令集,指令集本身也可以随机化

其他优势

  • 移除dll文件中不必要的字段,文件更小
  • 加载完元数据后可以释放dll文件所占内存,同时释放一些非延迟加载的元表所占内存,不必像原始dll那样保持整个dll文件内容在内存中,更节省内存
  • 由于已经离线提前转换为自定义寄存器指令集,指令翻译更快
  • 与高级指令优化技术配合,最大程度提升执行效率
- + \ No newline at end of file diff --git a/docs/business/advancedoptimization.html b/docs/business/advancedoptimization.html index 70eb270ce..a21e68379 100644 --- a/docs/business/advancedoptimization.html +++ b/docs/business/advancedoptimization.html @@ -9,7 +9,7 @@ - + @@ -17,7 +17,7 @@
跳到主要内容

离线指令优化

危险

离线指令优化技术正开发中,目前仅可使用标准指令优化技术。

离线指令优化(Offline Instruction Optimization,简称OIO)离线将原始IL指令转换为自定义的寄存器指令。 由于离线没有编译性能限制,可以使用更丰富的编译优化技术,极大提升了解释模块的性能。

优化后的指令执行性能整体提升100%-1000%(没看错,10倍以上)甚至更高,尤其是数值指令整体提升近300%。 而且由于已经提前转换,加载和指令翻译过程更快,卡顿更小。

离线指令优化技术支持代码加固方案中的虚拟化技术,极大提升了代码安全。

实现

离线指令优化技术包含了以下优化技术:

  • 彻底的无用栈指令消除。消除掉所有不必要的栈操作
  • 窥孔优化
  • 常量复制优化
  • 局部复制传播优化
  • 全局复制传播优化
  • 解释函数inline
  • AOT函数inline(专利技术)
  • 提供更多instinct指令,大幅提升常见的指令组合性能
  • 条件检查消除技术。消除不必要的空指针检查、类型强转检查、数组越界检查
  • CheckOnce运行时检查动态消除优化。例如访问静态成员变量的指令,在第2次执行时不再检查类型是否已经初始化过
  • 其他优化

性能

TODO。

- + \ No newline at end of file diff --git a/docs/business/basicencryption.html b/docs/business/basicencryption.html index 418d03107..fbc9397b7 100644 --- a/docs/business/basicencryption.html +++ b/docs/business/basicencryption.html @@ -9,7 +9,7 @@ - + @@ -19,7 +19,7 @@ 这些信息必须通过对元数据本身的混淆,即从原始级别就彻底丢失了原始明文信息来增加安全性。

因为指令混淆会对性能有明显伤害,所以只提供类型、字段、函数之类的纯信息类型的混淆。我们主要通过虚拟化技术来保护代码安全。

元数据加密

技术结构虚拟化加密虚拟化延迟解密安全指数
自定义dll文件结构
~string流加密⭐⭐
~blob流加密⭐⭐
~US流加密⭐⭐⭐
~table流加密⭐⭐⭐⭐
method body数据加密⭐⭐⭐

自定义dll文件结构

原始dll文件为PE格式,我们改为自定义文件结构,无法使用ILSpy等反编译工具打开。

支持结构虚拟化技术,也就是每个版本的dll结构都可以完全不一样,显著增加了破解成本。

~string流加密

~string流保存了元数据内部使用的字符串,如类型名、字段名之类。对~string流数据加密使得无法从dll文件中直接获得元数据字符串。

支持加密虚拟化技术,显著增加了离线破解难度。

~blob流加密

~blob流保存了一些复杂元数据(如类型签名)。对~blob流加密使得无法从dll文件中直接获得原始lob数据。

支持加密虚拟化技术,显著增加了离线破解成本。

~US流加密

~US流中保存了用户字符串(即代码中使用的字符串)元数据。

支持加密虚拟化技术,阻止了破解者从dll文件中直接获得原始~US元数据。

支持延迟解密,阻止破解者使用内存dump技术直接还原出所有数据。

~table流加密

~table流保存了大多数结构化的元数据。

支持结构虚拟化技术,每个版本都使用不同的元数据数据结构,大幅增加了破解成本,即使被破解也无法通过简单的数据移动或者复制还原为原始的~table流结构。

支持加密虚拟化,显著提升了破解成本。

支持延迟解密,阻止破解者使用内存dump技术直接还原出所有数据。

method body数据加密

method body中保存了函数体元数据信息。

支持加密虚拟化,显著提升了破解成本。

支持延迟解密,阻止破解者使用内存dump技术直接还原出所有数据。

结构虚拟化技术

结构虚拟化技术指使用元数据结构完全随机,使用专用结构虚拟机来解析元数据的结构,并且在不重新构建App的情况下可以动态调整,使得破解者需要每个版本都重新破解,极大提升了破解者的成本。

加密虚拟化技术

加密虚拟化技术指使用加密方式完全随机,使用专用加密虚拟机来加密元数据,并且在不重新构建App的情况下可以动态调整,使得破解者需要每个版本都重新破解,极大提升了破解者的成本。

延迟解密技术

延迟解密技术指第一次才解密数据,有效防止破解者hook关键路径,直接内存dump出完整的原始数据。

指令虚拟化技术

指令虚拟化技术指将原始IL指令转换为自定义的寄存器虚拟机指令,有效阻止破解者使用现成的反编译工具分析出原始代码。

指令虚拟化技术支持随机化指令结构,每次重新构建App(为了提升解码指令的性能,不像结构虚拟化和加密虚拟化那样支持动态调整)时都使用全新的指令集(指令号和指令长度都完全不同),极大增加了破解者的成本。

配置

HybridCLR Settings中Encryption字段配置了加固相关参数。

参数名加密dll时需要与主包一致描述
encryptGlobalMetdataDat加密global-metadata.dat文件
vmSeed加密虚拟机的随机化种子
metadataSeed元数据的随机化加密种子
key加解密时所用的加密参数
stringEncCodeLength~string流的加密指令长度
blobEncCodeLength~blob流的加密指令长度
userStringEncCodeLength~US流的加密指令长度
tableEncCodeLength~table流的加密指令长度
lazyUserStringEncCodeLength~US流的延迟加密指令长度
methdBodyEncCodeLength~函数体的延迟加密指令长度

vmSeed是加密虚拟机的随机化种子。这个随机会种子会影响生成的加密虚拟机的代码,并且编译到主包的原生代码中。因此生成加密dll时,要确保vmSeed与主包打包时所用的vmSeed一致。 推荐每次发布新主包时修改此参数。

metadtaSeed和key为均为动态参数,不需要与主包一致。每次加密热更新dll都可以修改此值。推荐每经过一段时间或者经过几个版本后修改这些值。

xxEncCodeLength为加密指令的长度,值越大则加密越复杂,解密耗时与加密指令长度成正比关系。由于解密过程会带来一定的开销,建议取默认值即可。如果加载加密的热更新程序集的时间过长,可以适当减少这些值。

加密热更新dll

提供了 HybridCLR.Editor.Encryption.EncryptUtil类对dll进行加密。示例代码如下:

    public static void EncryptDll(string originalDll, string encryptedDll)
{
HybridCLR.Editor.Encryption.EncryptionUtil.EncryptDll(originalDll, encryptedDll, SettingsUtil.EncryptionSettings);
}

对于旗舰版本用户,由于默认的dhao文件记录了加密前的currentDll的MD5值,因此如果对dll进行加密,需要同步更新dhao文件,否则Runtime.LoadDifferentialHybridAssembly会运行失败。 为了方便使用,我们单独提供HybridCLR.Editor.DHE.BuildUtil.EncryptDllAndGenerateDHAODatas函数用于一次性完成加密和生成dhao文件的工作。

运行时加载热更新dll

与普通热更新dll没有任何区别,使用Assembly.Load即可。

补充元数据dll也可以加密,加载方式与未加密时相同。

- + \ No newline at end of file diff --git a/docs/business/basicoptimization.html b/docs/business/basicoptimization.html index f4ac4a974..2128cd4ab 100644 --- a/docs/business/basicoptimization.html +++ b/docs/business/basicoptimization.html @@ -9,13 +9,13 @@ - +
跳到主要内容

标准解释优化

提示

标准解释优化技术仅在商业化版本上可用。

标准解释优化使用多种技术大幅提升了解释执行的性能,基础指令(如变量访问、数值计算)受益极大。

以数值计算指令为例,使用标准解释优化技术后性能有了质的飞跃,是原来的280%-735%!像一些特殊代码如typeof指令的性能,提升了1000%以上。

实现

标准解释优化使用了以下技术提升了解释性能。

  • 指令分发优化
  • 指令合并
  • 无用指令消除
  • 特殊instinct指令

性能报告

以下是OnePlus 9R ArmV8 实机测试报告,测试代码附录最后。

AOT耗时 vs 商业化版本耗时 vs 社区版本耗时 (越小越好)

data

商业化版本耗时/AOT耗时 vs 社区版本耗时/AOT耗时 (越小越好)

AOT版本性能是社区版本的4.1 - 90倍,是商业化版本的1.30 - 12.9倍。

data

商业化版本性能/社区版本性能 (越大越好)

商业化版本性能是社区版本的2.87-7.35倍。

data

商业化版本耗时/AOT版本耗时 (越小越好)

AOT版本性能是是商业化版本的1.30 - 12.9倍。

data

开启和关闭标准指令优化

默认已经开启标准优化,此设置为全局设置,对所有程序集包括补充元数据程序集都生效。

可以通过 RuntimeApi.EnableTransformOptimization函数主动开启或者关闭这个特性。


/// 禁用标准指令优化
void DisableCodeOptimization()
{
RuntimeApi.EnbleTransformOptimization(false);
}
- + \ No newline at end of file diff --git a/docs/business/businesscase.html b/docs/business/businesscase.html index c8073d3eb..9d4c45840 100644 --- a/docs/business/businesscase.html +++ b/docs/business/businesscase.html @@ -9,13 +9,13 @@ - +
跳到主要内容

商业项目案例

我们已经与业内许多公司进行高级合作,很好地解决了他们的问题。出于商业保密原因,我们只罗列了极有限的愿意公开信息的商业合作伙伴信息。

项目游戏公司介绍旗舰版专业版热重载版
奇葩战斗家雷霆游戏iOS 11w评论,taptap 上有169万关注和357万下载。
- + \ No newline at end of file diff --git a/docs/business/differentialhybridexecution.html b/docs/business/differentialhybridexecution.html index e979a923e..d4ce61141 100644 --- a/docs/business/differentialhybridexecution.html +++ b/docs/business/differentialhybridexecution.html @@ -9,7 +9,7 @@ - + @@ -18,7 +18,7 @@ 由于实践中两个版本往往不会修改太多代码,DHE基本上能接近原生的性能水平。

特性与优势

  • 未变化部分代码性能与原生完全相同,相较纯解释版本提升惊人的3-30倍甚至更高,整体几乎达到原生性能水平。
  • 可以任意变更代码,对代码基本无入侵,几乎没有特殊注意事项,使用方式跟社区版本近似。
  • 工作流简单,不需要像xxxfix之类的方案那样自己标注哪些函数发生变化,由工具全部自动处理
  • 对项目的改造成本比纯热更新版本更低。例如可以直接在DHE中定义extern函数,而不需要移到AOT模块。
  • 原生代码已全部在包体中,被iOS拒审的风险大幅降低

未支持特性

  • 加载DHE热更新代码前不能执行DHE对应的AOT assembly中的任何代码。意味着DHE不支持像mscorlib这种基础库的差分混合,但支持传统热更新assembly的差分热更新
  • 由于第一条的限制,不支持在DHE程序集中使用[InitializeOnLoadMethod]Script Execution Order settings
  • 不支持DHE脚本挂载在随包资源中,包括Resources
  • 不能在DHE程序集中通过热更新新增extern函数

dhao文件

dhao文件是DHE技术的核心概念。dhao文件中包含了离线计算好的最新的热更新dll中变化的类型和函数的信息,运行时直接根据dhao文件中信息决定执行某个热更新函数时,应该使用最新的解释版本还是直接调用原始的AOT函数。 离线计算好的dhao文件对于DHE技术极为关键,如果没有dhao文件,需要额外携带原始AOT dll,并且计算函数变化的代价极其高昂。

通过对比最新的热更新dll与打包时生成的AOT dll,离线计算出变化的类型与函数,保存成dhao文件。因此DHE机制要正常工作,必须依赖于dhao文件的正确性,而dhao文件的正确性 则依赖精确提供最新的热更新dll和打包时生成的AOT dll。

- + \ No newline at end of file diff --git a/docs/business/fullgenericsharing.html b/docs/business/fullgenericsharing.html index 43532ef4d..c77d32bde 100644 --- a/docs/business/fullgenericsharing.html +++ b/docs/business/fullgenericsharing.html @@ -9,7 +9,7 @@ - + @@ -18,7 +18,7 @@ 加载补充元数据不仅导致内存占用明显增加,还增加了启动时间。对于微信小游戏这些对包体和内存要求严苛的场合,这是一个影响较大的问题。 另外,被补充的泛型函数以解释方式执行,还降低了运行性能。

HybridCLR支持full genric sharing后,不再需要补充元数据,简化了工作流,较好地解决了补充元数据的以上缺点。

支持的版本

支持 Unity 2021及更高的LTS版本。

原理

旧的泛型共享技术只能对class类型进行泛型共享。自2021.3.x LTS版本起,il2cpp已经支持完全泛型共享(full generic sharing)技术, 即泛型参数无论是任何类型(包含值类型)都可以共享。HybridCLR利用这个机制实现了不需要补充元数据,也可以完美支持AOT泛型。

设置

危险

faster (smaller build)会对泛型函数性能有较大影响(15%甚至更高),因此建议不要开启这个选项。

如果是2021版本并且没有内存压力的情况下,建议仍然使用补充元技术来解决泛型问题。

  • 2020版本不支持完全泛型共享
  • 2021版本需要设置 IL2CPP Code Generation选项为faster(smaller build)
  • 2022版本默认开启完全泛型共享,无法关闭。如果设置 IL2CPP Code Generation选项为faster(smaller build)则能进一步减少包体。
- + \ No newline at end of file diff --git a/docs/business/intro.html b/docs/business/intro.html index f3eba0311..ae72abd09 100644 --- a/docs/business/intro.html +++ b/docs/business/intro.html @@ -9,14 +9,14 @@ - +
跳到主要内容

介绍

我们提供多种高端商业版本及可灵活定制的技术服务,满足游戏项目在各种应用场景下的需求。

商业化版本

提示

所有商业化版本都会长期支持Unity 2019-2022 LTS版本,确保项目有长久稳定的技术支持。

目前有三个商业化版本,具体特性对比如下:

  • 专业版。显著提升了解释执行性能(数值指令性能是社区版本的280%-735%)、优化了元数据内存,支持代码加密,有效保障了代码安全
  • 旗舰版。包含专业版的所有功能,另外包含了我们最核心的DHE技术,极大提升了性能,几乎(未改动时为100%)达到同等的原生AOT水平
  • 热重载版。包含专业版的所有功能,同时支持卸载和重新加载单独的assembly,当前可以卸载掉程序集100%的元数据内存
特性社区版专业版旗舰版热重载版
解释执行
MonoBehaviour
补充元数据
增量式GC
Unity 2019-2022 LTS
DOTS
完全泛型共享
元数据优化
标准解释性能优化
离线指令优化
global-metadata.dat加密
代码加固
热重载
访问控制机制
DHE技术
技术支持1年2年2年

价格标准

版本价格(人民币)描述
社区版0无限期使用
专业版邮件咨询商务买断一个项目的使用权,同时包含1年技术支持,提供1年代码更新
热重载版邮件咨询商务买断一个项目的使用权,同时包含2年技术支持,提供2年代码更新
旗舰版邮件咨询商务买断一个项目的使用权,同时包含2年技术支持,提供2年代码更新

试用

提示

无论免费还是付费试用都只面向企业用户,请使用企业邮箱联系 business@code-philosophy.com 获得试用相关支持。

所有商业化版本都支持以下试用方式:

试用方式价格服务描述
不含完整源码的免费试用免费如需技术支持,需要按照400元/小时价格付费只支持基于预编译的libil2cpp.a构建iOS平台目标、不提供c++源码、不提供工具源码。没有试用期限限制,但试用版本的运行时会在启动半小时或者使用三个月后随机崩溃,请不要用于正式发布的版本
含完整源码的付费试用商业化版本总价的10%与正式付费用户等同的技术支持服务提供商业化版本的试用版本的源码及相关工具,由开发者自行构建及测试。如果最终购买正式版本则可以抵扣服务费用,否则除非有虚假宣传或者重大缺陷,不退还试用费用

注意,免费试用版本均不支持以下特性:

  • dots支持
  • 增量式GC

免费试用版本相关文档:

企业技术支持

可以灵活选择企业所需要的技术服务项目,如果按年订阅则根据服务项计费,否则根据服务时长计费。

技术支持内容

  • Bug标准响应及解决,包含一对一远程协助指导,大多数可复现bug会在2-7天内修复或者提供规避方案
  • 解决一些特殊的平台兼容性问题
  • 支持一些当前未支持的版本(不含2018及更早的版本)
  • 优化指导
  • 其他服务

价格标准

由于hybridclr使用简单、运行稳定,大多数公司并不需要长时间的技术支持,因此只提供计时技术支持服务。 单次服务中未使用完的时间可以保留下次使用。为了节约商务成本,总价1500及以内的按时计费不提供合同与发票,敬请谅解。

服务级别解决问题范围价格
标准提供基础使用问题的技术答疑,不含bug及未实现特性的解决100人民币/小时,以15分钟为计费间隔(如15分钟、2小时)
专家解决复杂问题,包含解决bug1500人民币/小时,以半小时为计费间隔(如半小时、2小时)

联系我们

请使用贵公司的公司邮箱向邮箱business@code-philosophy.com发起咨询,以QQ或者126邮箱之类个人发起的邮件会被忽略,敬请谅解。

- + \ No newline at end of file diff --git a/docs/business/metadataoptimization.html b/docs/business/metadataoptimization.html index 96fede92a..81a2ce8c4 100644 --- a/docs/business/metadataoptimization.html +++ b/docs/business/metadataoptimization.html @@ -9,7 +9,7 @@ - + @@ -20,7 +20,7 @@ 商业化版本开启完全泛型共享后由于不需要补充元数据,此项为0。商业化版本相比社区降低了67%(开启完全泛型共享时为100%)的内存。

详细数据

aot-metadata-data

消耗内存:

aot-metadata-memory

消耗内存/dll大小

aot-metadata-dll-rate

优化热更新程序集内存

我们测试了常见的插件以解释模式加载后消耗的内存。社区版本消耗的内存大约dll大小的4.7倍,商业化版本为2.9倍。商业化版本相比社区降低了39%的内存。

提示

此数据未包含运行时延迟数据化的 Il2CppClass、MethodInfo及翻译后指令占据的内存,此部分延迟初始化的内存大约为2.9-3.5倍dll大小。最终消耗的元数据内存,社区版本为7.6-8.2倍,商业化版本为5.8-6.4倍。 商业化版本相比社区版本降低了大约25%内存。

详细数据

aot-metadata-data

消耗内存:

aot-metadata-memory

消耗内存/dll大小

aot-metadata-dll-rate

- + \ No newline at end of file diff --git a/docs/business/pro/commonerrors.html b/docs/business/pro/commonerrors.html index ba46b7201..207cff14e 100644 --- a/docs/business/pro/commonerrors.html +++ b/docs/business/pro/commonerrors.html @@ -9,13 +9,13 @@ - + - + \ No newline at end of file diff --git a/docs/business/pro/freetrial.html b/docs/business/pro/freetrial.html index 7819a8d0a..0a4249d47 100644 --- a/docs/business/pro/freetrial.html +++ b/docs/business/pro/freetrial.html @@ -9,14 +9,14 @@ - +
跳到主要内容

免费试用

相比于付费试用版本和正式购买后的版本都包含HybridCLR所有运行时及Editor源码,免费试用版本只提供预编译的 二进制lib及工具,只支持构建iOS平台目标。

试用规则

  • 只面向企业用户
  • 试用免费,没有试用期限,但试用过程中如需技术支持,需要额外付费
  • 试用版本会在App启动半小时后随机崩溃,请不要用于正式发布的版本

支持的版本

只支持以下特定的Unity版本。邻近的小版本如果在构建时没有出现编译错误则也可使用

  • 2019.4.40f1 待支持
  • 2020.3.48f1 待支持
  • 2021.3.31f1
  • 2022.3.11f1

不支持的特性

提示

在付费试用或者正式付费版本中均支持以下特性

  • dots
  • 增量式GC
  • Profiler、Script Debugging等development Build选项

不支持增量式GC及各种编译选项是因为每种编译参数都需要单独的libil2cpp.a,大大提高了维护成本。

与标准商业版本的区别

试用版本中移除了部分模块的源码,改为调用编译好的二进制程序。以下是相关模块。

  • DllEncryptor dll加密模块

安装

相关libil2cpp代码已经直接包含到Package中,打开HybridCLR/Installer菜单,点击Install按钮即可完成安装。

设置

相比于社区版本或者正式的商业化版本,需要修改以下设置:

  • 关闭增量式GC
  • 关闭development选项
  • 不可修改加密参数vmSeed的值,必须保持为0。其他加密参数可修改

使用

除了安装与设置如上面文档说明有少许调整外,剩余完全参照快速上手即可。

- + \ No newline at end of file diff --git a/docs/business/pro/intro.html b/docs/business/pro/intro.html index 682a5e31a..453de39dd 100644 --- a/docs/business/pro/intro.html +++ b/docs/business/pro/intro.html @@ -9,13 +9,13 @@ - +
跳到主要内容

介绍

专业版提供了一些社区版本所不支持的高级特性,适合对内存和包体要求较高或者有一定性能压力的场合,如WebGL游戏。

优势

  • 支持完全泛型共享技术,不再需要补充元数据了
  • 包含元数据优化技术,显著优化了元数据内存
  • 包含 标准解释优化,极大提升了解释性能。以数值计算指令为例,使用标准解释优化技术后性能有了质的飞跃,是原来的280%-735%!像一些特殊代码如typeof指令的性能,提升了1000%以上。
  • 支持代码加密,更安全
  • 包含一年技术支持

支持的版本

支持所有 Unity 2019-2022 LTS版本。

- + \ No newline at end of file diff --git a/docs/business/pro/quickstart.html b/docs/business/pro/quickstart.html index b47f2d7a9..e1ba42f18 100644 --- a/docs/business/pro/quickstart.html +++ b/docs/business/pro/quickstart.html @@ -9,13 +9,13 @@ - +
跳到主要内容

快速上手

与社区版本的快速上手几乎相同,本文档只介绍不同之处。

安装

  • 将hybridclr_unity解压后,放到项目Packages目录下,改名为com.code-philosophy.hybridclr
  • 根据你的unity版本解压对应的il2cpp_plus-{version}.zip
  • 解压 hybridclr.zip
  • hybridclr.zip解压后的hybridclr目录放到il2cpp-{version}.zip解压后的libil2cpp目录下
  • 打开 HybridCLR/Installer,开启从本地复制libil2cpp选项,选中刚才解压的libil2cpp目录,进行安装

installer

使用

- + \ No newline at end of file diff --git a/docs/business/reload/commonerrors.html b/docs/business/reload/commonerrors.html index 8423f87e1..ce9e0533b 100644 --- a/docs/business/reload/commonerrors.html +++ b/docs/business/reload/commonerrors.html @@ -9,14 +9,14 @@ - +
跳到主要内容

常见问题

Json序列化的问题

程序集卸载后,会卸载所有类型元数据。而几乎所有常用的序列化库都会缓存类型的反射信息,这意味着如果你在代码中使用了Unity的JsonUtility或者LitJson之类的 -序列库,它们会错误地缓存反射信息,导致你第二次(或者第三次)重新加载,并且反序列化时,会出错。

解决办法有几种:

  • 给被序列化或者反序列化的类型加上[Serializable]特性,如果这些类型中成员字段的类型也是class类型A或者List&lt;A&gt;,则也要给A也加上[Serializable]
  • 修改这些反序列化库的代码,在卸载程序集后,清空它们的反射缓存。像Unity的JsonUtility是native实现,无法清空缓存,只能更换为其他Json库。
- +序列库,它们会错误地缓存反射信息,导致你第二次(或者第三次)重新加载,并且反序列化时,会出错。

解决办法有几种:

  • 给被序列化或者反序列化的类型加上[Serializable]特性,如果这些类型中成员字段的类型也是class类型A或者A[],则也要给A也加上[Serializable]
  • 修改这些反序列化库的代码,在卸载程序集后,清空它们的反射缓存。像Unity的JsonUtility是native实现,无法清空缓存,只能更换为其他Json库。
+ \ No newline at end of file diff --git a/docs/business/reload/freetrial.html b/docs/business/reload/freetrial.html index 84d87df41..a90e34123 100644 --- a/docs/business/reload/freetrial.html +++ b/docs/business/reload/freetrial.html @@ -9,14 +9,14 @@ - +
跳到主要内容

免费试用

相比于付费试用版本和正式购买后的版本都包含HybridCLR所有运行时及Editor源码,免费试用版本只提供预编译的 二进制lib及工具,只支持构建iOS平台目标。

试用规则

  • 只面向企业用户
  • 试用免费,没有试用期限,但试用过程中如需技术支持,需要额外付费
  • 试用版本会在App启动半小时后随机崩溃,请不要用于正式发布的版本

支持的版本

只支持以下特定的Unity版本。邻近的小版本如果在构建时没有出现编译错误则也可使用

  • 2019.4.40f1 待支持
  • 2020.3.48f1 待支持
  • 2021.3.31f1
  • 2022.3.11f1

不支持的特性

提示

在付费试用或者正式付费版本中均支持以下特性

  • dots
  • 增量式GC
  • Profiler、Script Debugging等development Build选项

不支持增量式GC及各种编译选项是因为每种编译参数都需要单独的libil2cpp.a,大大提高了维护成本。

与标准商业版本的区别

试用版本中移除了部分模块的源码,改为调用编译好的二进制程序。以下是相关模块。

  • DllEncryptor dll加密模块

安装

相关libil2cpp代码已经直接包含到Package中,打开HybridCLR/Installer菜单,点击Install按钮即可完成安装。

设置

相比于社区版本或者正式的商业化版本,需要修改以下设置:

  • 关闭增量式GC
  • 关闭development选项
  • 不可修改加密参数vmSeed的值,必须保持为0。其他加密参数可修改

使用

除了安装与设置如上面文档说明有少许调整外,剩余完全参照快速上手即可。

- + \ No newline at end of file diff --git a/docs/business/reload/hotreloadassembly.html b/docs/business/reload/hotreloadassembly.html index 2f663ed07..e5ead1cec 100644 --- a/docs/business/reload/hotreloadassembly.html +++ b/docs/business/reload/hotreloadassembly.html @@ -9,13 +9,13 @@ - +
-
跳到主要内容

热重载技术

热重载技术用于完全卸载或者重新加载一个assembly,适用于小游戏合集类型的游戏。该方案只提供商业化版本

支持的特性

  • 支持卸载assembly,卸载100%的assembly所占用的内存
  • 支持重新加载assembly,代码可以任意变化甚至完全不同(MonoBehaviour和Scriptable有一定的限制)
  • 支持限定热更新assembly中能访问的函数的集合,适合UGC游戏中创建沙盒环境,避免恶意玩家代码造成破坏。

不支持特性及特殊要求

  • 要求业务代码不会再使用被卸载的Assembly中的对象或者函数,并且退出所有在执行的旧逻辑
  • 不能直接卸载被依赖的Assembly,必须按照逆依赖顺序先卸载依赖者,再卸载被依赖者。例如A.dll依赖B.dll,则需要先卸载A.dll,再卸载B.dll
  • MonoBehaviour和ScriptableObject相关
    • 要求重载的MonoBehaviour中的事件或消息函数如Awake、OnEable之类不发生增删(但函数体可以变化)
    • 要求重载后在旧Assembly中存在同名脚本类的序列化字段名不发生变化(类型可以变)
    • 如果字段类型为可卸载程序集中的自定义类型A(class或struct或enum),必须给它加上[Serializable]特性
    • 不支持字段类型为List&lt;A&gt;其中A为可卸载的程序集中的类型,请替换为A[]
    • 不能继承泛型类型,例如class MyScript : CommonScript<int>
  • 一些会缓存反射信息的库(这种行为在序列化相关的库中最为普遍,如LitJson),在热重载后需要清理掉缓存的反射信息
  • 不支持析构函数,~XXX()。也不允许实例化泛型参数带本程序集类型的带析构函数的泛型类
  • 与dots不兼容。由于dots大量缓存的类型信息,实现复杂,很难单独清理掉缓存信息。

一些不兼容的库

  • 2022的Jobs会缓存类型相关信息,需要自行小幅修改UnityEngine.CoreModule.dll的代码。 低于2022的版本不需要修改
  • LitJson之类的反序列化库会缓存反射信息,需要在热重载后清理掉库中缓存的反射信息,具体操作跟库的实现相关

解决被卸载对象的引用问题

热重载技术要求在未卸载的程序集或者全局内存中不能持有已卸载的程序集U的元数据。包括但不限于:

  • 被卸载程序集的类型的实例
  • 泛型类或者函数的泛型参数中包含被卸载程序集的类型
  • 被卸载程序集相关的反射信息,如Assembly、Type、MethodInfo、PropertyInfo等等
  • 指向被卸载程序集中某函数的delegate
  • 被卸载程序集中定义的异步Task
  • 其他

实际工程可能很复杂,开发者找出所有非法引用是很困难和不切实际的。我们已经实现了非法引用检查,卸载过程中会打印出所有非法引用的日志。开发者根据打印的日志清除所有非法引用即可。

- +
跳到主要内容

热重载技术

热重载技术用于完全卸载或者重新加载一个assembly,适用于小游戏合集类型的游戏。该方案只提供商业化版本

支持的特性

  • 支持卸载assembly,卸载100%的assembly所占用的内存
  • 支持重新加载assembly,代码可以任意变化甚至完全不同(MonoBehaviour和Scriptable有一定的限制)
  • 支持限定热更新assembly中能访问的函数的集合,适合UGC游戏中创建沙盒环境,避免恶意玩家代码造成破坏。

不支持特性及特殊要求

  • 要求业务代码不会再使用被卸载的Assembly中的对象或者函数,并且退出所有在执行的旧逻辑
  • 不能直接卸载被依赖的Assembly,必须按照逆依赖顺序先卸载依赖者,再卸载被依赖者。例如A.dll依赖B.dll,则需要先卸载A.dll,再卸载B.dll
  • MonoBehaviour和ScriptableObject相关
    • 要求重载的MonoBehaviour中的事件或消息函数如Awake、OnEable之类不发生增删(但函数体可以变化)
    • 要求重载后在旧Assembly中存在同名脚本类的序列化字段名不发生变化(类型可以变)
    • 如果字段类型为可卸载程序集中的自定义类型A(class或struct或enum),必须给它加上[Serializable]特性
    • 不支持字段类型为List<A>其中A为可卸载的程序集中的类型,请替换为A[]
    • 不能继承泛型类型,例如class MyScript : CommonScript<int>
  • 一些会缓存反射信息的库(这种行为在序列化相关的库中最为普遍,如LitJson),在热重载后需要清理掉缓存的反射信息
  • 不支持析构函数,~XXX()。也不允许实例化泛型参数带本程序集类型的带析构函数的泛型类
  • 与dots不兼容。由于dots大量缓存的类型信息,实现复杂,很难单独清理掉缓存信息。

一些不兼容的库

  • 2022的Jobs会缓存类型相关信息,需要自行小幅修改UnityEngine.CoreModule.dll的代码。 低于2022的版本不需要修改
  • LitJson之类的反序列化库会缓存反射信息,需要在热重载后清理掉库中缓存的反射信息,具体操作跟库的实现相关

解决被卸载对象的引用问题

热重载技术要求在未卸载的程序集或者全局内存中不能持有已卸载的程序集U的元数据。包括但不限于:

  • 被卸载程序集的类型的实例
  • 泛型类或者函数的泛型参数中包含被卸载程序集的类型
  • 被卸载程序集相关的反射信息,如Assembly、Type、MethodInfo、PropertyInfo等等
  • 指向被卸载程序集中某函数的delegate
  • 被卸载程序集中定义的异步Task
  • 其他

实际工程可能很复杂,开发者找出所有非法引用是很困难和不切实际的。我们已经实现了非法引用检查,卸载过程中会打印出所有非法引用的日志。开发者根据打印的日志清除所有非法引用即可。

+ \ No newline at end of file diff --git a/docs/business/reload/intro.html b/docs/business/reload/intro.html index d65b4b218..09f7ec8b0 100644 --- a/docs/business/reload/intro.html +++ b/docs/business/reload/intro.html @@ -9,13 +9,13 @@ - +
跳到主要内容

介绍

热重载特别版提供独创的热重载技术的支持。可以运行中完全卸载或者重新加载一个assembly,尤其适用于小游戏合集类型的游戏。

优势

  • 包含专业版的所有功能
  • 支持热重载技术
  • 支持访问控制机制限定热更新assembly中能访问的函数的集合。适合UGC游戏中创建沙盒环境,避免恶意玩家代码造成破坏。
  • 包含两年技术支持

支持的版本

支持所有 Unity 2019-2022 LTS版本。

- + \ No newline at end of file diff --git a/docs/business/reload/modifydll.html b/docs/business/reload/modifydll.html index 22939cf57..fa3a87c1f 100644 --- a/docs/business/reload/modifydll.html +++ b/docs/business/reload/modifydll.html @@ -9,7 +9,7 @@ - + @@ -17,7 +17,7 @@
跳到主要内容

修改UnityEngine dll

由于有些版本的dll与热重载并不兼容,需要小幅修改代码。

使用dnspy工具

我们使用 dnspy 来修改 dll文件。而dnspy只能在Win下运行,故哪怕是mac版本dll, 你也得先将相应dll复制到Win下后再修改。下载 dnspy,选择 Win64版本

修改dll的操作大致如下:

  • dnspy中清空左侧所有dll
  • 打开dll
  • 找到你要修改的函数 ToModifiedType.ToModifiedMethod 函数, 右键菜单 -> 编辑方法(c#)...,弹出源码编辑界面。
  • 如果编辑器提示缺少某些dll引用,点击源码编辑窗口左下角类似文件夹的按钮,进行添加。
  • 修改代码
  • 点击右下角的 编译 按钮,如果成功,则无任何提示,退出编辑界面,返回反编译查看模式。如果失败,请自行处理编译错误。有时候dnspy会有莫名其妙的引用错误,退出源码编辑模式,重新右键编辑方法,再次进入就能解决。
  • 菜单 文件 -> 保存模块 保存修改后的dll文件。如果在Win或Mac下,有可能会遇到权限问题,请酌情处理(比如先保存到其他位置,再手动覆盖)

修改 UnityEngine.CoreModule.dll

警告

只有 Unity 2022+版本才需要修改。

Unity对于每个BuildTarget提供了单独一套UnityEngine dll,它们位置在 {editor_install_dir}/Editor/Data/PlaybackEngines/{platform}/Variations/il2cpp(iOS平台为iOSSupport\Variations\il2cpp\Releasearm64_managed)目录下, 请根据自己需要打包的平台,替换每个平台下的相关dll。

由于UnityEngine.CoreModule.dll引用了NetStandard 2.1,编译前需要先将Editor\Data\NetStandard\ref\2.1.0\netstandard.dll拉入 dnspy左侧程序集资源管理器中。

原始代码:

namespace Unity.Collections.LowLevel.Unsafe
{
// Token: 0x020000A6 RID: 166
internal static partial class BurstRuntime
{
// Token: 0x020000A7 RID: 167
private partial struct HashCode64<T>
{
// Token: 0x06000348 RID: 840 RVA: 0x000063F5 File Offset: 0x000045F5
// Note: this type is marked as 'beforefieldinit'.
static HashCode64()
{
BurstRuntime.HashCode64<T>.Value = BurstRuntime.HashStringWithFNV1A64(typeof(T).AssemblyQualifiedName);
}
}
}
}

修改后的代码:

using System;

namespace Unity.Collections.LowLevel.Unsafe
{
// Token: 0x020000A6 RID: 166
internal static partial class BurstRuntime
{
// Token: 0x020000A7 RID: 167
private partial struct HashCode64<T>
{
// Token: 0x06000348 RID: 840 RVA: 0x000063F5 File Offset: 0x000045F5
// Note: this type is marked as 'beforefieldinit'.
static HashCode64()
{
BurstRuntime.HashCode64<T>.Value = BurstRuntime.HashStringWithFNV1A64(typeof(T).AssemblyQualifiedName + ":" + typeof(T).GetHashCode().ToString());
}
}
}
}
- + \ No newline at end of file diff --git a/docs/business/reload/quickstart.html b/docs/business/reload/quickstart.html index 0df30ae7f..89810ed0e 100644 --- a/docs/business/reload/quickstart.html +++ b/docs/business/reload/quickstart.html @@ -9,13 +9,13 @@ - +
跳到主要内容

快速上手

与社区版本的快速上手几乎完全相同,本文档只介绍不同之处。

安装

  • 将hybridclr_unity解压后,放到项目Packages目录下,改名为com.code-philosophy.hybridclr
  • 根据你的unity版本解压对应的il2cpp_plus-{version}.zip
  • 解压 hybridclr.zip
  • hybridclr.zip解压后的hybridclr目录放到il2cpp-{version}.zip解压后的libil2cpp目录下
  • 打开 HybridCLR/Installer,开启从本地复制libil2cpp选项,选中刚才解压的libil2cpp目录,进行安装

installer

代码中使用

调用 RuntimeApi.TryUnloadAssembly或者RuntimeApi.ForceUnloadAssembly 卸载程序集,使用Assembly.Load重新加载程序集。必须成功卸载程序集后才能再次加载该程序集。

当前有两种卸载工作流:

  • TryUnloadAssembly
  • ForceUnloadAssembly

TryUnloadAssembly

尝试卸载,如果AppDomain中存在对被卸载程序集中对象的引用,则保持现状,返回失败,否则返回成功。

示例代码如下:


// 第一次加载
Assembly ass = Assembly.Load(yyy);

// 执行一些代码
Type mainType = ass.GetType("Entry");
mainType.GetMethod("Main").Invoke(null, null);

// 第一次卸载
// printObjectReferenceLink参数为true表示当卸载失败时,会打印出详细的非法对象的引用链日志,方便开发者定位出哪儿保持了非法引用。
// 建议只在开发期为true,正式上线后改为false
if (!RuntimeApi.TryUnloadAssembly(ass, true))
{
throw new Exception("unload fail");
}

// 第二次加载
Assembly newAss = Assembly.Load(yyy);

// 执行一些代码
Type mainType = ass.GetType("Entry");
mainType.GetMethod("Main").Invoke(null, null);

// 第二次卸载
if (!RuntimeApi.TryUnloadAssembly(ass, true))
{
throw new Exception("unload fail");
}

ForceUnloadAssembly

强制卸载,即使AppDomain中存在对被卸载程序集中对象的引用。返回true表示没有问题,返回false表示卸载过程中检查出非法引用。如果返回false,在运行一段时间后有可能会崩溃。此操作慎用,建议与官方技术支持详细沟通。


// 第一次加载
Assembly ass = Assembly.Load(yyy);

// 执行一些代码
Type mainType = ass.GetType("Entry");
mainType.GetMethod("Main").Invoke(null, null);

// 第一次卸载
// ignoreObjectReferenceValidation参数为true表示卸载过程中不检查非法对象引用,可以缩短卸载时间。但建议无论开发期还是正式发布都取false
// printObjectReferenceLink参数为true表示当卸载失败时,会打印出详细的非法对象的引用链日志,方便开发者定位出哪儿保持了非法引用。建议只在开发期为true,正式上线后改为false
if (!RuntimeApi.ForceUnloadAssembly(ass, false, true))
{
throw new Exception("unload fail");
}

// 第二次加载
Assembly newAss = Assembly.Load(yyy);

// 执行一些代码
Type mainType = ass.GetType("Entry");
mainType.GetMethod("Main").Invoke(null, null);

// 第二次卸载
if (!RuntimeApi.ForceUnloadAssembly(ass, false, true))
{
throw new Exception("unload fail");
}

注意事项

  • async或者协程很容易隐式地在其他线程保持了对卸载程序集代码的引用,卸载前请务必清理所有异步或者协程函数
  • UI的OnClick或者各种回调事件很容易导致保持了对卸载程序集的引用,一定要清理干净
  • 注册到全局的事件或者其他加高,容易意外保持了对卸载程序集的引用,一定要清理干净
  • 根据卸载过程中打印的非法引用的日志,清理掉代码中的非法引用
- + \ No newline at end of file diff --git a/docs/business/ultimate/commonerrors.html b/docs/business/ultimate/commonerrors.html index f69d65e08..da5f49391 100644 --- a/docs/business/ultimate/commonerrors.html +++ b/docs/business/ultimate/commonerrors.html @@ -9,13 +9,13 @@ - +
跳到主要内容

常见问题

ExecutionEngineException: Could not run the type initializer for origin DHE type 'xxx'

原因是 意外创建了被DHE替换的原始AOT类型。使用DHE后,不能再创建DHE程序集中类型对应的原始类型。有几个导致这个错误的原因:

  • 未加载DHE程序集前就执行了DHE程序集中代码
  • 热更新dhe dll与dhao文件不匹配,导致错误地执行了本不应该被执行的原始AOT代码,创建了原始AOT类型。只有4.5.7及更早的没有严格校验dll的版本才会有此错误
  • 抛出异常或打印日志时,获得函数帧栈过程中,意外访问了DHE程序集的原始函数。4.5.8及更早版本有此bug
  • 开启了 script debugging构建参数
- + \ No newline at end of file diff --git a/docs/business/ultimate/freetrial.html b/docs/business/ultimate/freetrial.html index 6a022f711..c8d58e297 100644 --- a/docs/business/ultimate/freetrial.html +++ b/docs/business/ultimate/freetrial.html @@ -9,14 +9,14 @@ - +
跳到主要内容

免费试用

相比于付费试用版本和正式购买后的版本都包含HybridCLR所有运行时及Editor源码,免费试用版本只提供预编译的 二进制lib及工具,只支持构建iOS平台目标。

试用规则

  • 只面向企业用户
  • 试用免费,没有试用期限,但试用过程中如需技术支持,需要额外付费
  • 试用版本会在App启动半小时后随机崩溃,请不要用于正式发布的版本

支持的版本

只支持以下特定的Unity版本。

  • 2019.4.40f1 待支持
  • 2020.3.48f1 待支持
  • 2021.3.31f1
  • 2022.3.11f1

不支持的特性

提示

在付费试用或者正式付费版本中均支持以下特性

  • dots
  • 增量式GC
  • Profiler、Script Debugging等development Build选项

不支持增量式GC及各种编译选项是因为每种编译参数都需要单独的libil2cpp.a,大大提高了维护成本。

安装

相关libil2cpp代码已经直接包含到Package中,打开HybridCLR/Installer菜单,点击Install按钮即可完成安装。

设置

相比于社区版本或者正式的商业化版本,需要修改以下设置:

  • 关闭增量式GC
  • 关闭development选项
  • 不可修改加密参数vmSeed的值,必须保持为0。其他加密参数可修改

与标准商业版本的区别

试用版本中移除了部分模块的源码,改为调用编译好的二进制程序。以下是相关模块。

  • DllEncryptor dll加密模块
  • DhaoGenerator dhao生成模块

使用

除了安装与设置如上面文档说明有少许调整外,剩余完全参照快速上手即可。

- + \ No newline at end of file diff --git a/docs/business/ultimate/injectrules.html b/docs/business/ultimate/injectrules.html index 009063f55..7ca38d2c2 100644 --- a/docs/business/ultimate/injectrules.html +++ b/docs/business/ultimate/injectrules.html @@ -9,7 +9,7 @@ - + @@ -24,7 +24,7 @@ 但不能配置List<int>的注入规则。

  • 如果某个函数满足多条规则,则以最后一条规则为准
  • property被当成 get_{name}set_{name}两条函数,因此int Count也能被&lt;method name="get_Count"&gt;匹配
名称类型可空描述
fullname属性类型全名称。支持通配符,如''、'Unity.'、'MyCustom.*.TestType'之类
method子元素函数规则
property子元素属性规则
event子元素事件规则

method

配置函数注入规则。

名称类型可空描述
name属性函数名。支持通配符,如''、'Run'之类
signature属性函数签名。支持通配符,如''、'System.Int32 (System.Int32)'
mode子元素注入类型,有效值为'none'或'proxy'。如果不填或者为空则取'none'

property

配置属性注入规则。注意,属性被当成 get_{name}set_{name}两条函数,因此int Count的getter函数get_Count也能被&lt;method name="get_Count"&gt;匹配。

名称类型可空描述
name属性函数名。支持通配符,如''、'Run'之类
signature属性函数签名。支持通配符,如'*'、'System.Int32 *'
mode子元素注入类型,有效值为'none'或'proxy'。如果不填或者为空则取'none'

event

配置事件注入规则。注意,事件被当成add_{name}remove_{name}两条函数,因此Action OnDone的add函数add_OnDone也能被&lt;method name="add_OnDone"&gt;匹配。

名称类型可空描述
name属性函数名。支持通配符,如''、'Run'之类
signature属性函数签名。支持通配符,如'*'、'Action<System.Int32> On*'
mode子元素注入类型,有效值为'none'或'proxy'。如果不填或者为空则取'none'

代码生成注入规则

手动添加注入规则有可能是一件比较繁琐的事情,当按名字通配不能满足需求时,例如当你想对指令数小于10的短函数不注入时,代码生成相应的注入规则可以极大简化 构造注入规则的工作量。

代码实现生成注入规则不复杂,大致就是遍历每个DHE程序集,如果函数满足某个规则,则添加相应的注入规则。示例代码如下:


public static void GenerateInjectRule(List<string> dheAssemblyNames, string outputInjectRuleFile)
{
int minInjectMethodInstructions = 10;

foreach (string dheDllPath in dheAssemblyNames)
{
using (var dheMod = ModuleDefMD.Load(dheDllPath))
{
// 添加注入规则 <assembly fullname="{dheMod.Assembly.Name}" />
for (uint i = 1, n = dheMod.Metadata.TablesStream.MethodTable.Rows; i <= n; i++)
{
MethodDef methodDef = dheMod.ResolveMethod(i);
if (methodDef.HasBody && methodDef.Body.Instructions.Count < minInjectMethodInstructions)
{
// 添加注入规则
// <type name="{methodDef.DeclaringType.Name}">
// <method name="{methodDef.Name}" />
// </type>
}
}
}
}
}

构建工作流相关

注入策略文件需要与构建的主包一致,既每个独立发布的主包都必须备份当时所用的注入策略文件。就如每次生成dhao文件时需要使用构建主包时生成的AOT dll, 生成dhao文件时必须使用构建主包时备份的注入策略文件。如果使用了错误注入策略文件,会导致生成错误dhao文件,这有可能会导致运行了错误的逻辑甚至崩溃!

- + \ No newline at end of file diff --git a/docs/business/ultimate/intro.html b/docs/business/ultimate/intro.html index e7b92d8a7..f4f36705c 100644 --- a/docs/business/ultimate/intro.html +++ b/docs/business/ultimate/intro.html @@ -9,14 +9,14 @@ - +
跳到主要内容

介绍

旗舰版主要面向有严格性能要求的项目。旗舰版相对社区版在性能方面有巨幅提升,几乎(未改动时为100%)达到原生性能水平,同时在安全性和内存方面有较好的优化。

支持的版本

支持所有 Unity 2019-2022 LTS版本。

优势

  • 包含专业版的所有功能
  • 包含独创的 DHE 技术,未变化部分代码性能与原生完全相同,相较社区版本纯解释方式提升惊人的3-30倍甚至更高,整体几乎达到原生性能水平
  • 含两年的技术支持

可预测的效果

DHE技术的效果是可预测的。不需要实际运行DHE也可以基本知道最终效果。

没有发生热更新前,性能与原生完全相同。发生热更新后,以函数为粒度,变化的函数变成以解释方式执行。生成dhao文件时,也会将所有变化函数打印出来。 因此开发者可以提前估计热更新行为影响了哪些函数的性能。

与injectfix之类方案的区别

从整体来说,旗舰版满足既希望逻辑任意热更新又想保持原生性能的项目的需求,而injectfix基本只能用于修复函数bug,两者有天壤之别。具体差异如下:

方案旗舰版injectfix
代码变更限制可以任意变更只支持修复函数及非常小范围地增加代码(因为有大量不支持的特性和bug)
支持的C#特性继承了社区版本的特点,几乎没有代码限制大量特性缺失,类型继承、delegate、泛型、反射、多线程、异步都有大量限制或者根本无法正常工作(本质上跟ILRuntime相似的缺陷)
性能未变化函数跟AOT相同,变化走解释,但解释性能极其高效(平均性能在injectfix十倍以上)未变化函数因为插桩缘故,即使不变更,性能也有一定程度下降。另外解释器性能极其低效
GC跟原生完全相同有大量额外GC
工作流自动标记,一键完成,无需人工参与手动标记,费时费力又容易出错,多版本维护是灾难
稳定性水平大量项目验证,稳定性极高大量不支持的特性及bug
- + \ No newline at end of file diff --git a/docs/business/ultimate/manual.html b/docs/business/ultimate/manual.html index c9f6d40b0..5ea537619 100644 --- a/docs/business/ultimate/manual.html +++ b/docs/business/ultimate/manual.html @@ -9,7 +9,7 @@ - + @@ -24,7 +24,7 @@ 严重情况下还会导致崩溃!

警告

除非特殊情况以及你是有经验的专家,不要手动标记。因为编译器经常生成一些隐藏类或字段,这些类名并不是稳定的。表面看起来一样的C#代码,实际生成的代码未必一样,强行标注为[Unchanged]会导致不正确的运行逻辑,甚至崩溃。

代码中使用

运行时,完成热更新后,对于每个dhe程序集,调用 RuntimeApi::LoadDifferentialHybridAssemblyRuntimeApi::LoadDifferentialHybridAssemblyUnchecked 加载热更新assembly。

注意事项:

  • 要按照assembly的依赖顺序加载 差分混合执行 assembly。
  • 如果某个程序集未发生改变,dhao字段可以传null,但此时一定要使用打包时生成的AOT dll,而不能使用通过HybridCLR/CompileDll/xxx命令生成的热更新dll。
  • DHE程序集本身已经包含了元数据,即使未开启完全泛型共享时也不要对DHE程序集进行补充元数据,补充了也会失败,其他非DHE的AOT程序集可以照常补充元数据。

RuntimeApi::LoadDifferentialHybridAssembly为带校验的工作流,需要传入原始dhe dll的md5及当前dhe dll的md5,与dhao文件中保存的md5进行对比。 为了originalDllMd5和currentDllMd5参数,极大增加了工作流的复杂度。

提示

推荐初学者在demo项目中使用带校验的工作流,熟悉工作流后在正式项目中使用不带校验的工作流。

带校验的 RuntimeApi::LoadDifferentialHybridAssembly

加载DHE程序集

public static string CreateMD5Hash(byte[] bytes)
{
return BitConverter.ToString(new MD5CryptoServiceProvider().ComputeHash(bytes)).Replace("-", "").ToUpperInvariant();
}

///
/// originalDllMd5 从构建时生成的`{manifest}`清单文件中获得,此清单文件由开发者自己生成
///
void LoadDifferentialHybridAssembly(string assemblyName, string originalDllMd5)
{
// currentDllMd5 既可以运行时生成,也可以发布热更新包时离线提前生成
string currentDllMd5 = CreateMD5Hash(dllBytes);
LoadImageErrorCode err = RuntimeApi.LoadDifferentialHybridAssembly(dllBytes, dhaoBytes, originalDllMd5, currentDllMd5);
if (err == LoadImageErrorCode.OK)
{
Debug.Log($"LoadDifferentialHybridAssembly {assName} OK");
}
else
{
Debug.LogError($"LoadDifferentialHybridAssembly {assName} failed, err={err}");
}
}

不带校验的 RuntimeApi::LoadDifferentialHybridAssemblyUnchecked

危险

使用不带校验的工作流,请务必保证原始dhe dll、当前dhe dll和dhao文件的一致性。如果不一致,轻则运行出错,重则进程崩溃。

加载DHE程序集

///
/// originalDllMd5 从构建时生成的`{manifest}`清单文件中获得,此清单文件由开发者自己生成
///
void LoadDifferentialHybridAssembly(string assemblyName)
{
LoadImageErrorCode err = RuntimeApi.LoadDifferentialHybridAssemblyUnchecked(dllBytes, dhaoBytes);
if (err == LoadImageErrorCode.OK)
{
Debug.Log($"LoadDifferentialHybridAssembly {assName} OK");
}
else
{
Debug.LogError($"LoadDifferentialHybridAssembly {assName} failed, err={err}");
}
}

配置函数注入策略

提示

在绝大多数项目中,默认的全注入策略对性能影响微乎其微,只要没有性能问题,不需要也不应该关心此项配置。

为了避免间接脏函数传染(即A函数调用了B函数,如果B改变了,A也会被标记为改变),默认会在所有函数头部注入一小段检查跳转代码。虽然是 非常简单的if (method->isInterpterImpl)语句,但对于int Age {get; set;}这样的短函数,这种插入可能会产生可观察的性能下降(甚至能达到10%)。

函数注入策略用于优化这种情况.对于不会变化的短函数,配置为不注入可以提升性能。详细请见InjectRules文档。

HybridCLR SettingsInjectRuleFiles字段中填写注入策略文件路径,文件的相对路径为项目根目录(如Assets/InjectRules/DefaultInjectRules.xml)。

打包

DHE技术中与构建相关的文件为dhe dll文件和对应的dhao文件。

非加密工作流

构建主包

  • 将构建后生成的裁剪AOT dll作为 首包(没有任何改动)的dhe dll
  • 使用HybridCLR.Editor.DHE.BuildUtils.GenerateUnchangedDHAODatas生成首包的dhao文件

如果使用带校验的工作流,则执行以下操作:

  • 为dhe dll生成一个至少包含 assemblyName,md5的{manifest}清单文件(由开发者自由决定怎么实现),因为RuntimeApi.LoadDifferentialHybridAssembly需要提供dhe dll的原始md5
  • 将 dhe dll、dhao文件及{manifest}文件加入热更新资源管理系统

如果使用不带校验的工作流,则执行以下操作:

  • 将 dhe dll、dhao文件加入热更新资源管理系统

如果想随包携带首包的dhe dll和dhao文件,请先导出工程,再按照上面的步骤生成dhe dll和dhao文件,再将它们加入到导出工程中。

热更新

  • 使用 HybridCLR/CompileDll/ActivedBuildTarget 生成热更新dll。
  • 使用HybridCLR.Editor.DHE.BuildUtils.GenerateDHAODatas生成最新的热更新dll的dhao文件
  • 将最新的热更新dll和dhao文件加入热更新资源管理系统
警告

如果打包使用 development build 选项,请一定要对应使用HybridCLR/CompileDll/ActivedBuildTarget_Development编译Development模式的热更新dll,否则对比结果为几乎所有函数都被判定为发生变化。

加密工作流

构建主包

  • 将构建后生成的裁剪AOT dll作为 原始dhe dll
  • 使用HybridCLR.Editor.DHE.BuildUtils.EncryptDllAndGenerateUnchangedDHAODatas生成首包的dhao文件及加密后的dhe dll文件

如果使用带校验的工作流,则执行以下操作:

  • 为dhe dll生成一个至少包含 assemblyName,md5的{manifest}清单文件(由开发者自由决定怎么实现),因为RuntimeApi.LoadDifferentialHybridAssembly需要提供dhe dll的原始md5
  • 将 加密后的dhe dll、dhao文件及{manifest}文件加入热更新资源管理系统

如果使用不带校验的工作流,则执行以下操作:

  • 将 加密后的dhe dll、dhao文件加入热更新资源管理系统

热更新

  • 使用 HybridCLR/CompileDll/ActivedBuildTarget 生成热更新dll。
  • 使用HybridCLR.Editor.DHE.BuildUtils.EncryptDllAndGenerateDHAODatas生成最新的dhe dll的加密后的文件及对应的dhao文件
  • 将加密后的dhe dll和dhao文件加入热更新资源管理系统

不支持特性

  • 不支持开启 script debugging 构建选项

注意事项

外部dll引发的计算dhao的结果有巨量差异

如果有外部dll被标记为DHE程序集,由于外部dll打包时会被裁剪,而计算dhao文件时,取的是原始的外部dll,导致产生巨量的差异,这不是所期望的。解决办法有几个:

  1. 在link.xml里<assembly fullname="YourExternDll" preserve="all"/> 完全保留外部dll
  2. 不用最新的热更新dll去计算差异,而是使用最新代码重新打包时生成的aot dll去计算差异
- + \ No newline at end of file diff --git a/docs/business/ultimate/quickstartchecked.html b/docs/business/ultimate/quickstartchecked.html index 138d5e5c1..61876cd58 100644 --- a/docs/business/ultimate/quickstartchecked.html +++ b/docs/business/ultimate/quickstartchecked.html @@ -9,7 +9,7 @@ - + @@ -19,7 +19,7 @@ 本文档介绍带校验的工作流。

提示

实践中不带校验的工作流会简单很多,不必传递originalDllMd5和currentDllMd5参数,所以省去了工作流中保存或者计算dll md5的过程。 但要求开发者确保aot dll、hot update dll、dhao文件的一致性。 推荐初学者在demo项目中使用带校验的工作流,熟悉工作流后在正式项目中使用不带校验的工作流。

体验目标

  • 创建热更新程序集
  • 加载热更新程序集,并执行其中热更新代码,打印 Hello, HybridCLR
  • 修改热更新代码,打印 Hello, World

准备环境

安装Unity

  • 安装 2019.4.x、2020.3.x、2021.3.x、2022.3.x 中任一版本。某些版本有特殊的安装要求,参见安装hybridclr
  • 根据你所用的操作系统,安装过程中选择模块时,必须选中 Windows Build Support(IL2CPP)Mac Build Support(IL2CPP)

select il2cpp modules

安装IDE及相关编译环境

  • Windows
    • Win下需要安装visual studio 2019或更高版本。安装时至少要包含 使用Unity的游戏开发使用c++的游戏开发 组件
    • 安装git
  • Mac
    • 要求MacOS版本 >= 12,xcode版本 >= 13,例如xcode 13.4.1, macos 12.4
    • 安装 git

初始化Unity热更新项目

从零开始构造热更新项目的过程较冗长,以下步骤中涉及的代码可参考dhe_demo项目,其仓库地址为 github

创建项目

创建空的Unity项目。

创建ConsoleToScreen.cs脚本

这个脚本对于演示热更新没有直接作用。它可以打印日志到屏幕上,方便定位错误。

创建 Assets/ConsoleToScreen.cs 脚本类,代码如下:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ConsoleToScreen : MonoBehaviour
{
const int maxLines = 50;
const int maxLineLength = 120;
private string _logStr = "";

private readonly List<string> _lines = new List<string>();

public int fontSize = 15;

void OnEnable() { Application.logMessageReceived += Log; }
void OnDisable() { Application.logMessageReceived -= Log; }

public void Log(string logString, string stackTrace, LogType type)
{
foreach (var line in logString.Split('\n'))
{
if (line.Length <= maxLineLength)
{
_lines.Add(line);
continue;
}
var lineCount = line.Length / maxLineLength + 1;
for (int i = 0; i < lineCount; i++)
{
if ((i + 1) * maxLineLength <= line.Length)
{
_lines.Add(line.Substring(i * maxLineLength, maxLineLength));
}
else
{
_lines.Add(line.Substring(i * maxLineLength, line.Length - i * maxLineLength));
}
}
}
if (_lines.Count > maxLines)
{
_lines.RemoveRange(0, _lines.Count - maxLines);
}
_logStr = string.Join("\n", _lines);
}

void OnGUI()
{
GUI.matrix = Matrix4x4.TRS(Vector3.zero, Quaternion.identity,
new Vector3(Screen.width / 1200.0f, Screen.height / 800.0f, 1.0f));
GUI.Label(new Rect(10, 10, 800, 370), _logStr, new GUIStyle() { fontSize = Math.Max(10, fontSize) });
}
}


创建主场景

  • 创建默认初始场景 main.scene
  • 场景中创建一个空GameObject,将ConsoleToScreen挂到上面
  • Build Settings中添加main场景到打包场景列表

创建 HotUpdate 热更新模块

  • 创建 Assets/HotUpdate 目录
  • 在目录下 右键 Create/Assembly Definition,创建一个名为HotUpdate的程序集模块

安装和配置HybridCLR

安装

  • 将hybridclr_unity.zip解压后,放到项目Packages目录下,改名为com.code-philosophy.hybridclr
  • 根据你的unity版本解压对应的il2cpp_plus-{version}.zip
  • 解压 hybridclr.zip
  • hybridclr.zip解压后的hybridclr目录放到il2cpp-{version}.zip解压后的libil2cpp目录下
  • 打开 HybridCLR/Installer,启用从本地复制libil2cpp选项,选中刚才解压的libil2cpp目录,进行安装
  • 根据你的Unity版本:
    • 如果版本 >= 2020,将 ModifiedDlls\{verions}\Unity.IL2CPP.dll 文件替换 {proj}\HybridCLRData\LocalIl2CppData-WindowsEditor\il2cpp\build\deploy\netcoreapp3.1\Unity.IL2CPP.dll(Unity 2020)或{proj}\HybridCLRData\LocalIl2CppData-WindowsEditor\il2cpp\build\deploy\Unity.IL2CPP.dll(Unity 2021+)。如果没有你的版本对应的文件,联系我们制作一个
    • 如果版本 为 2019,不需要任何操作,因为Install过程中已经自动复制

installer

配置HybridCLR

  • 打开菜单 HybridCLR/Settings
  • differentialHybridAssemblies列表中添加HotUpdate程序集

settings

配置PlayerSettings

  • Scripting Backend 切换为 IL2CPP
  • Api Compatability Level 切换为 .Net 4.x(Unity 2019-2020) 或 .Net Framework(Unity 2021+)

player settings

创建Editor脚本

Assets/Editor目录下创建 BuildTools.cs 文件,内容如下:


using HybridCLR.Editor;
using HybridCLR.Editor.DHE;
using HybridCLR.Runtime;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;

public static class BuildTools
{
public const string BackupAOTDllDir = "HybridCLRData/BackupAOT";

public const string EncrypedDllDir = "HybridCLRData/EncryptedDll";

public const string DhaoDir = "HybridCLRData/Dhao";

public const string ManifestFile = "manifest.txt";


/// <summary>
/// 备份构建主包时生成的裁剪AOT dll
/// </summary>
[MenuItem("BuildTools/BackupAOTDll")]
public static void BackupAOTDllFromAssemblyPostStrippedDir()
{
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
var backupDir = $"{BackupAOTDllDir}/{target}";
System.IO.Directory.CreateDirectory(backupDir);
var dlls = System.IO.Directory.GetFiles(SettingsUtil.GetAssembliesPostIl2CppStripDir(target));
foreach (var dll in dlls)
{
var fileName = System.IO.Path.GetFileName(dll);
string dstFile = $"{BackupAOTDllDir}/{target}/{fileName}";
System.IO.File.Copy(dll, dstFile, true);
Debug.Log($"BackupAOTDllFromAssemblyPostStrippedDir: {dll} -> {dstFile}");
}
}

/// <summary>
/// 创建dhe manifest文件,格式为每行一个 'dll名,原始dll的md5'
/// </summary>
/// <param name="outputDir"></param>
[MenuItem("BuildTools/CreateManifestAtBackupDir")]
public static void CreateManifest()
{
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
string backupDir = $"{BackupAOTDllDir}/{target}";
CreateManifest(backupDir);
}

public static void CreateManifest(string outputDir)
{
Directory.CreateDirectory(outputDir);
var lines = new List<string>();
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
string backupDir = $"{BackupAOTDllDir}/{target}";
foreach (string dheDll in SettingsUtil.DifferentialHybridAssemblyNames)
{
string originalDll = $"{backupDir}/{dheDll}.dll";
string originalDllMd5 = AssemblyOptionDataGenerator.CreateMD5Hash(File.ReadAllBytes(originalDll));
lines.Add($"{dheDll},{originalDllMd5}");
}
string manifestFile = $"{outputDir}/{ManifestFile}";
File.WriteAllBytes(manifestFile, System.Text.Encoding.UTF8.GetBytes(string.Join("\n", lines)));
Debug.Log($"CreateManifest: {manifestFile}");
}

/// <summary>
/// 生成首包的没有任何代码改动对应的dhao数据
/// </summary>
[MenuItem("BuildTools/GenerateUnchangedDHAODatas")]
public static void GenerateUnchangedDHAODatas()
{
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
string backupDir = $"{BackupAOTDllDir}/{target}";
string dhaoDir = $"{DhaoDir}/{target}";
BuildUtils.GenerateUnchangedDHAODatas(SettingsUtil.DifferentialHybridAssemblyNames, backupDir, dhaoDir);
}

/// <summary>
/// 生成热更包的dhao数据
/// </summary>
[MenuItem("BuildTools/GenerateDHAODatas")]
public static void GenerateDHAODatas()
{
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
string backupDir = $"{BackupAOTDllDir}/{target}";
string dhaoDir = $"{DhaoDir}/{target}";
string currentDllDir = SettingsUtil.GetHotUpdateDllsOutputDirByTarget(target);
BuildUtils.GenerateDHAODatas(SettingsUtil.DifferentialHybridAssemblyNames, backupDir, currentDllDir, null, null, dhaoDir);
}

/// <summary>
/// 生成首包的加密dll和没有任何代码改动对应的dhao数据
/// </summary>
[MenuItem("BuildTools/GenerateUnchangedEncryptedDllAndDhaoDatas")]
public static void GenerateUnchangedEncryptedDllAndDhaoDatas()
{
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
string backupDir = $"{BackupAOTDllDir}/{target}";
string dhaoDir = $"{DhaoDir}/{target}";
string encryptedDllDir = $"{EncrypedDllDir}/{target}";
BuildUtils.EncryptDllAndGenerateUnchangedDHAODatas(SettingsUtil.DifferentialHybridAssemblyNames, backupDir, encryptedDllDir, dhaoDir);
}


/// <summary>
/// 生成热更包的加密dll和dhao数据
/// </summary>
[MenuItem("BuildTools/GenerateEncryptedDllAndDhaoDatas")]
public static void GenerateEncryptedDllAndDhaoDatas()
{
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
string backupDir = $"{BackupAOTDllDir}/{target}";
string dhaoDir = $"{DhaoDir}/{target}";
string currentDllDir = SettingsUtil.GetHotUpdateDllsOutputDirByTarget(target);
string encryptedDllDir = $"{EncrypedDllDir}/{target}";
BuildUtils.EncryptDllAndGenerateDHAODatas(SettingsUtil.DifferentialHybridAssemblyNames, backupDir, currentDllDir, null, null, encryptedDllDir, dhaoDir);
}

/// <summary>
/// 复制没有改动的首包dll和dhao文件到StreamingAssets
/// </summary>
[MenuItem("BuildTools/CopyUnchangedDllAndDhaoFileAndManifestToStreamingAssets")]
public static void CopyUnchangedDllAndDhaoFileToStreamingAssets()
{
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
string streamingAssetsDir = Application.streamingAssetsPath;
Directory.CreateDirectory(streamingAssetsDir);

string manifestFile = $"{BackupAOTDllDir}/{target}/{ManifestFile}";
string dstManifestFile = $"{streamingAssetsDir}/{ManifestFile}";
System.IO.File.Copy(manifestFile, dstManifestFile, true);
Debug.Log($"CopyUnchangedDllAndDhaoFileToStreamingAssets: {manifestFile} -> {dstManifestFile}");

string dllDir = $"{BackupAOTDllDir}/{target}";
string dhaoDir = $"{DhaoDir}/{target}";
foreach (var dll in SettingsUtil.DifferentialHybridAssemblyNames)
{
string srcFile = $"{dllDir}/{dll}.dll";
string dstFile = $"{streamingAssetsDir}/{dll}.dll.bytes";
System.IO.File.Copy(srcFile, dstFile, true);
Debug.Log($"CopyUnchangedDllAndDhaoFileToStreamingAssets: {srcFile} -> {dstFile}");
string dhaoFile = $"{dhaoDir}/{dll}.dhao.bytes";
dstFile = $"{streamingAssetsDir}/{dll}.dhao.bytes";
System.IO.File.Copy(dhaoFile, dstFile, true);
Debug.Log($"CopyUnchangedDllAndDhaoFileToStreamingAssets: {dhaoFile} -> {dstFile}");
}
}

/// <summary>
/// 复制热更新dll和dhao文件到StreamingAssets
/// </summary>
[MenuItem("BuildTools/CopyDllAndDhaoFileToStreamingAssets")]
public static void CopyDllAndDhaoFileToStreamingAssets()
{
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
string streamingAssetsDir = Application.streamingAssetsPath;
Directory.CreateDirectory(streamingAssetsDir);

string dllDir = SettingsUtil.GetHotUpdateDllsOutputDirByTarget(target);
string dhaoDir = $"{DhaoDir}/{target}";
foreach (var dll in SettingsUtil.DifferentialHybridAssemblyNames)
{
string srcFile = $"{dllDir}/{dll}.dll";
string dstFile = $"{streamingAssetsDir}/{dll}.dll.bytes";
System.IO.File.Copy(srcFile, dstFile, true);
Debug.Log($"CopyUnchangedDllAndDhaoFileToStreamingAssets: {srcFile} -> {dstFile}");
string dhaoFile = $"{dhaoDir}/{dll}.dhao.bytes";
dstFile = $"{streamingAssetsDir}/{dll}.dhao.bytes";
System.IO.File.Copy(dhaoFile, dstFile, true);
Debug.Log($"CopyUnchangedDllAndDhaoFileToStreamingAssets: {dhaoFile} -> {dstFile}");
}
}

}


创建热更新脚本

创建 Assets/HotUpdate/Hello.cs 文件,代码内容如下

using System.Collections;
using UnityEngine;

public class Hello
{
public static void Run()
{
Debug.Log("Hello, HybridCLR");
}
}

加载热更新程序集

为了简化演示,我们不通过http服务器下载HotUpdate.dll,而是直接将HotUpdate.dll放到StreamingAssets目录下。

创建Assets/LoadDll.cs脚本,然后在main场景中创建一个GameObject对象,挂载LoadDll脚本

using HybridCLR;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;

public class LoadDll : MonoBehaviour
{

void Start()
{
// Editor环境下,HotUpdate.dll.bytes已经被自动加载,不需要加载,重复加载反而会出问题。
#if !UNITY_EDITOR
var manifests = LoadManifest($"{Application.streamingAssetsPath}/manifest.txt");
Assembly hotUpdateAss = LoadDifferentialHybridAssembly(manifests["HotUpdate"], "HotUpdate");
#else
// Editor下无需加载,直接查找获得HotUpdate程序集
Assembly hotUpdateAss = System.AppDomain.CurrentDomain.GetAssemblies().First(a => a.GetName().Name == "HotUpdate");
#endif
Type helloType = hotUpdateAss.GetType("Hello");
MethodInfo runMethod = helloType.GetMethod("Run");
runMethod.Invoke(null, null);
}

class Manifest
{
public string AssemblyName { get; set; }

public string OriginalDllMd5 { get; set; }
}

private Dictionary<string, Manifest> LoadManifest(string manifestFile)
{
var manifest = new Dictionary<string, Manifest>();
var lines = File.ReadAllLines(manifestFile, Encoding.UTF8);
foreach (var line in lines)
{
string[] args = line.Split(",");
if (args.Length != 2)
{
Debug.LogError($"manifest file format error, line={line}");
return null;
}
manifest.Add(args[0], new Manifest()
{
AssemblyName = args[0],
OriginalDllMd5 = args[1],
});
}
return manifest;
}


public static string CreateMD5Hash(byte[] bytes)
{
return BitConverter.ToString(new MD5CryptoServiceProvider().ComputeHash(bytes)).Replace("-", "").ToUpperInvariant();
}

private Assembly LoadDifferentialHybridAssembly(Manifest manifest, string assName)
{
byte[] dllBytes = File.ReadAllBytes($"{Application.streamingAssetsPath}/{assName}.dll.bytes");
byte[] dhaoBytes = File.ReadAllBytes($"{Application.streamingAssetsPath}/{assName}.dhao.bytes");
string currentDllMd5 = CreateMD5Hash(dllBytes);
LoadImageErrorCode err = RuntimeApi.LoadDifferentialHybridAssembly(dllBytes, dhaoBytes, manifest.OriginalDllMd5, currentDllMd5);
if (err == LoadImageErrorCode.OK)
{
Debug.Log($"LoadDifferentialHybridAssembly {assName} OK");
return System.AppDomain.CurrentDomain.GetAssemblies().First(a => a.GetName().Name == assName);
}
else
{
Debug.LogError($"LoadDifferentialHybridAssembly {assName} failed, err={err}");
return null;
}
}
}

至此,完成整个热更新工程的创建工作!!!

Editor中试运行

运行main场景,屏幕上会显示 'Hello,HybridCLR',表示代码工作正常。

打包运行

  • 运行菜单 HybridCLR/Generate/All 进行必要的生成操作。这一步不可遗漏!!!
  • 打开 Build Settings 对话框,点击Build,选择输出目录{build},执行构建
  • 运行 BuildTools/BackupAOTDll 备份裁剪后的dhe dll。 实践中这些dll应该加入版本管理,用于后面生成dhao文件,这些文件不会再被修改
  • 运行 BuildTools/CreateManifestAtBackupDir生成原始dhe dll的清单文件。实践中这个清单文件应该加入版本管理,而且不会再被修改
  • 运行 BuildTools/GenerateUnchangedDHAODatas 生成首包的dhao文件
  • 运行 BuildTools/CopyUnchangedDllAndDhaoFileAndManifestToStreamingAssets 复制首包 dhe程序集、dhao文件、清单文件到 StreamingAssets
  • Assets/StreamingAssets目录复制到{build}\dhe_demo2_Data\StreamingAssets
  • 运行{build}/Xxx.exe,屏幕显示 Hello,HybridCLR,表示热更新代码被顺利执行!

测试热更新

  • 修改Assets/HotUpdate/Hello.cs的Run函数中Debug.Log("Hello, HybridCLR");代码,改成Debug.Log("Hello, World");
  • 运行HybridCLR/CompileDll/ActiveBulidTarget生成热更新dll
  • 运行BuildTools/GenerateDHAODatas 生成dhao文件
  • 运行BuildTools/CopyDllAndDhaoFileToStreamingAssets复制热更新dll和dhao文件到StreamingAssets目录
  • Assets/StreamingAssets目录复制到{build}\dhe_demo2_Data\StreamingAssets
  • 重新运行程序,会发现屏幕中显示Hello, World,表示热更新代码生效了!

至此完成热更新体验!!!

- + \ No newline at end of file diff --git a/docs/business/ultimate/quickstartunchecked.html b/docs/business/ultimate/quickstartunchecked.html index 5f650e778..e3389a4a7 100644 --- a/docs/business/ultimate/quickstartunchecked.html +++ b/docs/business/ultimate/quickstartunchecked.html @@ -9,7 +9,7 @@ - + @@ -19,7 +19,7 @@ 本文档介绍不带校验的工作流。

提示

实践中不带校验的工作流会简单很多,不必传递originalDllMd5和currentDllMd5参数,所以省去了工作流中保存或者计算dll md5的过程。 但要求开发者确保aot dll、hot update dll、dhao文件的一致性。 推荐初学者在demo项目中使用带校验的工作流,熟悉工作流后在正式项目中使用不带校验的工作流。

体验目标

  • 创建热更新程序集
  • 加载热更新程序集,并执行其中热更新代码,打印 Hello, HybridCLR
  • 修改热更新代码,打印 Hello, World

准备环境

安装Unity

  • 安装 2019.4.x、2020.3.x、2021.3.x、2022.3.x 中任一版本。某些版本有特殊的安装要求,参见安装hybridclr
  • 根据你所用的操作系统,安装过程中选择模块时,必须选中 Windows Build Support(IL2CPP)Mac Build Support(IL2CPP)

select il2cpp modules

安装IDE及相关编译环境

  • Windows
    • Win下需要安装visual studio 2019或更高版本。安装时至少要包含 使用Unity的游戏开发使用c++的游戏开发 组件
    • 安装git
  • Mac
    • 要求MacOS版本 >= 12,xcode版本 >= 13,例如xcode 13.4.1, macos 12.4
    • 安装 git

初始化Unity热更新项目

从零开始构造热更新项目的过程较冗长,以下步骤中涉及的代码可参考dhe_demo项目,其仓库地址为 github

创建项目

创建空的Unity项目。

创建ConsoleToScreen.cs脚本

这个脚本对于演示热更新没有直接作用。它可以打印日志到屏幕上,方便定位错误。

创建 Assets/ConsoleToScreen.cs 脚本类,代码如下:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ConsoleToScreen : MonoBehaviour
{
const int maxLines = 50;
const int maxLineLength = 120;
private string _logStr = "";

private readonly List<string> _lines = new List<string>();

public int fontSize = 15;

void OnEnable() { Application.logMessageReceived += Log; }
void OnDisable() { Application.logMessageReceived -= Log; }

public void Log(string logString, string stackTrace, LogType type)
{
foreach (var line in logString.Split('\n'))
{
if (line.Length <= maxLineLength)
{
_lines.Add(line);
continue;
}
var lineCount = line.Length / maxLineLength + 1;
for (int i = 0; i < lineCount; i++)
{
if ((i + 1) * maxLineLength <= line.Length)
{
_lines.Add(line.Substring(i * maxLineLength, maxLineLength));
}
else
{
_lines.Add(line.Substring(i * maxLineLength, line.Length - i * maxLineLength));
}
}
}
if (_lines.Count > maxLines)
{
_lines.RemoveRange(0, _lines.Count - maxLines);
}
_logStr = string.Join("\n", _lines);
}

void OnGUI()
{
GUI.matrix = Matrix4x4.TRS(Vector3.zero, Quaternion.identity,
new Vector3(Screen.width / 1200.0f, Screen.height / 800.0f, 1.0f));
GUI.Label(new Rect(10, 10, 800, 370), _logStr, new GUIStyle() { fontSize = Math.Max(10, fontSize) });
}
}


创建主场景

  • 创建默认初始场景 main.scene
  • 场景中创建一个空GameObject,将ConsoleToScreen挂到上面
  • Build Settings中添加main场景到打包场景列表

创建 HotUpdate 热更新模块

  • 创建 Assets/HotUpdate 目录
  • 在目录下 右键 Create/Assembly Definition,创建一个名为HotUpdate的程序集模块

安装和配置HybridCLR

安装

  • 将hybridclr_unity.zip解压后,放到项目Packages目录下,改名为com.code-philosophy.hybridclr
  • 根据你的unity版本解压对应的il2cpp_plus-{version}.zip
  • 解压 hybridclr.zip
  • hybridclr.zip解压后的hybridclr目录放到il2cpp-{version}.zip解压后的libil2cpp目录下
  • 打开 HybridCLR/Installer,启用从本地复制libil2cpp选项,选中刚才解压的libil2cpp目录,进行安装
  • 根据你的Unity版本:
    • 如果版本 >= 2020,将 ModifiedDlls\{verions}\Unity.IL2CPP.dll 文件替换 {proj}\HybridCLRData\LocalIl2CppData-WindowsEditor\il2cpp\build\deploy\netcoreapp3.1\Unity.IL2CPP.dll(Unity 2020)或{proj}\HybridCLRData\LocalIl2CppData-WindowsEditor\il2cpp\build\deploy\Unity.IL2CPP.dll(Unity 2021+)。如果没有你的版本对应的文件,联系我们制作一个
    • 如果版本 为 2019,不需要任何操作,因为Install过程中已经自动复制

installer

配置HybridCLR

  • 打开菜单 HybridCLR/Settings
  • differentialHybridAssemblies列表中添加HotUpdate程序集

settings

配置PlayerSettings

  • Scripting Backend 切换为 IL2CPP
  • Api Compatability Level 切换为 .Net 4.x(Unity 2019-2020) 或 .Net Framework(Unity 2021+)

player settings

创建Editor脚本

Assets/Editor目录下创建 BuildTools.cs 文件,内容如下:


using HybridCLR.Editor;
using HybridCLR.Editor.DHE;
using HybridCLR.Runtime;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;

public static class BuildTools
{
public const string BackupAOTDllDir = "HybridCLRData/BackupAOT";

public const string DhaoDir = "HybridCLRData/Dhao";


/// <summary>
/// 备份构建主包时生成的裁剪AOT dll
/// </summary>
[MenuItem("BuildTools/BackupAOTDll")]
public static void BackupAOTDllFromAssemblyPostStrippedDir()
{
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
var backupDir = $"{BackupAOTDllDir}/{target}";
System.IO.Directory.CreateDirectory(backupDir);
var dlls = System.IO.Directory.GetFiles(SettingsUtil.GetAssembliesPostIl2CppStripDir(target));
foreach (var dll in dlls)
{
var fileName = System.IO.Path.GetFileName(dll);
string dstFile = $"{BackupAOTDllDir}/{target}/{fileName}";
System.IO.File.Copy(dll, dstFile, true);
Debug.Log($"BackupAOTDllFromAssemblyPostStrippedDir: {dll} -> {dstFile}");
}
}

/// <summary>
/// 生成首包的没有任何代码改动对应的dhao数据
/// </summary>
[MenuItem("BuildTools/GenerateUnchangedDHAODatas")]
public static void GenerateUnchangedDHAODatas()
{
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
string backupDir = $"{BackupAOTDllDir}/{target}";
string dhaoDir = $"{DhaoDir}/{target}";
BuildUtils.GenerateUnchangedDHAODatas(SettingsUtil.DifferentialHybridAssemblyNames, backupDir, dhaoDir);
}

/// <summary>
/// 生成热更包的dhao数据
/// </summary>
[MenuItem("BuildTools/GenerateDHAODatas")]
public static void GenerateDHAODatas()
{
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
string backupDir = $"{BackupAOTDllDir}/{target}";
string dhaoDir = $"{DhaoDir}/{target}";
string currentDllDir = SettingsUtil.GetHotUpdateDllsOutputDirByTarget(target);
BuildUtils.GenerateDHAODatas(SettingsUtil.DifferentialHybridAssemblyNames, backupDir, currentDllDir, null, null, dhaoDir);
}

/// <summary>
/// 复制没有改动的首包dll和dhao文件到StreamingAssets
/// </summary>
[MenuItem("BuildTools/CopyUnchangedDllAndDhaoFileToStreamingAssets")]
public static void CopyUnchangedDllAndDhaoFileToStreamingAssets()
{
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
string streamingAssetsDir = Application.streamingAssetsPath;
Directory.CreateDirectory(streamingAssetsDir);

string dllDir = $"{BackupAOTDllDir}/{target}";
string dhaoDir = $"{DhaoDir}/{target}";
foreach (var dll in SettingsUtil.DifferentialHybridAssemblyNames)
{
string srcFile = $"{dllDir}/{dll}.dll";
string dstFile = $"{streamingAssetsDir}/{dll}.dll.bytes";
System.IO.File.Copy(srcFile, dstFile, true);
Debug.Log($"CopyUnchangedDllAndDhaoFileToStreamingAssets: {srcFile} -> {dstFile}");
string dhaoFile = $"{dhaoDir}/{dll}.dhao.bytes";
dstFile = $"{streamingAssetsDir}/{dll}.dhao.bytes";
System.IO.File.Copy(dhaoFile, dstFile, true);
Debug.Log($"CopyUnchangedDllAndDhaoFileToStreamingAssets: {dhaoFile} -> {dstFile}");
}
}

/// <summary>
/// 复制热更新dll和dhao文件到StreamingAssets
/// </summary>
[MenuItem("BuildTools/CopyDllAndDhaoFileToStreamingAssets")]
public static void CopyDllAndDhaoFileToStreamingAssets()
{
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
string streamingAssetsDir = Application.streamingAssetsPath;
Directory.CreateDirectory(streamingAssetsDir);

string dllDir = SettingsUtil.GetHotUpdateDllsOutputDirByTarget(target);
string dhaoDir = $"{DhaoDir}/{target}";
foreach (var dll in SettingsUtil.DifferentialHybridAssemblyNames)
{
string srcFile = $"{dllDir}/{dll}.dll";
string dstFile = $"{streamingAssetsDir}/{dll}.dll.bytes";
System.IO.File.Copy(srcFile, dstFile, true);
Debug.Log($"CopyUnchangedDllAndDhaoFileToStreamingAssets: {srcFile} -> {dstFile}");
string dhaoFile = $"{dhaoDir}/{dll}.dhao.bytes";
dstFile = $"{streamingAssetsDir}/{dll}.dhao.bytes";
System.IO.File.Copy(dhaoFile, dstFile, true);
Debug.Log($"CopyUnchangedDllAndDhaoFileToStreamingAssets: {dhaoFile} -> {dstFile}");
}
}
}


创建热更新脚本

创建 Assets/HotUpdate/Hello.cs 文件,代码内容如下

using System.Collections;
using UnityEngine;

public class Hello
{
public static void Run()
{
Debug.Log("Hello, HybridCLR");
}
}

加载热更新程序集

为了简化演示,我们不通过http服务器下载HotUpdate.dll,而是直接将HotUpdate.dll放到StreamingAssets目录下。

创建Assets/LoadDll.cs脚本,然后在main场景中创建一个GameObject对象,挂载LoadDll脚本

using HybridCLR;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;

public class LoadDll : MonoBehaviour
{

void Start()
{
// Editor环境下,HotUpdate.dll.bytes已经被自动加载,不需要加载,重复加载反而会出问题。
#if !UNITY_EDITOR
Assembly hotUpdateAss = LoadDifferentialHybridAssembly("HotUpdate");
#else
// Editor下无需加载,直接查找获得HotUpdate程序集
Assembly hotUpdateAss = System.AppDomain.CurrentDomain.GetAssemblies().First(a => a.GetName().Name == "HotUpdate");
#endif
Type helloType = hotUpdateAss.GetType("Hello");
MethodInfo runMethod = helloType.GetMethod("Run");
runMethod.Invoke(null, null);
}

private Assembly LoadDifferentialHybridAssembly(string assName)
{
byte[] dllBytes = File.ReadAllBytes($"{Application.streamingAssetsPath}/{assName}.dll.bytes");
byte[] dhaoBytes = File.ReadAllBytes($"{Application.streamingAssetsPath}/{assName}.dhao.bytes");
LoadImageErrorCode err = RuntimeApi.LoadDifferentialHybridAssemblyUnchecked(dllBytes, dhaoBytes);
if (err == LoadImageErrorCode.OK)
{
Debug.Log($"LoadDifferentialHybridAssembly {assName} OK");
return System.AppDomain.CurrentDomain.GetAssemblies().First(a => a.GetName().Name == assName);
}
else
{
Debug.LogError($"LoadDifferentialHybridAssembly {assName} failed, err={err}");
return null;
}
}
}

至此,完成整个热更新工程的创建工作!!!

Editor中试运行

运行main场景,屏幕上会显示 'Hello,HybridCLR',表示代码工作正常。

打包运行

  • 运行菜单 HybridCLR/Generate/All 进行必要的生成操作。这一步不可遗漏!!!
  • 打开 Build Settings 对话框,点击Build,选择输出目录{build},执行构建
  • 运行 BuildTools/BackupAOTDll 备份裁剪后的dhe dll。 实践中这些dll应该加入版本管理,用于后面生成dhao文件,这些文件不会再被修改
  • 运行 BuildTools/GenerateUnchangedDHAODatas 生成首包的dhao文件
  • 运行 BuildTools/CopyUnchangedDllAndDhaoFileToStreamingAssets 复制首包 dhe程序集、dhao文件到 StreamingAssets
  • Assets/StreamingAssets目录复制到{build}\dhe_demo2_Data\StreamingAssets
  • 运行{build}/Xxx.exe,屏幕显示 Hello,HybridCLR,表示热更新代码被顺利执行!

测试热更新

  • 修改Assets/HotUpdate/Hello.cs的Run函数中Debug.Log("Hello, HybridCLR");代码,改成Debug.Log("Hello, World");
  • 运行HybridCLR/CompileDll/ActiveBulidTarget生成热更新dll
  • 运行BuildTools/GenerateDHAODatas 生成dhao文件
  • 运行BuildTools/CopyDllAndDhaoFileToStreamingAssets复制热更新dll和dhao文件到StreamingAssets目录
  • Assets/StreamingAssets目录复制到{build}\dhe_demo2_Data\StreamingAssets
  • 重新运行程序,会发现屏幕中显示Hello, World,表示热更新代码生效了!

至此完成热更新体验!!!

- + \ No newline at end of file diff --git a/docs/business/ultimate/workflow.html b/docs/business/ultimate/workflow.html index b301dcce8..bda361d88 100644 --- a/docs/business/ultimate/workflow.html +++ b/docs/business/ultimate/workflow.html @@ -9,7 +9,7 @@ - + @@ -17,7 +17,7 @@
跳到主要内容

简化dhao工作流

基于同一版本的原始工程,在发布不同平台时原始dll会有微小区别,导致需要为每个平台都计算单独的dhao文件(即使是相同平台,由于代码编译的不稳定性, 生成的原始dll也可能有微小区别),这导致维护dhao工作变得复杂易错。当多个新旧游戏包同时存在时,这个问题尤为严重。

解决这个问题主要靠合并dhao文件

合并dhao文件

基于相同或者相似源码发布的游戏包,它们的原始dhe程序集在不同平台之间仅有微小区分,热更新时生成的dhao文件也只有微小区别。 可以考虑将同一个dhe程序集对应的多个平台的dhao文件合并,不影响运行正确性的同时对性能的影响也很小。

我们提供了HybridCLR.Editor.DHE.BuildUtil::MergeDHAOFiles函数实现合并dhao文件目标。

注意,带校验的工作流无法使用合并dhao文件的方式,因为带校验的工作流会检查原始dll的md5码,这个肯定是不匹配的。

- + \ No newline at end of file diff --git a/docs/help.html b/docs/help.html index 3fab3949e..a6c715d99 100644 --- a/docs/help.html +++ b/docs/help.html @@ -9,13 +9,13 @@ - + - + \ No newline at end of file diff --git a/docs/help/commonerrors.html b/docs/help/commonerrors.html index b2e66df96..d9303056f 100644 --- a/docs/help/commonerrors.html +++ b/docs/help/commonerrors.html @@ -9,7 +9,7 @@ - + @@ -26,7 +26,7 @@ 修复使用Momery Profiler创建快照时发生崩溃的bug 的改动合并到你当前版本即可。

profiler的 BeginSample和EndSample 无法生效

因为 BeginSample之类的函数有[Condition]编译注解,以Release方式编译dll时,会自动剔除这些代码,导致Profile失效。解决办法是以Developemnt方式编译热更新dll即可,代码如下。 如果你使用v3.0.2及更高版本,已经附带了HybridCLR/CompileDll/ActivedBuildTarget_Development菜单命令。

    var group = BuildPipeline.GetBuildTargetGroup(target);

ScriptCompilationSettings scriptCompilationSettings = new ScriptCompilationSettings();
scriptCompilationSettings.group = group;
scriptCompilationSettings.target = target;
if (developmentBuild)
{
// 核心是这行,使得以Debug模式编译dll,保留Profiler.BeginSample之类的函数调用。
scriptCompilationSettings.options |= ScriptCompilationOptions.DevelopmentBuild;
}
Directory.CreateDirectory(buildDir);
ScriptCompilationResult scriptCompilationResult = PlayerBuildInterface.CompilePlayerScripts(scriptCompilationSettings, buildDir);

iOS使用相机没有任何响应,但也不报错

这是WebCamTexture.devices未在AOT中保留导致。需要手动在AOT中引用 WebCamTexture.devices。

AVProMovieCapture插件工作不正常

由于AVProMovieCapture自身实现的原因,你需要先初始化插件,再进行HybridCLR的加载之类的操作。

EncodeImageAndMetadataIndex函数出现IL2CPP_ASSERT断言失败的错误

由于你们项目的热更新dll过大导致。解决办法:

  • 升级到v5.2.0+版本,支持最大64M的dll
  • 将热更新dll拆分成多个更小的dll

启动时执行AutomaticWorldBootstrap::Initialize过程中调用ResourceCatalogData::GetGUIDFromPath崩溃

你当前使用的entities版本不能直接使用Player Building中打包,必须安装com.unity.platforms,使用它单独的提供的打包方式,详细文档

Job.ScheduleBatch 崩溃

hybridclr与dots不兼容导致,商业化版本可以解决这个问题。

WebGL 运行时出现 function signature mismatch错误

WebGL平台打包时默认使用 faster (smaller) build选项,该选项会开启完全泛型共享,而社区版本必须补充元数据后才能与完全泛型共享机制配合工作。请依次尝试以下办法:

  1. 确保hybridclr为v4.0.0+版本,如果低于此版本请升级
  2. 尝试补充元数据,补充函数栈最顶部的c#代码所在的程序集
  3. 如果仍有问题,则可能是桥接函数与最终构建的包不匹配导致,比如说'Generate/all'时开启了'development'但构建时却未开启'development'。解决办法为使用构建时的参数,运行generate/all,清除build缓存后重新构造
  4. 如果仍有问题,将 Player SettingsIL2CPP Code Generation 切换到 Faster Runtime
  5. 如果仍有问题,升级到最新的hybridclr版本
  6. 如果极有问题,请联系我们技术支持

使用 Unity.netcode.runtime 后出现 NotSupportNative2Managed 桥接函数缺失异常

原因是 在Unity.netcode.runtime.dll中 NetworkManager.RpcReceiveHandler 是internal, 定义如下

internal delegate void RpcReceiveHandler(NetworkBehaviour behaviour, FastBufferReader reader, __RpcParams parameters);

导致生成工具没有为它生成桥接函数。但Unity又非常trick地在打包时为 标记了 [ClientRpc][ServerRpc] 的函数生成 RpcReceiveHandler 处理函数,并且引用了 internal 的RpcReceiveHandler类!居然没报错。 导致出现桥接函数缺失的问题。

解决办法为你在AOT工程里也定义一个相同签名的delegate。

    // 由于 __RpcParams也是internal的,我们这儿自己重新定义了一个一样的类型
public struct __RpcParams
#pragma warning restore IDE1006 // restore naming rule violation check
{
public ServerRpcParams Server;
public ClientRpcParams Client;
}

public delegate void MyRpcReceiveHandler(NetworkBehaviour behaviour, FastBufferReader reader, __RpcParams parameters);

- + \ No newline at end of file diff --git a/docs/help/faq.html b/docs/help/faq.html index da48fc88b..150f8cc33 100644 --- a/docs/help/faq.html +++ b/docs/help/faq.html @@ -9,13 +9,13 @@ - +
跳到主要内容

FAQ

HybridCLR支持哪些平台?

il2cpp支持的平台都支持

HybridCLR会增加多大的包体

以 2019版本为例,release模式下导出Android工程的libil2cpp.a文件, 原始版本12.69M,HybridCLR版本13.97M,也就是增加了大约1.3M。

为什么使用HybridCLR打出的包体增大很多

HybridCLR本身只会增加很少包体(1-2M)。包体增大很多是因为你错误地在link.xml保留了太多类,导致包体急剧增大。请自行参照Unity的裁剪规则优化。

HybridCLR是嵌了mono吗?

不是。HybridCLR给il2cpp补充了完全独立自主实现的完整的寄存器解释器。

HybridCLR写代码有什么限制吗?

几乎没有限制,参见未支持的特性

支持泛型类和泛型函数吗?

彻底完整的支持,无任何限制。

支持热更新MonoBehaviour吗?

完全支持。不仅能在代码中添加,也可以直接挂在热更新资源上。具体参见使用热更新MonoBehaviour

支持反射吗?

支持, 无任何限制。

对多线程支持如何?

完整支持。 支持Thread, Task, volatile, ThreadStatic, async。

支持多个Assembly吗?

支持。但是不会自动加载依赖dll。需要你手动按依赖顺序加载热更dll。

支持最多同时加载多少个dll?

最多可以同时加载3个最大64M的dll、16个最大16M的dll、64个最大4M的dll、255个最大1M的dll。也就是最多可以同时加载338个dll。

支持 .net standard 2.0 吗?

支持。但请注意,主工程打包用.net standard,而热更新dll打包必须用.net 4.x。详细解释请参照常见错误文档

支持Unity的DOTS框架吗?

支持。AOT部分的burst代码工作正常,但热更新部分的burst代码以解释方式执行。这个是显然的。

- + \ No newline at end of file diff --git a/docs/help/issue.html b/docs/help/issue.html index 9783e00cc..e5831dfb4 100644 --- a/docs/help/issue.html +++ b/docs/help/issue.html @@ -9,13 +9,13 @@ - +
跳到主要内容

BUG反馈模板

反馈bug前,请确认已经完成以下步骤:

  • 仔细查看常见错误文档,大多数新手问题都在里面。
  • 至此,如果还确定是bug,请按照下面 反馈模板 发给给技术客服(QQ1732047670)。

Bug反馈

如果确定是bug,请按以下 bug反馈模板提交issue(一些较大的如导出工程之类的文件不用提交),然后直接将issue反馈给技术客服,同时在QQ上附带材料(如导出工程之类)。

bug反馈模板

  • Unity Editor版本。如 2020.3.33
  • 操作系统。 如Win 10
  • 出错的BuildTarget。如 Android 64
  • com.code-philosophy.hybridclr的版本号。如v2.3.1
  • 截图及日志文件
  • 复现条件
  • 出错的c#代码位置(如果能定位出的话)
  • 免费用户必须提供符合以下条件的材料之一,否则会被拒绝,因为不符合标准的bug反馈信息会浪费我们太多时间,敬请理解。
    • 可复现的一段代码
    • 可复现的最小Unity项目,要求在hybridclr_trial基础上修改。并且打包后立即复现
    • Win 64可复现的导出Debug工程(必须启动即复现)及热更新dll(用于跟踪指令)
  • 商业化用户可以提供以下材料之一。
    • 可复现的最小Unity项目,尽量在hybridclr_trial基础上修改。
    • Win 64可复现的导出Debug工程(必须启动即复现)及热更新dll(用于跟踪指令)
    • Android (64或32)可复现的导出Debug工程,必须可以直接打包成功,不能有key store缺失之类的错误!!!必须build完后运行即可复现。
    • xcode 导出工程。必须运行即可复现。
- + \ No newline at end of file diff --git a/docs/intro.html b/docs/intro.html index bd923483a..630c32548 100644 --- a/docs/intro.html +++ b/docs/intro.html @@ -9,14 +9,14 @@ - +
跳到主要内容

HybridCLR

license

logo



HybridCLR是一个特性完整、零成本、高性能、低内存近乎完美的Unity全平台原生c#热更方案。

HybridCLR扩充了il2cpp的代码,使它由纯AOT runtime变成AOT+Interpreter 混合runtime,进而原生支持动态加载assembly,使得基于il2cpp backend打包的游戏不仅能在Android平台,也能在IOS、Consoles等限制了JIT的平台上高效地以AOT+interpreter混合模式执行,从底层彻底支持了热更新。

HybridCLR不仅支持传统的全解释执行模式,还开创性地实现了 Differential Hybrid Execution(DHE) 差分混合执行技术。即可以对AOT dll任意增删改,会智能地让变化或者新增的类和函数以interpreter模式运行,但未改动的类和函数以AOT方式运行,让热更新的游戏逻辑的运行性能基本达到原生AOT的水平。

欢迎拥抱现代原生C#热更新技术 !!!

特性

  • 特性完整。 近乎完整实现了ECMA-335规范,只有极少量的未支持特性特性。
  • 零学习和使用成本。 HybridCLR将纯AOT runtime增强为完整的runtime,使得热更新代码与AOT代码无缝工作。脚本类与AOT类在同一个运行时内,可以随意写继承、反射、多线程(volatile、ThreadStatic、Task、async)之类的代码。不需要额外写任何特殊代码、没有代码生成,几乎没有限制。
  • 执行高效。实现了一个极其高效的寄存器解释器,所有指标都大幅优于其他热更新方案。性能测试报告
  • 内存高效。 热更新脚本中定义的类跟普通c#类占用一样的内存空间,远优于其他热更新方案。内存占用报告
  • 由于对泛型的完美支持,使得因为AOT泛型问题跟il2cpp不兼容的库现在能够完美地在il2cpp下运行
  • 支持一些il2cpp不支持的特性,如makeref、 reftype、__refvalue指令
  • 独创性的Differential Hybrid Execution(DHE) 差分混合执行技术,让热更新的运行性能基本达到原生AOT的水平。

工作原理

HybridCLR从mono的 mixed mode execution 技术中得到启发,为unity的il2cpp runtime额外提供了interpreter模块,将它们由纯AOT运行时改造为AOT + Interpreter混合运行方式。

icon

更具体地说,HybridCLR做了以下几点工作:

  • 实现了一个高效的元数据(dll)解析库
  • 改造了元数据管理模块,实现了元数据的动态注册
  • 实现了一个IL指令集到自定义的寄存器指令集的compiler
  • 实现了一个高效的寄存器解释器
  • 额外提供大量的instinct函数,提升解释器性能

与其他流行的c#热更新方案的区别

HybridCLR是原生的c#热更新方案。通俗地说,il2cpp相当于mono的aot模块,HybridCLR相当于mono的interpreter模块,两者合一成为完整mono。HybridCLR使得il2cpp变成一个全功能的runtime,原生(即通过System.Reflection.Assembly.Load)支持动态加载dll,从而支持ios平台的热更新。

正因为HybridCLR是原生runtime级别实现,热更新部分的类型与主工程AOT部分类型是完全等价并且无缝统一的。可以随意调用、继承、反射、多线程,不需要生成代码或者写适配器。

其他热更新方案则是独立vm,与il2cpp的关系本质上相当于mono中嵌入lua的关系。因此类型系统不统一,为了让热更新类型能够继承AOT部分类型,需要写适配器,并且解释器中的类型不能为主工程的类型系统所识别。特性不完整、开发麻烦、运行效率低下。

支持的版本与平台

  • 支持2019.4.x、2020.3.x、2021.3.x、2022.3.x、2023.2.x、6000.x.y全系列LTS版本
  • 支持所有il2cpp支持的平台

低拒审风险

提示

HybridCLR在中国大陆地区非常流行,目前已经至少有数百款使用了HybridCLR的游戏上架了App Store和Google Play。

HybridCLR的底层原理仍然是解释执行,从这点来说与lua并无本质区别。因此符合App Store及Google Play商店的要求,并无特殊的拒审风险。而且因为HybridCLR与il2cpp的高度集成, 它甚至比lua方案要安全很多,拒审的概率很低。

关于作者

walonCode Philosophy(代码哲学) 创始人

毕业于清华大学物理系,2006年CMO金牌,奥数国家集训队成员,保送清华基科班。专注于游戏技术,擅长开发架构和基础技术设施。

license

HybridCLR is licensed under the MIT license

- + \ No newline at end of file diff --git a/docs/other.html b/docs/other.html index 34f79db04..324570956 100644 --- a/docs/other.html +++ b/docs/other.html @@ -9,13 +9,13 @@ - + - + \ No newline at end of file diff --git a/docs/other/businesscase.html b/docs/other/businesscase.html index e886f5d55..01aed67ad 100644 --- a/docs/other/businesscase.html +++ b/docs/other/businesscase.html @@ -9,7 +9,7 @@ - + @@ -20,7 +20,7 @@ 未使用热更新技术的App按25%计算,在使用热更新技术的Unity游戏中,使用HybridCLR的比例大约为26.7%。

类似的,我们统计了2024.2.2 App Store免费榜top500名中除了不在畅销榜中的326款游戏app的数据。其中Unity游戏有140款,非Unity游戏有186款。 Unity游戏中使用HybridCLR热更新的13款,使用lua热更新的有27款,没有使用热更新的有100款。 在使用热更新技术的Unity游戏中,使用HybridCLR的比例大约为32.5%。

我们大致可以得出结论,最新上线的表现最优秀的采用了热更新技术的Unity游戏App中,使用HybridCLR的比例在25%-35%之间

畅销榜前200名中使用HybridCLR的项目

有15款使用了HybridCLR技术。

appIDbundleID游戏名排名
996509117com.juzi.balls球球大作战64
1278845241com.yomob.yyzy月圆之夜196
6469103697com.cyou.xxygb西游:笔绘西行24
891616303com.zengame.xzdd指尖四川麻将-主播最爱麻将26
6451241596com.cmge.dpcq.ios斗破苍穹:巅峰对决34
1037198513com.sanguosha.sgsol三国杀OL39
6470348464com.caohua.hymc荒野迷城-废土求生52
6444853360com.xxzyios.xh行侠仗义五千年-国风割草手游73
1508509620com.minigame.skyforce.os孤独战机88
1562260653com.zy.wqmt.cn无期迷途106
6448353442com.ddsw.zombiewaves.zw手机反恐特别行动137
6475003560com.hero.rpg.xj52q星际52区142
6446756603com.leiting.dragonraider飞吧龙骑士-东方火龙145
1561085506com.chillyroom.soulknightprequel元气骑士前传154
6476655022com.csbyios.game传世霸业-正版授权 复古高爆端游移植192

免费榜前500名中使用HybridCLR的项目

有51款使用了HybridCLR技术。

appIDbundleID游戏名排名
1561085506com.chillyroom.soulknightprequel元气骑士前传4
1669193324ioa.chenz.leisure.gn我的休闲时光5
6451466022com.m76cf.kumqffwjv三国吧兄弟-休闲割草解压手游9
1619727250com.shengtiangames.clwg潮灵王国:起源13
6451241596com.cmge.dpcq.ios斗破苍穹:巅峰对决13
6470348464com.caohua.hymc荒野迷城-废土求生19
6446756603com.leiting.dragonraider飞吧龙骑士-东方火龙27
6444853360com.xxzyios.xh行侠仗义五千年-国风割草手游30
6469103697com.cyou.xxygb西游:笔绘西行43
1508509620com.minigame.skyforce.os孤独战机45
1576661186com.xd.t3game火力苏打(T3)72
6448353442com.ddsw.zombiewaves.zw手机反恐特别行动83
1590274853com.Sunborn.SnqxExilium少女前线2:追放93
891616303com.zengame.xzdd指尖四川麻将-主播最爱麻将112
6449481979com.honggame.yzmj.txzr天选之人112
6450107254com.qmjh.qmjhios全民江湖-热血江湖正版手游115
1544895560com.dgames.g15002002.apple宿命回响:弦上的叹息122
6476655022com.csbyios.game传世霸业-正版授权 复古高爆端游移植144
6449188289com.zhaimiao.ygl摇光录:乱世公主148
1546338773badminton.blitz.sports.free.game.cn.ios决战羽毛球 - 联机对战149
6475003560com.hero.rpg.xj52q星际52区160
1434798394com.jys.qipa奇葩战斗家164
1102002812com.zongyi.ndoudizhu斗地主经典版-单机游戏欢乐版棋牌残局173
1663156162com.youzu.shjh.ios山海镜花-归来173
1594550177com.sfgame.zq台球王者-3D真人版175
6452948314com.qulu.yongzhemijing.yzkdk勇者无敌-割草休闲动作游戏186
1583935305com.bkkj.js1朕的江山2:三国策略国战192
1585105852com.feelingtouch.zfsniper.cn僵尸前线3D-末日狙击战争手游196
1507863649com.yongshi.tenojo.ios深空之眼203
1634133454com.da.china飞龙岛历险记209
1523017982com.leiting.mole摩尔庄园212
6447148068com.yofijoy.fcdjios方寸对决212
1629567830com.gwstudio.pixelpeerless.cn勇者秘境218
1562260653com.zy.wqmt.cn无期迷途241
6446361475com.zhsm.sjxh.cn.ios战火使命246
1517370204com.rsg.wdwtApp闪亮的你-娱乐圈养成游戏247
1641223717com.saiyun.wshty我是航天员257
1562937112com.mzzcnew.0414末日之城-策略塔防 卡牌放置258
6443768967com.racoondigi.jqys街球艺术-重新定义街球手游259
1636463825com.leiting.picatown皮卡堂之梦想起源268
1037198513com.sanguosha.sgsol三国杀OL269
1326740391com.setagame.survivordangerzone幸存者危城-末日生存僵尸游戏285
6447829907com.hoolai.qsmy.ios秦时明月:沧海311
6443928984com.hiplay.mergeland.cn糖果精灵传奇-爱丽丝合合仙境320
6450256080com.altgsqj.yhsjMU变态版:永恒世纪358
1673645173com.ra.xkzhanz.ios虚空战争390
1448486874com.bilibili.queji雀姬麻将417
1667162072com.wingjoy.coderustle2不一样传说 2418
6471483492com.saiyun3.wjmc超燃之战-3D割草428
1641048780com.yoozoo.ik.cn战火与永恒451
6469281520com.man4fun.lsz琉生传490

按头部公司统计的使用HybridCLR的项目

公司上线项目
funplusBingo Aloha
散爆流浪地球
畅游ハイキュ-!!FLY HIGH
畅游JUMP:群星集结
网易瑶台
百度希壤
紫龙钢岚
- + \ No newline at end of file diff --git a/docs/other/changelog.html b/docs/other/changelog.html index 9f1295886..616bbfa3f 100644 --- a/docs/other/changelog.html +++ b/docs/other/changelog.html @@ -9,13 +9,13 @@ - +
跳到主要内容

改动日志

此文档只记录关键性事件,更具体的发布日志请看 RELEASELOG

  • 2024.6.11 发布v6.0.0版本,正式支持2023和6000
  • 2024.5.29 支持visionOS
  • 2024.1.26 发布v5.0.0版本,重新支持2019。更新到2021.3.34和2022.3.17版本。
  • 2023.10.11 支持2022.3.11
  • 2023.10.10 更新到最新的2021.3.31和2022.3.10版本
  • 2023.08.24 重构桥接函数,支持所有il2cpp支持的平台
  • 2023.07.11 支持 2021.3.28和2022.3.4
  • 2023.06.29 支持2023.2.0a20版本
  • 2023.06.25 支持增量式GC
  • 2023.06.10 支持完全泛型共享
  • 2023.05.21 发布v3.0.0,正式支持 2022.3.0
  • 2023.05.19 更新到Unity最新LTS版本 2020.3.48及2021.3.25。已经完成最后一个2020LTS版本支持。
  • 2023.04.26 更新到Unity最新LTS版本 2020.3.47及2021.3.23
  • 2023.03.20 com.code-philosophy.hybridclr生成AOTGenericReference时包含aot assembly列表及美化的泛型类及函数名
  • 2023.3.11 更新到Unity最新LTS版本 2020.3.46及2021.3.20
  • 2023.2.4 2021版本WebGL平台支持资源上挂载脚本
  • 2023.2.4 完成DHE最终版本
  • 2023.2.3 发布2.0版本
  • 2023.1.14 更新到Unity最新LTS版本 2020.3.43及2021.3.16
  • 2022.12.08 支持netstandard 2.0
  • 2022.11.28 正式启动多分支管理hybridclr和il2cpp_plus,同时创建1.0正式版本分支
  • 2022.11.24 更新到Unity最新LTS版本 2020.3.42及2021.3.14
  • 2022.11.21 DHE可以正确处理AOT函数中涉及的元数据及泛型
  • 2022.11.09 更新到Unity最新LTS版本 2020.3.41及2021.3.13
  • 2022.11.07 开启LTS版本,进入稳定维护的阶段。
  • 2022.10.26 官方主QQ群满3000人!
  • 2022.10.26 支持SuperSet元数据模式。可以直接加载原始AOT dll,简化了打包工作流。
  • 2022.10.19 WebGL平台跑通了除了平台自身限制外的所有测试用例,并且支持资源上挂载脚本。
  • 2022.10.08 支持profile
  • 2022.9.23 实现了完善的工作流工具,一键打包
  • 2022.8.29 更新2020.3.33到最新的2020.3.38版本,更新2021.3.1到最新的2020.3.8版本!hybridclr_trial安装器支持使用兼容版本,不再强制要求安装分支对应版本。
  • 2022.8.27 支持Unity 2019.4.x LTS系列版本
  • 2022.8.9 正式支持macOS intel+silicon 平台
  • 2022.7.24 支持MonoPInvokeCallbackAttribute。
  • 2022.7.22 正式支持Unity 2021 LTS系列版本。
  • 2022.7.15 支持WebGL平台,跑通几乎所有单元测试(1600多个单元测试仅有11个未通过)
  • 2022.7.14 支持Win 32(x86)
  • 2022.7.13 与UWA合作课程上线
  • 2022.7.10 正式支持Android armv7 32位版本!!!
  • 2022.7.6 改名HybridCLR
  • 2022.7.4 github star数破2000
  • 2022.6.7 上线了第一个Android、iOS双平台的中度游戏
  • 2022.5.31 第一次在PC、Android、iOS 三平台,完整稳定流畅运行起一个重度RPG卡牌项目!!!
  • 2022.5.19 有两个完整跑通的Android项目
  • 2022.5.18 支持Android(armv8)和iOS(64)平台!!!跑通所有单元测试
  • 2022.4.19 QQ主群人数破1000!!!
  • 2022.4.16 首次在PC平台完整运行一个大型MMORPG项目
  • 2022.3.23 开源
  • 2022.3.19 跑通了第一个小游戏2048
  • 2022.3.16
    • 利用泛型共享机制,解决了一部分AOT泛型问题
    • 正常加载luban配置,第一次运行一个较大规模的代码
  • 2022.3.6
    • 支持泛型函数(普通泛型函数和虚泛型函数)
  • 2022.3.3
    • 支持泛型类
    • 支持delegate调用
  • 2022.2.27 版本
    • 完整支持异常机制
  • 2022.2.25 版本
    • 实现完整的寄存器指令集
  • 2022.1.16 preview 2版本
    • 支持 interface
    • 支持泛型interface
    • 支持对值类型的处理
    • 实现了较完整的IL指令集支持
  • 2022.1.10 发布 preview 1版本
    • 支持对象定义和继承
    • 支持虚函数调用
    • 使用原始IL指令集,支持所有基础指令:如数值计算、分支跳转
  • 2021.12.19 开始开发
  • 2021.11.2 创建了HybridCLR QQ主群
- + \ No newline at end of file diff --git a/docs/other/contactme.html b/docs/other/contactme.html index 82bdaa52a..662c89a20 100644 --- a/docs/other/contactme.html +++ b/docs/other/contactme.html @@ -9,13 +9,13 @@ - + - + \ No newline at end of file diff --git a/docs/other/donate.html b/docs/other/donate.html index 24bd9c3d7..dcd862d91 100644 --- a/docs/other/donate.html +++ b/docs/other/donate.html @@ -9,13 +9,13 @@ - +
跳到主要内容

致谢名单

感谢这些朋友的慷慨赞助!!!

李旭,慷概解囊捐赠 20000

- + \ No newline at end of file diff --git a/docs/other/relativepojects.html b/docs/other/relativepojects.html index 29909cd9b..807b644c5 100644 --- a/docs/other/relativepojects.html +++ b/docs/other/relativepojects.html @@ -9,13 +9,13 @@ - +
跳到主要内容

相关的第三方项目

- + \ No newline at end of file diff --git a/docs/other/roadmap.html b/docs/other/roadmap.html index e09686420..c2f70a1de 100644 --- a/docs/other/roadmap.html +++ b/docs/other/roadmap.html @@ -9,13 +9,13 @@ - + - + \ No newline at end of file diff --git a/docs/pro.html b/docs/pro.html index eca3f0dee..7ca68a74e 100644 --- a/docs/pro.html +++ b/docs/pro.html @@ -9,13 +9,13 @@ - + - + \ No newline at end of file diff --git a/docs/reload.html b/docs/reload.html index bded9901b..590c8d7c3 100644 --- a/docs/reload.html +++ b/docs/reload.html @@ -9,13 +9,13 @@ - + - + \ No newline at end of file diff --git a/docs/ultimate.html b/docs/ultimate.html index 6f67d59d9..813ad0a3a 100644 --- a/docs/ultimate.html +++ b/docs/ultimate.html @@ -9,13 +9,13 @@ - +
跳到主要内容
- + \ No newline at end of file diff --git a/en/404.html b/en/404.html index d065ffa55..5fa451da0 100644 --- a/en/404.html +++ b/en/404.html @@ -9,13 +9,13 @@ - +
Skip to main content

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

- + \ No newline at end of file diff --git a/en/assets/js/aa55467b.b4511e05.js b/en/assets/js/aa55467b.7c964d75.js similarity index 67% rename from en/assets/js/aa55467b.b4511e05.js rename to en/assets/js/aa55467b.7c964d75.js index 82b7f4d94..0183c42c1 100644 --- a/en/assets/js/aa55467b.b4511e05.js +++ b/en/assets/js/aa55467b.7c964d75.js @@ -1 +1 @@ -"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[6690],{3905:(e,t,n)=>{n.d(t,{Zo:()=>d,kt:()=>f});var a=n(7294);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function l(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var s=a.createContext({}),c=function(e){var t=a.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):l(l({},t),e)),n},d=function(e){var t=c(e.components);return a.createElement(s.Provider,{value:t},e.children)},u="mdxType",p={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},m=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,o=e.originalType,s=e.parentName,d=i(e,["components","mdxType","originalType","parentName"]),u=c(n),m=r,f=u["".concat(s,".").concat(m)]||u[m]||p[m]||o;return n?a.createElement(f,l(l({ref:t},d),{},{components:n})):a.createElement(f,l({ref:t},d))}));function f(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var o=n.length,l=new Array(o);l[0]=m;var i={};for(var s in t)hasOwnProperty.call(t,s)&&(i[s]=t[s]);i.originalType=e,i[u]="string"==typeof e?e:r,l[1]=i;for(var c=2;c{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>l,default:()=>p,frontMatter:()=>o,metadata:()=>i,toc:()=>c});var a=n(7462),r=(n(7294),n(3905));const o={},l="Hot Reload Technology",i={unversionedId:"business/reload/hotreloadassembly",id:"business/reload/hotreloadassembly",title:"Hot Reload Technology",description:"Hot reload technology is used to completely unload or reload an assembly, suitable for small game collections. This solution is only available in the commercial version.",source:"@site/i18n/en/docusaurus-plugin-content-docs/current/business/reload/hotreloadassembly.md",sourceDirName:"business/reload",slug:"/business/reload/hotreloadassembly",permalink:"/en/docs/business/reload/hotreloadassembly",draft:!1,tags:[],version:"current",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"Quick Start",permalink:"/en/docs/business/reload/quickstart"},next:{title:"Free Trial",permalink:"/en/docs/business/reload/freetrial"}},s={},c=[{value:"Supported Features",id:"supported-features",level:2},{value:"Unsupported Features and Special Requirements",id:"unsupported-features-and-special-requirements",level:2},{value:"Incompatible Libraries",id:"incompatible-libraries",level:2},{value:"Resolving References to Unloaded Objects",id:"resolving-references-to-unloaded-objects",level:2}],d={toc:c},u="wrapper";function p(e){let{components:t,...n}=e;return(0,r.kt)(u,(0,a.Z)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h1",{id:"hot-reload-technology"},"Hot Reload Technology"),(0,r.kt)("p",null,"Hot reload technology is used to completely unload or reload an assembly, suitable for small game collections. This solution is only available in the ",(0,r.kt)("strong",{parentName:"p"},"commercial version"),"."),(0,r.kt)("h2",{id:"supported-features"},"Supported Features"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Supports unloading assemblies, freeing up 100% of the memory occupied by the assembly."),(0,r.kt)("li",{parentName:"ul"},"Supports reloading assemblies, allowing code to change arbitrarily or even be completely different (with certain limitations on MonoBehaviour and Scriptable)."),(0,r.kt)("li",{parentName:"ul"},"Supports ",(0,r.kt)("strong",{parentName:"li"},"restricting the set of functions that can be accessed in hot-updated assemblies"),", suitable for creating sandbox environments in UGC games to prevent malicious player code from causing damage.")),(0,r.kt)("h2",{id:"unsupported-features-and-special-requirements"},"Unsupported Features and Special Requirements"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Ensure that the business code no longer uses objects or functions from the unloaded Assembly and exits all executing old logic."),(0,r.kt)("li",{parentName:"ul"},"You cannot directly unload a dependent Assembly; you must unload the dependents before the dependencies in reverse dependency order. For example, if A.dll depends on B.dll, you need to unload A.dll first, then B.dll."),(0,r.kt)("li",{parentName:"ul"},"Related to MonoBehaviour and ScriptableObject:",(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},"Ensure that overloaded MonoBehaviour event or message functions like Awake and OnEnable are not added or removed (but the function body can change)."),(0,r.kt)("li",{parentName:"ul"},"Ensure that the serialized field names in the script classes with the same name in the old Assembly do not change (the types can change)."),(0,r.kt)("li",{parentName:"ul"},"If the field type is a custom type A (class, struct, or enum) from an unloadable Assembly, it must be marked with the ",(0,r.kt)("inlineCode",{parentName:"li"},"[Serializable]")," attribute."),(0,r.kt)("li",{parentName:"ul"},"Field types such as ",(0,r.kt)("inlineCode",{parentName:"li"},"List<A>"),", where A is a type from an unloadable Assembly, are not supported; replace them with ",(0,r.kt)("inlineCode",{parentName:"li"},"A[]"),"."),(0,r.kt)("li",{parentName:"ul"},"Generic types cannot be inherited, e.g., ",(0,r.kt)("inlineCode",{parentName:"li"},"class MyScript : CommonScript"),"."))),(0,r.kt)("li",{parentName:"ul"},"Some libraries that cache reflection information (most common in serialization-related libraries like LitJson) need to clear cached reflection information after hot reload."),(0,r.kt)("li",{parentName:"ul"},"Destructors ",(0,r.kt)("inlineCode",{parentName:"li"},"~XXX()")," are not supported. Instantiating generic classes with destructors where the generic parameter is a type from the current Assembly is also not allowed."),(0,r.kt)("li",{parentName:"ul"},"Incompatible with DOTS. Due to the extensive caching of type information and the complexity of implementation, it is difficult to individually clear the cached information.")),(0,r.kt)("h2",{id:"incompatible-libraries"},"Incompatible Libraries"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Jobs in 2022 cache type-related information and require minor ",(0,r.kt)("a",{parentName:"li",href:"/en/docs/business/reload/modifydll"},"modifications to UnityEngine.CoreModule.dll")," code. Versions earlier than 2022 do not require modifications."),(0,r.kt)("li",{parentName:"ul"},"Deserialization libraries like LitJson cache reflection information and need to clear the cached reflection information in the library after hot reload. The specific operation depends on the implementation of the library.")),(0,r.kt)("h2",{id:"resolving-references-to-unloaded-objects"},"Resolving References to Unloaded Objects"),(0,r.kt)("p",null,"Hot reload technology requires that metadata of unloaded assembly U cannot be held in the assembly or global memory that has not been unloaded. This includes, but is not limited to:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Instances of types in the unloaded assembly"),(0,r.kt)("li",{parentName:"ul"},"Generic parameters of generic classes or functions that include types from the unloaded assembly"),(0,r.kt)("li",{parentName:"ul"},"Reflection information related to the unloaded assembly, such as Assembly, Type, MethodInfo, PropertyInfo, etc."),(0,r.kt)("li",{parentName:"ul"},"Delegates pointing to functions in the unloaded assembly"),(0,r.kt)("li",{parentName:"ul"},"Tasks defined in the unloaded assembly"),(0,r.kt)("li",{parentName:"ul"},"Others")),(0,r.kt)("p",null,"Real-world projects can be complex, and it is difficult and impractical for developers to find all illegal references. We have implemented illegal reference checks, and when unloading, logs of all illegal references will be printed. Developers can clear all illegal references based on the printed logs."))}p.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[6690],{3905:(e,t,n)=>{n.d(t,{Zo:()=>d,kt:()=>f});var a=n(7294);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function l(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var s=a.createContext({}),c=function(e){var t=a.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):l(l({},t),e)),n},d=function(e){var t=c(e.components);return a.createElement(s.Provider,{value:t},e.children)},u="mdxType",p={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},m=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,o=e.originalType,s=e.parentName,d=i(e,["components","mdxType","originalType","parentName"]),u=c(n),m=r,f=u["".concat(s,".").concat(m)]||u[m]||p[m]||o;return n?a.createElement(f,l(l({ref:t},d),{},{components:n})):a.createElement(f,l({ref:t},d))}));function f(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var o=n.length,l=new Array(o);l[0]=m;var i={};for(var s in t)hasOwnProperty.call(t,s)&&(i[s]=t[s]);i.originalType=e,i[u]="string"==typeof e?e:r,l[1]=i;for(var c=2;c{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>l,default:()=>p,frontMatter:()=>o,metadata:()=>i,toc:()=>c});var a=n(7462),r=(n(7294),n(3905));const o={},l="Hot Reload Technology",i={unversionedId:"business/reload/hotreloadassembly",id:"business/reload/hotreloadassembly",title:"Hot Reload Technology",description:"Hot reload technology is used to completely unload or reload an assembly, suitable for small game collections. This solution is only available in the commercial version.",source:"@site/i18n/en/docusaurus-plugin-content-docs/current/business/reload/hotreloadassembly.md",sourceDirName:"business/reload",slug:"/business/reload/hotreloadassembly",permalink:"/en/docs/business/reload/hotreloadassembly",draft:!1,tags:[],version:"current",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"Quick Start",permalink:"/en/docs/business/reload/quickstart"},next:{title:"Free Trial",permalink:"/en/docs/business/reload/freetrial"}},s={},c=[{value:"Supported Features",id:"supported-features",level:2},{value:"Unsupported Features and Special Requirements",id:"unsupported-features-and-special-requirements",level:2},{value:"Incompatible Libraries",id:"incompatible-libraries",level:2},{value:"Resolving References to Unloaded Objects",id:"resolving-references-to-unloaded-objects",level:2}],d={toc:c},u="wrapper";function p(e){let{components:t,...n}=e;return(0,r.kt)(u,(0,a.Z)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h1",{id:"hot-reload-technology"},"Hot Reload Technology"),(0,r.kt)("p",null,"Hot reload technology is used to completely unload or reload an assembly, suitable for small game collections. This solution is only available in the ",(0,r.kt)("strong",{parentName:"p"},"commercial version"),"."),(0,r.kt)("h2",{id:"supported-features"},"Supported Features"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Supports unloading assemblies, freeing up 100% of the memory occupied by the assembly."),(0,r.kt)("li",{parentName:"ul"},"Supports reloading assemblies, allowing code to change arbitrarily or even be completely different (with certain limitations on MonoBehaviour and Scriptable)."),(0,r.kt)("li",{parentName:"ul"},"Supports ",(0,r.kt)("strong",{parentName:"li"},"restricting the set of functions that can be accessed in hot-updated assemblies"),", suitable for creating sandbox environments in UGC games to prevent malicious player code from causing damage.")),(0,r.kt)("h2",{id:"unsupported-features-and-special-requirements"},"Unsupported Features and Special Requirements"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Ensure that the business code no longer uses objects or functions from the unloaded Assembly and exits all executing old logic."),(0,r.kt)("li",{parentName:"ul"},"You cannot directly unload a dependent Assembly; you must unload the dependents before the dependencies in reverse dependency order. For example, if A.dll depends on B.dll, you need to unload A.dll first, then B.dll."),(0,r.kt)("li",{parentName:"ul"},"Related to MonoBehaviour and ScriptableObject:",(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},"Ensure that overloaded MonoBehaviour event or message functions like Awake and OnEnable are not added or removed (but the function body can change)."),(0,r.kt)("li",{parentName:"ul"},"Ensure that the serialized field names in the script classes with the same name in the old Assembly do not change (the types can change)."),(0,r.kt)("li",{parentName:"ul"},"If the field type is a custom type A (class, struct, or enum) from an unloadable Assembly, it must be marked with the ",(0,r.kt)("inlineCode",{parentName:"li"},"[Serializable]")," attribute."),(0,r.kt)("li",{parentName:"ul"},"Field types such as ",(0,r.kt)("inlineCode",{parentName:"li"},"List"),", where A is a type from an unloadable Assembly, are not supported; replace them with ",(0,r.kt)("inlineCode",{parentName:"li"},"A[]"),"."),(0,r.kt)("li",{parentName:"ul"},"Generic types cannot be inherited, e.g., ",(0,r.kt)("inlineCode",{parentName:"li"},"class MyScript : CommonScript"),"."))),(0,r.kt)("li",{parentName:"ul"},"Some libraries that cache reflection information (most common in serialization-related libraries like LitJson) need to clear cached reflection information after hot reload."),(0,r.kt)("li",{parentName:"ul"},"Destructors ",(0,r.kt)("inlineCode",{parentName:"li"},"~XXX()")," are not supported. Instantiating generic classes with destructors where the generic parameter is a type from the current Assembly is also not allowed."),(0,r.kt)("li",{parentName:"ul"},"Incompatible with DOTS. Due to the extensive caching of type information and the complexity of implementation, it is difficult to individually clear the cached information.")),(0,r.kt)("h2",{id:"incompatible-libraries"},"Incompatible Libraries"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Jobs in 2022 cache type-related information and require minor ",(0,r.kt)("a",{parentName:"li",href:"/en/docs/business/reload/modifydll"},"modifications to UnityEngine.CoreModule.dll")," code. Versions earlier than 2022 do not require modifications."),(0,r.kt)("li",{parentName:"ul"},"Deserialization libraries like LitJson cache reflection information and need to clear the cached reflection information in the library after hot reload. The specific operation depends on the implementation of the library.")),(0,r.kt)("h2",{id:"resolving-references-to-unloaded-objects"},"Resolving References to Unloaded Objects"),(0,r.kt)("p",null,"Hot reload technology requires that metadata of unloaded assembly U cannot be held in the assembly or global memory that has not been unloaded. This includes, but is not limited to:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Instances of types in the unloaded assembly"),(0,r.kt)("li",{parentName:"ul"},"Generic parameters of generic classes or functions that include types from the unloaded assembly"),(0,r.kt)("li",{parentName:"ul"},"Reflection information related to the unloaded assembly, such as Assembly, Type, MethodInfo, PropertyInfo, etc."),(0,r.kt)("li",{parentName:"ul"},"Delegates pointing to functions in the unloaded assembly"),(0,r.kt)("li",{parentName:"ul"},"Tasks defined in the unloaded assembly"),(0,r.kt)("li",{parentName:"ul"},"Others")),(0,r.kt)("p",null,"Real-world projects can be complex, and it is difficult and impractical for developers to find all illegal references. We have implemented illegal reference checks, and when unloading, logs of all illegal references will be printed. Developers can clear all illegal references based on the printed logs."))}p.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/en/assets/js/runtime~main.95569b58.js b/en/assets/js/runtime~main.e472ef8e.js similarity index 99% rename from en/assets/js/runtime~main.95569b58.js rename to en/assets/js/runtime~main.e472ef8e.js index f4be9e7ea..7ad6d5e22 100644 --- a/en/assets/js/runtime~main.95569b58.js +++ b/en/assets/js/runtime~main.e472ef8e.js @@ -1 +1 @@ -(()=>{"use strict";var e,a,c,d,f,b={},t={};function r(e){var a=t[e];if(void 0!==a)return a.exports;var c=t[e]={exports:{}};return b[e].call(c.exports,c,c.exports,r),c.exports}r.m=b,e=[],r.O=(a,c,d,f)=>{if(!c){var b=1/0;for(i=0;i=f)&&Object.keys(r.O).every((e=>r.O[e](c[o])))?c.splice(o--,1):(t=!1,f0&&e[i-1][2]>f;i--)e[i]=e[i-1];e[i]=[c,d,f]},r.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return r.d(a,{a:a}),a},c=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,r.t=function(e,d){if(1&d&&(e=this(e)),8&d)return e;if("object"==typeof e&&e){if(4&d&&e.__esModule)return e;if(16&d&&"function"==typeof e.then)return e}var f=Object.create(null);r.r(f);var b={};a=a||[null,c({}),c([]),c(c)];for(var t=2&d&&e;"object"==typeof t&&!~a.indexOf(t);t=c(t))Object.getOwnPropertyNames(t).forEach((a=>b[a]=()=>e[a]));return b.default=()=>e,r.d(f,b),f},r.d=(e,a)=>{for(var c in a)r.o(a,c)&&!r.o(e,c)&&Object.defineProperty(e,c,{enumerable:!0,get:a[c]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce(((a,c)=>(r.f[c](e,a),a)),[])),r.u=e=>"assets/js/"+({26:"9f3352c6",33:"6582a1da",53:"935f2afb",60:"7008f309",145:"1de28ba9",321:"c637a75c",439:"18ac6946",499:"01e35b3c",639:"0f26255b",687:"ad4ac6bf",1171:"d896988d",1175:"5eaad662",1693:"12aa5f76",1811:"7c709933",1904:"756c405a",1970:"53decd28",2034:"21ad55e6",2163:"1f4f5b8d",2241:"fa5dbaff",2261:"528693c6",2365:"a7626ec9",2535:"814f3328",2616:"e9748e8f",2757:"9e04c579",2828:"b7eeea20",2854:"08a2e2e3",2857:"cab0a0b1",2896:"e8c48351",2990:"743ad547",3089:"a6aa9e1f",3092:"5d75b024",3134:"31bbbef9",3378:"e4a581d5",3608:"9e4087bc",3665:"cd40287e",3777:"303a7ab0",3814:"ff80323f",3836:"f6cbeee1",4106:"3bd86665",4191:"42d29336",4195:"c4f5d8e4",4255:"e272dbc8",4340:"e1c419d2",4364:"fba6c282",4449:"359abbd3",4569:"39b1bd06",4812:"f97df8c0",4916:"90ae8e83",5133:"3d63e4cd",5306:"29217152",5428:"45bec6ca",5638:"d243f57f",5657:"3dd68940",5672:"eacdb088",5746:"5a96aca1",6103:"ccc49370",6686:"a73ec7fd",6690:"aa55467b",6727:"a2720703",7020:"ba76a366",7198:"82b0332e",7561:"1216cbe4",7566:"aac64df7",7589:"0ccd1bc3",7738:"530cdc5e",7842:"a6590bfd",7906:"50ff9c4d",7918:"17896441",7920:"1a4e3797",7995:"375669b6",8052:"b7e34b9a",8091:"05ba35d7",8373:"f9d8bc1b",8572:"673d675b",8656:"757481ff",8699:"445802cd",8983:"09708493",9024:"02b1d4e6",9107:"82373234",9167:"369df949",9178:"dd55ff2d",9225:"5b44acae",9235:"a85eafba",9251:"4d918de2",9388:"64772a77",9451:"355d470d",9479:"0874f5ae",9514:"1be78505",9570:"40a20ee1",9643:"a2309c10",9695:"27f4d295",9722:"7b9528aa",9731:"c225ef2b",9792:"8fc0d838",9817:"14eb3368",9892:"1a8c3b80",9973:"e2c08c7d"}[e]||e)+"."+{26:"e04c55de",33:"e957d922",53:"4d91f531",60:"79a620ab",145:"c6d01e7d",321:"8f5474e8",439:"da4b7714",499:"c5491186",639:"0e944f9d",687:"c516a8de",1171:"dca3ec8e",1175:"02e9a659",1426:"de2b7f72",1693:"a0ca1f60",1811:"7cc37b2e",1904:"936046dd",1970:"a57b0074",2034:"e6a67a2a",2163:"84c11ff6",2241:"02b5e2b9",2261:"01eabc1a",2365:"1a7ed67a",2535:"184cc1e2",2616:"8ff4f04a",2757:"ccf90f29",2828:"cbb0c6af",2854:"56d696de",2857:"11a341e7",2896:"48347776",2990:"337aa9b3",3089:"d1467cbe",3092:"a97fbbb4",3134:"3a234ef2",3378:"7929c073",3608:"e989768d",3665:"fb7c1c9c",3777:"365866cc",3814:"682699bc",3836:"ce2b79a4",4106:"3477426f",4191:"c09a9f73",4195:"e054211d",4255:"069044fe",4340:"7629659a",4364:"ada73ec4",4449:"29132be4",4569:"443223c6",4812:"ec28707c",4916:"0e6b5afa",4972:"3d0f496c",5133:"ad5fd11b",5306:"e8ae6ee8",5428:"becbb798",5638:"d8e8d6ed",5657:"00eba38b",5672:"491150b1",5746:"f49f1414",6048:"779f8c90",6103:"1d3911bc",6186:"170d1bc9",6686:"08a9c34c",6690:"b4511e05",6727:"552e21ef",6945:"94f4a660",7020:"d6d0e9af",7198:"2d5679e9",7561:"8e03a462",7566:"942ebb73",7589:"199747a5",7738:"33315437",7842:"a849f3ec",7906:"354701ea",7918:"a70e39a9",7920:"275f830e",7995:"f3933e33",8052:"139bf164",8091:"6a9d4f9c",8373:"224eb03e",8572:"b687203c",8656:"189e347f",8699:"66f7870b",8894:"91734414",8983:"7bc4227d",9024:"2e97ef25",9107:"9e8525e0",9167:"c8d26917",9178:"c0c79448",9225:"570487f1",9235:"aa0c15e0",9251:"2d6d3f2f",9388:"8c2f72f9",9451:"a68c3273",9479:"8deab972",9514:"d5cf2d0b",9570:"3d816f98",9643:"3572f86d",9695:"5df73254",9722:"07bf8fc9",9731:"0b7db44c",9792:"e35ef483",9817:"91502cf3",9892:"8cb56873",9973:"bb736e33"}[e]+".js",r.miniCssF=e=>{},r.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),r.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),d={},f="my-website:",r.l=(e,a,c,b)=>{if(d[e])d[e].push(a);else{var t,o;if(void 0!==c)for(var n=document.getElementsByTagName("script"),i=0;i{t.onerror=t.onload=null,clearTimeout(s);var f=d[e];if(delete d[e],t.parentNode&&t.parentNode.removeChild(t),f&&f.forEach((e=>e(c))),a)return a(c)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:t}),12e4);t.onerror=l.bind(null,t.onerror),t.onload=l.bind(null,t.onload),o&&document.head.appendChild(t)}},r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.p="/en/",r.gca=function(e){return e={17896441:"7918",29217152:"5306",82373234:"9107","9f3352c6":"26","6582a1da":"33","935f2afb":"53","7008f309":"60","1de28ba9":"145",c637a75c:"321","18ac6946":"439","01e35b3c":"499","0f26255b":"639",ad4ac6bf:"687",d896988d:"1171","5eaad662":"1175","12aa5f76":"1693","7c709933":"1811","756c405a":"1904","53decd28":"1970","21ad55e6":"2034","1f4f5b8d":"2163",fa5dbaff:"2241","528693c6":"2261",a7626ec9:"2365","814f3328":"2535",e9748e8f:"2616","9e04c579":"2757",b7eeea20:"2828","08a2e2e3":"2854",cab0a0b1:"2857",e8c48351:"2896","743ad547":"2990",a6aa9e1f:"3089","5d75b024":"3092","31bbbef9":"3134",e4a581d5:"3378","9e4087bc":"3608",cd40287e:"3665","303a7ab0":"3777",ff80323f:"3814",f6cbeee1:"3836","3bd86665":"4106","42d29336":"4191",c4f5d8e4:"4195",e272dbc8:"4255",e1c419d2:"4340",fba6c282:"4364","359abbd3":"4449","39b1bd06":"4569",f97df8c0:"4812","90ae8e83":"4916","3d63e4cd":"5133","45bec6ca":"5428",d243f57f:"5638","3dd68940":"5657",eacdb088:"5672","5a96aca1":"5746",ccc49370:"6103",a73ec7fd:"6686",aa55467b:"6690",a2720703:"6727",ba76a366:"7020","82b0332e":"7198","1216cbe4":"7561",aac64df7:"7566","0ccd1bc3":"7589","530cdc5e":"7738",a6590bfd:"7842","50ff9c4d":"7906","1a4e3797":"7920","375669b6":"7995",b7e34b9a:"8052","05ba35d7":"8091",f9d8bc1b:"8373","673d675b":"8572","757481ff":"8656","445802cd":"8699","09708493":"8983","02b1d4e6":"9024","369df949":"9167",dd55ff2d:"9178","5b44acae":"9225",a85eafba:"9235","4d918de2":"9251","64772a77":"9388","355d470d":"9451","0874f5ae":"9479","1be78505":"9514","40a20ee1":"9570",a2309c10:"9643","27f4d295":"9695","7b9528aa":"9722",c225ef2b:"9731","8fc0d838":"9792","14eb3368":"9817","1a8c3b80":"9892",e2c08c7d:"9973"}[e]||e,r.p+r.u(e)},(()=>{var e={1303:0,532:0};r.f.j=(a,c)=>{var d=r.o(e,a)?e[a]:void 0;if(0!==d)if(d)c.push(d[2]);else if(/^(1303|532)$/.test(a))e[a]=0;else{var f=new Promise(((c,f)=>d=e[a]=[c,f]));c.push(d[2]=f);var b=r.p+r.u(a),t=new Error;r.l(b,(c=>{if(r.o(e,a)&&(0!==(d=e[a])&&(e[a]=void 0),d)){var f=c&&("load"===c.type?"missing":c.type),b=c&&c.target&&c.target.src;t.message="Loading chunk "+a+" failed.\n("+f+": "+b+")",t.name="ChunkLoadError",t.type=f,t.request=b,d[1](t)}}),"chunk-"+a,a)}},r.O.j=a=>0===e[a];var a=(a,c)=>{var d,f,b=c[0],t=c[1],o=c[2],n=0;if(b.some((a=>0!==e[a]))){for(d in t)r.o(t,d)&&(r.m[d]=t[d]);if(o)var i=o(r)}for(a&&a(c);n{"use strict";var e,a,c,d,f,b={},t={};function r(e){var a=t[e];if(void 0!==a)return a.exports;var c=t[e]={exports:{}};return b[e].call(c.exports,c,c.exports,r),c.exports}r.m=b,e=[],r.O=(a,c,d,f)=>{if(!c){var b=1/0;for(i=0;i=f)&&Object.keys(r.O).every((e=>r.O[e](c[o])))?c.splice(o--,1):(t=!1,f0&&e[i-1][2]>f;i--)e[i]=e[i-1];e[i]=[c,d,f]},r.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return r.d(a,{a:a}),a},c=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,r.t=function(e,d){if(1&d&&(e=this(e)),8&d)return e;if("object"==typeof e&&e){if(4&d&&e.__esModule)return e;if(16&d&&"function"==typeof e.then)return e}var f=Object.create(null);r.r(f);var b={};a=a||[null,c({}),c([]),c(c)];for(var t=2&d&&e;"object"==typeof t&&!~a.indexOf(t);t=c(t))Object.getOwnPropertyNames(t).forEach((a=>b[a]=()=>e[a]));return b.default=()=>e,r.d(f,b),f},r.d=(e,a)=>{for(var c in a)r.o(a,c)&&!r.o(e,c)&&Object.defineProperty(e,c,{enumerable:!0,get:a[c]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce(((a,c)=>(r.f[c](e,a),a)),[])),r.u=e=>"assets/js/"+({26:"9f3352c6",33:"6582a1da",53:"935f2afb",60:"7008f309",145:"1de28ba9",321:"c637a75c",439:"18ac6946",499:"01e35b3c",639:"0f26255b",687:"ad4ac6bf",1171:"d896988d",1175:"5eaad662",1693:"12aa5f76",1811:"7c709933",1904:"756c405a",1970:"53decd28",2034:"21ad55e6",2163:"1f4f5b8d",2241:"fa5dbaff",2261:"528693c6",2365:"a7626ec9",2535:"814f3328",2616:"e9748e8f",2757:"9e04c579",2828:"b7eeea20",2854:"08a2e2e3",2857:"cab0a0b1",2896:"e8c48351",2990:"743ad547",3089:"a6aa9e1f",3092:"5d75b024",3134:"31bbbef9",3378:"e4a581d5",3608:"9e4087bc",3665:"cd40287e",3777:"303a7ab0",3814:"ff80323f",3836:"f6cbeee1",4106:"3bd86665",4191:"42d29336",4195:"c4f5d8e4",4255:"e272dbc8",4340:"e1c419d2",4364:"fba6c282",4449:"359abbd3",4569:"39b1bd06",4812:"f97df8c0",4916:"90ae8e83",5133:"3d63e4cd",5306:"29217152",5428:"45bec6ca",5638:"d243f57f",5657:"3dd68940",5672:"eacdb088",5746:"5a96aca1",6103:"ccc49370",6686:"a73ec7fd",6690:"aa55467b",6727:"a2720703",7020:"ba76a366",7198:"82b0332e",7561:"1216cbe4",7566:"aac64df7",7589:"0ccd1bc3",7738:"530cdc5e",7842:"a6590bfd",7906:"50ff9c4d",7918:"17896441",7920:"1a4e3797",7995:"375669b6",8052:"b7e34b9a",8091:"05ba35d7",8373:"f9d8bc1b",8572:"673d675b",8656:"757481ff",8699:"445802cd",8983:"09708493",9024:"02b1d4e6",9107:"82373234",9167:"369df949",9178:"dd55ff2d",9225:"5b44acae",9235:"a85eafba",9251:"4d918de2",9388:"64772a77",9451:"355d470d",9479:"0874f5ae",9514:"1be78505",9570:"40a20ee1",9643:"a2309c10",9695:"27f4d295",9722:"7b9528aa",9731:"c225ef2b",9792:"8fc0d838",9817:"14eb3368",9892:"1a8c3b80",9973:"e2c08c7d"}[e]||e)+"."+{26:"e04c55de",33:"e957d922",53:"4d91f531",60:"79a620ab",145:"c6d01e7d",321:"8f5474e8",439:"da4b7714",499:"c5491186",639:"0e944f9d",687:"c516a8de",1171:"dca3ec8e",1175:"02e9a659",1426:"de2b7f72",1693:"a0ca1f60",1811:"7cc37b2e",1904:"936046dd",1970:"a57b0074",2034:"e6a67a2a",2163:"84c11ff6",2241:"02b5e2b9",2261:"01eabc1a",2365:"1a7ed67a",2535:"184cc1e2",2616:"8ff4f04a",2757:"ccf90f29",2828:"cbb0c6af",2854:"56d696de",2857:"11a341e7",2896:"48347776",2990:"337aa9b3",3089:"d1467cbe",3092:"a97fbbb4",3134:"3a234ef2",3378:"7929c073",3608:"e989768d",3665:"fb7c1c9c",3777:"365866cc",3814:"682699bc",3836:"ce2b79a4",4106:"3477426f",4191:"c09a9f73",4195:"e054211d",4255:"069044fe",4340:"7629659a",4364:"ada73ec4",4449:"29132be4",4569:"443223c6",4812:"ec28707c",4916:"0e6b5afa",4972:"3d0f496c",5133:"ad5fd11b",5306:"e8ae6ee8",5428:"becbb798",5638:"d8e8d6ed",5657:"00eba38b",5672:"491150b1",5746:"f49f1414",6048:"779f8c90",6103:"1d3911bc",6186:"170d1bc9",6686:"08a9c34c",6690:"7c964d75",6727:"552e21ef",6945:"94f4a660",7020:"d6d0e9af",7198:"2d5679e9",7561:"8e03a462",7566:"942ebb73",7589:"199747a5",7738:"33315437",7842:"a849f3ec",7906:"354701ea",7918:"a70e39a9",7920:"275f830e",7995:"f3933e33",8052:"139bf164",8091:"6a9d4f9c",8373:"224eb03e",8572:"b687203c",8656:"189e347f",8699:"66f7870b",8894:"91734414",8983:"7bc4227d",9024:"2e97ef25",9107:"9e8525e0",9167:"c8d26917",9178:"c0c79448",9225:"570487f1",9235:"aa0c15e0",9251:"2d6d3f2f",9388:"8c2f72f9",9451:"a68c3273",9479:"8deab972",9514:"d5cf2d0b",9570:"3d816f98",9643:"3572f86d",9695:"5df73254",9722:"07bf8fc9",9731:"0b7db44c",9792:"e35ef483",9817:"91502cf3",9892:"8cb56873",9973:"bb736e33"}[e]+".js",r.miniCssF=e=>{},r.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),r.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),d={},f="my-website:",r.l=(e,a,c,b)=>{if(d[e])d[e].push(a);else{var t,o;if(void 0!==c)for(var n=document.getElementsByTagName("script"),i=0;i{t.onerror=t.onload=null,clearTimeout(s);var f=d[e];if(delete d[e],t.parentNode&&t.parentNode.removeChild(t),f&&f.forEach((e=>e(c))),a)return a(c)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:t}),12e4);t.onerror=l.bind(null,t.onerror),t.onload=l.bind(null,t.onload),o&&document.head.appendChild(t)}},r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.p="/en/",r.gca=function(e){return e={17896441:"7918",29217152:"5306",82373234:"9107","9f3352c6":"26","6582a1da":"33","935f2afb":"53","7008f309":"60","1de28ba9":"145",c637a75c:"321","18ac6946":"439","01e35b3c":"499","0f26255b":"639",ad4ac6bf:"687",d896988d:"1171","5eaad662":"1175","12aa5f76":"1693","7c709933":"1811","756c405a":"1904","53decd28":"1970","21ad55e6":"2034","1f4f5b8d":"2163",fa5dbaff:"2241","528693c6":"2261",a7626ec9:"2365","814f3328":"2535",e9748e8f:"2616","9e04c579":"2757",b7eeea20:"2828","08a2e2e3":"2854",cab0a0b1:"2857",e8c48351:"2896","743ad547":"2990",a6aa9e1f:"3089","5d75b024":"3092","31bbbef9":"3134",e4a581d5:"3378","9e4087bc":"3608",cd40287e:"3665","303a7ab0":"3777",ff80323f:"3814",f6cbeee1:"3836","3bd86665":"4106","42d29336":"4191",c4f5d8e4:"4195",e272dbc8:"4255",e1c419d2:"4340",fba6c282:"4364","359abbd3":"4449","39b1bd06":"4569",f97df8c0:"4812","90ae8e83":"4916","3d63e4cd":"5133","45bec6ca":"5428",d243f57f:"5638","3dd68940":"5657",eacdb088:"5672","5a96aca1":"5746",ccc49370:"6103",a73ec7fd:"6686",aa55467b:"6690",a2720703:"6727",ba76a366:"7020","82b0332e":"7198","1216cbe4":"7561",aac64df7:"7566","0ccd1bc3":"7589","530cdc5e":"7738",a6590bfd:"7842","50ff9c4d":"7906","1a4e3797":"7920","375669b6":"7995",b7e34b9a:"8052","05ba35d7":"8091",f9d8bc1b:"8373","673d675b":"8572","757481ff":"8656","445802cd":"8699","09708493":"8983","02b1d4e6":"9024","369df949":"9167",dd55ff2d:"9178","5b44acae":"9225",a85eafba:"9235","4d918de2":"9251","64772a77":"9388","355d470d":"9451","0874f5ae":"9479","1be78505":"9514","40a20ee1":"9570",a2309c10:"9643","27f4d295":"9695","7b9528aa":"9722",c225ef2b:"9731","8fc0d838":"9792","14eb3368":"9817","1a8c3b80":"9892",e2c08c7d:"9973"}[e]||e,r.p+r.u(e)},(()=>{var e={1303:0,532:0};r.f.j=(a,c)=>{var d=r.o(e,a)?e[a]:void 0;if(0!==d)if(d)c.push(d[2]);else if(/^(1303|532)$/.test(a))e[a]=0;else{var f=new Promise(((c,f)=>d=e[a]=[c,f]));c.push(d[2]=f);var b=r.p+r.u(a),t=new Error;r.l(b,(c=>{if(r.o(e,a)&&(0!==(d=e[a])&&(e[a]=void 0),d)){var f=c&&("load"===c.type?"missing":c.type),b=c&&c.target&&c.target.src;t.message="Loading chunk "+a+" failed.\n("+f+": "+b+")",t.name="ChunkLoadError",t.type=f,t.request=b,d[1](t)}}),"chunk-"+a,a)}},r.O.j=a=>0===e[a];var a=(a,c)=>{var d,f,b=c[0],t=c[1],o=c[2],n=0;if(b.some((a=>0!==e[a]))){for(d in t)r.o(t,d)&&(r.m[d]=t[d]);if(o)var i=o(r)}for(a&&a(c);n

数组访问相关指令

比较常规直接,不过有个特殊点:根据规范index变量可以是i4或者native int类型。由于数组访问是非常频繁的操作,我们不想插入运行时数据类型类型及转换,因为我们根据index变量的size为每条数组相关指令设计了2条hybridclr指令。

以ldelem.i4 指令的index是i4类型的情形为例

struct IRGetArrayElementVarVar_i4_4 : IRCommon
{
uint16_t dst;
uint16_t arr;
uint16_t index;
};

// 对应解释执行代码
case HiOpcodeEnum::GetArrayElementVarVar_i4_4:
{
uint16_t __dst = *(uint16_t*)(ip + 2);
uint16_t __arr = *(uint16_t*)(ip + 4);
uint16_t __index = *(uint16_t*)(ip + 6);
Il2CppArray* arr = (*(Il2CppArray**)(localVarBase + __arr));
CHECK_NOT_NULL_AND_ARRAY_BOUNDARY(arr, (*(int32_t*)(localVarBase + __index)));
(*(int32_t*)(localVarBase + __dst)) = il2cpp_array_get(arr, int32_t, (*(int32_t*)(localVarBase + __index)));
ip += 8;
continue;
}

函数调用指令

目前调用AOT函数和调用Interpreter函数使用不同的指令,因为Interpreter函数可以直接复用已经压到栈顶的数据,可以完全优化掉 Manged2Native -> Native2Managed 这个过程,提升性能。

调用解释器函数时可以复用当前 InterpreterModule::Execute函数帧,也节省了函数调用开销,同时也避免了解释器嵌套调用过深导致native栈overflow的问题。

对于带返回值的函数,由于多了一个返回值地址参数ret,与返回void的函数分别设计了不同指令。

如果调用的是AOT函数,由于每条函数的参数不定,我们将参数信息记录到resolvedDatas,然后argIdxs中保存这个间接索引。另外还需要通过桥接函数完成解释器函数参数到native abi函数参数的转换,为了避免运行时查找的开销,也提前计算了这个桥接函数,记录到resolvedDatas中,然后在managed2NativeMethod中保存了这个间接索引。

以call指令为例,为它设计了5条指令

  • IRCallNative_void
  • IRCallNative_ret
  • IRCallNative_ret_expand
  • IRCallInterp_void
  • IRCallInterp_ret

以IRCallNative_ret的实现为例,介绍调用AOT函数的指令:


struct IRCallNative_ret : IRCommon
{
uint16_t ret;
uint32_t managed2NativeMethod;
uint32_t methodInfo;
uint32_t argIdxs;
};

// 对应解释执行代码
case HiOpcodeEnum::CallNative_ret:
{
uint32_t __managed2NativeMethod = *(uint32_t*)(ip + 4);
uint32_t __methodInfo = *(uint32_t*)(ip + 8);
uint32_t __argIdxs = *(uint32_t*)(ip + 12);
uint16_t __ret = *(uint16_t*)(ip + 2);
void* _ret = (void*)(localVarBase + __ret);
((Managed2NativeCallMethod)imi->resolveDatas[__managed2NativeMethod])(((MethodInfo*)imi->resolveDatas[__methodInfo]), ((uint16_t*)&imi->resolveDatas[__argIdxs]), localVarBase, _ret);
ip += 16;
continue;
}

如果调用Interpreter函数,由于函数参数已经按顺序压到栈上,只需要一个argBase参数指定arg0逻辑地址即可,不需要借助resolvedDatas,也不需要managed2NativeMethod桥接函数指针。 这也是解释器函数不受桥接函数影响的原因。

以IRCallInterp_ret为例,介绍调用Interpreter函数的指令:

struct IRCallInterp_ret : IRCommon
{
uint16_t argBase;
uint16_t ret;
uint8_t __pad6;
uint8_t __pad7;
uint32_t methodInfo;
};

// 对应解释执行代码
case HiOpcodeEnum::CallInterp_ret:
{
MethodInfo* __methodInfo = *(MethodInfo**)(ip + 8);
uint16_t __argBase = *(uint16_t*)(ip + 2);
uint16_t __ret = *(uint16_t*)(ip + 4);
CALL_INTERP_RET((ip + 16), __methodInfo, (StackObject*)(void*)(localVarBase + __argBase), (void*)(localVarBase + __ret));
continue;
}

异常机制相关指令

异常机制相关指令本身不复杂,但异常处理机制非常复杂。

异常这种特殊的流程控制指令,跟分支跳转指令相似,原始指令里包含了相对offset,为了简单起见,指令转换时我们改成int32_t类型的绝对offset。

以leave指令为例

struct IRLeaveEx : IRCommon
{
uint8_t __pad2;
uint8_t __pad3;
int32_t offset;
};

// 对应解释执行代码
case HiOpcodeEnum::LeaveEx:
{
int32_t __offset = *(int32_t*)(ip + 4);
LEAVE_EX(__offset);
continue;
}

一些额外的instinct 指令

对于一些特别常见的函数,为了优化性能,hybridclr直接内置了相应的指令,例如 new Vector{2,3,4},如可空变量相关操作。这些instinct指令的执行性能基本与AOT持平。

以 new Vector3() 为例


struct IRNewVector3_3 : IRCommon
{
uint16_t obj;
uint16_t x;
uint16_t y;
uint16_t z;
uint8_t __pad10;
uint8_t __pad11;
uint8_t __pad12;
uint8_t __pad13;
uint8_t __pad14;
uint8_t __pad15;
};

// 对应解释执行代码
case HiOpcodeEnum::NewVector3_3:
{
uint16_t __obj = *(uint16_t*)(ip + 2);
uint16_t __x = *(uint16_t*)(ip + 4);
uint16_t __y = *(uint16_t*)(ip + 6);
uint16_t __z = *(uint16_t*)(ip + 8);
*(HtVector3f*)(*(void**)(localVarBase + __obj)) = {(*(float*)(localVarBase + __x)), (*(float*)(localVarBase + __y)), (*(float*)(localVarBase + __z))};
ip += 16;
continue;
}

InitOnce 指令

有一些指令(如ldsfld)第一次执行的时候需要进行初始化操作,但后续再次执行时,不需要再执行初始化操作。但即使这样,免不了一个检查是否已经初始化的操作,我们希望完全优化掉这个检查行为。InitOnce动态JIT技术用于解决这个问题。

InitOnce是hybridclr的专利技术,暂未在代码中实现,这儿不详细介绍。

其他技术相关指令

限于篇幅,对于这些指令,会在单独的文章中介绍

总结

至此我们完成hybridclr指令集实现相关介绍。

· 23 min read

我们在上一节完成了hybridclr可行性分析。由于hybridclr内容极多,限于篇幅本篇文章主要概述性介绍hybridclr的技术实现。

CLR和il2cpp基础

给纯AOT的il2cpp运行时添加一个原生interpreter模块,最终实现hybrid mode execution,这看起来是非常复杂的事情。

其实不然,程序不外乎代码+数据。CLR运行中做的事情,综合起来主要就几种:

  1. 执行简单的内存操作或者计算或者逻辑跳转。这部分与CLI的Base指令集大致对应
  2. 执行一个依赖于元数据信息的基础操作。例如 a.x, arr[3] 这种,依赖于元数据信息才能正确工作的代码。对应部分CLI的Object Model指令集。
  3. 执行一个依赖元数据的较复杂的操作。如 typeof(object),a is string、(object)5 这种依赖于运行时提供的函数及相应元数据才正确工作的代码。对应部分CLI的Object Model指令集。
  4. 函数调用。包括且不限于被AOT函数调用及调用AOT函数,及interpreter之间的函数调用。对应CLI指令集中的 call、callvir、newobj 等Object Model指令。

如果对CLR有深入的了解和透彻的分析,为了实现hybrid mode execution,hybridclr核心要完成的就以下两件事,其他则是无碍全局的细节:

  • assembly信息能够加载和注册。 在此基础可以实现 1-3
  • 确保interpreter函数能被找到并且被调用,并且能执行出正确的结果。则可以实现 4

由于彻底理解以上内容需要较丰富的对CLR的认知以及较强的洞察力,我们不再费口舌解释,不能理解的开发者不必深究,继续看后续章节。

核心模块

从功能来看包含以下核心部分:

  • metadata初级解析
  • metadata高级元数据结构解析
  • metadata动态注册
  • 寄存器指令集设计
  • IL指令集到hybridclr寄存器指令集的转换
  • 解释执行hybridclr指令集
  • 其他如GC、多线程相关处理

从代码结构来看包含三个目录:

  • metadata 元数据相关
  • transform 指令集转换相关
  • interpreter 解释器相关

metadata 初级解析

这部分内容技术门槛不高,但比较琐碎和辛苦,忠实地按照 ECMA-335规范 的文档实现即可。对于少量有疑惑的地方,可以网上的资料或者借鉴mono的代码。

相关代码在hybridclr\metadata目录,主要在RawImage.h和RawImage.cpp中实现。如果再细分,相关实现分为以下几个部分。

PE 文件结构解析

managed dll扩展了PE文件结构,增加了CLI相关metadata部分。这环节的主要工作有:

  • 解析PE headers
  • 解析 section headers,找出CLI header,定位出cli数据段
  • 解析出所有stream。Stream是CLI中最底层的数据结构之一,CLI将元数据根据特性分为几个大类
    • #~ 流。包含所有tables定义,是最核心的元数据结构
    • #Strings 流。包括代码中非文档类型的字符串,如类型名、字段名等等
    • #GUID 流
    • #Blob 流。一些元数据类型过于复杂,以blob格式保存。还有一些数据如数组初始化数据列表,也常常保存到Blob流。
    • #- 流
    • #Pdb 流。用于调试

解析PE文件和代码在RawImage::Load,解析stream对应的代码在RawImage::LoadStreams。

tables metadata 解析

CLI中大多数metadata被为几十种类型,每个类型的数据组织成一个table。对于每个table,每行记录都是相同大小。

初级解析中不解析table中每行记录,只解析table的每行记录大小和每个字段偏移。有一大类字段为Coded Index类型,有可能是2或4字节,并不固定,需要根据其他表的Row Count来决定table中这一列的字段大小。由于table很多,这个计算过程比较琐碎易错。

对应代码在RawImage::LoadTables,截取部分代码如下

void RawImage::BuildTableRowMetas()
{
{
auto& table = _tableRowMetas[(int)TableType::MODULE];
table.push_back({ 2 });
table.push_back({ ComputStringIndexByte() });
table.push_back({ ComputGUIDIndexByte() });
table.push_back({ ComputGUIDIndexByte() });
table.push_back({ ComputGUIDIndexByte() });
}
{
auto& table = _tableRowMetas[(int)TableType::TYPEREF];
table.push_back({ ComputTableIndexByte(TableType::MODULE, TableType::MODULEREF, TableType::ASSEMBLYREF, TableType::TYPEREF, TagBits::ResoulutionScope) });
table.push_back({ ComputStringIndexByte() });
table.push_back({ ComputStringIndexByte() });
}

// ... 其他
}

table 解析

上一节已经解析出每个table的起始数据位置、row count、表中每个字段的偏移和大小,有足够的信息可以解析出每个table中任意row的数据。table中row的id从1开始。

每个table的row的解析方式根据ECMA规范实现即可。每个table的row定义在 metadata\Coff.h文件,Row解析代码在 RawImage.h。这些解析代码都非常相似,为了避免错误,使用了大量的宏,截取部分代码如下:

TABLE2(GenericParamConstraint, TableType::GENERICPARAMCONSTRAINT, owner, constraint)
TABLE3(MemberRef, TableType::MEMBERREF, classIdx, name, signature)
TABLE1(StandAloneSig, TableType::STANDALONESIG, signature)
TABLE3(MethodImpl, TableType::METHODIMPL, classIdx, methodBody, methodDeclaration)
TABLE2(FieldRVA, TableType::FIELDRVA, rva, field)
TABLE2(FieldLayout, TableType::FIELDLAYOUT, offset, field)
TABLE3(Constant, TableType::CONSTANT, type, parent, value)
TABLE2(MethodSpec, TableType::METHODSPEC, method, instantiation)
TABLE3(CustomAttribute, TableType::CUSTOMATTRIBUTE, parent, type, value)

metadata高级元数据结构解析

从tables里直接读出来的都是持久化的初始metadata,而运行时需要的不只是这些简单原始数据,经常需要进一步resolve后的数据。例如

  • Il2CppType 。即可以是简单的 int,也可以是比较复杂的List<int>,甚至是特别复杂的List<(int,int)>&
  • MethodInfo 。 即可以是简单的object.ToString,也有复杂的泛型 IEnumerator<int>.Count

CLI的泛型机制导致元数据变得极其复杂,典型的是TypeSpec,MethodSpec,MemberSpec相关元数据的运行时解析。核心实现代码在Image.cpp中实现,剩余一部分在 InterpreterImage.cpp及AOTHomologousImage.cpp中实现。后面会有专门介绍。

metadata动态注册

根据粒度从大到小,主要分为以下几类

  • Assembly 注册。即将加载的assembly注册到il2cpp的元数据管理中。
  • TypeDefinition 注册。 这一步会生成基础运行时类型 Il2CppClass。
  • VTable虚表计算。 由于il2cpp的虚表计算是个黑盒,内部相当复杂,我们费了很多功夫才研究明白它的计算机制。后面会有专门章节介绍VTable计算,这儿不再赘述。
  • 其他元数据,如CustomAttribute计算等等。

Assembly 注册

Assembly加载的关键函数在 il2cpp::vm::MetadataCache::LoadAssemblyFromBytes 。由于il2cpp是AOT运行时,原始实现只是简单地抛出异常。我们修改和完善了实现,在其中调用了hybridclr::metadata::Assembly::LoadFromBytes,完成了Assembly的创建,然后再注册到全局Assemblies列表。相关代码实现如下:

const Il2CppAssembly* il2cpp::vm::MetadataCache::LoadAssemblyFromBytes(const char* assemblyBytes, size_t length)
{
il2cpp::os::FastAutoLock lock(&il2cpp::vm::g_MetadataLock);

Il2CppAssembly* newAssembly = hybridclr::metadata::Assembly::LoadFromBytes(assemblyBytes, length, true);
if (newAssembly)
{
// avoid register placeholder assembly twicely.
for (Il2CppAssembly* ass : s_cliAssemblies)
{
if (ass == newAssembly)
{
return ass;
}
}
il2cpp::vm::Assembly::Register(newAssembly);
s_cliAssemblies.push_back(newAssembly);
return newAssembly;
}

return nullptr;
}

TypeDefinition 注册

Assembly使用了延迟初始化方式,注册后Assembly中的类型信息并未创建相应的运行时metadata Il2CppClass,只有当第一次访问到该类型时才进行初始化。

由于交叉依赖以及为了优化性能,Il2Class的创建是个分步过程

  • Il2CppClass 基础创建
  • Il2CppClass的子元数据延迟初始化
  • 运行时Class初始化

Il2CppClass基础创建

在上一节加载Assembly时已经创建好所有类型对应的定义数据Il2CppTypeDefinition,在 il2cpp::vm::GlobalMetadata::FromTypeDefinition 中完成Il2CppClass创建工作。代码如下:

Il2CppClass* il2cpp::vm::GlobalMetadata::FromTypeDefinition(TypeDefinitionIndex index)
{
/// ... 省略其他
Il2CppClass* typeInfo = (Il2CppClass*)IL2CPP_CALLOC(1, sizeof(Il2CppClass) + (sizeof(VirtualInvokeData) * typeDefinition->vtable_count));
typeInfo->klass = typeInfo;
typeInfo->image = GetImageForTypeDefinitionIndex(index);
typeInfo->name = il2cpp::vm::GlobalMetadata::GetStringFromIndex(typeDefinition->nameIndex);
typeInfo->namespaze = il2cpp::vm::GlobalMetadata::GetStringFromIndex(typeDefinition->namespaceIndex);
typeInfo->byval_arg = *il2cpp::vm::GlobalMetadata::GetIl2CppTypeFromIndex(typeDefinition->byvalTypeIndex);
typeInfo->this_arg = typeInfo->byval_arg;
typeInfo->this_arg.byref = true;
typeInfo->typeMetadataHandle = reinterpret_cast<const Il2CppMetadataTypeHandle>(typeDefinition);
typeInfo->genericContainerHandle = GetGenericContainerFromIndex(typeDefinition->genericContainerIndex);
typeInfo->instance_size = typeDefinitionSizes->instance_size;
typeInfo->actualSize = typeDefinitionSizes->instance_size; // actualySize is instance_size for compiler generated values
typeInfo->native_size = typeDefinitionSizes->native_size;
typeInfo->static_fields_size = typeDefinitionSizes->static_fields_size;
typeInfo->thread_static_fields_size = typeDefinitionSizes->thread_static_fields_size;
typeInfo->thread_static_fields_offset = -1;
typeInfo->flags = typeDefinition->flags;
typeInfo->valuetype = (typeDefinition->bitfield >> (kBitIsValueType - 1)) & 0x1;
typeInfo->enumtype = (typeDefinition->bitfield >> (kBitIsEnum - 1)) & 0x1;
typeInfo->is_generic = typeDefinition->genericContainerIndex != kGenericContainerIndexInvalid; // generic if we have a generic container
typeInfo->has_finalize = (typeDefinition->bitfield >> (kBitHasFinalizer - 1)) & 0x1;
typeInfo->has_cctor = (typeDefinition->bitfield >> (kBitHasStaticConstructor - 1)) & 0x1;
typeInfo->is_blittable = (typeDefinition->bitfield >> (kBitIsBlittable - 1)) & 0x1;
typeInfo->is_import_or_windows_runtime = (typeDefinition->bitfield >> (kBitIsImportOrWindowsRuntime - 1)) & 0x1;
typeInfo->packingSize = ConvertPackingSizeEnumToValue(static_cast<PackingSize>((typeDefinition->bitfield >> (kPackingSize - 1)) & 0xF));
typeInfo->method_count = typeDefinition->method_count;
typeInfo->property_count = typeDefinition->property_count;
typeInfo->field_count = typeDefinition->field_count;
typeInfo->event_count = typeDefinition->event_count;
typeInfo->nested_type_count = typeDefinition->nested_type_count;
typeInfo->vtable_count = typeDefinition->vtable_count;
typeInfo->interfaces_count = typeDefinition->interfaces_count;
typeInfo->interface_offsets_count = typeDefinition->interface_offsets_count;
typeInfo->token = typeDefinition->token;
typeInfo->interopData = il2cpp::vm::MetadataCache::GetInteropDataForType(&typeInfo->byval_arg);

// 省略其他

return typeInfo;
}

可以看到TypeDefinition中字段相当多,这些都是在Assembly加载环节计算好的。

Il2CppClass的子metadata延迟初始化

由于交互依赖以及为了优化性能,Il2Class的子metadata数据使用了延迟初始化策略,分步进行,在第一次使用时才初始化。以下代码截取自 Class.h 文件:

class Class
{
// ... 其他代码
static bool Init(Il2CppClass *klass);

static void SetupEvents(Il2CppClass *klass);
static void SetupFields(Il2CppClass *klass);
static void SetupMethods(Il2CppClass *klass);
static void SetupNestedTypes(Il2CppClass *klass);
static void SetupProperties(Il2CppClass *klass);
static void SetupTypeHierarchy(Il2CppClass *klass);
static void SetupInterfaces(Il2CppClass *klass);
// ... 其他代码
};

重点来了!!!函数metadata的执行指针的绑定在SetupMethods函数中完成,其中关键代码片段如下:

void SetupMethodsLocked(Il2CppClass *klass, const il2cpp::os::FastAutoLock& lock)
{
/// ... 其他忽略的代码
for (MethodIndex index = 0; index < end; ++index)
{
Il2CppMetadataMethodInfo methodInfo = MetadataCache::GetMethodInfo(klass, index);

newMethod->name = methodInfo.name;

if (klass->valuetype)
{
Il2CppMethodPointer adjustorThunk = MetadataCache::GetAdjustorThunk(klass->image, methodInfo.token);
if (adjustorThunk != NULL)
newMethod->methodPointer = adjustorThunk;
}

// We did not find an adjustor thunk, or maybe did not need to look for one. Let's get the real method pointer.
if (newMethod->methodPointer == NULL)
newMethod->methodPointer = MetadataCache::GetMethodPointer(klass->image, methodInfo.token);

newMethod->invoker_method = MetadataCache::GetMethodInvoker(klass->image, methodInfo.token);
}
/// ... 其他忽略的代码
}

函数运行时元数据结构为 MethodInfo,定义如下,

typedef struct MethodInfo
{
Il2CppMethodPointer methodPointer;
InvokerMethod invoker_method;
const char* name;
Il2CppClass *klass;
const Il2CppType *return_type;
const ParameterInfo* parameters;

// ... 省略其他
} MethodInfo;

其中我们比较关心的是methodPointer和invoker_method这两个字段。 methodPointer指向普通执行函数,invoker_method指向反射执行函数。

我们以 methodPointer为例,进一步跟踪它的设置过程, il2cpp::vm::MetadataCache::GetMethodPointer 的实现如下:

Il2CppMethodPointer il2cpp::vm::MetadataCache::GetMethodPointer(const Il2CppImage* image, uint32_t token)
{
uint32_t rid = GetTokenRowId(token);
uint32_t table = GetTokenType(token);
if (rid == 0)
return NULL;

// ==={{ hybridclr
if (hybridclr::metadata::IsInterpreterImage(image))
{
return hybridclr::metadata::MetadataModule::GetMethodPointer(image, token);
}
// ===}} hybridclr

IL2CPP_ASSERT(rid <= image->codeGenModule->methodPointerCount);

return image->codeGenModule->methodPointers[rid - 1];
}

可以看出,如果是解释器assembly,就跳转到解释器元数据模块获得对应的MethodPointer指针。 继续跟踪,相关代码如下:


Il2CppMethodPointer InterpreterImage::GetMethodPointer(uint32_t token)
{
uint32_t methodIndex = DecodeTokenRowIndex(token) - 1;
IL2CPP_ASSERT(methodIndex < (uint32_t)_methodDefines.size());
const Il2CppMethodDefinition* methodDef = &_methodDefines[methodIndex];
return hybridclr::interpreter::InterpreterModule::GetMethodPointer(methodDef);
}

Il2CppMethodPointer InterpreterModule::GetMethodPointer(const Il2CppMethodDefinition* method)
{
const NativeCallMethod* ncm = GetNativeCallMethod(method, false);
if (ncm)
{
return ncm->method;
}
//RaiseMethodNotSupportException(method, "GetMethodPointer");
return (Il2CppMethodPointer)NotSupportNative2Managed;
}

// interpreter/InterpreterModule.cpp
template<typename T>
const NativeCallMethod* GetNativeCallMethod(const T* method, bool forceStatic)
{
char sigName[1000];
ComputeSignature(method, !forceStatic, sigName, sizeof(sigName) - 1);
auto it = s_calls.find(sigName);
return (it != s_calls.end()) ? &it->second : nullptr;
}

// s_calls 定义
static std::unordered_map<const char*, NativeCallMethod, CStringHash, CStringEqualTo> s_calls;

void InterpreterModule::Initialize()
{
for (size_t i = 0; ; i++)
{
NativeCallMethod& method = g_callStub[i];
if (!method.signature)
{
break;
}
s_calls.insert({ method.signature, method });
}

for (size_t i = 0; ; i++)
{
NativeInvokeMethod& method = g_invokeStub[i];
if (!method.signature)
{
break;
}
s_invokes.insert({ method.signature, method });
}
}

这儿根据函数定义计算其签名并且返回了一个函数指针,这个函数指针是什么呢? s_calls在InterpreterModule::Initialize中使用g_callStub初始化。那g_calStub又是什么呢?它在 interpreter/MethodBridge_xxx.cpp 中定义,原来是桥接函数相关的数据结构!

为什么要返回一个这样的函数,而不是直接将methodPointer指向 InterpreterModule::Execute 函数呢? 以 int Foo::Sum(int,int) 函数为例,这个函数的实际的签名为 int32_t (int32_t, int32_t, MethodInfo*),在调用这个methodPointer函数时,调用方一定会传递这三个参数。这些参数每个函数都不一样,如果直接指向 InterpreterModule::Execute 函数,由于ABI调用无法自省(就算可以,性能也比较差),Execute函数既无法提取出普通参数,也无法提取出MethodInfo*参数,因而无法正确运行。因此需要对每个函数,适当地将ABI调用中的这些参数传递给Execute函数。

桥接函数如其名,承担了native ABI函数参数和interpreter函数之间双向的参数的转换作用。截取一段示例代码:


/// AOT 到 interpreter 的调用参数转换
static int64_t __Native2ManagedCall_i8srr8sr(void* __arg0, double __arg1, void* __arg2, const MethodInfo* method)
{
StackObject args[4] = {*(void**)&__arg0, *(void**)&__arg1, *(void**)&__arg2 };
StackObject* ret = args + 3;
Interpreter::Execute(method, args, ret);
return *(int64_t*)ret;
}

// interpreter 到 AOT 的调用参数转换
static void __Managed2NativeCall_i8srr8sr(const MethodInfo* method, uint16_t* argVarIndexs, StackObject* localVarBase, void* ret)
{
if (hybridclr::metadata::IsInstanceMethod(method) && !localVarBase[argVarIndexs[0]].obj)
{
il2cpp::vm::Exception::RaiseNullReferenceException();
}
Interpreter::RuntimeClassCCtorInit(method);
typedef int64_t (*NativeMethod)(void* __arg0, double __arg1, void* __arg2, const MethodInfo* method);
*(int64_t*)ret = ((NativeMethod)(method->methodPointer))((void*)(localVarBase+argVarIndexs[0]), *(double*)(localVarBase+argVarIndexs[1]), (void*)(localVarBase+argVarIndexs[2]), method);
}

运行时Class初始化

即程序运行过程中第一次访问类的静态字段或者函数时或者创建对象时触发的类型初始化。在il2cpp::vm::Runtime::ClassInit(klass)中完成。不是特别关键,我们后面在单独文章中介绍。

VTable虚表计算

虚表是多态的核心。CLI的虚表计算非常复杂,但不理解它的实现并不影响开发者理解hybridclr的核心运行流程,我们后面在单独文章中介绍。

其他元数据

CustomAttribute使用延迟初始化方式,计算也很复杂,我们后面单独文章介绍。

寄存器指令集设计

直接解释原始IL指令有几个问题:

  • IL是基于栈的指令,运行时维护执行栈是个无谓的开销
  • IL有大量单指令多功能的指令,如add指令可以用于计算int、long、float、double类型的和,导致运行时需要根据上文判断到底该执行哪种计算。不仅增加了运行时判定的开销,还增加了运行时维护执行栈数据类型的开销
  • IL指令包含一些需要运行时resolve的数据,如newobj指令第一个参数是method token。token resolve是一个开销很大的操作,每次执行都进行resolve会极大拖慢执行性能
  • IL是基于栈的指令,压栈退栈相关指令数较多。像a=b+c这样的指令需要4条指令完成,而如果采用基于寄存器的指令,完全可以一条指令完成。
  • IL不适合做其他优化操作,如我们的InitOnce JIT技术。
  • 其他

因此我们需要将原始IL指令转换为更高效的寄存器指令。由于指令很多,这儿不介绍寄存器指令集的详细设计。以add指令举例


// 包含type字段,即指令ID。
struct IRCommon
{
HiOpcodeEnum type;
};

// add int, int -> int 对应的寄存器指令
struct IRBinOpVarVarVar_Add_i4 : IRCommon
{
uint16_t ret; // 计算结果对应的 栈位置
uint16_t op1; // 操作数1对应的栈位置
uint16_t op2; // 操作数2对应的栈位置
};

指令集的转换

理解这节需要初步的编译原理相关知识,我们使用了非常朴素的转换算法,并且基本没有做指令优化。转换过程分为几步:

  • BasicBlock 划分。 将IL指令块切成一段段不包含任何跳转指令的代码块,称之为BasicBlock。
  • 模拟指令执行流程,同时使用广度优先遍历算法遍历所有BasicBlock,将每个BasicBlock转换为IRBasicBlock。

BasicBlock到IRBasicBlock转换采用了最朴素的一对一指令转换算法,转换相关代码在transform::HiTransform::Transform。我们以add指令为例:


case OpcodeValue::ADD:
{
IL2CPP_ASSERT(evalStackTop >= 2);
EvalStackVarInfo& op1 = evalStack[evalStackTop - 2];
EvalStackVarInfo& op2 = evalStack[evalStackTop - 1];

CreateIR(ir, BinOpVarVarVar_Add_i4);
ir->op1 = op1.locOffset;
ir->op2 = op2.locOffset;
ir->ret = op1.locOffset;

EvalStackReduceDataType resultType;
switch (op1.reduceType)
{
case EvalStackReduceDataType::I4:
{
switch (op2.reduceType)
{
case EvalStackReduceDataType::I4:
{
resultType = EvalStackReduceDataType::I4;
ir->type = HiOpcodeEnum::BinOpVarVarVar_Add_i4;
break;
}
case EvalStackReduceDataType::I:
case EvalStackReduceDataType::Ref:
{
CreateAddIR(irConv, ConvertVarVar_i4_i8);
irConv->dst = irConv->src = op1.locOffset;

resultType = op2.reduceType;
ir->type = HiOpcodeEnum::BinOpVarVarVar_Add_i8;
break;
}
default:
{
IL2CPP_ASSERT(false);
break;
}
}
break;
}
case EvalStackReduceDataType::I8:
{
switch (op2.reduceType)
{
case EvalStackReduceDataType::I8:
case EvalStackReduceDataType::I: // not support i8 + i ! but we support
{
resultType = EvalStackReduceDataType::I8;
ir->type = HiOpcodeEnum::BinOpVarVarVar_Add_i8;
break;
}
default:
{
IL2CPP_ASSERT(false);
break;
}
}
break;
}
case EvalStackReduceDataType::I:
case EvalStackReduceDataType::Ref:
{
switch (op2.reduceType)
{
case EvalStackReduceDataType::I4:
{
CreateAddIR(irConv, ConvertVarVar_i4_i8);
irConv->dst = irConv->src = op2.locOffset;

resultType = op1.reduceType;
ir->type = HiOpcodeEnum::BinOpVarVarVar_Add_i8;
break;
}
case EvalStackReduceDataType::I:
case EvalStackReduceDataType::I8:
{
resultType = op1.reduceType;
ir->type = HiOpcodeEnum::BinOpVarVarVar_Add_i8;
break;
}
default:
{
IL2CPP_ASSERT(false);
break;
}
}
break;
}
case EvalStackReduceDataType::R4:
{
switch (op2.reduceType)
{
case EvalStackReduceDataType::R4:
{
resultType = op2.reduceType;
ir->type = HiOpcodeEnum::BinOpVarVarVar_Add_f4;
break;
}
default:
{
IL2CPP_ASSERT(false);
break;
}
}
break;
}
case EvalStackReduceDataType::R8:
{
switch (op2.reduceType)
{
case EvalStackReduceDataType::R8:
{
resultType = op2.reduceType;
ir->type = HiOpcodeEnum::BinOpVarVarVar_Add_f8;
break;
}
default:
{
IL2CPP_ASSERT(false);
break;
}
}
break;
}
default:
{
IL2CPP_ASSERT(false);
break;
}
}

PopStack();
op1.reduceType = resultType;
op1.byteSize = GetSizeByReduceType(resultType);
AddInst(ir);
ip++;
continue;
}

从代码可以看出,其实转换算法非常简单,就是根据add指令的参数类型,决定转换为哪条寄存器指令,同时正确设置指令的字段值。

解释执行hybridclr指令集

解释执行在代码 interpreter::InterpreterModule::Execute 函数中完成。涉及到几部分:

  • 函数帧构建,参数、局部变量、执行栈的初始化
  • 执行普通指令
  • 调用子函数
  • 异常处理

这块内容也很多,我们会在多篇文章中详细介绍实现,这里简单摘取 BinOpVarVarVar_Add_i4 指令的实现代码:

case HiOpcodeEnum::BinOpVarVarVar_Add_i4:
{
uint16_t __ret = *(uint16_t*)(ip + 2);
uint16_t __op1 = *(uint16_t*)(ip + 4);
uint16_t __op2 = *(uint16_t*)(ip + 6);
(*(int32_t*)(localVarBase + __ret)) = (*(int32_t*)(localVarBase + __op1)) + (*(int32_t*)(localVarBase + __op2));
ip += 8;
continue;
}

相信这段代码还是比较好理解的。指令集转换和指令解释相关代码是hybridclr的核心,但复杂度却不高,这得感谢il2cpp运行时帮我们承担了绝大多数复杂的元数据相关操作的支持。

其他如GC、多线程相关处理

我们在hybridclr可行性的思维实验中分析过这两部分实现。

GC

对于对象分配,我们使用il2cpp::vm::Object::New函数分配对象即可。还有一些其他涉及到GC的部分如ldstr指令中Il2CppString对象的缓存,利用了一些其他il2cpp运行时提供的GC机制。

多线程相关处理

  • volatile 。对于指令中包含volatile前缀指令,我们简单在执行代码前后插入MemoryBarrier。
  • ThreadStatic 。 使用il2cpp内置的Class的ThreadStatic变量机制即可。
  • Thread。 我们对于每个托管线程,都创建了一个对应的解释器栈。
  • async 相关。由于异步相关只是语法糖,由编译器和标准库完成了所有内容。hybridclr只需要解决其中产生的AOT泛型实例化的问题即可。

总结

概括地说,hybridclr的实现为:

  • MetadataCache::LoadAssemblyFromBytes (c#层调用Assembly.Load时触发)时加载并注册interpreter Assembly
  • il2cpp运行过程中延迟初始化类型相关元数据,其中关键为正确设置了MethodInfo元数据中methodPointer指针
  • il2cpp运行时通过methodPointer或者methodInvoke指针,再经过桥接函数跳转,最终执行了Interpreter::Execute函数。
    • Execute函数在第一次执行某interpreter函数时触发HiTransform::Transform操作,将原始IL指令翻译为hybridclr的寄存器指令。
    • 然后执行该函数对应的hybridclr寄存器指令。

至此完成hybridclr的技术原理介绍。

· 10 min read

在确定目标,动手实现hybridclr前,有一个必须考虑的问题——我们如何确定hybridclr的可行性?

il2cpp虽然不是一个极其完整的运行时,但代码仍高达12w行,复杂度相当高,想要短期内深入了解它的实现是非常困难的。除了官方几个介绍il2cpp的博客外,几乎找不到其他文档, 而且Hybrid mode execution 的实现复杂度也很高。磨刀不误砍柴工,在动手前从理论上确信这套方案有极高可行性,是完全必要的。

以我们对CLR运行时的认识,要实现 hybrid mode execution 机制,至少要解决以下几个问题

  • 能够动态注册元数据,这些动态注册的元数据必须在运行时中跟AOT元数据完全等价。
  • 所有调用动态加载的assembly中函数的路径,都能定向到正确的解释器实现。包括虚函数override、delegate回调、反射调用等等。
  • 解释器中的gc,必须能够与AOT部分的gc统一处理。
  • 多线程相关能正常工作。包括且不限于创建Thread、async、volatile、ThreadStatic等等。

我们下面一一分析解决这些问题。

动态注册元数据

我们大略地分析了il2cpp元数据初始化相关代码,得出以下结论。

首先,动态修改globalmetadata.dat这个方式不可行。因为globalmetadata.dat保存了持久化的元数据,元数据之间关系大量使用id来相互引用,添加新的数据很容易引入错误,变成极难检测的bug。另外,globalmetadata里有不少数据项由于没有文档,无法分析实际用途,也不得而知如何设置正确的值。另外,运行时会动态加载新的dll,重新计算globalmetadata.dat是成本高昂的事情。而且il2cpp中元数据管理并不支持二次加载,重复加载globalmetadata.dat会产生相当大的代码改动。

一个较可行办法,修改所有元数据访问的底层函数,检查被访问的元数据的类型,如果是AOT元数据,则保持之前的调用,如果来自动态加载,则跳转到hybridclr的元数据管理模块,返回一个恰当的值。但这儿又遇到一个问题,其次globalmetadata为了优化性能,所有dll中的元数据在统一的id命名空间下。很多元数据查询操作仅仅使用一个id参数,如何根据id区别出到底是AOT还是interpreter的元数据?

我们发现实际项目生成的globalmetadata.dat中这些元数据id的值都较小,最大也不过几十万级别。思考后用一个技巧:我们将id分成两部分: 高位为image id,低位为实际上的id,将image id=0保留给AOT元数据使用。我们为每个动态加载的dll分配一个image id,这个image中解析出的所有元数据id的高位为相应的image id。

我们通过这个技巧,hook了所有底层访问元数据的方法。大约修改了几十处,基本都是如下这样的代码,尽量不修改原始逻辑,很容易保证正确性。

const char* il2cpp::vm::GlobalMetadata::GetStringFromIndex(StringIndex index)
{
// ==={{ hybridclr
if (hybridclr::metadata::IsInterpreterIndex(index))
{
return hybridclr::metadata::MetadataModule::GetStringFromEncodeIndex(index);
}
// ===}} hybridclr
IL2CPP_ASSERT(index <= s_GlobalMetadataHeader->stringSize);
const char* strings = MetadataOffset<const char*>(s_GlobalMetadata, s_GlobalMetadataHeader->stringOffset, index);
#if __ENABLE_UNITY_PLUGIN__
if (g_get_string != NULL)
{
g_get_string((char*)strings, index);
}
#endif // __ENABLE_UNITY_PLUGIN__
return strings;
}

我们在动手前检查了多个相关函数,基本没有问题。虽然不敢确定这一定是可行的,但元数据加载是hybridclr第一阶段的开发任务,万一发现问题,及时中止hybridclr开发损失不大。于是我们认为算是解决了第一个问题。

所有调用动态加载的assembly中函数的路径,都能定向到正确的解释器实现

我们分析了il2cpp中关于Method元数据的管理方式,发现MethodInfo结构中保存了运行时实际执行逻辑的函数指针。如果我们简单地设置动态加载的函数元数据的MethodInfo结构的指针为正确的解释器函数,能否保证所有流程对该函数的调用,都能正确定向到解释器函数呢?

严谨思考后的结论是肯定的。首先AOT部分不可能直接调用动态加载的dll中的函数。其次,运行时并没有其他地方保存了函数指针。意味着,如果想调用动态加载的函数,必须获得MethodInfo中的函数指针,才能正确执行到目标函数。意味着我们运行过程中所有对该函数的调用一定会调用到正确的解释器函数。

至于我们解决了第二个问题。

解释器中的gc,必须能够与AOT部分的gc统一处理

很容易观察到,通过il2cpp::vm::Object::New可以分配托管对象,通过gc模块的函数可以分配一些能够被gc自动管理的内存。但我们如何保证,使用这种方式就一定能保存正确性呢,会不会有特殊的使用规则 ,hybridclr的解释器代码无法与之配合工作呢?

考虑到AOT代码中也有很多gc相关的操作,我们检查了一些il2cpp为这些操作生成的c++代码,都是简简单单直接调用 il2cpp::vm::Object::New 之类的函数,并无特殊之处。 可以这么分析:il2cpp生成的代码是普通的c++代码,hybridclr解释器代码也是c++代码,既然生成的代码的内存使用方式能够正确工作,那么hybridclr解释器中gc相关代码,肯定也能正确工作。

至此,我们解决了第三个问题。

多线程相关代码能正常工作

与上一个问题相似。我们检查了il2cpp生成的c++代码,发现并无特殊之处也能在多线程环境下正常运行,那我们也可以非常确信,hybridclr解释器的代码只要符合常规的多线程的要求,也能在多线程环境下正常运行。

至此,我们解决了第四个问题。

总结

我们通过少量的对实际il2cpp代码的观察,以及对CLR运行时原理的了解,再配合思维实验,可以99.9%以上确定,既然il2cpp生成的代码都能在运行时正确运行,那hybridclr解释模式下执行的代码,也能正确运行。

我们在完成思维实验的那一刻,难掩内心激动的心情。作为一名物理专业的IT人,脑海里第一时间浮现出爱因斯坦在思考广义相对论时的,使用电梯思维实验得出引力使时空弯曲这一惊人结论。我们不敢比肩这种伟大的科学家,但我们确实在使用类似的思维技巧。可以说,hybridclr不是简单的经验总结,是深刻洞察力与分析能力孕育的结果。

· 5 min read

我们在实现hybridclr过程中,深入研究了CLI规范与il2cpp实现,积累了大量宝贵的经验。考虑到国内游戏行业对clr及il2cpp相关的资料不多,我们希望将这些知识系统性地整理出来,帮助那些渴望深入研究Unity下CLR Runtime实现的开发者们,更好了掌握相关知识。

Inspect il2cpp 目录

  • il2cpp 序章
    • il2cpp 介绍
    • il2cpp il2cpp 架构及源码结构介绍
    • il2cpp 安装、编译及调试
  • il2cpp 运行时实现
    • il2cpp Runtime 初始化流程剖析
    • il2cpp metadata (此节内容极其庞大)
      • CLI metadata 简略介绍
      • il2cpp metadata 初始化流程剖析
      • persistent metadata 即 global-metadata.dat 介绍
      • runtime metadata 介绍
    • il2cpp IL to c++ 代码的转换
      • 基础指令集
      • 对象模型相关指令 (内容极其庞大)
      • 异常机制
      • 泛型共享机制
      • PInvoke 与 MonoPInvokeCallbackAttribute相关。(一个有趣的问题:il2cpp中lua回调c#函数相比与回调普通c函数,多了哪些开销?)
      • icalls
      • delegate
      • 反射相关支持
      • 跨平台相关
    • 类型初始化 Class::Init 流程剖析
    • 泛型类实现
    • 泛型函数实现
    • 泛型共享机制
    • 异常机制
    • 反射相关实现
    • 值类型相关机制
    • box与unbox相关机制
    • object、string、Array、TypedReference等一些基础BCL类型的探究
    • icalls 实现
    • il2cpp及mono的bug介绍
    • il2cpp gc管理
    • il2cpp 多线程及内存模型处理
  • 2018-2022中il2cpp实现的演化

Inspect hybridclr 目录

  • 1 导论
    • 手游热更新技术的发展史
    • 当前主流热更新技术的缺陷
    • 下一代热更新技术探索——unity引擎下的原生c#热更新技术
  • 2 hybridclr概览
    • 1 hybridclr介绍
    • 2 关于hybridclr可行性的思维实验
    • 3 hybridclr技术原理剖析
  • 3 metadata 加载
    • 1 coff文件解析
    • 2 stream 解析
    • 3 原始tables解析
    • 4 复杂元数据解析
  • 4 metadata 注册
    • 1 assembly 注册
    • 2 TypeDefinition 注册(复杂)
    • 3 generic class
    • 4 generic method
    • 5 桥接函数
  • 5 寄存器指令集设计
    • IL指令集介绍
    • 基于栈的指令集的缺陷
    • 寄存器指令集
      • 基础转换规则
      • 指令静态特例化
      • resolve data
      • 其他特殊处理
    • 一些用于解释器JIT技术
      • InitOnce JIT优化技术
  • 6 指令集transform实现
    • 基础思路介绍
    • transform算法
      • basic block划分
      • 基于basic block的指令流遍历及转换
      • 普通指令
      • 函数调用指令
      • branch相关指令
      • 异常相关指令
    • 指令集优化
      • 指令合并
      • ValueType相关指令优化
      • 函数inline
      • instinct函数替换
    • virtual Execution System
      • Thread Interpreter Stack
      • Interpreter Frame实现与优化
      • localloc 与 Local Memory Pool
      • 桥接函数
      • 指令实现
      • instinct函数
      • reflection相关实现
      • extern函数实现
    • 跨平台兼容性处理
      • 32位与64位
      • 内存对齐访问
      • x86与arm系列区别
        • float与int之间转换
        • abi
      • 虚拟地址空间差异
      • 一些行为不定的函数
        • memcpy
    • AOT泛型 (基于补充元数据的泛型实例化技术)
    • AOT hotfix实现
  • misc
    • 解决Unity资源上挂载interpreter脚本
    • gc 处理
    • 多线程相关处理
  • test框架
    • 测试用例项目
      • bootstrap cpp测试集
      • .net c#测试集
      • 生成测试报告
    • 测试工具
      • 创建多版本多平台的测试项目
      • 运行测试用例,收集测试报告
      • 生成最终测试报告
    • 自动化测试DevOps框架
- + \ No newline at end of file diff --git a/en/blog/archive.html b/en/blog/archive.html index d37fea942..f26f36ae8 100644 --- a/en/blog/archive.html +++ b/en/blog/archive.html @@ -9,13 +9,13 @@ - + - + \ No newline at end of file diff --git a/en/blog/catelog.html b/en/blog/catelog.html index 40aa704cb..c52ba5a1f 100644 --- a/en/blog/catelog.html +++ b/en/blog/catelog.html @@ -9,13 +9,13 @@ - +

深入探究hybridclr 目录

· 5 min read

我们在实现hybridclr过程中,深入研究了CLI规范与il2cpp实现,积累了大量宝贵的经验。考虑到国内游戏行业对clr及il2cpp相关的资料不多,我们希望将这些知识系统性地整理出来,帮助那些渴望深入研究Unity下CLR Runtime实现的开发者们,更好了掌握相关知识。

Inspect il2cpp 目录

  • il2cpp 序章
    • il2cpp 介绍
    • il2cpp il2cpp 架构及源码结构介绍
    • il2cpp 安装、编译及调试
  • il2cpp 运行时实现
    • il2cpp Runtime 初始化流程剖析
    • il2cpp metadata (此节内容极其庞大)
      • CLI metadata 简略介绍
      • il2cpp metadata 初始化流程剖析
      • persistent metadata 即 global-metadata.dat 介绍
      • runtime metadata 介绍
    • il2cpp IL to c++ 代码的转换
      • 基础指令集
      • 对象模型相关指令 (内容极其庞大)
      • 异常机制
      • 泛型共享机制
      • PInvoke 与 MonoPInvokeCallbackAttribute相关。(一个有趣的问题:il2cpp中lua回调c#函数相比与回调普通c函数,多了哪些开销?)
      • icalls
      • delegate
      • 反射相关支持
      • 跨平台相关
    • 类型初始化 Class::Init 流程剖析
    • 泛型类实现
    • 泛型函数实现
    • 泛型共享机制
    • 异常机制
    • 反射相关实现
    • 值类型相关机制
    • box与unbox相关机制
    • object、string、Array、TypedReference等一些基础BCL类型的探究
    • icalls 实现
    • il2cpp及mono的bug介绍
    • il2cpp gc管理
    • il2cpp 多线程及内存模型处理
  • 2018-2022中il2cpp实现的演化

Inspect hybridclr 目录

  • 1 导论
    • 手游热更新技术的发展史
    • 当前主流热更新技术的缺陷
    • 下一代热更新技术探索——unity引擎下的原生c#热更新技术
  • 2 hybridclr概览
    • 1 hybridclr介绍
    • 2 关于hybridclr可行性的思维实验
    • 3 hybridclr技术原理剖析
  • 3 metadata 加载
    • 1 coff文件解析
    • 2 stream 解析
    • 3 原始tables解析
    • 4 复杂元数据解析
  • 4 metadata 注册
    • 1 assembly 注册
    • 2 TypeDefinition 注册(复杂)
    • 3 generic class
    • 4 generic method
    • 5 桥接函数
  • 5 寄存器指令集设计
    • IL指令集介绍
    • 基于栈的指令集的缺陷
    • 寄存器指令集
      • 基础转换规则
      • 指令静态特例化
      • resolve data
      • 其他特殊处理
    • 一些用于解释器JIT技术
      • InitOnce JIT优化技术
  • 6 指令集transform实现
    • 基础思路介绍
    • transform算法
      • basic block划分
      • 基于basic block的指令流遍历及转换
      • 普通指令
      • 函数调用指令
      • branch相关指令
      • 异常相关指令
    • 指令集优化
      • 指令合并
      • ValueType相关指令优化
      • 函数inline
      • instinct函数替换
    • virtual Execution System
      • Thread Interpreter Stack
      • Interpreter Frame实现与优化
      • localloc 与 Local Memory Pool
      • 桥接函数
      • 指令实现
      • instinct函数
      • reflection相关实现
      • extern函数实现
    • 跨平台兼容性处理
      • 32位与64位
      • 内存对齐访问
      • x86与arm系列区别
        • float与int之间转换
        • abi
      • 虚拟地址空间差异
      • 一些行为不定的函数
        • memcpy
    • AOT泛型 (基于补充元数据的泛型实例化技术)
    • AOT hotfix实现
  • misc
    • 解决Unity资源上挂载interpreter脚本
    • gc 处理
    • 多线程相关处理
  • test框架
    • 测试用例项目
      • bootstrap cpp测试集
      • .net c#测试集
      • 生成测试报告
    • 测试工具
      • 创建多版本多平台的测试项目
      • 运行测试用例,收集测试报告
      • 生成最终测试报告
    • 自动化测试DevOps框架
- + \ No newline at end of file diff --git a/en/blog/instructions.html b/en/blog/instructions.html index b21476dd9..41add87db 100644 --- a/en/blog/instructions.html +++ b/en/blog/instructions.html @@ -9,7 +9,7 @@ - + @@ -19,7 +19,7 @@ 尽管可以针对64位和32位设计两套完全不同的指令,但出于方便维护考虑,hybridclr还是统一使用了一套指令集。

hybridclr指令的一些设计约束:

  • 每条指令的前2字节必须为opcode
  • 满足内存对齐。指令param的size可能是1、2、4、8。为了满足内存对齐的要求,我们在param之间插入一些uint8_t类型的无用padding数据。

padding优化

为了最大程度减少浪费的padding数据空间,我们将所有param排序,从小到大排列,同时插入padding以满足内存对齐。经过不太复杂的推理,我们可以知道,每条指令最多浪费7字节的padding空间。

指令实现

由于IL指令众多,我们无法一一介绍所有指令对应的hybridclr指令集设计,我们分为几大类详细介绍。

空指令

如nop、pop指令,直接在transform阶段就被消除,完全不产生对应的hybridclr指令。

简单数据复制指令

典型有

  • 操作函数参数的指令。如 ldarg、starg、ldarga
  • 操作函数局部变量的指令。如 ldloc、stloc、ldloca
  • 隐含操作eval stack栈顶数据的指令。如add、dup

对于操作函数帧栈的指令,一般要做以下几类处理

  • 为源数据和目标数据添加对应的逻辑地址字段
  • 对于源数据或者目标数据有多个变种的指令,统一为带逻辑地址字段的指令。如ldarg.0 - ldarg.3、ldarg、ldarg.s 都统一为一条指令。

以典型的ldarg指令为例。如果被操作函数参数的类型为int时,对应的hybridclr指令为

struct IRCommon
{
uint16_t opcode;
}

struct IRLdlocVarVar : IRCommon
{
uint16_t dst;
uint16_t src;
uint8_t __pad6;
uint8_t __pad7;
};

// 对应解释执行代码
case HiOpcodeEnum::LdlocVarVar:
{
uint16_t __dst = *(uint16_t*)(ip + 2);
uint16_t __src = *(uint16_t*)(ip + 4);
(*(uint64_t*)(localVarBase + __dst)) = (*(uint64_t*)(localVarBase + __src));
ip += 8;
continue;
}

  • dst 指向当前执行栈顶的逻辑地址
  • src ldarg中要加载的变量的逻辑地址
  • __pad6 为了内存对齐而插入的
  • __pad7 同上

需要expand目标数据的指令

根据CLI规范,像byte、sbyte、short、ushort这种size小于4的primitive类型,以及underlying type为这些primitive类型的枚举,它们被加载到evaluate stack时,需要符号扩展为int32_t类型数据。我们不想执行ldarg指令时作运行时判断,因为这样会降低性能。因此为这些size小于4的操作,单独设计了对应的指令。

以byte类型为例,对应的hybridclr指令为

struct IRLdlocExpandVarVar_u1 : IRCommon
{
uint16_t dst;
uint16_t src;
uint8_t __pad6;
uint8_t __pad7;
};

// 对应解释执行代码
case HiOpcodeEnum::LdlocExpandVarVar_u1:
{
uint16_t __dst = *(uint16_t*)(ip + 2);
uint16_t __src = *(uint16_t*)(ip + 4);
(*(int32_t*)(localVarBase + __dst)) = (*(uint8_t*)(localVarBase + __src));
ip += 8;
continue;
}

静态特例化的指令

有一类指令的实际执行方式跟它的参数类型有关,如add。当操作的数是int、long、float、double时,执行对应类型的数据相加操作。但实际上由于IL程序的静态性,每条指令操作的数据类型肯定是固定的,并不需要运行时维护数据类型,并且根据数据类型决定执行什么操作。我们使用一种叫静态特例化的技术,为这种指令设计了多条hybridclr指令,在transform时,根据具体的操作数据类型,生成相应的指令。

以add 对两个int32_t类型数据相加为例

struct IRBinOpVarVarVar_Add_i4 : IRCommon
{
uint16_t ret;
uint16_t op1;
uint16_t op2;
};

// 对应解释执行代码
case HiOpcodeEnum::BinOpVarVarVar_Add_i4:
{
uint16_t __ret = *(uint16_t*)(ip + 2);
uint16_t __op1 = *(uint16_t*)(ip + 4);
uint16_t __op2 = *(uint16_t*)(ip + 6);
(*(int32_t*)(localVarBase + __ret)) = (*(int32_t*)(localVarBase + __op1)) + (*(int32_t*)(localVarBase + __op2));
ip += 8;
continue;
}

直接包含常量的指令

有一些指令包含普通字面常量,如ldc指令。相应的寄存器指令只是简单地添加了相应大小的字段。

以ldc int32_t类型数据为例

struct IRLdcVarConst_4 : IRCommon
{
uint16_t dst;
uint32_t src;
};

// 对应解释执行代码
case HiOpcodeEnum::LdcVarConst_4:
{
uint16_t __dst = *(uint16_t*)(ip + 2);
uint32_t __src = *(uint32_t*)(ip + 4);
(*(int32_t*)(localVarBase + __dst)) = __src;
ip += 8;
continue;
}

隐含常量的指令

有一些指令隐含了所操作的常量,如 ldnull、ldc.i4.0 - ldc.i4.8 等等。对于这类指令,如果有对应的直接包含常量的指令的实现,则简单转换为 上一节中介绍的 直接包含常量的指令。后续可能会进一步优化。

以ldnull为例

struct IRLdnullVar : IRCommon
{
uint16_t dst;
uint8_t __pad4;
uint8_t __pad5;
uint8_t __pad6;
uint8_t __pad7;
};

// 对应解释执行代码
case HiOpcodeEnum::LdnullVar:
{
uint16_t __dst = *(uint16_t*)(ip + 2);
(*(void**)(localVarBase + __dst)) = nullptr;
ip += 8;
continue;
}

指令共享

为了减少指令数量,操作相同size常量的ldc指令会被合并为同一个。如ldloc.r4 指令就被合并到ldloc.i4指令的实现。

包含resolved后数据的指令

有一些指令包含metadata token,如sizeof、ldstr、newobj。为了避免巨大的运行时resolve开销,hybridclr在transform这些指令时就已经将包含token数据resolve为对应的runtime metadata。

更细致一些,又分为两类。

直接包含resolved后数据的指令

以sizeof为例,原始指令token为类型信息,transform时,直接计算了对应ValueType的size,甚至都不需要专门为sizeof设计对应的指令,直接使用现成的LdcVarConst_4指令。

case OpcodeValue::SIZEOF:
{
uint32_t token = (uint32_t)GetI4LittleEndian(ip + 2);
Il2CppClass* objKlass = image->GetClassFromToken(token, klassContainer, methodContainer, genericContext);
IL2CPP_ASSERT(objKlass);
int32_t typeSize = GetTypeValueSize(objKlass);
CI_ldc4(typeSize, EvalStackReduceDataType::I4);
ip += 6;
continue;
}

间接包含resolved后数据的指令

像ldstr、newobj这些指令包含的token经过resolve后,变成对应runtime metadata的指针,考虑到指针在不同平台大小不一,因此不直接将这个指针放到指令中,而是换成一个uint32_t类型的指向InterpMethodInfo::resolvedData字段的index param。执行过程中需要一次向resolvedData的查询操作,时间复杂度为O(1)。

以newobj指令为例

struct IRLdstrVar : IRCommon
{
uint16_t dst;
uint32_t str;
};

// 对应解释执行代码
case HiOpcodeEnum::LdstrVar:
{
uint16_t __dst = *(uint16_t*)(ip + 2);
uint32_t __str = *(uint32_t*)(ip + 4);
(*(Il2CppString**)(localVarBase + __dst)) = ((Il2CppString*)imi->resolveDatas[__str]);
ip += 8;
continue;
}

分支跳转指令

原始IL字节码使用了相对offset的跳转目标,并且几乎为每条跳转相关指令都设计了near和far offset 两条指令,hybridclr为了简单起见,直接使用4字节的绝对跳转地址。

以br无条件跳转指令为例


struct IRBranchUncondition_4 : IRCommon
{
uint8_t __pad2;
uint8_t __pad3;
int32_t offset;
};

// 对应解释执行代码
case HiOpcodeEnum::BranchUncondition_4:
{
int32_t __offset = *(int32_t*)(ip + 4);
ip = ipBase + __offset;
continue;
}

offset为转换后的指令地址的绝对偏移。

对象成员访问指令

由于字段在对象中的偏移已经完全确定,transform时计算出字段在对象中的偏移,保存为指令的offset param, 执行时根据对象大小,使用this指针和偏移,直接访问字段数据。

以ldfld 读取int类型字段为例


struct IRLdfldVarVar_i4 : IRCommon
{
uint16_t dst;
uint16_t obj;
uint16_t offset;
};

// 对应解释执行代码
case HiOpcodeEnum::LdfldVarVar_i4:
{
uint16_t __dst = *(uint16_t*)(ip + 2);
uint16_t __obj = *(uint16_t*)(ip + 4);
uint16_t __offset = *(uint16_t*)(ip + 6);
CHECK_NOT_NULL_THROW((*(Il2CppObject**)(localVarBase + __obj)));
(*(int32_t*)(localVarBase + __dst)) = *(int32_t*)((uint8_t*)(*(Il2CppObject**)(localVarBase + __obj)) + __offset);
ip += 8;
continue;
}

ThreadStatic 成员访问指令

在初始化Il2CppClass时,如果它包含ThreadStatic属性标记的静态成员变量,则为它分配一个可以放下这个类型所有ThreadStatic变量的ThreadLocalStorage的连续空间。 借助于il2cpp运行时对ThreadStatic的支持,相关指令实现相当简单直接。

以ldsfld指令为例


struct IRLdthreadlocalVarVar_i4 : IRCommon
{
uint16_t dst;
int32_t offset;
int32_t klass;
};

// 对应解释执行代码
case HiOpcodeEnum::LdthreadlocalVarVar_i4:
{
uint16_t __dst = *(uint16_t*)(ip + 2);
uint32_t __klass = *(uint32_t*)(ip + 8);
int32_t __offset = *(int32_t*)(ip + 4);

Il2CppClass* _klass = (Il2CppClass*)imi->resolveDatas[__class];
Interpreter::RuntimeClassCCtorInit(_klass);
(*(int32_t*)(localVarBase + __dst)) = *(int32_t*)((byte*)il2cpp::vm::Thread::GetThreadStaticData(_klass->thread_static_fields_offset) + __offset);
ip += 16;
continue;
}

数组访问相关指令

比较常规直接,不过有个特殊点:根据规范index变量可以是i4或者native int类型。由于数组访问是非常频繁的操作,我们不想插入运行时数据类型类型及转换,因为我们根据index变量的size为每条数组相关指令设计了2条hybridclr指令。

以ldelem.i4 指令的index是i4类型的情形为例

struct IRGetArrayElementVarVar_i4_4 : IRCommon
{
uint16_t dst;
uint16_t arr;
uint16_t index;
};

// 对应解释执行代码
case HiOpcodeEnum::GetArrayElementVarVar_i4_4:
{
uint16_t __dst = *(uint16_t*)(ip + 2);
uint16_t __arr = *(uint16_t*)(ip + 4);
uint16_t __index = *(uint16_t*)(ip + 6);
Il2CppArray* arr = (*(Il2CppArray**)(localVarBase + __arr));
CHECK_NOT_NULL_AND_ARRAY_BOUNDARY(arr, (*(int32_t*)(localVarBase + __index)));
(*(int32_t*)(localVarBase + __dst)) = il2cpp_array_get(arr, int32_t, (*(int32_t*)(localVarBase + __index)));
ip += 8;
continue;
}

函数调用指令

目前调用AOT函数和调用Interpreter函数使用不同的指令,因为Interpreter函数可以直接复用已经压到栈顶的数据,可以完全优化掉 Manged2Native -> Native2Managed 这个过程,提升性能。

调用解释器函数时可以复用当前 InterpreterModule::Execute函数帧,也节省了函数调用开销,同时也避免了解释器嵌套调用过深导致native栈overflow的问题。

对于带返回值的函数,由于多了一个返回值地址参数ret,与返回void的函数分别设计了不同指令。

如果调用的是AOT函数,由于每条函数的参数不定,我们将参数信息记录到resolvedDatas,然后argIdxs中保存这个间接索引。另外还需要通过桥接函数完成解释器函数参数到native abi函数参数的转换,为了避免运行时查找的开销,也提前计算了这个桥接函数,记录到resolvedDatas中,然后在managed2NativeMethod中保存了这个间接索引。

以call指令为例,为它设计了5条指令

  • IRCallNative_void
  • IRCallNative_ret
  • IRCallNative_ret_expand
  • IRCallInterp_void
  • IRCallInterp_ret

以IRCallNative_ret的实现为例,介绍调用AOT函数的指令:


struct IRCallNative_ret : IRCommon
{
uint16_t ret;
uint32_t managed2NativeMethod;
uint32_t methodInfo;
uint32_t argIdxs;
};

// 对应解释执行代码
case HiOpcodeEnum::CallNative_ret:
{
uint32_t __managed2NativeMethod = *(uint32_t*)(ip + 4);
uint32_t __methodInfo = *(uint32_t*)(ip + 8);
uint32_t __argIdxs = *(uint32_t*)(ip + 12);
uint16_t __ret = *(uint16_t*)(ip + 2);
void* _ret = (void*)(localVarBase + __ret);
((Managed2NativeCallMethod)imi->resolveDatas[__managed2NativeMethod])(((MethodInfo*)imi->resolveDatas[__methodInfo]), ((uint16_t*)&imi->resolveDatas[__argIdxs]), localVarBase, _ret);
ip += 16;
continue;
}

如果调用Interpreter函数,由于函数参数已经按顺序压到栈上,只需要一个argBase参数指定arg0逻辑地址即可,不需要借助resolvedDatas,也不需要managed2NativeMethod桥接函数指针。 这也是解释器函数不受桥接函数影响的原因。

以IRCallInterp_ret为例,介绍调用Interpreter函数的指令:

struct IRCallInterp_ret : IRCommon
{
uint16_t argBase;
uint16_t ret;
uint8_t __pad6;
uint8_t __pad7;
uint32_t methodInfo;
};

// 对应解释执行代码
case HiOpcodeEnum::CallInterp_ret:
{
MethodInfo* __methodInfo = *(MethodInfo**)(ip + 8);
uint16_t __argBase = *(uint16_t*)(ip + 2);
uint16_t __ret = *(uint16_t*)(ip + 4);
CALL_INTERP_RET((ip + 16), __methodInfo, (StackObject*)(void*)(localVarBase + __argBase), (void*)(localVarBase + __ret));
continue;
}

异常机制相关指令

异常机制相关指令本身不复杂,但异常处理机制非常复杂。

异常这种特殊的流程控制指令,跟分支跳转指令相似,原始指令里包含了相对offset,为了简单起见,指令转换时我们改成int32_t类型的绝对offset。

以leave指令为例

struct IRLeaveEx : IRCommon
{
uint8_t __pad2;
uint8_t __pad3;
int32_t offset;
};

// 对应解释执行代码
case HiOpcodeEnum::LeaveEx:
{
int32_t __offset = *(int32_t*)(ip + 4);
LEAVE_EX(__offset);
continue;
}

一些额外的instinct 指令

对于一些特别常见的函数,为了优化性能,hybridclr直接内置了相应的指令,例如 new Vector{2,3,4},如可空变量相关操作。这些instinct指令的执行性能基本与AOT持平。

以 new Vector3() 为例


struct IRNewVector3_3 : IRCommon
{
uint16_t obj;
uint16_t x;
uint16_t y;
uint16_t z;
uint8_t __pad10;
uint8_t __pad11;
uint8_t __pad12;
uint8_t __pad13;
uint8_t __pad14;
uint8_t __pad15;
};

// 对应解释执行代码
case HiOpcodeEnum::NewVector3_3:
{
uint16_t __obj = *(uint16_t*)(ip + 2);
uint16_t __x = *(uint16_t*)(ip + 4);
uint16_t __y = *(uint16_t*)(ip + 6);
uint16_t __z = *(uint16_t*)(ip + 8);
*(HtVector3f*)(*(void**)(localVarBase + __obj)) = {(*(float*)(localVarBase + __x)), (*(float*)(localVarBase + __y)), (*(float*)(localVarBase + __z))};
ip += 16;
continue;
}

InitOnce 指令

有一些指令(如ldsfld)第一次执行的时候需要进行初始化操作,但后续再次执行时,不需要再执行初始化操作。但即使这样,免不了一个检查是否已经初始化的操作,我们希望完全优化掉这个检查行为。InitOnce动态JIT技术用于解决这个问题。

InitOnce是hybridclr的专利技术,暂未在代码中实现,这儿不详细介绍。

其他技术相关指令

限于篇幅,对于这些指令,会在单独的文章中介绍

总结

至此我们完成hybridclr指令集实现相关介绍。

- + \ No newline at end of file diff --git a/en/blog/mindexperiment.html b/en/blog/mindexperiment.html index 42c6b39b6..b16b17f3f 100644 --- a/en/blog/mindexperiment.html +++ b/en/blog/mindexperiment.html @@ -9,14 +9,14 @@ - +

关于hybridclr可行性的思维实验

· 10 min read

在确定目标,动手实现hybridclr前,有一个必须考虑的问题——我们如何确定hybridclr的可行性?

il2cpp虽然不是一个极其完整的运行时,但代码仍高达12w行,复杂度相当高,想要短期内深入了解它的实现是非常困难的。除了官方几个介绍il2cpp的博客外,几乎找不到其他文档, 而且Hybrid mode execution 的实现复杂度也很高。磨刀不误砍柴工,在动手前从理论上确信这套方案有极高可行性,是完全必要的。

以我们对CLR运行时的认识,要实现 hybrid mode execution 机制,至少要解决以下几个问题

  • 能够动态注册元数据,这些动态注册的元数据必须在运行时中跟AOT元数据完全等价。
  • 所有调用动态加载的assembly中函数的路径,都能定向到正确的解释器实现。包括虚函数override、delegate回调、反射调用等等。
  • 解释器中的gc,必须能够与AOT部分的gc统一处理。
  • 多线程相关能正常工作。包括且不限于创建Thread、async、volatile、ThreadStatic等等。

我们下面一一分析解决这些问题。

动态注册元数据

我们大略地分析了il2cpp元数据初始化相关代码,得出以下结论。

首先,动态修改globalmetadata.dat这个方式不可行。因为globalmetadata.dat保存了持久化的元数据,元数据之间关系大量使用id来相互引用,添加新的数据很容易引入错误,变成极难检测的bug。另外,globalmetadata里有不少数据项由于没有文档,无法分析实际用途,也不得而知如何设置正确的值。另外,运行时会动态加载新的dll,重新计算globalmetadata.dat是成本高昂的事情。而且il2cpp中元数据管理并不支持二次加载,重复加载globalmetadata.dat会产生相当大的代码改动。

一个较可行办法,修改所有元数据访问的底层函数,检查被访问的元数据的类型,如果是AOT元数据,则保持之前的调用,如果来自动态加载,则跳转到hybridclr的元数据管理模块,返回一个恰当的值。但这儿又遇到一个问题,其次globalmetadata为了优化性能,所有dll中的元数据在统一的id命名空间下。很多元数据查询操作仅仅使用一个id参数,如何根据id区别出到底是AOT还是interpreter的元数据?

我们发现实际项目生成的globalmetadata.dat中这些元数据id的值都较小,最大也不过几十万级别。思考后用一个技巧:我们将id分成两部分: 高位为image id,低位为实际上的id,将image id=0保留给AOT元数据使用。我们为每个动态加载的dll分配一个image id,这个image中解析出的所有元数据id的高位为相应的image id。

我们通过这个技巧,hook了所有底层访问元数据的方法。大约修改了几十处,基本都是如下这样的代码,尽量不修改原始逻辑,很容易保证正确性。

const char* il2cpp::vm::GlobalMetadata::GetStringFromIndex(StringIndex index)
{
// ==={{ hybridclr
if (hybridclr::metadata::IsInterpreterIndex(index))
{
return hybridclr::metadata::MetadataModule::GetStringFromEncodeIndex(index);
}
// ===}} hybridclr
IL2CPP_ASSERT(index <= s_GlobalMetadataHeader->stringSize);
const char* strings = MetadataOffset<const char*>(s_GlobalMetadata, s_GlobalMetadataHeader->stringOffset, index);
#if __ENABLE_UNITY_PLUGIN__
if (g_get_string != NULL)
{
g_get_string((char*)strings, index);
}
#endif // __ENABLE_UNITY_PLUGIN__
return strings;
}

我们在动手前检查了多个相关函数,基本没有问题。虽然不敢确定这一定是可行的,但元数据加载是hybridclr第一阶段的开发任务,万一发现问题,及时中止hybridclr开发损失不大。于是我们认为算是解决了第一个问题。

所有调用动态加载的assembly中函数的路径,都能定向到正确的解释器实现

我们分析了il2cpp中关于Method元数据的管理方式,发现MethodInfo结构中保存了运行时实际执行逻辑的函数指针。如果我们简单地设置动态加载的函数元数据的MethodInfo结构的指针为正确的解释器函数,能否保证所有流程对该函数的调用,都能正确定向到解释器函数呢?

严谨思考后的结论是肯定的。首先AOT部分不可能直接调用动态加载的dll中的函数。其次,运行时并没有其他地方保存了函数指针。意味着,如果想调用动态加载的函数,必须获得MethodInfo中的函数指针,才能正确执行到目标函数。意味着我们运行过程中所有对该函数的调用一定会调用到正确的解释器函数。

至于我们解决了第二个问题。

解释器中的gc,必须能够与AOT部分的gc统一处理

很容易观察到,通过il2cpp::vm::Object::New可以分配托管对象,通过gc模块的函数可以分配一些能够被gc自动管理的内存。但我们如何保证,使用这种方式就一定能保存正确性呢,会不会有特殊的使用规则 ,hybridclr的解释器代码无法与之配合工作呢?

考虑到AOT代码中也有很多gc相关的操作,我们检查了一些il2cpp为这些操作生成的c++代码,都是简简单单直接调用 il2cpp::vm::Object::New 之类的函数,并无特殊之处。 可以这么分析:il2cpp生成的代码是普通的c++代码,hybridclr解释器代码也是c++代码,既然生成的代码的内存使用方式能够正确工作,那么hybridclr解释器中gc相关代码,肯定也能正确工作。

至此,我们解决了第三个问题。

多线程相关代码能正常工作

与上一个问题相似。我们检查了il2cpp生成的c++代码,发现并无特殊之处也能在多线程环境下正常运行,那我们也可以非常确信,hybridclr解释器的代码只要符合常规的多线程的要求,也能在多线程环境下正常运行。

至此,我们解决了第四个问题。

总结

我们通过少量的对实际il2cpp代码的观察,以及对CLR运行时原理的了解,再配合思维实验,可以99.9%以上确定,既然il2cpp生成的代码都能在运行时正确运行,那hybridclr解释模式下执行的代码,也能正确运行。

我们在完成思维实验的那一刻,难掩内心激动的心情。作为一名物理专业的IT人,脑海里第一时间浮现出爱因斯坦在思考广义相对论时的,使用电梯思维实验得出引力使时空弯曲这一惊人结论。我们不敢比肩这种伟大的科学家,但我们确实在使用类似的思维技巧。可以说,hybridclr不是简单的经验总结,是深刻洞察力与分析能力孕育的结果。

- + \ No newline at end of file diff --git a/en/blog/principle.html b/en/blog/principle.html index f1a382d1a..a19fbc051 100644 --- a/en/blog/principle.html +++ b/en/blog/principle.html @@ -9,13 +9,13 @@ - +

hybridclr技术原理剖析

· 23 min read

我们在上一节完成了hybridclr可行性分析。由于hybridclr内容极多,限于篇幅本篇文章主要概述性介绍hybridclr的技术实现。

CLR和il2cpp基础

给纯AOT的il2cpp运行时添加一个原生interpreter模块,最终实现hybrid mode execution,这看起来是非常复杂的事情。

其实不然,程序不外乎代码+数据。CLR运行中做的事情,综合起来主要就几种:

  1. 执行简单的内存操作或者计算或者逻辑跳转。这部分与CLI的Base指令集大致对应
  2. 执行一个依赖于元数据信息的基础操作。例如 a.x, arr[3] 这种,依赖于元数据信息才能正确工作的代码。对应部分CLI的Object Model指令集。
  3. 执行一个依赖元数据的较复杂的操作。如 typeof(object),a is string、(object)5 这种依赖于运行时提供的函数及相应元数据才正确工作的代码。对应部分CLI的Object Model指令集。
  4. 函数调用。包括且不限于被AOT函数调用及调用AOT函数,及interpreter之间的函数调用。对应CLI指令集中的 call、callvir、newobj 等Object Model指令。

如果对CLR有深入的了解和透彻的分析,为了实现hybrid mode execution,hybridclr核心要完成的就以下两件事,其他则是无碍全局的细节:

  • assembly信息能够加载和注册。 在此基础可以实现 1-3
  • 确保interpreter函数能被找到并且被调用,并且能执行出正确的结果。则可以实现 4

由于彻底理解以上内容需要较丰富的对CLR的认知以及较强的洞察力,我们不再费口舌解释,不能理解的开发者不必深究,继续看后续章节。

核心模块

从功能来看包含以下核心部分:

  • metadata初级解析
  • metadata高级元数据结构解析
  • metadata动态注册
  • 寄存器指令集设计
  • IL指令集到hybridclr寄存器指令集的转换
  • 解释执行hybridclr指令集
  • 其他如GC、多线程相关处理

从代码结构来看包含三个目录:

  • metadata 元数据相关
  • transform 指令集转换相关
  • interpreter 解释器相关

metadata 初级解析

这部分内容技术门槛不高,但比较琐碎和辛苦,忠实地按照 ECMA-335规范 的文档实现即可。对于少量有疑惑的地方,可以网上的资料或者借鉴mono的代码。

相关代码在hybridclr\metadata目录,主要在RawImage.h和RawImage.cpp中实现。如果再细分,相关实现分为以下几个部分。

PE 文件结构解析

managed dll扩展了PE文件结构,增加了CLI相关metadata部分。这环节的主要工作有:

  • 解析PE headers
  • 解析 section headers,找出CLI header,定位出cli数据段
  • 解析出所有stream。Stream是CLI中最底层的数据结构之一,CLI将元数据根据特性分为几个大类
    • #~ 流。包含所有tables定义,是最核心的元数据结构
    • #Strings 流。包括代码中非文档类型的字符串,如类型名、字段名等等
    • #GUID 流
    • #Blob 流。一些元数据类型过于复杂,以blob格式保存。还有一些数据如数组初始化数据列表,也常常保存到Blob流。
    • #- 流
    • #Pdb 流。用于调试

解析PE文件和代码在RawImage::Load,解析stream对应的代码在RawImage::LoadStreams。

tables metadata 解析

CLI中大多数metadata被为几十种类型,每个类型的数据组织成一个table。对于每个table,每行记录都是相同大小。

初级解析中不解析table中每行记录,只解析table的每行记录大小和每个字段偏移。有一大类字段为Coded Index类型,有可能是2或4字节,并不固定,需要根据其他表的Row Count来决定table中这一列的字段大小。由于table很多,这个计算过程比较琐碎易错。

对应代码在RawImage::LoadTables,截取部分代码如下

void RawImage::BuildTableRowMetas()
{
{
auto& table = _tableRowMetas[(int)TableType::MODULE];
table.push_back({ 2 });
table.push_back({ ComputStringIndexByte() });
table.push_back({ ComputGUIDIndexByte() });
table.push_back({ ComputGUIDIndexByte() });
table.push_back({ ComputGUIDIndexByte() });
}
{
auto& table = _tableRowMetas[(int)TableType::TYPEREF];
table.push_back({ ComputTableIndexByte(TableType::MODULE, TableType::MODULEREF, TableType::ASSEMBLYREF, TableType::TYPEREF, TagBits::ResoulutionScope) });
table.push_back({ ComputStringIndexByte() });
table.push_back({ ComputStringIndexByte() });
}

// ... 其他
}

table 解析

上一节已经解析出每个table的起始数据位置、row count、表中每个字段的偏移和大小,有足够的信息可以解析出每个table中任意row的数据。table中row的id从1开始。

每个table的row的解析方式根据ECMA规范实现即可。每个table的row定义在 metadata\Coff.h文件,Row解析代码在 RawImage.h。这些解析代码都非常相似,为了避免错误,使用了大量的宏,截取部分代码如下:

TABLE2(GenericParamConstraint, TableType::GENERICPARAMCONSTRAINT, owner, constraint)
TABLE3(MemberRef, TableType::MEMBERREF, classIdx, name, signature)
TABLE1(StandAloneSig, TableType::STANDALONESIG, signature)
TABLE3(MethodImpl, TableType::METHODIMPL, classIdx, methodBody, methodDeclaration)
TABLE2(FieldRVA, TableType::FIELDRVA, rva, field)
TABLE2(FieldLayout, TableType::FIELDLAYOUT, offset, field)
TABLE3(Constant, TableType::CONSTANT, type, parent, value)
TABLE2(MethodSpec, TableType::METHODSPEC, method, instantiation)
TABLE3(CustomAttribute, TableType::CUSTOMATTRIBUTE, parent, type, value)

metadata高级元数据结构解析

从tables里直接读出来的都是持久化的初始metadata,而运行时需要的不只是这些简单原始数据,经常需要进一步resolve后的数据。例如

  • Il2CppType 。即可以是简单的 int,也可以是比较复杂的List<int>,甚至是特别复杂的List<(int,int)>&
  • MethodInfo 。 即可以是简单的object.ToString,也有复杂的泛型 IEnumerator<int>.Count

CLI的泛型机制导致元数据变得极其复杂,典型的是TypeSpec,MethodSpec,MemberSpec相关元数据的运行时解析。核心实现代码在Image.cpp中实现,剩余一部分在 InterpreterImage.cpp及AOTHomologousImage.cpp中实现。后面会有专门介绍。

metadata动态注册

根据粒度从大到小,主要分为以下几类

  • Assembly 注册。即将加载的assembly注册到il2cpp的元数据管理中。
  • TypeDefinition 注册。 这一步会生成基础运行时类型 Il2CppClass。
  • VTable虚表计算。 由于il2cpp的虚表计算是个黑盒,内部相当复杂,我们费了很多功夫才研究明白它的计算机制。后面会有专门章节介绍VTable计算,这儿不再赘述。
  • 其他元数据,如CustomAttribute计算等等。

Assembly 注册

Assembly加载的关键函数在 il2cpp::vm::MetadataCache::LoadAssemblyFromBytes 。由于il2cpp是AOT运行时,原始实现只是简单地抛出异常。我们修改和完善了实现,在其中调用了hybridclr::metadata::Assembly::LoadFromBytes,完成了Assembly的创建,然后再注册到全局Assemblies列表。相关代码实现如下:

const Il2CppAssembly* il2cpp::vm::MetadataCache::LoadAssemblyFromBytes(const char* assemblyBytes, size_t length)
{
il2cpp::os::FastAutoLock lock(&il2cpp::vm::g_MetadataLock);

Il2CppAssembly* newAssembly = hybridclr::metadata::Assembly::LoadFromBytes(assemblyBytes, length, true);
if (newAssembly)
{
// avoid register placeholder assembly twicely.
for (Il2CppAssembly* ass : s_cliAssemblies)
{
if (ass == newAssembly)
{
return ass;
}
}
il2cpp::vm::Assembly::Register(newAssembly);
s_cliAssemblies.push_back(newAssembly);
return newAssembly;
}

return nullptr;
}

TypeDefinition 注册

Assembly使用了延迟初始化方式,注册后Assembly中的类型信息并未创建相应的运行时metadata Il2CppClass,只有当第一次访问到该类型时才进行初始化。

由于交叉依赖以及为了优化性能,Il2Class的创建是个分步过程

  • Il2CppClass 基础创建
  • Il2CppClass的子元数据延迟初始化
  • 运行时Class初始化

Il2CppClass基础创建

在上一节加载Assembly时已经创建好所有类型对应的定义数据Il2CppTypeDefinition,在 il2cpp::vm::GlobalMetadata::FromTypeDefinition 中完成Il2CppClass创建工作。代码如下:

Il2CppClass* il2cpp::vm::GlobalMetadata::FromTypeDefinition(TypeDefinitionIndex index)
{
/// ... 省略其他
Il2CppClass* typeInfo = (Il2CppClass*)IL2CPP_CALLOC(1, sizeof(Il2CppClass) + (sizeof(VirtualInvokeData) * typeDefinition->vtable_count));
typeInfo->klass = typeInfo;
typeInfo->image = GetImageForTypeDefinitionIndex(index);
typeInfo->name = il2cpp::vm::GlobalMetadata::GetStringFromIndex(typeDefinition->nameIndex);
typeInfo->namespaze = il2cpp::vm::GlobalMetadata::GetStringFromIndex(typeDefinition->namespaceIndex);
typeInfo->byval_arg = *il2cpp::vm::GlobalMetadata::GetIl2CppTypeFromIndex(typeDefinition->byvalTypeIndex);
typeInfo->this_arg = typeInfo->byval_arg;
typeInfo->this_arg.byref = true;
typeInfo->typeMetadataHandle = reinterpret_cast<const Il2CppMetadataTypeHandle>(typeDefinition);
typeInfo->genericContainerHandle = GetGenericContainerFromIndex(typeDefinition->genericContainerIndex);
typeInfo->instance_size = typeDefinitionSizes->instance_size;
typeInfo->actualSize = typeDefinitionSizes->instance_size; // actualySize is instance_size for compiler generated values
typeInfo->native_size = typeDefinitionSizes->native_size;
typeInfo->static_fields_size = typeDefinitionSizes->static_fields_size;
typeInfo->thread_static_fields_size = typeDefinitionSizes->thread_static_fields_size;
typeInfo->thread_static_fields_offset = -1;
typeInfo->flags = typeDefinition->flags;
typeInfo->valuetype = (typeDefinition->bitfield >> (kBitIsValueType - 1)) & 0x1;
typeInfo->enumtype = (typeDefinition->bitfield >> (kBitIsEnum - 1)) & 0x1;
typeInfo->is_generic = typeDefinition->genericContainerIndex != kGenericContainerIndexInvalid; // generic if we have a generic container
typeInfo->has_finalize = (typeDefinition->bitfield >> (kBitHasFinalizer - 1)) & 0x1;
typeInfo->has_cctor = (typeDefinition->bitfield >> (kBitHasStaticConstructor - 1)) & 0x1;
typeInfo->is_blittable = (typeDefinition->bitfield >> (kBitIsBlittable - 1)) & 0x1;
typeInfo->is_import_or_windows_runtime = (typeDefinition->bitfield >> (kBitIsImportOrWindowsRuntime - 1)) & 0x1;
typeInfo->packingSize = ConvertPackingSizeEnumToValue(static_cast<PackingSize>((typeDefinition->bitfield >> (kPackingSize - 1)) & 0xF));
typeInfo->method_count = typeDefinition->method_count;
typeInfo->property_count = typeDefinition->property_count;
typeInfo->field_count = typeDefinition->field_count;
typeInfo->event_count = typeDefinition->event_count;
typeInfo->nested_type_count = typeDefinition->nested_type_count;
typeInfo->vtable_count = typeDefinition->vtable_count;
typeInfo->interfaces_count = typeDefinition->interfaces_count;
typeInfo->interface_offsets_count = typeDefinition->interface_offsets_count;
typeInfo->token = typeDefinition->token;
typeInfo->interopData = il2cpp::vm::MetadataCache::GetInteropDataForType(&typeInfo->byval_arg);

// 省略其他

return typeInfo;
}

可以看到TypeDefinition中字段相当多,这些都是在Assembly加载环节计算好的。

Il2CppClass的子metadata延迟初始化

由于交互依赖以及为了优化性能,Il2Class的子metadata数据使用了延迟初始化策略,分步进行,在第一次使用时才初始化。以下代码截取自 Class.h 文件:

class Class
{
// ... 其他代码
static bool Init(Il2CppClass *klass);

static void SetupEvents(Il2CppClass *klass);
static void SetupFields(Il2CppClass *klass);
static void SetupMethods(Il2CppClass *klass);
static void SetupNestedTypes(Il2CppClass *klass);
static void SetupProperties(Il2CppClass *klass);
static void SetupTypeHierarchy(Il2CppClass *klass);
static void SetupInterfaces(Il2CppClass *klass);
// ... 其他代码
};

重点来了!!!函数metadata的执行指针的绑定在SetupMethods函数中完成,其中关键代码片段如下:

void SetupMethodsLocked(Il2CppClass *klass, const il2cpp::os::FastAutoLock& lock)
{
/// ... 其他忽略的代码
for (MethodIndex index = 0; index < end; ++index)
{
Il2CppMetadataMethodInfo methodInfo = MetadataCache::GetMethodInfo(klass, index);

newMethod->name = methodInfo.name;

if (klass->valuetype)
{
Il2CppMethodPointer adjustorThunk = MetadataCache::GetAdjustorThunk(klass->image, methodInfo.token);
if (adjustorThunk != NULL)
newMethod->methodPointer = adjustorThunk;
}

// We did not find an adjustor thunk, or maybe did not need to look for one. Let's get the real method pointer.
if (newMethod->methodPointer == NULL)
newMethod->methodPointer = MetadataCache::GetMethodPointer(klass->image, methodInfo.token);

newMethod->invoker_method = MetadataCache::GetMethodInvoker(klass->image, methodInfo.token);
}
/// ... 其他忽略的代码
}

函数运行时元数据结构为 MethodInfo,定义如下,

typedef struct MethodInfo
{
Il2CppMethodPointer methodPointer;
InvokerMethod invoker_method;
const char* name;
Il2CppClass *klass;
const Il2CppType *return_type;
const ParameterInfo* parameters;

// ... 省略其他
} MethodInfo;

其中我们比较关心的是methodPointer和invoker_method这两个字段。 methodPointer指向普通执行函数,invoker_method指向反射执行函数。

我们以 methodPointer为例,进一步跟踪它的设置过程, il2cpp::vm::MetadataCache::GetMethodPointer 的实现如下:

Il2CppMethodPointer il2cpp::vm::MetadataCache::GetMethodPointer(const Il2CppImage* image, uint32_t token)
{
uint32_t rid = GetTokenRowId(token);
uint32_t table = GetTokenType(token);
if (rid == 0)
return NULL;

// ==={{ hybridclr
if (hybridclr::metadata::IsInterpreterImage(image))
{
return hybridclr::metadata::MetadataModule::GetMethodPointer(image, token);
}
// ===}} hybridclr

IL2CPP_ASSERT(rid <= image->codeGenModule->methodPointerCount);

return image->codeGenModule->methodPointers[rid - 1];
}

可以看出,如果是解释器assembly,就跳转到解释器元数据模块获得对应的MethodPointer指针。 继续跟踪,相关代码如下:


Il2CppMethodPointer InterpreterImage::GetMethodPointer(uint32_t token)
{
uint32_t methodIndex = DecodeTokenRowIndex(token) - 1;
IL2CPP_ASSERT(methodIndex < (uint32_t)_methodDefines.size());
const Il2CppMethodDefinition* methodDef = &_methodDefines[methodIndex];
return hybridclr::interpreter::InterpreterModule::GetMethodPointer(methodDef);
}

Il2CppMethodPointer InterpreterModule::GetMethodPointer(const Il2CppMethodDefinition* method)
{
const NativeCallMethod* ncm = GetNativeCallMethod(method, false);
if (ncm)
{
return ncm->method;
}
//RaiseMethodNotSupportException(method, "GetMethodPointer");
return (Il2CppMethodPointer)NotSupportNative2Managed;
}

// interpreter/InterpreterModule.cpp
template<typename T>
const NativeCallMethod* GetNativeCallMethod(const T* method, bool forceStatic)
{
char sigName[1000];
ComputeSignature(method, !forceStatic, sigName, sizeof(sigName) - 1);
auto it = s_calls.find(sigName);
return (it != s_calls.end()) ? &it->second : nullptr;
}

// s_calls 定义
static std::unordered_map<const char*, NativeCallMethod, CStringHash, CStringEqualTo> s_calls;

void InterpreterModule::Initialize()
{
for (size_t i = 0; ; i++)
{
NativeCallMethod& method = g_callStub[i];
if (!method.signature)
{
break;
}
s_calls.insert({ method.signature, method });
}

for (size_t i = 0; ; i++)
{
NativeInvokeMethod& method = g_invokeStub[i];
if (!method.signature)
{
break;
}
s_invokes.insert({ method.signature, method });
}
}

这儿根据函数定义计算其签名并且返回了一个函数指针,这个函数指针是什么呢? s_calls在InterpreterModule::Initialize中使用g_callStub初始化。那g_calStub又是什么呢?它在 interpreter/MethodBridge_xxx.cpp 中定义,原来是桥接函数相关的数据结构!

为什么要返回一个这样的函数,而不是直接将methodPointer指向 InterpreterModule::Execute 函数呢? 以 int Foo::Sum(int,int) 函数为例,这个函数的实际的签名为 int32_t (int32_t, int32_t, MethodInfo*),在调用这个methodPointer函数时,调用方一定会传递这三个参数。这些参数每个函数都不一样,如果直接指向 InterpreterModule::Execute 函数,由于ABI调用无法自省(就算可以,性能也比较差),Execute函数既无法提取出普通参数,也无法提取出MethodInfo*参数,因而无法正确运行。因此需要对每个函数,适当地将ABI调用中的这些参数传递给Execute函数。

桥接函数如其名,承担了native ABI函数参数和interpreter函数之间双向的参数的转换作用。截取一段示例代码:


/// AOT 到 interpreter 的调用参数转换
static int64_t __Native2ManagedCall_i8srr8sr(void* __arg0, double __arg1, void* __arg2, const MethodInfo* method)
{
StackObject args[4] = {*(void**)&__arg0, *(void**)&__arg1, *(void**)&__arg2 };
StackObject* ret = args + 3;
Interpreter::Execute(method, args, ret);
return *(int64_t*)ret;
}

// interpreter 到 AOT 的调用参数转换
static void __Managed2NativeCall_i8srr8sr(const MethodInfo* method, uint16_t* argVarIndexs, StackObject* localVarBase, void* ret)
{
if (hybridclr::metadata::IsInstanceMethod(method) && !localVarBase[argVarIndexs[0]].obj)
{
il2cpp::vm::Exception::RaiseNullReferenceException();
}
Interpreter::RuntimeClassCCtorInit(method);
typedef int64_t (*NativeMethod)(void* __arg0, double __arg1, void* __arg2, const MethodInfo* method);
*(int64_t*)ret = ((NativeMethod)(method->methodPointer))((void*)(localVarBase+argVarIndexs[0]), *(double*)(localVarBase+argVarIndexs[1]), (void*)(localVarBase+argVarIndexs[2]), method);
}

运行时Class初始化

即程序运行过程中第一次访问类的静态字段或者函数时或者创建对象时触发的类型初始化。在il2cpp::vm::Runtime::ClassInit(klass)中完成。不是特别关键,我们后面在单独文章中介绍。

VTable虚表计算

虚表是多态的核心。CLI的虚表计算非常复杂,但不理解它的实现并不影响开发者理解hybridclr的核心运行流程,我们后面在单独文章中介绍。

其他元数据

CustomAttribute使用延迟初始化方式,计算也很复杂,我们后面单独文章介绍。

寄存器指令集设计

直接解释原始IL指令有几个问题:

  • IL是基于栈的指令,运行时维护执行栈是个无谓的开销
  • IL有大量单指令多功能的指令,如add指令可以用于计算int、long、float、double类型的和,导致运行时需要根据上文判断到底该执行哪种计算。不仅增加了运行时判定的开销,还增加了运行时维护执行栈数据类型的开销
  • IL指令包含一些需要运行时resolve的数据,如newobj指令第一个参数是method token。token resolve是一个开销很大的操作,每次执行都进行resolve会极大拖慢执行性能
  • IL是基于栈的指令,压栈退栈相关指令数较多。像a=b+c这样的指令需要4条指令完成,而如果采用基于寄存器的指令,完全可以一条指令完成。
  • IL不适合做其他优化操作,如我们的InitOnce JIT技术。
  • 其他

因此我们需要将原始IL指令转换为更高效的寄存器指令。由于指令很多,这儿不介绍寄存器指令集的详细设计。以add指令举例


// 包含type字段,即指令ID。
struct IRCommon
{
HiOpcodeEnum type;
};

// add int, int -> int 对应的寄存器指令
struct IRBinOpVarVarVar_Add_i4 : IRCommon
{
uint16_t ret; // 计算结果对应的 栈位置
uint16_t op1; // 操作数1对应的栈位置
uint16_t op2; // 操作数2对应的栈位置
};

指令集的转换

理解这节需要初步的编译原理相关知识,我们使用了非常朴素的转换算法,并且基本没有做指令优化。转换过程分为几步:

  • BasicBlock 划分。 将IL指令块切成一段段不包含任何跳转指令的代码块,称之为BasicBlock。
  • 模拟指令执行流程,同时使用广度优先遍历算法遍历所有BasicBlock,将每个BasicBlock转换为IRBasicBlock。

BasicBlock到IRBasicBlock转换采用了最朴素的一对一指令转换算法,转换相关代码在transform::HiTransform::Transform。我们以add指令为例:


case OpcodeValue::ADD:
{
IL2CPP_ASSERT(evalStackTop >= 2);
EvalStackVarInfo& op1 = evalStack[evalStackTop - 2];
EvalStackVarInfo& op2 = evalStack[evalStackTop - 1];

CreateIR(ir, BinOpVarVarVar_Add_i4);
ir->op1 = op1.locOffset;
ir->op2 = op2.locOffset;
ir->ret = op1.locOffset;

EvalStackReduceDataType resultType;
switch (op1.reduceType)
{
case EvalStackReduceDataType::I4:
{
switch (op2.reduceType)
{
case EvalStackReduceDataType::I4:
{
resultType = EvalStackReduceDataType::I4;
ir->type = HiOpcodeEnum::BinOpVarVarVar_Add_i4;
break;
}
case EvalStackReduceDataType::I:
case EvalStackReduceDataType::Ref:
{
CreateAddIR(irConv, ConvertVarVar_i4_i8);
irConv->dst = irConv->src = op1.locOffset;

resultType = op2.reduceType;
ir->type = HiOpcodeEnum::BinOpVarVarVar_Add_i8;
break;
}
default:
{
IL2CPP_ASSERT(false);
break;
}
}
break;
}
case EvalStackReduceDataType::I8:
{
switch (op2.reduceType)
{
case EvalStackReduceDataType::I8:
case EvalStackReduceDataType::I: // not support i8 + i ! but we support
{
resultType = EvalStackReduceDataType::I8;
ir->type = HiOpcodeEnum::BinOpVarVarVar_Add_i8;
break;
}
default:
{
IL2CPP_ASSERT(false);
break;
}
}
break;
}
case EvalStackReduceDataType::I:
case EvalStackReduceDataType::Ref:
{
switch (op2.reduceType)
{
case EvalStackReduceDataType::I4:
{
CreateAddIR(irConv, ConvertVarVar_i4_i8);
irConv->dst = irConv->src = op2.locOffset;

resultType = op1.reduceType;
ir->type = HiOpcodeEnum::BinOpVarVarVar_Add_i8;
break;
}
case EvalStackReduceDataType::I:
case EvalStackReduceDataType::I8:
{
resultType = op1.reduceType;
ir->type = HiOpcodeEnum::BinOpVarVarVar_Add_i8;
break;
}
default:
{
IL2CPP_ASSERT(false);
break;
}
}
break;
}
case EvalStackReduceDataType::R4:
{
switch (op2.reduceType)
{
case EvalStackReduceDataType::R4:
{
resultType = op2.reduceType;
ir->type = HiOpcodeEnum::BinOpVarVarVar_Add_f4;
break;
}
default:
{
IL2CPP_ASSERT(false);
break;
}
}
break;
}
case EvalStackReduceDataType::R8:
{
switch (op2.reduceType)
{
case EvalStackReduceDataType::R8:
{
resultType = op2.reduceType;
ir->type = HiOpcodeEnum::BinOpVarVarVar_Add_f8;
break;
}
default:
{
IL2CPP_ASSERT(false);
break;
}
}
break;
}
default:
{
IL2CPP_ASSERT(false);
break;
}
}

PopStack();
op1.reduceType = resultType;
op1.byteSize = GetSizeByReduceType(resultType);
AddInst(ir);
ip++;
continue;
}

从代码可以看出,其实转换算法非常简单,就是根据add指令的参数类型,决定转换为哪条寄存器指令,同时正确设置指令的字段值。

解释执行hybridclr指令集

解释执行在代码 interpreter::InterpreterModule::Execute 函数中完成。涉及到几部分:

  • 函数帧构建,参数、局部变量、执行栈的初始化
  • 执行普通指令
  • 调用子函数
  • 异常处理

这块内容也很多,我们会在多篇文章中详细介绍实现,这里简单摘取 BinOpVarVarVar_Add_i4 指令的实现代码:

case HiOpcodeEnum::BinOpVarVarVar_Add_i4:
{
uint16_t __ret = *(uint16_t*)(ip + 2);
uint16_t __op1 = *(uint16_t*)(ip + 4);
uint16_t __op2 = *(uint16_t*)(ip + 6);
(*(int32_t*)(localVarBase + __ret)) = (*(int32_t*)(localVarBase + __op1)) + (*(int32_t*)(localVarBase + __op2));
ip += 8;
continue;
}

相信这段代码还是比较好理解的。指令集转换和指令解释相关代码是hybridclr的核心,但复杂度却不高,这得感谢il2cpp运行时帮我们承担了绝大多数复杂的元数据相关操作的支持。

其他如GC、多线程相关处理

我们在hybridclr可行性的思维实验中分析过这两部分实现。

GC

对于对象分配,我们使用il2cpp::vm::Object::New函数分配对象即可。还有一些其他涉及到GC的部分如ldstr指令中Il2CppString对象的缓存,利用了一些其他il2cpp运行时提供的GC机制。

多线程相关处理

  • volatile 。对于指令中包含volatile前缀指令,我们简单在执行代码前后插入MemoryBarrier。
  • ThreadStatic 。 使用il2cpp内置的Class的ThreadStatic变量机制即可。
  • Thread。 我们对于每个托管线程,都创建了一个对应的解释器栈。
  • async 相关。由于异步相关只是语法糖,由编译器和标准库完成了所有内容。hybridclr只需要解决其中产生的AOT泛型实例化的问题即可。

总结

概括地说,hybridclr的实现为:

  • MetadataCache::LoadAssemblyFromBytes (c#层调用Assembly.Load时触发)时加载并注册interpreter Assembly
  • il2cpp运行过程中延迟初始化类型相关元数据,其中关键为正确设置了MethodInfo元数据中methodPointer指针
  • il2cpp运行时通过methodPointer或者methodInvoke指针,再经过桥接函数跳转,最终执行了Interpreter::Execute函数。
    • Execute函数在第一次执行某interpreter函数时触发HiTransform::Transform操作,将原始IL指令翻译为hybridclr的寄存器指令。
    • 然后执行该函数对应的hybridclr寄存器指令。

至此完成hybridclr的技术原理介绍。

- + \ No newline at end of file diff --git a/en/docs/basic.html b/en/docs/basic.html index f79d5d979..97f85d418 100644 --- a/en/docs/basic.html +++ b/en/docs/basic.html @@ -9,13 +9,13 @@ - +

Guides

- + \ No newline at end of file diff --git a/en/docs/basic/aotgeneric.html b/en/docs/basic/aotgeneric.html index 87f688d3e..70073f287 100644 --- a/en/docs/basic/aotgeneric.html +++ b/en/docs/basic/aotgeneric.html @@ -9,7 +9,7 @@ - + @@ -29,7 +29,7 @@ It has positive significance for memory-sensitive situations.

Supplementary metadata technology only uses the metadata information of generic functions in the supplementary metadata dll. The metadata of non-generic functions contained in the supplementary metadata dll is redundant. Eliminating them completely will not Affects the normal working of the supplementary metadata mechanism. Therefore com.code-philosophy.hybridclr provides a supplementary metadata optimization tool class HybridCLR.Editor.AOT.AOTAssemblyMetadataStripper since version v4.0.16 Implement this elimination optimization work.

This elimination effect varies greatly from assembly to assembly. The following are our test results in the unit test project:

Assembly nameOriginal sizeOptimized sizeOptimization rate
mscorlib2139k1329k37.9%
System186k63.0k66.2%
System.Core96.3k89.1k7.4%

The sample code is as follows:


/// remove the non-generic function metadata from the trimmed AOT dlls and output it to the StrippedAOTAssembly2 directory.
public static void StripAOTAssembly()
{
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
string srcDir = SettingsUtil.GetAssembliesPostIl2CppStripDir(target);
string dstDir = $"{SettingsUtil.HybridCLRDataDir}/StrippedAOTAssembly2/{target}";
foreach (var src in Directory.GetFiles(srcDir, "*.dll"))
{
string dllName = Path.GetFileName(src);
string dstFile = $"{dstDir}/{dllName}";
AOTAssemblyMetadataStripper.Strip(src, dstFile);
}
}

AOT generic problems caused by some C# special mechanisms

The compiler may generate implicit AOT generic references for complex syntactic sugar such as async. Therefore, in order for these mechanisms to work properly, the AOT generic instantiation problems caused by them must also be resolved.

Taking async as an example, the compiler generates several classes, state machines and some codes for async. These hidden generated codes contain calls to multiple AOT generic functions. The common ones are:

  • void AsyncTaskMethodBuilder::Start<TStateMachine>(ref TStateMachine stateMachine)
  • void AsyncTaskMethodBuilder::AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
  • void AsyncTaskMethodBuilder::SetException(Exception exception)
  • void AsyncTaskMethodBuilder::SetResult()
  • void AsyncTaskMethodBuilder<T>::Start<TStateMachine>(ref TStateMachine stateMachine)
  • void AsyncTaskMethodBuilder<T>::AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
  • void AsyncTaskMethodBuilder<T>::SetException(Exception exception)
  • void AsyncTaskMethodBuilder<T>::SetResult(T result)

Both generic instantiation techniques can solve these problems. You can use the generic sharing mechanism, that is, instantiate these functions in advance in AOT, but Note, the state machine generated by Unity in the dll compiled in release mode is of the ValueType type, which makes it impossible to sharing generics, but The state machine generated in debug mode is of class type and can be shared generically. Therefore, if you use the il2cpp generic sharing mechanism, in order to use the async syntax in the hot update, when using the script to compile the dll, you must add scriptCompilationSettings.options = ScriptCompilationOptions.DevelopmentBuild; code, so that the compiled state machine is a class type, Works fine in hot update code. If Supplementary Metadata Technology has been used, due to full support for AOT generics, there are unlimited compilation methods.

Instantiating these generics in AOT is tedious and strongly recommended to use the supplementary metadata mechanism.

full generic sharing technical supplementary introduction

Since the 2021.3.x LTS version, il2cpp has fully supported the full generic sharing' technology. When the Il2Cpp Code Generationoption in Build Settings isfaster runtime, it is the generic sharing mechanism introduced in the previous chapter , enabled for faster(smaller) buildfull generic sharingmechanism. Thefull generic sharing` technology can overcome the defect that the value type generics of traditional il2cpp cannot be shared. All generic instances of generic functions (regardless of whether the generic parameters are value types or class types) completely sharing one code.

The advantage of full generic sharing is that it can be instantiated arbitrarily, and it can save code size. The disadvantage is that it greatly hurts the performance of generic functions. The fully generic shared code is sometimes several to ten times slower than the standard generic shared code, and even worse than the purely interpreted version. Therefore it is strongly recommended to not enable the faster(smaller) build option. Because of this, although HybridCLR can work with the full generic sharing mechanism, it does not take advantage of this mechanism at all. Because this mechanism has basically no practical significance except when you want to reduce the inclusion extremely.

Appendix: Example of shared generic instantiation of AOT generics

Example 1

error log

MissingMethodException: AOT generic method isn't instantiated in aot module
void System.Collections.Generic.List<System.String>.ctor()

You add a call to List<string>.ctor() in RefType, which is new List<string>(). Thanks to the generic sharing mechanism, you just call new List<object>().

class RefTypes
{
public void MyAOTRefs()
{
new List<object>(); // can also use new List<string>()
}
}

Example 2

error log

MissingMethodException: AOT generic method isn't instantiated in aot module
void System.ValueType<System.Int32, System.String>.ctor()
info

The empty constructor of the value type does not call the corresponding constructor, but corresponds to the initobj instruction. In fact, you can't directly reference it, but you just need to force the instantiation of this type, and all functions of the preserve class will naturally include the .ctor function.

In practice you can use forced boxing (object)(default(ValueTuple<int, object>)).

class RefTypes
{
public void MyAOTRefs()
{
// The following two ways of writing are both possible
_ = (object)(new ValueTuple<int, object>());
_ = (object)(default(ValueTuple<int, object>));
}
}

Example 3

error log

MissingMethodException: AOT generic method isn't instantiated in aot module
System.Void System.Runtime.CompilerService.AsyncVoidMethodBuilder::Start<UIMgr+ShowUId__2>(UIMgr+<ShowUI>d__2&)
class RefTypes
{
public void MyAOTRefs()
{
System.Runtime.CompilerService.AsyncVoidMethodBuilder builder = default;
IAsyncStateMachine asm = default;
builder.Start(ref asm);
}
}
- + \ No newline at end of file diff --git a/en/docs/basic/architecture.html b/en/docs/basic/architecture.html index 995e04fc7..96d453d49 100644 --- a/en/docs/basic/architecture.html +++ b/en/docs/basic/architecture.html @@ -9,7 +9,7 @@ - + @@ -17,7 +17,7 @@

Code Architecture And Version

The complete HybridCLR code consists of three repositories:

-il2cpp_plus

  • hybridclr
  • com.code-philosophy.hybridclr

These three warehouses have independent version numbers, so when talking about the HybridCLR version, these three version numbers are generally included.

##il2cpp_plus

Warehouse address github gitee.

When HybridCLR extends il2cpp to run, it needs to make some adjustments to the original il2cpp code to support the hybrid running mode. This part of the code corresponds to the il2cpp_plus repository. Since each major version of il2cpp changes greatly, each major version of Unity needs to be individually adapted.

Each annual release corresponds to a {version}-main master branch, such as 2021-main.

Each current annual version also has an old 1.0 branch {version}-1.0, such as 2019-1.0.

##hybridclr

Warehouse address github gitee

The hybridclr warehouse contains the core code of the interpreter. All il2cpp_plus sharing the same set of hybridclr codes, regardless of the major version of Unity. There are currently two branches:

  • main
  • 1.0

com.code-philosophy.hybridclr

Warehouse address github gitee

com.code-philosophy.hybridclr is a Unity Package that contains some runtime code and editor workflow tools needed to use HybridCLR.

com.code-philosophy.hybridclr does not distinguish between major versions of Unity, so like hybridclr, there are currently two branches:

  • main
  • 1.0

In earlier versions (such as the 1.0 branch), you need to specify the branch of il2cpp_plus and hybridclr you want to install in the Installer. The branches of the two repositories must match, That is, {version}-main of il2cpp_plus matches main of hybridclr, and {version}-1.0 matches 1.0.

Since the v2.0.0-rc version (belonging to the main branch), com.code-philosophy.hybridclr is directly configured with the version numbers of the compatible il2cpp_plus and hybridclr warehouses. For developers, Just install the appropriate version of com.code-philosophy.hybridclr.

- + \ No newline at end of file diff --git a/en/docs/basic/bestpractice.html b/en/docs/basic/bestpractice.html index 68032a04c..2cc56dac3 100644 --- a/en/docs/basic/bestpractice.html +++ b/en/docs/basic/bestpractice.html @@ -9,14 +9,14 @@ - +

Best Practices

Unity version recommendation

It is recommended to use 2020.3.x(x >= 21) series and 2021.3.x series, which are the most stable.

It is recommended to mount the startup script to the startup hot update scene, so that the non-hot update project can be transformed into a hot update project with zero changes, and no reflection operation is required.

When RuntimeApi.LoadMetadataForAOTAssembly is called

You just need to call it before using AOT generics (you only need to call it once). In theory, the earlier the loading, the better. In practice, a more reasonable time is after the hot update is completed, or after the hot update dll is loaded but before any code is executed. If the dll that supplements the metadata is also entered into the main package as an additional data file, it will be better loaded when the main project starts. Please refer to HybridCLR_trial project

Do not use reflection to interact with native and interpreter performance-sensitive occasions, you should use Delegate or virtual function

Taking the Update function as an example, most people would think that the interaction between the main project and the update part is like this:

var klass = ass. GetType("App");
var method = klass. GetMethod("Update");
method.Invoke(null, new object[] {deltaTime});

The disadvantage of this method is that the cost of reflection is high. In case there are parameters and additional gc, there is actually a more efficient method. There are two main ways:

The hot update layer returns a Delegate

// Hotfix.asmdf hot update part
class app
{
public static Action<float> GetUpdateDelegate()
{
return Update;
}

public static void Update(float deltaTime)
{
}
}

// Main.asmdf main project
var klass = ass. GetType("App");
var method = klass. GetMethod("GetUpdateDelegate");
var updateDel = (Action<float>)method. Invoke(null, null);

updateDel(deltaTime);

Through Delegate.Create, create the corresponding Delegate according to MethodInfo

var klass = ass. GetType("App");
var method = klass. GetMethod("Update");
updateDel = (Action<float>)System.Delegate.CreateDelegate(typeof(Action<float>), null, method);
updateDel(deltaTime);

2021 version don't use faster(smaller) builds option

Since the 2021.3.x LTS version, il2cpp has fully supported the full generic sharing technology. When the Il2Cpp Code Generation option in Build Settings is faster runtime, it is a standard generic sharing mechanism, and faster(smaller) builds open when full generic sharing mechanism.

When full generic sharing is enabled, each generic function (regardless of whether the generic parameter is a value type or a class type) will completely sharing a code. The advantage is to save the size of the package body, and the disadvantage is that it greatly hurts the performance of the generic function . The fully generic shared code is sometimes several to ten times slower than the standard generic shared code, and even worse than the purely interpreted version. Therefore it is strongly recommended to not enable the faster(smaller) builds option.

- + \ No newline at end of file diff --git a/en/docs/basic/buildpipeline.html b/en/docs/basic/buildpipeline.html index 2c23fbe02..b07aac788 100644 --- a/en/docs/basic/buildpipeline.html +++ b/en/docs/basic/buildpipeline.html @@ -9,7 +9,7 @@ - + @@ -17,7 +17,7 @@

Building Pipeline

Due to the requirements of the hot update itself and some limitations of Unity resource management, some special processing is required for the buiding workflow, which is mainly divided into several parts:

  • Set the UNITY_IL2CPP_PATH environment variable
  • Automatically exclude hot update assembly when buiding
  • Add the hot update dll name to the assembly list when buiding
  • Copy the trimmed aot dll generated during the buiding process for supplementary metadata
  • Compile hot update dll
  • Generate some files and codes needed for buiding
  • Special handling for iOS platform

Manually operating these is cumbersome and error-prone. The com.code-philosophy.hybridclr package contains standard tool scripts related to buiding workflows, simplifying these complex processes into one-click operations. For detailed implementation, please refer to the source code or com.code-philosophy.hybridclr introduction

Buiding Steps

  1. Run the menu HybridCLR/Generate/All to execute the necessary generation operations with one click
  2. Add the hot update dll under HybridCLRData/HotUpdateDlls to the hot update resource management system of the project
  3. Add the supplementary metadata dll under HybridCLRData/AssembliesPostIl2CppStrip to the hot update resource management system of the project
  4. Pack according to the original buiding process of your project

Optimized buiding pipeline

During the HybridCLR/Generate/All command, an export project will be executed to generate the trimmed AOT dll. This step can be time-consuming for large projects, almost doubling the buiding time. If you need to optimize the buiding time, you can follow the process below to package at one time.

  • Run HybridCLR/Generate/LinkXml
  • Export project
  • run HybridCLR/Generate/Il2cppDef
  • Run HybridCLR/Generate/MethodBridge to generate the bridge function
  • Run HybridCLR/Generate/PReverseInvokeWrapper. Projects that do not need to interact with lua can skip this step.
  • Replace the {proj}\HybridCLRData\LocalIl2CppData-{platform}\il2cpp\libil2cpp\hybridclr\generated directory with this directory in the exported project.
  • Execute build on the exported project

Special handling for iOS platform

When com.code-philosophy.hybridclr version v3.2.0

No need for any processing, just export the xcode project directly, and then pack it. Since the libil2cpp source code is added to the xcode project after the build is completed, you can only export xcode first, and then compile it manually or on the command line. If you try to Build And Run directly, you will get an error.

danger

If your com.code-philosophy.hybridclr version is < v3.3.0, since the path of libil2cpp-related code is hard-coded in the xcode project, if you export the xcode project and push it to other computers for buiding, the code file will not be found mistake!

When com.code-philosophy.hybridclr version < v3.2.0

Platforms other than iOS compile the target program based on the libil2cpp source code, and the iOS platform uses the pre-compiled libil2cpp.a file. The xcode project exported by Unity references the pre-generated libil2cpp.a, but does not contain the libil2cpp source code. Direct buiding cannot support hot updates. Therefore, when compiling an iOS program, you need to compile libil2cpp.a separately, then replace the libil2cpp.a file of the xcode project, and then package it.

Please replace the libil2cpp.a file in the xcode project by yourself.

The com.code-philosophy.hybridclr/Data~/iOSBuild directory contains the scripts needed to compile libil2cpp.a. After completing the installation using HybridCLR/Installer..., the iOSBuild directory will be copied to the {project}/HybridCLRData/iOSBuild directory.

Compile libil2cpp.a

  • Run HybridCLR/Generate/All to generate all necessary files
  • Open the command console and switch to the {project}/HybridCLRData/iOSBuild directory. Please make sure the absolute path of this path does not contain spaces! Otherwise an error will occur.
  • bash ./build_libil2cpp.sh compiles libil2cpp.a. After running, if the libil2cpp.a file can be found in the iOSBuild/build directory and the size is greater than 60M, it means the compilation is successful

Common errors

  • Installation did not complete in HybridCLR/Installer...
  • Didn't run HybridCLR/Generate/All
  • Newer macOS (above 12) and latest xcode not installed
  • cmake is not installed
  • Due to the git setting, the pulled build_libil2cpp.sh and build_lump.sh contain incorrect file end characters, which cause errors in the first few lines of code when the script runs. Error messages are also obvious, such as /bin/bash^M file does not exist. Run the command cat -v build_libil2cpp.sh to check that the line breaks are correct. Run git config --global core.autocrlf input, and then pull these two script files again. For details, please refer to Git Line Break Settings.
  • The absolute path to {project}/HybridCLRData/iOSBuild contains spaces, causing the gen_lump.sh script to generate wrong results
- + \ No newline at end of file diff --git a/en/docs/basic/buildwebgl.html b/en/docs/basic/buildwebgl.html index 2899ae88a..172968dca 100644 --- a/en/docs/basic/buildwebgl.html +++ b/en/docs/basic/buildwebgl.html @@ -9,13 +9,13 @@ - +

Publish WebGL Platform

Due to the particularity of the WebGL platform, a separate document introduces how to release the WebGL platform. This document is published on the hybridclr_trial project (github gitee ) process.

tip

Starting from Unity 2021.3.4+ and 2022.3.0+ versions, global installation is no longer required, that is, the construction process of the webgl platform is exactly the same as other platforms.

version used

The release process of different Unity versions and hybridclr package is similar and will not be repeated here.

  • Unity 2021.3.1f1
  • com.code-philosophy.hybridclr v3.4.0

Preparation

tip

Beginners, please at least read the Quick Start document, and have mastered the release process of platforms such as Win or Android.

  • Make sure that the WebGL module is installed in Unity Editor, as shown below
  • Complete HybridCLR installation and configuration according to install document
  • In HybridCLRSettings, enable the Use Global Il2cpp option because the webgl platform only supports global installation. From 2021.3.4+, 2022.3.0+, it is no longer necessary to turn this option on

select_il2cpp_module_webgl

Create a soft (hard) reference from libil2cpp in the Editor directory to the local libil2cpp directory

danger

Note: Starting from Unity 2021.3.4+ and 2022.3.0+ versions, since local installation is supported, it is no longer necessary to establish this reference.

Win platform

Developers who are not familiar with the command line should first master the basic usage of the command line.

  • Open the command line window with administrator privileges. This operation is different for different operating system versions, please handle it as appropriate. Under Win11, it is right click on the start menu and select the terminal administrator menu item.
  • Run cd /d {editor_install_dir}/Editor/Data/il2cpp, switch directory to the il2cpp directory of the installation directory
  • Run ren libil2cpp libil2cpp-origin to rename the original libil2cpp to libil2cpp-origin
  • Run mklink /D libil2cpp "{project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp/libil2cpp" to create a symbolic reference from libil2cpp in the Editor directory to the local libil2cpp directory

MacOS or Linux platform

  • Open command line window
  • Run cd /d {editor_install_dir}/Editor/Data/il2cpp to switch directories to the il2cpp directory of the installation directory. The specific directory may vary depending on the operating system, please handle accordingly
  • Run mv libil2cpp libil2cpp-origin to rename the original libil2cpp to libil2cpp-origin
  • Run ln -s "{project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp/libil2cpp" libil2cpp to create a symbolic reference from libil2cpp in the Editor directory to the local libil2cpp directory

Pack

  • Run HybridCLR/Generate/All
  • Run HybridCLR/Build/BuildAssetsAndCopyToStreamingAssets. Notice! This menu is added by the hybridclr_trial project, not a command that comes with the hybridclr package.
  • Just run Build And Run in Build Player
- + \ No newline at end of file diff --git a/en/docs/basic/codestriping.html b/en/docs/basic/codestriping.html index a6765449a..1606d6193 100644 --- a/en/docs/basic/codestriping.html +++ b/en/docs/basic/codestriping.html @@ -9,7 +9,7 @@ - + @@ -22,7 +22,7 @@ type. Therefore, you still need to plan ahead in Assets/link.xml (note! Not the automatically generated link.xml) to reserve your future types that may be used. Remember not to miss it, so as to avoid the embarrassing situation that the type used in a certain update is cut after it goes online!

Check whether the pruned type or function is referenced in the hot update code

As long as HybridCLR/Generate/All is executed correctly when building the game, running the hot update code at that time will not cause problems with missing types or functions. But as the subsequent hot update code continues to iterate, It is possible that a clipped type or function was accessed. If you can check it in advance when hot update code is released, problems can be discovered and solved early.

Since version v5.0.0, the HybridCLR.Editor.HotUpdate.MissingMetadataChecker class is provided to check whether clipped types and functions are accessed. The sample code is as follows:

         public static void CheckAccessMissingMetadata()
{
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
// aotDir is the stripped aot dll directory generated when building the main package, not the latest SettingsUtil.GetAssembliesPostIl2CppStripDir(target) directory.
// Generally speaking, when releasing a hot update package, since generate/all may have been called in the middle, the SettingsUtil.GetAssembliesPostIl2CppStripDir(target) directory contains the latest aot dll.
// Definitely cannot check for type or function pruning issues.
// After building the main package, you need to save the aot dll at that time for later supplementary metadata or cropping inspection.
string aotDir = "xxxx";

// The second parameter excludeDllNames is the aot dll to be excluded. Generally, an empty list will suffice. For ultimate edition users,
// excludeDllNames needs to be the dhe assembly list, because the dhe assembly will be hot updated, and the hot update code
// The type or function in the referenced dhe assembly must exist.
var checker = new MissingMetadataChecker(aotDir, new List<string>());

string hotUpdateDir = SettingsUtil.GetHotUpdateDllsOutputDirByTarget(target);
foreach (var dll in SettingsUtil.HotUpdateAssemblyFilesExcludePreserved)
{
string dllPath = $"{hotUpdateDir}/{dll}";
bool notAnyMissing = checker.Check(dllPath);
if (!notAnyMissing)
{
// DO SOMETHING
}
}
}

- + \ No newline at end of file diff --git a/en/docs/basic/com.code-philosophy.hybridclr.html b/en/docs/basic/com.code-philosophy.hybridclr.html index 4cae0ec74..6e811b025 100644 --- a/en/docs/basic/com.code-philosophy.hybridclr.html +++ b/en/docs/basic/com.code-philosophy.hybridclr.html @@ -9,7 +9,7 @@ - + @@ -42,7 +42,7 @@ The script provides the function of automatically generating stub functions. For details, see MonoPInvokeCallback support and HybridCLR+lua/js/python documents

Each function with the [MonoPInvokeCallback] attribute requires a unique corresponding wrapper function. These wrapper functions must be pre-generated during packaging and cannot be changed. Therefore, if a function with the [MonoPInvokeCallback] feature is added in subsequent hot updates, there will be insufficient wrapper functions. ReversePInvokeWrapperGenerationAttribute It is used to reserve the specified number of wrapper functions for the functions currently added with the [MonoPInvokeCallback] feature. In the following example, 10 wrapper functions are reserved for functions signed by LuaFunction.

     delegate int LuaFunction(IntPtr luaState);

public class MonoPInvokeWrapperPreserves
{
[ReversePInvokeWrapperGeneration(10)]
[MonoPInvokeCallback(typeof(LuaFunction))]
public static int LuaCallback(IntPtr luaState)
{
return 0;
}

[MonoPInvokeCallback(typeof(Func<int, int, int>))]
public static int Sum(int a, int b)
{
return a + b;
}

[MonoPInvokeCallback(typeof(Func<int, int, int>))]
public static int Sum2(int a, int b)
{
return a + b;
}

[MonoPInvokeCallback(typeof(Func<int>))]
public static int Sum3()
{
return 0;
}
}
- + \ No newline at end of file diff --git a/en/docs/basic/compileassembly.html b/en/docs/basic/compileassembly.html index 2c85824be..a9c10891c 100644 --- a/en/docs/basic/compileassembly.html +++ b/en/docs/basic/compileassembly.html @@ -9,7 +9,7 @@ - + @@ -20,7 +20,7 @@ The hot update dll under. The compilation result is output to {proj}/HybridCLRData/HotUpdateDlls/{target} directory.

Run the menu HybridCLR/Compile/xxx command to directly compile the hot update dll. Running HybridCLR/Generate/All will also implicitly compile the latest hotupdate assemblies. After calling this command, you can directly copy the hot update dll without running HybridCLR/Compile/xxx again. Since the interface does not distinguish between AOT and hot update when compiling, the project is compiled as a whole, and developers only need to add the output hot update dll to the resource management system of the project.

The HybridCLR.Editor assembly of com.code-philosophy.hybridclr provides the HybridCLR.Editor.Commands.CompileDllCommand.CompileDll(BuildTarget target) interface, It is convenient for developers to compile hot update dll by themselves flexibly.

After releasing the main package, you only need to simply use the HybridCLR/Compile/xxx command to recompile the hot update dll, and then release the hot update dll, without running the HybridCLR/Generate/xxx command.

- + \ No newline at end of file diff --git a/en/docs/basic/dots.html b/en/docs/basic/dots.html index 6c7b966a7..2d05611ee 100644 --- a/en/docs/basic/dots.html +++ b/en/docs/basic/dots.html @@ -9,13 +9,13 @@ - +

DOTS Support

The initialization timing of TypeManager in DOTS is too early, and it does not support dynamically registering Component and System types. To ensure the normal operation of the hot update module in the DOTS system, modifications need to be made to the DOTS source code to adjust the initialization timing of the World.

Supported Versions

Due to the rapid iteration and modification of DOTS, in order to reduce maintenance costs, only the following versions of com.unity.entities are maintained:

  • 0.51.1-preview.21
  • 1.0.16

Currently, compatibility testing has only been completed on Unity 2021+ versions, and compatibility with Unity 2020 and lower versions has not been tested. Generally, as long as the corresponding version of com.unity.entities can run normally on that Unity version, hybridclr support should also be available.

Developers with special requirements for DOTS versions need to contact us for separate customized payment due to the higher maintenance cost of maintaining separate DOTS versions.

Supported Features

Currently, most DOTS features can run normally under hybridclr, except for features related to BurstCompile and resource serialization.

Version 1.0.16

FeatureCommunity VersionProfessional VersionEnterprise VersionHot Reload Version
Jobs
Managed Component
Unmanaged Component
Managed System
Unmanaged System
Aspect
IJobEntity
BurstCompile
SubScene

Version 0.51.1-preview.21

FeatureCommunity VersionProfessional VersionEnterprise VersionHot Reload Version
Jobs
Managed Component
Unmanaged Component
Managed System
Unmanaged System
IJobEntity
BurstCompile
SubScene

Installation

Installing com.unity.entities

  • Remove the com.unity.entities package from the project, exit the Unity Editor, and clear the directory corresponding to the package under Library\PackageCache.
  • Download the modified com.unity.entities according to the version used by the project, and unzip the com.unity.entities.7z in the corresponding directory to the Packages directory. Make sure that the directory name after decompression is com.unity.entities.

When reopening the Unity Editor, you may be prompted to perform API upgrades. Decide whether to upgrade according to the project situation.

Modify Project Settings

To avoid potential issues caused by dynamically registering Component or System during DOTS runtime, adjust the initialization timing of World to ensure that all hot update types are registered before running all Worlds.

Add the compilation macro UNITY_DISABLE_AUTOMATIC_SYSTEM_BOOTSTRAP_RUNTIME_WORLD in Player Settings under Scripting Define Symbols. For detailed instructions, refer to the World's custom initialization documentation.

Initialization

To avoid encountering issues, perform initialization after loading the hot update code and before running any dots code.

Initialization mainly consists of two parts:

  • Register hot update dots types.
  • Initialize World.

There are slight differences in the initialization implementations of different versions of com.unity.entities.

For version 0.51.1, the initialization code is as follows:

    private static void InitializeWorld()
{
#if !UNITY_EDITOR
var dotsAssemblies = new Assembly[] { ... };
var componentTypes = new HashSet<System.Type>();
TypeManager.CollectComponentTypes(dotsAssemblies, componentTypes);
TypeManager.AddNewComponentTypes(componentTypes.ToArray());
TypeManager.EarlyInitAssemblies(dotsAssemblies);
#endif


DefaultWorldInitialization.Initialize("Default World", false);

}

For version 1.0.16, the initialization code is as follows:

    private static void InitializeWorld()
{
#if !UNITY_EDITOR
var dotsAssemblies = new Assembly[] { ... };
var componentTypes = new HashSet<Type>();
TypeManager.CollectComponentTypes(dotsAssemblies, componentTypes);
TypeManager.AddComponentTypes(dotsAssemblies, componentTypes);
TypeManager.RegisterSystemTypes(dotsAssemblies);
TypeManager.InitializeSharedStatics();
TypeManager.EarlyInitAssemblies(dotsAssemblies);
#endif


DefaultWorldInitialization.Initialize("Default World", false);
}

Resolve ReversePInvokeCallback Issues

When initializing Unmanaged Systems, the DOTS system will attempt to obtain the Marshal pointer of its OnStart-like functions. hybridclr needs to bind a runtime-unique cpp function pointer for each of these functions, otherwise the error GetReversePInvokeWrapper fail. exceed max wrapper num of method will occur during runtime. For detailed instructions, refer to the HybridCLR+lua/js/python documentation.

In simple terms, a sufficient number of SystemBaseRegistry.ForwardingFunc corresponding wrapper functions need to be reserved. Add the following code to the hot update module (or DHE assembly, but not in AOT assemblies):


public static class PreserveDOTSReversePInvokeWrapper
{
[ReversePInvokeWrapperGeneration(100)]
[MonoPInvokeCallback(typeof(SystemBaseRegistry.ForwardingFunc))]
public static void ForwordMethod(IntPtr system, IntPtr state)
{

}
}


Change the number 100 in the code to an appropriate number, recommended to be 5-10 times the number of Unmanaged System types.

Hot update functions containing [BurstCompile] have changed, remove the [BurstCompile] attribute, otherwise errors will occur during runtime. This issue may be optimized in the future.

- + \ No newline at end of file diff --git a/en/docs/basic/hotupdateassemblysetting.html b/en/docs/basic/hotupdateassemblysetting.html index c500edc1b..de982c674 100644 --- a/en/docs/basic/hotupdateassemblysetting.html +++ b/en/docs/basic/hotupdateassemblysetting.html @@ -9,7 +9,7 @@ - + @@ -18,7 +18,7 @@ There are no restrictions on how to split the assembly, and even the code in the third-party project can be used as a hot update assembly. Generally speaking, when the game is just started, at least one AOT assembly is required to be responsible for the work related to startup and hot update.

There are several common split methods:

  • Assembly-CSharp as AOT assembly. The rest of the code itself is split into N AOT assemblies and M hot update assemblies.
  • Assembly-CSharp as a hot update assembly. The rest of the code itself is split into N AOT assemblies and M hot update assemblies.

Regardless of the splitting method, it is enough to correctly set the reference relationship between assemblies. Please do not refer to the hot update assembly in the AOT assembly, otherwise it will cause packaging errors. especially Use Assembly-CSharp as an AOT assembly, since Assembly-CSharp is the top-level assembly, it will automatically reference all remaining assemblies, which is easy to appear A case where a hot update assembly is incorrectly referenced.

- + \ No newline at end of file diff --git a/en/docs/basic/il2cppbugs.html b/en/docs/basic/il2cppbugs.html index 0b373bd59..59f800230 100644 --- a/en/docs/basic/il2cppbugs.html +++ b/en/docs/basic/il2cppbugs.html @@ -9,13 +9,13 @@ - +

il2cpp Bugs

Contravariant covariant generic interface call error

There is an error in finding the interface implementation of obj. According to the specification, the following code should print "Comput B". For example, .net 6 is the result, but "Comput A" is printed under mono and il2cpp.


interface ITest<out T>
{
T Comput();
}

class A : ITest<object>
{
public object Comput()
{
return "Comput A";
}
}

class B : A, ITest<string>
{
public string Comput()
{
return "Comput B";
}
}

class app
{
public static void Main()
{
ITest<object> f = new B();
Debug. Log(f. Comput());
}
}

obj.Func() non-virtual call does not conform to the specification

The ECMA specification allows non-virtual calls to null using the call instruction, but il2cpp inserts a NullCheck operation before the call. As a result, the following code will print "hello" under mono, but throw NullReferenceException under il2cpp.


class TestNull
{
public void Show()
{
Debug. Log("hello");
}
}

class app
{
public void Main()
{
TestNull nu = null;
nu. Show();
}
}

When the struct contains class type objects, the pack of StructLayout will not take effect

     [StructLayout( LayoutKind. Sequential, Pack = 1)]
struct StructWithoutClass
{
byte a;
long b;
}

[StructLayout(LayoutKind. Sequential, Pack = 1)]
struct StructWithClass
{
byte a;
object b;
}

The size calculated by these two structs under x64 should both be 9, and this is also verified by running the .net 6 program test. But in mono, the first structure calculates the value as 9 and the 2nd as 16.

Generic array function does not set token

metadata/ArrayMetadata.cpp

     static MethodInfo* ConstructGenericArrayMethod(const GenericArrayMethod& genericArrayMethod, Il2CppClass* klass, Il2CppGenericContext* context)
{
MethodInfo* inflatedMethod = (MethodInfo*)MetadataCalloc(1, sizeof(MethodInfo));
inflatedMethod->name = StringUtils::StringDuplicate(genericArrayMethod.name.c_str());
inflatedMethod->klass = klass;

const MethodInfo* methodToCopyDataFrom = genericArrayMethod. method;
if (genericArrayMethod. method->is_generic)
{
const Il2CppGenericMethod* genericMethod = MetadataCache::GetGenericMethod(genericArrayMethod.method, context->class_inst, context->method_inst);
methodToCopyDataFrom = GenericMethod::GetMethod(genericMethod);

inflatedMethod->is_inflated = true;
inflatedMethod->genericMethod = genericMethod;
inflatedMethod->rgctx_data = methodToCopyDataFrom->rgctx_data;
}
// ==={{ add by HybridCLR
inflatedMethod->token = methodToCopyDataFrom->token;
// ===}} add by HybridCLR
inflatedMethod->slot = methodToCopyDataFrom->slot;
inflatedMethod->parameters_count = methodToCopyDataFrom->parameters_count;
inflatedMethod->parameters = methodToCopyDataFrom->parameters;
inflatedMethod->return_type = methodToCopyDataFrom->return_type;

inflatedMethod->methodPointer = methodToCopyDataFrom->methodPointer;
inflatedMethod->invoker_method = methodToCopyDataFrom->invoker_method;

return inflatedMethod;
}

throw null will cause a crash

For c# code throw ex; will generate the following code, which crashes when ex = null.

     IL2CPP_RAISE_MANAGED_EXCEPTION(L_107, TestCase_Run_m5B897FE9D1ABDC1AA114D3482A6613BAAE3243F6_RuntimeMethod_var);

When the this of the close delegate is null, the exception thrown is out of specification

Delegate.Create(XXInstanceMethod, null) should throw a NullReferenceException when called, but the unity2021 version throws an ArgumentException.

The delegate calling code generated in 2019 does not handle the open delegate correctly and this is ValueType

When using open delegate, and ref ValueType as this parameter, two calls will be made by mistake!

     if (targetThis == NULL && il2cpp_codegen_class_is_value_type(il2cpp_codegen_method_get_declaring_type(targetMethod)))
{
typedef int32_t (*FunctionPointerType) (RuntimeObject*, int32_t, const RuntimeMethod*);
result = ((FunctionPointerType)targetMethodPointer)((reinterpret_cast<RuntimeObject*>(___a0) - 1), ___b1, targetMethod);
}
if (targetThis == NULL)
{
typedef int32_t (*FunctionPointerType) (RuntimeObject*, int32_t, const RuntimeMethod*);
result = ((FunctionPointerType)targetMethodPointer)((RuntimeObject*)(reinterpret_cast<RuntimeObject*>(___a0) - 1), ___b1, targetMethod);
}
else
{
typedef int32_t (*FunctionPointerType) (void*, FT_AOT_ValueType_t851DF541610F2A3DE72568571355F3953F0063AF *, int32_t, const RuntimeMethod*);
result = ((FunctionPointerType)targetMethodPointer)(targetThis, ___a0, ___b1, targetMethod);
}

mono and il2cpp do not support calling InvokeDyanmic on the open delegate of the instance method

will throw an 'Object does not match target type' error.

     public void void_class_intp_open_reflection()
{
var b = new FT_Class() { x = 1, y = 2f, z = "abc" };
var m = typeof(FT_Class).GetMethod("Run");
var del = (Action<FT_Class, int>)Delegate.CreateDelegate(typeof(Action<FT_Class, int>), null, m);
del. DynamicInvoke(b, 4);
Assert.Equal(5, b.x);

var dd = del + del;
dd.DynamicInvoke(b, 1);
Assert.Equal(7, b.x);

Assert. ExpectException<NullReferenceException>();
del.DynamicInvoke(null, 4);
Assert. Fail();
}

2019 WebGL platform generated object member access code does not check for null references

A null pointer is not checked when fetching a class member field. It is currently found that this is only the case with the WebGL platform.


//WebGL platform does not have NullCheck
IL2CPP_EXTERN_C IL2CPP_METHOD_ATTR void FT_AOT_Class_Run2_m0451FFC153671CD294EB1178A01AB2D92202624C (FT_AOT_Class_t03C2F346FF0EA8694088FD3F901E6536935FB2BA * ___s0, int32_t ___b1, const RuntimeMethod* method)
{
{
// s.x += b;
FT_AOT_Class_t03C2F346FF0EA8694088FD3F901E6536935FB2BA * L_0 = ___s0;
FT_AOT_Class_t03C2F346FF0EA8694088FD3F901E6536935FB2BA * L_1 = L_0;
int32_t L_2 = L_1->get_x_0();
int32_t L_3 = ___b1;
L_1->set_x_0(((int32_t)il2cpp_codegen_add((int32_t)L_2, (int32_t)L_3)));
// }
return;
}
}

// Other platforms have NullCheck
IL2CPP_EXTERN_C IL2CPP_METHOD_ATTR void FT_AOT_Class_Run2_m0451FFC153671CD294EB1178A01AB2D92202624C (FT_AOT_Class_t03C2F346FF0EA8694088FD3F901E6536935FB2BA * ___s0, int32_t ___b1, const RuntimeMethod* method)
{
{
// s.x += b;
FT_AOT_Class_t03C2F346FF0EA8694088FD3F901E6536935FB2BA * L_0 = ___s0;
FT_AOT_Class_t03C2F346FF0EA8694088FD3F901E6536935FB2BA * L_1 = L_0;
NullCheck(L_1);
int32_t L_2 = L_1->get_x_0();
int32_t L_3 = ___b1;
NullCheck(L_1);
L_1->set_x_0(((int32_t)il2cpp_codegen_add((int32_t)L_2, (int32_t)L_3)));
// }
return;
}
}
- + \ No newline at end of file diff --git a/en/docs/basic/install.html b/en/docs/basic/install.html index 26822159d..b4acbdad7 100644 --- a/en/docs/basic/install.html +++ b/en/docs/basic/install.html @@ -9,7 +9,7 @@ - + @@ -25,7 +25,7 @@ HybridCLRData/hybridclr_repo or HybridCLRData/il2cpp_plus_repo is empty when finding failed, please try again.

The most common cause of failure is that git is not installed, or UnityEditor and UnityHub have not been restarted after installing git. If you are sure that git is installed and git can indeed be run in cmd, try restarting the computer.

If the automated installation cannot be completed due to various special reasons, please refer to the following Installation Principle to manually simulate the entire installation process.

Special handling after installation

WebGL Platform

tip

Local installation has been supported since Unity 2021.3.4+ and 2022.3.0+ versions, and the WebGL platform construction process is exactly the same as other platforms.

Due to Unity's own reasons, if the Unity version used is lower than 2021.3.4, the WebGL platform must be installed globally. Please consult the Global Installation documentation in the following section.

Unity 2019

In order to support 2019, the source code generated by il2cpp needs to be modified, so we modified the 2019 version of the il2cpp tool. Therefore, there is an additional step in the Installer installation process: copy {package}/Data~/ModifiedUnityAssemblies/2019.4.40/Unity.IL2CPP.dll to {project}/HybridCLRData/LocalIl2CppData/il2cpp/build/deploy/net471 /Unity.IL2CPP.dll

Note that this operation is automatically completed when the Installer is installed, no manual operation is required.

For developers using the 2019.4.0-2019.4.39 version, please switch to the 2019.4.40 version to complete the installation, and then switch back to your current version.

Using HybridCLR in non-compatible versions of Unity

Since we haven't fully tested all Unity versions, in fact, some Unity versions that are not in the supported list may also be able to use HybridCLR normally. The installation method is as follows:

  • Find a version in the support list that is closest to your version, for example, if your version number is 2021.2.20, then the latest version from you is 2021.3.0.
  • First switch your Unity project to this latest supported version, install HybridCLR.
  • Switch back to your Unity version.
  • Try to package, if it can run smoothly, it means that HybridCLR supports your version, if there is a problem, then upgrade the version.

If you must use this version, you can contact us for Business Technical Support.

How HybridCLR/Installer works

This section is just an introduction to the principle. The operation of installing libil2cpp has been completed by the installer, and you do not need to do it manually.

The HybridCLR installation process mainly includes these parts:

  • Make libil2cpp that supports hot update
  • Install locally or globally to make the new version of libil2cpp take effect
  • Minor improvements to the Unity Editor

Replace libil2cpp code

The original libil2cpp code is AOT runtime and needs to be replaced with the modified libil2cpp to support hot updates. The modified libil2cpp consists of two parts

-il2cpp_plus

  • hybridclr

The il2cpp_plus repository is a slightly modified version of the original libil2cpp to support dynamic register metadata (changed hundreds of lines of code). This repository is highly comparable to the original libil2cpp code resemblance. hybridclr is the core code of the interpreter, including metadata loading, code transform (compilation), and code interpretation and execution.

As shown in the figure below, merge the il2cpp_plus/libil2cpp directory with the hybridclr/hybridclr directory to create the final libil2cpp that supports hot updates.

merge_hybridclr_dir

Local installation

Unity allows you to use the environment variable UNITY_IL2CPP_PATH to customize the location of il2cpp, so you can create an il2cpp directory locally in the project, replace the libil2cpp directory under the il2cpp directory with the modified libil2cpp, Then point the UNITY_IL2CPP_PATH environment variable to this directory. The general process is as follows:

  • Copy the il2cpp directory from the Editor installation directory to {project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp
  • Create the final libil2cpp directory from the clone il2cpp_plus and hybridclr repositories
  • Replace {project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp/libil2cpp with the final libil2cpp directory
  • Copy the MonoBleedingEdge directory from the Editor installation directory to {project}/HybridCLRData/LocalIl2CppData-{platform}/MonoBleedingEdge
  • Other processing. For the 2019 version, copy {package}/Data~/ModifiedUnityAssemblies/2019.4.40/Unity.IL2CPP.dll to {project}/HybridCLRData/LocalIl2CppData/il2cpp/build/deploy/net471/Unity.IL2CPP.dll

Create the upper-level LocalIl2CppData-{platform} directory instead of only creating il2cpp because it is found that only specifying the location of the il2cpp directory is not enough. When packaging, Unity implicitly assumes that il2cpp has a MonoBleedingEdge directory at the same level, so the upper level is created directory, copy both the il2cpp and MonoBleedingEdge directories.

Because the il2cpp directory that comes with Editor on different platforms is slightly different, LocalIl2CppData needs to distinguish the platform.

Global installation

Global installation needs to replace (or link) the libil2cpp directory of the Editor installation directory ({editor}/Data/il2cpp/libil2cpp under Win, similar to Mac) with the modified libil2cpp, and additionally replace some modified files (for example, 2019 also needs to be modified Unity.IL2CPP.dll). There are several flaws:

  • Due to directory permissions, auto-completion may not be possible
  • Will affect other projects that don't use hybridclr
  • The HybridCLR/Generate/xxxx operation needs to modify the files in the libil2cpp directory, which may fail due to directory permissions.

After completing the installation using HybridCLR/Installer, enable the useGlobalIl2Cpp option in HybridCLR/Settings to start the global installation, and the environment variable UNITY_IL2CPP_PATH will be cleared.

If you use the replacement directory for global installation, and your com.code-philosophy.hybridclr version >= 2.1.0, please run HybridCLR/Generate/Il2cppDef before overriding libil2cpp for the first time (Only this time, it is no longer needed later, unless you switch the project Unity version) to generate the correct version macro, and then overwrite the original libil2cpp directory. Symbolic link installation method or com.code-philosophy.hybridclr version lower than 2.1.0 does not need to perform this operation, just overwrite the original libil2cpp directory.

Due to permissions, even if it is installed globally, the Generate/xxx command modifies the files under the local {project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp/libil2cpp. Please overwrite the local libil2cpp directory with the global installation directory after each generate.

It is very troublesome to replace the libil2cpp directory every time. It is recommended to link the libil2cpp directory of the installation directory to the local libil2cpp directory. Methods as below:

  • Windows platform. Open the command line window with administrator privileges, delete or rename the original libil2cpp, and then run mklink /D "<libil2cpp directory path of Editor installation directory>" "{project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp/libil2cpp" .
  • Linux or Mac platform. Open the command line window with administrator privileges, delete or rename the original libil2cpp, and then run ln -s "{project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp/libil2cpp" "<libil2cpp directory path of Editor installation directory>" .

For the 2019 version replace Unity.IL2CPP.dll, also use a method similar to the above replacement or soft link.

Precautions

Due to Unity's caching mechanism, after updating HybridCLR, be sure to clear the Library\Il2cppBuildCache directory, otherwise the latest code will not be used when packaging. If you use Installer to automatically install or update HybridCLR, it will automatically clear these directories without any additional action on your part.

- + \ No newline at end of file diff --git a/en/docs/basic/memory.html b/en/docs/basic/memory.html index 54962cd6a..017e9ec7d 100644 --- a/en/docs/basic/memory.html +++ b/en/docs/basic/memory.html @@ -9,13 +9,13 @@ - +

Memory and GC

object memory size

HybridCLR is a CLR-level implementation. The hot update type is executed in the interpretation mode, and the other methods are exactly the same as the AOT part. Therefore define equivalent types, whether in AOT or hot update, the object size is exactly the same.

primitive type

Such as byte, int. As we all know, byte occupies 1 byte, int occupies 4 bytes, and others will not be described in detail.

struct value type

In the case where Explicit Layout is not specified, the total size is calculated according to the field size and memory alignment rules, which is similar to the struct calculation rules of C++. I won’t elaborate here, just give an example.

// V1 object size 1
struct V1
{
public byte a1;
}

// V2 object size 8
struct V2
{
public byte a1;
public int a2;
}

// V3 object size 24
struct V3
{
public int a1;
public int a2;
public object a3;
public byte a4;
}

class type

Similar to the value type, but with 16 bytes of object header, and enforces memory alignment to 8 bytes. Example:

// C1 object size 24
class C1
{
public byte a1;
}
// C2 object size 24
class C2
{
public byte a1;
public int a2;
}
// C3 object size 40
class C3
{
public int a1;
public int a2;
public object a3;
public byte a4;
}

Compared with the object memory size of lua and ILRuntime

The calculation rules of lua are slightly complicated, see third-party article. An empty table occupies 56 bytes, and each additional field occupies at least 32 bytes.

The type of ILRuntime is expressed in IlTypeInstance except enum. The empty type occupies 72 bytes, and each additional field uses at least 16 bytes. If the object contains reference type data, there will be at least 24 bytes more overall, and each additional object field will add 8 bytes.

TypeXluaILRuntimeHybridCLR/native il2cpp
V188+881
V2120+1048
V3184+16824
C188+8824
C2120+10424
C3184+16840

The memory occupied by the loaded assembly

When loading an assembly, a dll file byte array is copied, and metadata is dynamically generated in memory. The final memory is generally 1-5 times the size of the assembly (not a lot of statistics).

GC during operation

HybridCLR is implemented strictly according to the specifications. Except that the additional CPU and memory will be consumed when the assembly is loaded and the function is transferred for the first time, the memory consumed at runtime is exactly the same as that of il2cpp.

So you don't have to ask questions such as whether foreach loop will generate GC. How many GCs are generated under il2cpp or mono, and the exact same GCs are also generated when interpreted and executed in HybridCLR.

- + \ No newline at end of file diff --git a/en/docs/basic/methodbridge.html b/en/docs/basic/methodbridge.html index 1cd47dccb..42a39793d 100644 --- a/en/docs/basic/methodbridge.html +++ b/en/docs/basic/methodbridge.html @@ -9,14 +9,14 @@ - +

Method Bridge

Two-way function calls are required between the Interpreter of HybridCLR and AOT. For example, the interpreter calls the AOT function, or the AOT calls back the interpreter through the interface interface or delegate.

The parameter passing and storage methods of the AOT part and the interpreter part are different. The interpreter part calls the AOT function, and the parameters of the interpreter are all on the interpreter stack, and the function parameters of the interpreter must be passed to the AOT function by means of a suitable method. Similarly, the interpreter cannot directly obtain the parameters of the AOT callback function. Corresponding bridge functions must be generated for each type of signature function to realize the two-way function parameter transfer between the interpreter and the aot part. Calling in the direction of interpreter -> AOT can be done through libraries like ffi, but the cost of function calls is too high. The most reasonable way is to generate this bidirectional bridge function in advance. The internal calls of the interpreter go directly to the interpreter stack, no bridge function is needed.

tip

According to the principle of bridge functions, for a fixed AOT part, the set of bridge functions is determined, and no new additional bridge functions will be needed no matter any subsequent hot updates. Therefore, there is no need to worry about the problem that the bridge function is missing suddenly after the hot update goes online.

Bridge function signature

The bridge function must be generated in the AOT part in advance, which is similar to the principle of lua's wrapper function.

In order to find the corresponding bridge function for each function called between AOT <-> interpreter, there must be a way to calculate the function signature. In addition, functions with completely equivalent parameter types and return value types can sharing the same bridge function, which greatly reduces the number of bridge functions. For the following example, for x64 and arm64 platforms, long and class types sharing the same signature. So they can all sharing a bridge function with long (long, long) signature.

object Fun1(object a, long b);
long Fun2(long a, long b);
object Fun3(object a, object b);

There are some differences in how the ABIs of different operating systems and architectures handle function parameter passing and return values. Considering that both Android v8 and iOS are arm64, in order to maximize the performance of these two common platforms and balance the cost of maintaining too many platforms, we simply designed the most stringent signature calculation rules for 32 and 64 bits respectively, called Universal32 and Universal64, as well as the Arm64 family bridge signature calculation rules are designed for the mobile game arm 64-bit platform.

-Arm64

  • Universal32 uses the abi intersection of all 32-bit platforms to calculate the signature
  • Universal64 calculates the signature using the abi intersection method other than the arm64 platform

Signature rules for Universal32

TypeSignature
bool, byteu1
sbytei1
shorti2
ushort, charu2
inti4
uintu4
longi8
ulongu8
floatr4
doubler8
IntPtri4
UintPtru4
Universal32 signature corresponding toenum
Value type reference and class typei4
value type{S,C}{size}

S and C correspond to the value types of aligment=1 and 8 respectively. For example, the signature of UnityEngine.Vector3 is S12.

Sharing rules for Universal64

TypeSignature
bool, byteu1
sbytei1
shorti2
ushort, charu2
inti4
uintu4
longi8
ulongu8
floatr4
doubler8
IntPtri4
UintPtru4
Universal32 signature corresponding toenum
value typeS{size}
Vector2fv2f
Vector3fv3f
Vector4fv4f
Vector2dv2d
Vector3dv3d
Vector4dv4d

Compared with Univeral32, the value type does not distinguish alignment, all use S.

Sharing Rules for Arm64

TypeSignature
bool, byteu1
sbytei1
shorti2
ushort, charu2
inti4
uintu4
longi8
ulongu8
floatr4
doubler8
IntPtri4
UintPtru4
Universal32 signature corresponding toenum
Value type reference and class typei8
Value type as parameter (size<=16)S16
Value type as parameter (size>16)sr
The value type of the return valueS{size}
Vector2fv2f
Vector3fv3f
Vector4fv4f
Vector2dv2d
Vector3dv3d
Vector4dv4d

Generate bridge function

The tool script is provided in the com.code-philosophy.hybridclr package, and it is recommended to use the menu command HybridCLR/Generate/All to automatically generate all bridge functions. You can also use HybridCLR/Generate/MethodBridge directly Generate bridge functions, but the command depends on Cropped AOT dll and Hot Update dll, and Cropped AOT dll depends on Generate LinkXml and Generate Il2CppDef. Therefore, if you do not use the HybridCLR/Generate/All command, you must first run in order:

  • HybridCLR/Generate/Il2CppDef
  • HybridCLR/Generate/LinkXml
  • HybridCLR/CompileDll/ActiveBuildTarget
  • HybridCLR/Generate/AotDlls
  • HybridCLR/Generate/MethodBridge
- + \ No newline at end of file diff --git a/en/docs/basic/migratefromnetstandard.html b/en/docs/basic/migratefromnetstandard.html index 1ba92fd6f..ccae7d452 100644 --- a/en/docs/basic/migratefromnetstandard.html +++ b/en/docs/basic/migratefromnetstandard.html @@ -9,7 +9,7 @@ - + @@ -22,7 +22,7 @@ Fortunately, these tasks are one-time only.

Migration steps

Migration mainly consists of two steps:

  • Convert the precompiled netstandard-based dll in the project to the .net framework-based dll
  • Switch the project's Api Level to .Net Framework

Convert external dll based on netstarndard

If you can directly find the .Net Framework-based version of the external dll, just replace the dll corresponding to the project. If not found, Then you can use Unity's building pipeline to generate the final aot dll based on .Net Framework, and generate the dll's .Net Framework version. The specific operation is as follows:

  • Make sure that the main project already has code that references this external dll, not just the dll that is referenced in the hot update code
  • Preserve this dll in any link.xml, such as adding <assembly fullname="xxx.dll" preserve="all"/> in Assets/link.xml
  • Run HybridCLR/Generate/AOTDlls
  • Obtain the file corresponding to the dll in the clipping directory of {project}/HybridCLRData/AssembliesPostIl2CppStrip/{target}
  • Use this dll to replace the corresponding dll in the project
- + \ No newline at end of file diff --git a/en/docs/basic/monobehaviour.html b/en/docs/basic/monobehaviour.html index 37713dc10..9ff2555fe 100644 --- a/en/docs/basic/monobehaviour.html +++ b/en/docs/basic/monobehaviour.html @@ -9,7 +9,7 @@ - + @@ -20,7 +20,7 @@ Since condition 3 is not met, the hot update script mounted in the hot update resource cannot be restored, and a Scripting Missing error will occur during runtime.

Therefore, we have made special processing in the Editor/BuildProcessors/PatchScriptingAssemblyList.cs script, adding the hot update dll to the assembly list file. You need to add the hot update assembly in the project to the HotUpdateAssemblyDefinitions or HotUpdateAssemblies field in the HybridCLRSettings configuration.

It only restricts hot update resources to be packaged in the form of ab package, and there is no limit to the way hot update dll is packaged. You can freely choose the hot update method according to the project requirements**, you can package the dll into ab, or bare data files, or encrypted compression, etc. As long as it can be guaranteed to use Assembly.Load to load the hot update resource before loading it.

assembly list file

The names and formats of the assembly list files are different in different Unity versions.

  • 2019 version. It is a globalgamemanagers file when uncompressed and packaged. When compressed and packaged, it is first saved to the globalgamemanagers file, and then packaged into the data.unity3d file in BundleFile format and other files.
  • 2020-2021 version. Saved in the ScriptingAssembles.json file.

Known issues

GameObject.GetComponent(string name) interface cannot get component

This is a known bug, which is related to the code implementation of unity. This problem occurs only when the hot update script is mounted on the hot update resource. The hot update script added through AddComponent in the code can be found by this method. If you encounter this problem please use GameObject.GetComponent<T>() or GameObject.GetComponent(typeof(T)) instead

Others

Do not modify the name of the dll where the script that needs to be linked to the resource is online, because the assembly list file cannot be modified after it is packaged.

It is recommended not to disable TypeTree when typing AB, otherwise the normal AB loading method will fail. (The reason is that for scripts that disable TypeTree, Unity will verify the signature of the script in order to prevent the binary mismatch from causing process crash during the deserialization of MonoBehaviour. The content of the signature is the Hash generated by the script FullName and TypeTree data, but because we The hot update script information does not exist in the packaged installation package, so the verification will definitely fail)

If TypeTree must be disabled, a workaround is to disable the Hash verification of the script. In this case, the user must ensure that the code is consistent with the resource version when packaging, otherwise it may cause Crash, sample code

     AssetBundleCreateRequest req = AssetBundle. LoadFromFileAsync(path);
req.SetEnableCompatibilityChecks(false); // Non-public, needs to be called by reflection
- + \ No newline at end of file diff --git a/en/docs/basic/notsupportedfeatures.html b/en/docs/basic/notsupportedfeatures.html index a78e6ca51..eaf0caeb9 100644 --- a/en/docs/basic/notsupportedfeatures.html +++ b/en/docs/basic/notsupportedfeatures.html @@ -9,13 +9,13 @@ - +

Unsupported Features

tip

Features that are not in the restrictions are supported by HybridCLR, please don't ask if HybridCLR supports a certain feature.

  • Temporarily does not support defining extern functions in hot update scripts, but you can call extern functions in AOT.
  • Fully supports the dots technology of 2022, but cannot take advantage of burst acceleration. If the burst part is in the AOT, it is still executed natively; if the burst part is in the hot update part, although the jobs are executed concurrently, they are executed in an interpreted manner.
  • Functions that serialize structures such as Marshal.StructureToPtr in System.Runtime.InteropServices.Marshal are not supported, but ordinary Marshal functions such as Marshal.PtrToStringAnsi can work normally.
  • [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.xxx)] is not supported. It's purely a matter of timing. Unity collects these functions very early, before the hot update dll is loaded. A recommended way is that you use reflection to collect these functions and call them actively at the right time.
  • Does not support C# level debugging of the interpreted code part, because there is no time to write a debugger
  • RequireComponent(typeof(AAA)) requires that AAA must have been instantiated or AddComponent in other resources, otherwise Unity will not recognize AAA as a script and ignore the processing.
- + \ No newline at end of file diff --git a/en/docs/basic/performance.html b/en/docs/basic/performance.html index 4fbf8d404..b494a0f22 100644 --- a/en/docs/basic/performance.html +++ b/en/docs/basic/performance.html @@ -9,7 +9,7 @@ - + @@ -22,7 +22,7 @@ For the memory data of the object, by calculating the offset of the field in the object in advance, this access operation can be completed directly by *(int32_t*)(obj + offset) = b;.

Compared with other hot update solutions, the efficiency is improved dozens of times.

Directly supports reference and pointer operations without indirect methods

Due to CLI specification restrictions, references in C# can only be placed on the managed stack and not on the interpreter stack (because it is heap memory). In order to handle code like ref int a = ref b; a = 5; one has to use very complex The trick is to maintain this reference indirectly. HybridCLR is implemented in c++ and can directly save and operate these data.

Compared with other hot update solutions, the efficiency is greatly improved.

Metadata is unified, object creation is more efficient, and memory usage is smaller.

Due to the unified metadata, you can directly call il2cpp::vm::Object::New to create objects. The efficiency is very close to the native one, and the memory is exactly the same. In contrast, other hot update schemes use fake types, Objects are bloated and the process of creating objects is more complex.

Compared with other hot update solutions, the efficiency is greatly improved.

Unified metadata, unified function calling methods, and no additional overhead of PInvoke and ReservePInvoke

HybridCLR can directly call C++ functions translated by IL functions without any intermediate links, while ILRuntime and xlua require various complex determinations and parameter conversions, as well as PInvoke and ReservePInvoke with C#, which brings a lot of additional overhead.

The interaction between HybridCLR and il2cpp AOT is extremely lightweight and efficient. No more performance issues.

Provides a large number of additional instinct functions

For common operations like new Vector{2,3,4}, new string(), Nullable<T>.Value, etc., we directly provide corresponding instructions, and the running overhead is even lower than the AOT implementation. .

Compared with other hot update solutions, the efficiency is improved dozens of times.

Strictly follow the specifications and do not introduce additional unnecessary costs

Due to careful design and optimization, HybridCLR tries to avoid unnecessary overhead. For example, the GC of the execution process is exactly the same as native il2cpp and mono.

Other instruction optimization techniques

Other optimization techniques

Appendix: Test case code

public class LongArithmetics
{
[Benchmark]
[Params(1000000)]
public long add_1(long n)
{
long a = 1;
long b = n;
long c = 2;
long d = n;

for(long i = 0; i < n; i++)
{
a = b + c;
b = c + d;
c = d + a;
d = a + b;
a = b + c;
b = c + d;
c = d + a;
d = a + b;
a = b + c;
b = c + d;
c = d + a;
d = a + b;
a = b + c;
b = c + d;
c = d + a;
d = a + b;
a = b + c;
b = c + d;
c = d + a;
d = a + b;
}
return a + b + c + d;
}


[Benchmark]
[Params(1000000)]
public long add_2(long n)
{
long a = 1;
long b = n;
long c = 2;
long d = n;
long e = 3;

for (long i = 0; i < n; i++)
{
a = b + c + d + e;
b = c + d + e + a;
c = d + e + a + b;
d = e + a + b + c;
e = a + b + c + d;
a = b + c + d + e;
b = c + d + e + a;
c = d + e + a + b;
d = e + a + b + c;
e = a + b + c + d;
a = b + c + d + e;
b = c + d + e + a;
c = d + e + a + b;
d = e + a + b + c;
e = a + b + c + d;
a = b + c + d + e;
b = c + d + e + a;
c = d + e + a + b;
d = e + a + b + c;
e = a + b + c + d;
}
return a + b + c + d;
}


[Benchmark]
[Params(1000000)]
public long mul_1(long n)
{
long a = 1;
long b = n;
long c = 2;
long d = n;

for (long i = 0; i < n; i++)
{
a = b * c;
b = c * d;
c = d * a;
d = a * b;
a = b * c;
b = c * d;
c = d * a;
d = a * b;
a = b * c;
b = c * d;
c = d * a;
d = a * b;
a = b * c;
b = c * d;
c = d * a;
d = a * b;
a = b * c;
b = c * d;
c = d * a;
d = a * b;
}
return a + b + c + d;
}


[Benchmark]
[Params(1000000)]
public long mul_2(long n)
{
long a = 1;
long b = n;
long c = 2;
long d = n;
long e = 3;

for (long i = 0; i < n; i++)
{
a = b * c * d * e;
b = c * d * e * a;
c = d * e * a * b;
d = e * a * b * c;
e = a * b * c * d;
a = b * c * d * e;
b = c * d * e * a;
c = d * e * a * b;
d = e * a * b * c;
e = a * b * c * d;
a = b * c * d * e;
b = c * d * e * a;
c = d * e * a * b;
d = e * a * b * c;
e = a * b * c * d;
a = b * c * d * e;
b = c * d * e * a;
c = d * e * a * b;
d = e * a * b * c;
e = a * b * c * d;
}
return a + b + c + d;
}


[Benchmark]
[Params(1000000)]
public long div_1(long n)
{
long a = 1;
long b = n;
long c = 2;
long d = n;

for (long i = 0; i < n; i++)
{
b = c / a;
c = d/a;
d = b / a;

b = c / a;
c = d/a;
d = b / a;
b = c / a;
c = d/a;
d = b / a;
b = c / a;
c = d/a;
d = b / a;
b = c / a;
c = d/a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d/a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
a = a / n + 1;
}
return a + b + c + d;
}


public class IntArithmetics
{
[Benchmark]
[Params(1000000)]
public int add_1(int n)
{
int a = 1;
int b = n;
int c = 2;
int d = n;

for(int i = 0; i < n; i++)
{
a = b + c;
b = c + d;
c = d + a;
d = a + b;
a = b + c;
b = c + d;
c = d + a;
d = a + b;
a = b + c;
b = c + d;
c = d + a;
d = a + b;
a = b + c;
b = c + d;
c = d + a;
d = a + b;
a = b + c;
b = c + d;
c = d + a;
d = a + b;
}
return a + b + c + d;
}


[Benchmark]
[Params(1000000)]
public int add_2(int n)
{
int a = 1;
int b = n;
int c = 2;
int d = n;
int e = 3;

for (int i = 0; i < n; i++)
{
a = b + c + d + e;
b = c + d + e + a;
c = d + e + a + b;
d = e + a + b + c;
e = a + b + c + d;
a = b + c + d + e;
b = c + d + e + a;
c = d + e + a + b;
d = e + a + b + c;
e = a + b + c + d;
a = b + c + d + e;
b = c + d + e + a;
c = d + e + a + b;
d = e + a + b + c;
e = a + b + c + d;
a = b + c + d + e;
b = c + d + e + a;
c = d + e + a + b;
d = e + a + b + c;
e = a + b + c + d;
}
return a + b + c + d;
}


[Benchmark]
[Params(1000000)]
public int mul_1(int n)
{
int a = 1;
int b = n;
int c = 2;
int d = n;

for (int i = 0; i < n; i++)
{
a = b * c;
b = c * d;
c = d * a;
d = a * b;
a = b * c;
b = c * d;
c = d * a;
d = a * b;
a = b * c;
b = c * d;
c = d * a;
d = a * b;
a = b * c;
b = c * d;
c = d * a;
d = a * b;
a = b * c;
b = c * d;
c = d * a;
d = a * b;
}
return a + b + c + d;
}


[Benchmark]
[Params(1000000)]
public int mul_2(int n)
{
int a = 1;
int b = n;
int c = 2;
int d = n;
int e = 3;

for (int i = 0; i < n; i++)
{
a = b * c * d * e;
b = c * d * e * a;
c = d * e * a * b;
d = e * a * b * c;
e = a * b * c * d;
a = b * c * d * e;
b = c * d * e * a;
c = d * e * a * b;
d = e * a * b * c;
e = a * b * c * d;
a = b * c * d * e;
b = c * d * e * a;
c = d * e * a * b;
d = e * a * b * c;
e = a * b * c * d;
a = b * c * d * e;
b = c * d * e * a;
c = d * e * a * b;
d = e * a * b * c;
e = a * b * c * d;
}
return a + b + c + d;
}


[Benchmark]
[Params(1000000)]
public int div_1(int n)
{
int a = 1;
int b = n;
int c = 2;
int d = n;

for (int i = 0; i < n; i++)
{
b = c / a;
c = d / a;
d = b / a;

b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
a = a / n + 1;
}
return a + b + c + d;
}
}

public class FloatArithmetics
{
[Benchmark]
[Params(1000000)]
public float add_1(int n)
{
float a = 1;
float b = n;
float c = 2;
float d = n;

for(int i = 0; i < n; i++)
{
a = b + c;
b = c + d;
c = d + a;
d = a + b;
a = b + c;
b = c + d;
c = d + a;
d = a + b;
a = b + c;
b = c + d;
c = d + a;
d = a + b;
a = b + c;
b = c + d;
c = d + a;
d = a + b;
a = b + c;
b = c + d;
c = d + a;
d = a + b;
}
return a + b + c + d;
}


[Benchmark]
[Params(1000000)]
public float add_2(int n)
{
float a = 1;
float b = n;
float c = 2;
float d = n;
float e = 3;

for (int i = 0; i < n; i++)
{
a = b + c + d + e;
b = c + d + e + a;
c = d + e + a + b;
d = e + a + b + c;
e = a + b + c + d;
a = b + c + d + e;
b = c + d + e + a;
c = d + e + a + b;
d = e + a + b + c;
e = a + b + c + d;
a = b + c + d + e;
b = c + d + e + a;
c = d + e + a + b;
d = e + a + b + c;
e = a + b + c + d;
a = b + c + d + e;
b = c + d + e + a;
c = d + e + a + b;
d = e + a + b + c;
e = a + b + c + d;
}
return a + b + c + d;
}


[Benchmark]
[Params(1000000)]
public float mul_1(int n)
{
float a = 1;
float b = n;
float c = 2;
float d = n;

for (int i = 0; i < n; i++)
{
a = b * c;
b = c * d;
c = d * a;
d = a * b;
a = b * c;
b = c * d;
c = d * a;
d = a * b;
a = b * c;
b = c * d;
c = d * a;
d = a * b;
a = b * c;
b = c * d;
c = d * a;
d = a * b;
a = b * c;
b = c * d;
c = d * a;
d = a * b;
}
return a + b + c + d;
}


[Benchmark]
[Params(1000000)]
public float mul_2(int n)
{
float a = 1;
float b = n;
float c = 2;
float d = n;
float e = 3;

for (int i = 0; i < n; i++)
{
a = b * c * d * e;
b = c * d * e * a;
c = d * e * a * b;
d = e * a * b * c;
e = a * b * c * d;
a = b * c * d * e;
b = c * d * e * a;
c = d * e * a * b;
d = e * a * b * c;
e = a * b * c * d;
a = b * c * d * e;
b = c * d * e * a;
c = d * e * a * b;
d = e * a * b * c;
e = a * b * c * d;
a = b * c * d * e;
b = c * d * e * a;
c = d * e * a * b;
d = e * a * b * c;
e = a * b * c * d;
}
return a + b + c + d;
}


[Benchmark]
[Params(1000000)]
public float div_1(int n)
{
float a = 1;
float b = n;
float c = 2;
float d = n;

for (int i = 0; i < n; i++)
{
b = c / a;
c = d / a;
d = b / a;

b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
a = a / n + 1;
}
return a + b + c + d;
}
}

public class DoubleArithmetics
{
[Benchmark]
[Params(1000000)]
public double add_1(int n)
{
double a = 1;
double b = n;
double c = 2;
double d = n;

for (int i = 0; i < n; i++)
{
a = b + c;
b = c + d;
c = d + a;
d = a + b;
a = b + c;
b = c + d;
c = d + a;
d = a + b;
a = b + c;
b = c + d;
c = d + a;
d = a + b;
a = b + c;
b = c + d;
c = d + a;
d = a + b;
a = b + c;
b = c + d;
c = d + a;
d = a + b;
}
return a + b + c + d;
}


[Benchmark]
[Params(1000000)]
public double add_2(int n)
{
double a = 1;
double b = n;
double c = 2;
double d = n;
double e = 3;

for (int i = 0; i < n; i++)
{
a = b + c + d + e;
b = c + d + e + a;
c = d + e + a + b;
d = e + a + b + c;
e = a + b + c + d;
a = b + c + d + e;
b = c + d + e + a;
c = d + e + a + b;
d = e + a + b + c;
e = a + b + c + d;
a = b + c + d + e;
b = c + d + e + a;
c = d + e + a + b;
d = e + a + b + c;
e = a + b + c + d;
a = b + c + d + e;
b = c + d + e + a;
c = d + e + a + b;
d = e + a + b + c;
e = a + b + c + d;
}
return a + b + c + d;
}


[Benchmark]
[Params(1000000)]
public double mul_1(int n)
{
double a = 1;
double b = n;
double c = 2;
double d = n;

for (int i = 0; i < n; i++)
{
a = b * c;
b = c * d;
c = d * a;
d = a * b;
a = b * c;
b = c * d;
c = d * a;
d = a * b;
a = b * c;
b = c * d;
c = d * a;
d = a * b;
a = b * c;
b = c * d;
c = d * a;
d = a * b;
a = b * c;
b = c * d;
c = d * a;
d = a * b;
}
return a + b + c + d;
}


[Benchmark]
[Params(1000000)]
public double mul_2(int n)
{
double a = 1;
double b = n;
double c = 2;
double d = n;
double e = 3;

for (int i = 0; i < n; i++)
{
a = b * c * d * e;
b = c * d * e * a;
c = d * e * a * b;
d = e * a * b * c;
e = a * b * c * d;
a = b * c * d * e;
b = c * d * e * a;
c = d * e * a * b;
d = e * a * b * c;
e = a * b * c * d;
a = b * c * d * e;
b = c * d * e * a;
c = d * e * a * b;
d = e * a * b * c;
e = a * b * c * d;
a = b * c * d * e;
b = c * d * e * a;
c = d * e * a * b;
d = e * a * b * c;
e = a * b * c * d;
}
return a + b + c + d;
}


[Benchmark]
[Params(1000000)]
public double div_1(int n)
{
double a = 1;
double b = n;
double c = 2;
double d = n;

for (int i = 0; i < n; i++)
{
b = c / a;
c = d / a;
d = b / a;

b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
b = c / a;
c = d / a;
d = b / a;
a = a / n + 1;
}
return a + b + c + d;
}
}

- + \ No newline at end of file diff --git a/en/docs/basic/projectsettings.html b/en/docs/basic/projectsettings.html index 76f180557..6718eecbb 100644 --- a/en/docs/basic/projectsettings.html +++ b/en/docs/basic/projectsettings.html @@ -9,14 +9,14 @@ - +

Settings

After installing the com.code-philosophy.hybridclr package, you need to set the relevant parameters correctly. Detailed configuration related documents can be found in hybridclr_unity package introduction.

Configure PlayerSettings

  • if your package version less than v4.0.0, you have to turn off the incremental GC (Use Incremental GC) option.
  • Scripting Backend is switched to il2cpp, WebGL platform does not need to set this option. Since v2.4.0, this option is set automatically, you can do it without manually.
  • Api Compatability Level switched to .NetFramework 4 (Unity 2019, 2020) or .Net Framework (Unity 2021+).

Configure hot update assembly

Obviously, the code that needs to be hot updated should be split into independent assemblies in order to facilitate hot updating. How to create and split hot update assembly, please see Create and configure hot update Assembly document.

Click the menu HybridCLR/Settings to open the configuration interface.

  • If it is an assembly defined by Assembly Definition (asmdef), add hotUpdateAssemblyDefinitions
  • If it is a common dll or Assembly-CSharp.dll, add the assembly name (excluding the '.dll' suffix, such as Main, Assembly-CSharp) to hotUpdateAssemblies.
  • If your hot update code is in an external project (for example, if you use a framework such as ET, its hot update code is not placed in the Unity project), you can use it in externalHotUpdateAssemblyDirs The search path of the external hot update dll is specified in the configuration item. Note that this path is a relative path, relative to the root directory of the Unity project (that is, the parent directory of Assets).

The hotUpdateAssemblyDefinitions and hotUpdateAssemblies lists are equivalent, do not add them repeatedly, otherwise an error will be reported.

caution

If the hot update assembly is a compiled dll, its search path must be configured in external dll search path at the same time. The search path is a relative path, relative to the project root directory (that is, the parent directory of Assets).

Other parameters

Most of the parameters can be kept at their default values, and developers generally don’t need to care about them. For details, please refer to com.code-philosophy.hybridclr package introduction.

- + \ No newline at end of file diff --git a/en/docs/basic/runhotupdatecodes.html b/en/docs/basic/runhotupdatecodes.html index b6ea4cb1a..3e9dd94cf 100644 --- a/en/docs/basic/runhotupdatecodes.html +++ b/en/docs/basic/runhotupdatecodes.html @@ -9,14 +9,14 @@ - +

Load And Run Code

Load assembly

According to your project resource management method, get the bytes data of the hot update dll. Then call Assembly.Load(byte[] assemblyData) directly. code like as follows:

     byte[] assemblyData = xxxx; // Get hot update dll data from your resource management system
Assembly ass = Assembly. Load(assemblyData);

If there are multiple hot update dlls, please be sure to load them in the order of dependencies, and load the dependent assembly first.

After loading the hot update dll, there are many ways to run the hot update code, and these techniques are exactly the same as when hot update is not considered.

Run the hot update function directly through reflection

Suppose there is a HotUpdateEntry class in the hot update set, the main entry is a static Main function, and the code is similar:

class HotUpdateEntry
{
public static void Main()
{
UnityEngine.Debug.Log("hello, HybridCLR");
}
}

You run it like this:

     // ass is the hot update assembly returned by Assembly.Load.
// You can also find it through code similar to the following after Assembly.Load.
// Assembly ass = AppDomain.CurrentDomain.GetAssemblies().First(assembly => assembly.GetName().Name == "Your-HotUpdate-Assembly");
Type entryType = ass. GetType("HotUpdateEntry");
MethodInfo method = entryType. GetMethod("Main");
method.Invoke(null, null);

Run after creating a Delegate through reflection

     Type entryType = ass. GetType("HotUpdateEntry");
MethodInfo method = entryType. GetMethod("Main");
Action mainFunc = (Action)Delegate.CreateDelegate(typeof(Action), method);
mainFunc();

After creating the object through reflection, call the interface function

Suppose there is such an interface in AOT

public interface IEntry
{
void Start();
}

Such a class is implemented in hot update

class HotUpdateEntry : IEntry
{
public void Start()
{
UnityEngine.Debug.Log("hello, HybridCLR");
}
}

You run it like this:

     Type entryType = ass. GetType("HotUpdateEntry");
IEntry entry = (IEntry) Activator. CreateInstance(entryType);
entry. Start();

Run script code through dynamic AddComponent

Suppose there is such code in hot update:

class Rotate : MonoBehaviour
{
void Update()
{

}
}

You run code like this in AOT:

     Type type = ass. GetType("Rotate");
GameObject go = new GameObject("Test");
go. AddComponent(type);

Restore the mounted hot update script from the prefab or scene packaged into assetbundle by initializing

Assuming that there is such an entry script in the hot update, this script is hung on HotUpdatePrefab.prefab.


public class HotUpdateMain : MonoBehaviour
{
void Start()
{
Debug. Log("hello, HybridCLR");
}
}

You can run hot update logic by instantiating this prefab.

         AssetBundle prefabAb = xxxxx; // Get the AssetBundle where HotUpdatePrefab.prefab is located
GameObject testPrefab = Instantiate(prefabAb.LoadAsset<GameObject>("HotUpdatePrefab.prefab"));

This method does not require any reflection, and is the same as the original startup process. It is recommended to use this method to initialize the hot update entry code!

- + \ No newline at end of file diff --git a/en/docs/basic/sourceinspect.html b/en/docs/basic/sourceinspect.html index d3eef7c83..1823a6679 100644 --- a/en/docs/basic/sourceinspect.html +++ b/en/docs/basic/sourceinspect.html @@ -9,13 +9,13 @@ - +

Source and Debug

HybridCLR module introduction

HybridCLR implements the following functions:

  • dll parsing library implemented by c++
  • Metadata registration. Since il2cpp is a static AOT, the original code does not support dynamic registration, because a small amount of modification (hundreds of lines)
  • Instruction set conversion. Convert raw IL instructions into more efficient register instructions
  • Register interpreter. Implemented an efficient interpreter.

In terms of directory structure, it corresponds to:

  • HybridCLR's own source code
    • interpreter module
    • metadata metadata parsing and registration module
    • transform instruction set conversion module
  • Minor modifications to il2cpp source code. HybridCLR mainly modifies the il2cpp source code to support dynamic registration of metadata. In most places, only hook processing is inserted, and the original implementation is not modified. For example:
const char* il2cpp::vm::GlobalMetadata::GetStringFromIndex(StringIndex index)
{
// ==={{ hybridclr
if (hybridclr::metadata::IsInterpreterIndex(index))
{
return hybridclr::metadata::MetadataModule::GetStringFromEncodeIndex(index);
}
// ===}} hybridclr

IL2CPP_ASSERT(index <= s_GlobalMetadataHeader->stringCount);
const char* strings = ((const char*)s_GlobalMetadata + s_GlobalMetadataHeader->stringOffset) + index;
return strings;
}

Transform Implementation Introduction

tip

The core code is the HiTransform::Transform function in hybridclr/transform/Transform.cpp.

Very similar to regular instruction tree analysis. divided into parts

  • BasicBlock division. Divide the original IL instruction into multiple BasicBlocks, each BasicBlock does not contain any jump function. Doing so can be more efficient to avoid accidental merging of instructions across jump blocks
  • Simulate the execution of all logical branches, including jumps and exception branches, and convert each IL instruction into a corresponding register instruction.
  • Instruction optimization (to be done). Development is expected to begin next month. At that time, most instructions can get 100-300% performance improvement.

Interpreter Implementation Introduction

tip

The core code is in the Interpreter::Execute function in hybridclr/interpreter/Interpreter_Execute.cpp.

More directly, it is a huge switch statement that interprets and executes instructions.

debug

The core work of the HybridCLR interpreter consists of two parts:

  • Instruction set conversion. Convert stack-based IL instructions to register-based versions. HiTransform::Transform function in HybridCLR/transform/transform.cpp.
  • Interpreted execution of register instructions. Interpreter::Execute function in HybridCLR/interpreter/interpreter_Execute.cpp.

As long as the breakpoints are to these two functions, it is easy to follow the entire process from the conversion of the IL function to the solution execution step by step.

PC, MAC create debugging project

  • Project Settings settings
    • Modify C++ Compiler Configuration to Debug.
  • Check "Create VisualStudio Solution" in Building Settings.

After the Build is completed, a debuggable project will be generated. For more information, please refer to Unity Official Documentation

Android create debug project

  • Project Settings settings
    • Modify C++ Compiler Configuration to Debug.
  • Building Settings check Export Project.
  • After the build is complete, use Android Studio to open the project.
  • Assuming that the packaging output path is build_android, select Build->Make Module 'build_android.unityLibrary' in Android Studio, compile unityLibrary, and wait for the compilation to complete
  • Select Run->Edit Configurations... and set as shown below.

android studio debug

  • Normal debugging is fine.
- + \ No newline at end of file diff --git a/en/docs/basic/supportedplatformanduniyversion.html b/en/docs/basic/supportedplatformanduniyversion.html index fe0e6c001..807cdba25 100644 --- a/en/docs/basic/supportedplatformanduniyversion.html +++ b/en/docs/basic/supportedplatformanduniyversion.html @@ -9,7 +9,7 @@ - + @@ -19,7 +19,7 @@ If a certain minor version is not our standard supported version, you can also contact us to provide [commercialization services] (../business/intro.md).

Supported platforms

Since version v4.0.0, all known platform-incompatible codes have been eliminated, and all platforms that il2cpp can run on are fully supported. But for some uncommon platforms, there may be some small bugs in Editor or Runtime remaining. If you encounter any problems, please contact us for business resolution.

The following platforms have been tested for a long time and are very stable and supported platforms:

  • Windows x86, x64
  • MacOS x86, x64
  • MacOS arm64(silicon)
  • Android armv7, armv8 (arm64)
  • iOS arm64
  • WebGL standard WebGL, MiniGame, WeChat mini games
  • PS4, PS5
  • UWP
  • tvOS
  • visionOS
  • other platforms

Special Instructions

WeChat Mini Games

The WeChat mini game conversion tool sets IL2CPP Code Generation to Faster (Smaller) builds mode by default. If metadata is not supplemented, AOT generic functions will not be accessible. Since version 2021.3.x, all commercial versions It supports full generic sharing, eliminating the need to supplement metadata, reducing the package body, significantly reducing memory usage, and greatly improving the execution performance of AOT generic functions that are not instantiated in the main project.

MiniGame

Additional notes on version compatibility:

  • The recommended versions of MiniGame 2019 and 2020 overlap with the compatible versions of HybridCLR. Try to directly choose those cross versions (such as 2019.4.35, 2020.3.33), because they have been verified by the project and basically will not encounter problems.
  • The recommended versions of the MiniGame2021 series are 2021.2.5-2021.2.18, LTS versions that are not supported by HybridCLR, but these versions have been verified by other developers and can also use HybridCLR normally (a small amount of code adjustments may be required). If you encounter problems, you can contact us to provide commercial technical support.
- + \ No newline at end of file diff --git a/en/docs/basic/workwithscriptlanguage.html b/en/docs/basic/workwithscriptlanguage.html index 18275ad8d..a0ba5f411 100644 --- a/en/docs/basic/workwithscriptlanguage.html +++ b/en/docs/basic/workwithscriptlanguage.html @@ -9,7 +9,7 @@ - + @@ -22,7 +22,7 @@ A wrapper function. If the [ReversePInvokeWrapperGeneration(xx)] attribute is added to multiple functions with the same signature, the total number of wrapper functions is the sum of all preserveCount + the number of functions that do not contain the ReversePInvokeWrapperGenerationAttribute attribute.

As shown below, there are 10 wrappers of type LuaFunction, 101 wrappers of type Func<int, int, int>, and 1 wrapper of type Func<int, int>.


delegate int LuaFunction(IntPtr luaState);

public class MonoPInvokeWrapperPreserves
{
[ReversePInvokeWrapperGeneration(10)]
[MonoPInvokeCallback(typeof(LuaFunction))]
public static int LuaCallback(IntPtr luaState)
{
return 0;
}

[ReversePInvokeWrapperGeneration(100)]
[MonoPInvokeCallback(typeof(Func<int, int, int>))]
public static int Sum(int a, int b)
{
return a + b;
}

[MonoPInvokeCallback(typeof(Func<int, int, int>))]
public static int Sum2(int a, int b)
{
return a + b;
}

[MonoPInvokeCallback(typeof(Func<int, int>))]
public static int Inc(int a)
{
return a + 1;
}
}

limit

caution

Please make sure that the function parameters are simple primitive types such as int, float and so on.

At present, there is no marshal processing for reference type parameters. Reference type parameters such as string are directly passed as parameters, which will inevitably lead to a crash after use! If there is such a need, you can put the callback function in the AOT, and call back the hot update in the AOT function.

- + \ No newline at end of file diff --git a/en/docs/beginner.html b/en/docs/beginner.html index a8335d7cc..4c513b775 100644 --- a/en/docs/beginner.html +++ b/en/docs/beginner.html @@ -9,13 +9,13 @@ - + - + \ No newline at end of file diff --git a/en/docs/beginner/generic.html b/en/docs/beginner/generic.html index 73a7350f1..0d7493d74 100644 --- a/en/docs/beginner/generic.html +++ b/en/docs/beginner/generic.html @@ -9,7 +9,7 @@ - + @@ -17,7 +17,7 @@

Use Generics

HybridCLR fully supports generic features without any restrictions.

Use generic classes or functions defined in hot update

Just use it directly.

Use generic classes or functions defined in AOT

If some generic class or function has been instantiated in AOT code, it can be used directly in hot update, for example:


// List<float> generic type has been used in AOT
class Foo
{
public void Run()
{
var arr = new List<float>();
}
}

// List<float> can be used in hot update
class HotUpdateGenericDemos
{
public void Run()
{
var arr = new List<float>();
}
}

However, if an AOT generic class or function has not been instantiated in the AOT, certain processing is required. There are two solutions:

  1. Add the corresponding instantiation code in the AOT code.
  2. Supplementary Metadata Technology. This is HybridCLR's patented technology.

Please read AOT Generics for detailed principles of AOT generics.

For method 1, there are several fatal flaws:

  • The instantiation code added to the AOT code needs to be repackaged. Not only is the development period very troublesome, but it is unrealistic to redistribute the main package in a short period of time after it goes online.
  • Generic parameters may be hot update types, which cannot be instantiated in advance in AOT. For example, if you define struct MyVector3 {int x, y, z;} in the hot update code, you cannot instantiate List<MyVector3> in advance in AOT.

Supplementary Metadata Technology completely solves this problem. Roughly speaking, after you supplement the original metadata of the AOT generic class (or generic function), you can instantiate the generic class arbitrarily. Take the above List<MyVector3> as an example, after you add the mscorlib.dll metadata where the List class (not MyVector3) is located, you can use any List<T> generic class in the hot update code up.

get supplementary metadata dll

The stripped AOT dll generated by build process can be used to supplement metadata. The com.code-philosophy.hybridclr plugin will automatically copy them to {project}/HybridCLRData/AssembliesPostIl2CppStrip/{target}.

danger

Stripped AOT dlls of different BuildTargets cannot be reused.

Using the HybridCLR/Generate/AotDlls command can also generate the trimmed AOT dll immediately, it works by exporting a Temp project to get the trimmed AOT dll.

Obtain the supplementary metadata dll you need from the {project}/HybridCLRData/AssembliesPostIl2CppStrip/{target} directory, and add it to the hot update resource management system of the project. The example projects are placed in the StreamingAssets directory for demonstration purposes. Take the List<T> type as an example, it needs to complement mscorlib.dll. Copy {project}/HybridCLRData/AssembliesPostIl2CppStrip/{target}/mscorlib.dll to Assets/StreamingAssets/mscorlib.dll.bytes.

Execute Supplementary Metadata

Use the HybridCLR.RuntimeApi.LoadMetadataForAOTAssembly function in the com.code-philosophy.hybridclr package to supplement metadata for AOT generics. Metadata only needs to be supplemented once, it is recommended before executing any hot update code. LoadDll.cs ends up looking like this.


public class LoadDll : MonoBehaviour
{

void Start()
{
// Add metadata first
LoadMetadataForAOTAAssemblies();
// In the Editor environment, HotUpdate.dll.bytes has been automatically loaded and does not need to be loaded. Repeated loading will cause problems.
#if !UNITY_EDITOR
Assembly hotUpdateAss = Assembly.Load(File.ReadAllBytes($"{Application.streamingAssetsPath}/HotUpdate.dll.bytes"));
#else
// No need to load under Editor, directly find the HotUpdate assembly
Assembly hotUpdateAss = System.AppDomain.CurrentDomain.GetAssemblies().First(a => a.GetName().Name == "HotUpdate");
#endif

Type type = hotUpdateAss. GetType("Hello");
type. GetMethod("Run"). Invoke(null, null);
}

private static void LoadMetadataForAOTAAssemblies()
{
List<string> aotDllList = new List<string>
{
"mscorlib.dll",
"System.dll",
"System.Core.dll", // required if using Linq
// "Newtonsoft.Json.dll",
// "protobuf-net.dll",
};

foreach (var aotDllName in aotDllList)
{
byte[] dllBytes = File.ReadAllBytes($"{Application.streamingAssetsPath}/{aotDllName}.bytes");
int err = HybridCLR.RuntimeApi.LoadMetadataForAOTAssembly(dllBytes, HomologousImageMode.SuperSet);
Debug.Log($"LoadMetadataForAOTAssembly:{aotDllName}.ret:{err}");
}
}
}

Now you can freely use AOT generics in hot update code.

Optimize supplementary metadata dll size

The supplementary metadata dll generated by default contains a large amount of data that is not required by the supplementary metadata mechanism. Starting from version v4.0.16, the supplementary metadata dll is supported for elimination and optimization. For detailed documentation, see [AOT Generics](../basic/ aotgeneric).

- + \ No newline at end of file diff --git a/en/docs/beginner/monobehaviour.html b/en/docs/beginner/monobehaviour.html index 8474d6da8..7eef936ab 100644 --- a/en/docs/beginner/monobehaviour.html +++ b/en/docs/beginner/monobehaviour.html @@ -9,13 +9,13 @@ - +

Use MonoBehaviour

HybridCLR fully supports the MonoBehaviour workflow. You can not only dynamically mount the hot update script in the code through AddComponent, but also hang the hot update script on the resource, and then restore the script by loading the resource.

Based on the project of the quickstart document, we demonstrate how to use the hot update script.

Create Print.cs hot update script

Create Assets/HotUpdate/Print.cs script, the code is as follows:

using System. Collections;
using System.Collections.Generic;
using UnityEngine;

public class Print : MonoBehaviour
{
public int value = 1;

void Start()
{
Debug.Log($"[Print] GameObject:{name} value:{value}");
}
}

Call AddComponent in the code to dynamically mount the hot update script

Modify the Hello.Run function and add the code to dynamically mount the Print script. The final code is as follows:

     public static void Run()
{
Debug. Log("Hello, World");

GameObject go = new GameObject("Test1");
go. AddComponent<Print>();
}

After the hot update, a line of log [Print] GameObject:Test1 value:1 will be added on the screen.

Mount the script to the hot update resource

Due to the limitations of the Unity resource management system, the resources (prefab, scene, ScriptableObject resources) mounted by the hot update script must be typed into assetbundle**, and the resources can be instantiated from the ab package to restore the script correctly.

danger

If you mount the hot update script to Resources and other resources that come with the main package, a scripting missing error will occur!

Since the whole process involves packing the ab package, it is relatively lengthy, so I won't detail it here. Try the hybridclr_trial project (github or gitee) directly.

For beginners, you just need to remember: the resource (scene or prefab) that mounts the hot update script must be packaged into ab, and the hot update dll can be loaded before instantiating the resource (this requirement is obvious!).

- + \ No newline at end of file diff --git a/en/docs/beginner/otherhelp.html b/en/docs/beginner/otherhelp.html index 32458ab82..7956838c3 100644 --- a/en/docs/beginner/otherhelp.html +++ b/en/docs/beginner/otherhelp.html @@ -9,13 +9,13 @@ - +

Other Information

Video Tutorials

You can search for HybridCLR-related videos on Bilibili, please try to choose a newer video, otherwise it may be quite different from the current HybridCLR, causing misleading.

Encounter problems

Please check the Common Errors first.

If it is not resolved, you can join the official group for help:

If it is determined to be a bug (HybridCLR is already very stable, the probability of novices encountering bugs is extremely low), please report to us according to Bug Feedback Template.

- + \ No newline at end of file diff --git a/en/docs/beginner/quickstart.html b/en/docs/beginner/quickstart.html index 28c346da8..21464fcc4 100644 --- a/en/docs/beginner/quickstart.html +++ b/en/docs/beginner/quickstart.html @@ -9,13 +9,13 @@ - +

Getting Started

This tutorial guides you to experience HybridCLR hot update from an empty project. For the sake of simplicity, only the case where the BuildTarget is Windows or MacOS Standalone platform is demonstrated.

Please run through the hot update process correctly on the Standalone platform and then try the hot update on the Android and iOS platforms. Their processes are very similar.

Experience Goals

  • Create hot update assembly
  • Load the hot update assembly and execute the hot update code, print Hello, HybridCLR
  • Modify the hot update code to print Hello, World

Prepare the environment

Install Unity

caution

HybridCLR supports all LTS versions of Unity 2019-2022. If the currently used version is not among the recommended versions below, please refer to the Install HybridCLR document.

  • Install any version 2019.4.40, 2020.3.26+, 2021.3.0+, 2022.3.0+
  • Depending on your operating system, when selecting modules during installation, you must select Windows Build Support(IL2CPP) or Mac Build Support(IL2CPP).

select il2cpp modules

  • Windows
    • Under Win, you need to install visual studio 2019 or later. The installation must include at least the Game Development with Unity and Game Development with C++ components.
    • install git
  • Mac
    • Requires MacOS version >= 12, xcode version >= 13, for example xcode 13.4.1, macos 12.4.
    • install git
    • install cmake

Initialize the Unity hot update project

The process of constructing a hot update project from scratch is tedious. The project structure, resources and codes can refer to the hybridclr_trial project, and its warehouse address is github or gitee.

Create project

Create an empty Unity project.

Create ConsoleToScreen.cs script

This script has no direct effect on demonstrating hot updates. It can print the log to the screen, which is convenient for locating errors.

Create Assets/ConsoleToScreen.cs script class, the code is as follows:

using System;
using System. Collections;
using System.Collections.Generic;
using UnityEngine;

public class ConsoleToScreen : MonoBehaviour
{
const int maxLines = 50;
const int maxLineLength = 120;
private string _logStr = "";

private readonly List<string>_lines = new List<string>();

public int fontSize = 15;

void OnEnable() { Application. logMessageReceived += Log; }
void OnDisable() { Application. logMessageReceived -= Log; }

public void Log(string logString, string stackTrace, LogType type)
{
foreach (var line in logString. Split('\n'))
{
if (line. Length <= maxLineLength)
{
_lines. Add(line);
continue;
}
var lineCount = line.Length / maxLineLength + 1;
for (int i = 0; i < lineCount; i++)
{
if ((i + 1) * maxLineLength <= line.Length)
{
_lines.Add(line.Substring(i * maxLineLength, maxLineLength));
}
else
{
_lines.Add(line.Substring(i * maxLineLength, line.Length - i * maxLineLength));
}
}
}
if (_lines. Count > maxLines)
{
_lines. RemoveRange(0, _lines. Count - maxLines);
}
_logStr = string. Join("\n", _lines);
}

void OnGUI()
{
GUI.matrix = Matrix4x4.TRS(Vector3.zero, Quaternion.identity,
new Vector3(Screen. width / 1200.0f, Screen. height / 800.0f, 1.0f));
GUI.Label(new Rect(10, 10, 800, 370), _logStr, new GUIStyle() { fontSize = Math.Max(10, fontSize) });
}
}


Create the main scene

  • Create default initial scene main.scene
  • Create an empty GameObject in the scene and hang ConsoleToScreen on it
  • Add the main scene to the list of packaged scenes in Build Settings

Create HotUpdate hot update module

  • Create Assets/HotUpdate directory
  • Right-click Create/Assembly Definition in the directory to create an assembly module named HotUpdate

Install and configure HybridCLR

Install com.code-philosophy.hybridclr package

Click Windows/Package Manager in the main menu to open the package manager. Click Add package from git URL... as shown below, fill in https://gitee.com/focus-creative-games/hybridclr_unity.git or https://github.com/focus-creative -games/hybridclr_unity.git.

add package

If you are not familiar with installing packages from url, please see install from giturl.

Due to domestic network reasons, you may encounter network exceptions in Unity and fail to install. You can first clone or download com.code-philosophy.hybridclr to the local, rename the folder to com.code-philosophy.hybridclr, and move it directly to the Packages directory of the project.

Initialize com.code-philosophy.hybridclr

Open the menu HybridCLR/Installer..., click the Install button to install. Wait patiently for about 30 seconds. After the installation is complete, the Installation Successful log will be printed at the end.

Configure HybridCLR

Open the menu HybridCLR/Settings, add the HotUpdate assembly in the Hot Update Assemblies configuration item, as shown below:

settings

Configure PlayerSettings

  • if your package version less than v4.0.0, you have to turn off the incremental GC (Use Incremental GC) option. Because incremental GC is not currently supported.
  • Scripting Backend switched to IL2CPP.
  • Api Compatability Level switched to .Net 4.x (Unity 2019-2020) or .Net Framework (Unity 2021+).

player settings

Create hot update script

Create Assets/HotUpdate/Hello.cs file, the code content is as follows

using System. Collections;
using UnityEngine;

public class Hello
{
public static void Run()
{
Debug. Log("Hello, HybridCLR");
}
}

You may be concerned about whether the code in the hot update part has restrictions on C# syntax like other solutions. HybridCLR is a nearly complete implementation, and there are almost no restrictions on hot update code, so let's write it by ourselves.

See Unsupported Features for rare exceptions.

Load hot update assembly

In order to simplify the demonstration, we do not download HotUpdate.dll through the http server, but directly put HotUpdate.dll in the StreamingAssets directory.

HybridCLR is a native runtime implementation, so call Assembly Assembly.Load(byte[]) to load the hot update assembly.

Create the Assets/LoadDll.cs script, then create a GameObject object in the main scene, mount the LoadDll script.

using HybridCLR;
using System;
using System. Collections;
using System.Collections.Generic;
using System.IO;
using System. Linq;
using System. Reflection;
using System. Threading. Tasks;
using UnityEngine;
using UnityEngine. Networking;
public class LoadDll : MonoBehaviour
{

void Start()
{
// In the Editor environment, HotUpdate.dll.bytes has been automatically loaded and does not need to be loaded. Repeated loading will cause problems.
#if !UNITY_EDITOR
Assembly hotUpdateAss = Assembly.Load(File.ReadAllBytes($"{Application.streamingAssetsPath}/HotUpdate.dll.bytes"));
#else
// No need to load under Editor, directly find the HotUpdate assembly
Assembly hotUpdateAss = System.AppDomain.CurrentDomain.GetAssemblies().First(a => a.GetName().Name == "HotUpdate");
#endif
}
}

Call hot update code

Obviously, the main project cannot directly reference the hot update code. There are many ways to call the code in the hot update assembly from the main project. Here, the hot update code is called through reflection.

Add the reflection calling code after the LoadDll.Start function, the final code is as follows:

     void Start()
{
// In the Editor environment, HotUpdate.dll.bytes has been automatically loaded and does not need to be loaded. Repeated loading will cause problems.
#if !UNITY_EDITOR
Assembly hotUpdateAss = Assembly.Load(File.ReadAllBytes($"{Application.streamingAssetsPath}/HotUpdate.dll.bytes"));
#else
// No need to load under Editor, directly find the HotUpdate assembly
Assembly hotUpdateAss = System.AppDomain.CurrentDomain.GetAssemblies().First(a => a.GetName().Name == "HotUpdate");
#endif

Type type = hotUpdateAss. GetType("Hello");
type. GetMethod("Run"). Invoke(null, null);
}

So far, the creation of the entire hot update project has been completed! ! !

Trial run in Editor

Run the main scene, 'Hello, HybridCLR' will be displayed on the screen, indicating that the code is working normally.

Package and run

  • Run the menu HybridCLR/Generate/All to perform the necessary generation operations. This step cannot be missed!!!
  • Copy HotUpdate.dll in {proj}/HybridCLRData/HotUpdateDlls/StandaloneWindows64 (StandaloneMacXxx under MacOS) directory to Assets/StreamingAssets/HotUpdate.dll.bytes, Note, you must add .bytes Suffix! ! !
  • Open the Build Settings dialog box, click Build And Run, package and run the hot update sample project.

If the packaging is successful, and 'Hello, HybridCLR' is displayed on the screen, it means that the hot update code has been successfully executed!

Test hot update

  • Modify the Debug.Log("Hello, HybridCLR"); code in the Run function of Assets/HotUpdate/Hello.cs to Debug.Log("Hello, World");.
  • Run the menu command HybridCLR/CompileDll/ActiveBulidTarget to recompile the hot update code.
  • Copy HotUpdate.dll in the {proj}/HybridCLRData/HotUpdateDlls/StandaloneWindows64 (StandaloneMacXxx under MacOS) directory to XXX_Data/StreamingAssets/HotUpdate.dll.bytes in the package output directory just now.
  • Re-run the program, and you will find Hello, World displayed on the screen, indicating that the hot update code has taken effect!

This completes the hot update experience! ! !

- + \ No newline at end of file diff --git a/en/docs/business.html b/en/docs/business.html index c47de712b..e03f88ae6 100644 --- a/en/docs/business.html +++ b/en/docs/business.html @@ -9,13 +9,13 @@ - +

Business Edition

- + \ No newline at end of file diff --git a/en/docs/business/accesspolicy.html b/en/docs/business/accesspolicy.html index 7d98f3961..1f0c3517b 100644 --- a/en/docs/business/accesspolicy.html +++ b/en/docs/business/accesspolicy.html @@ -9,7 +9,7 @@ - + @@ -22,7 +22,7 @@ The sample code is as follows:

[MenuItem("Test/ConvertXmlAccessPolicyToBinary")]
public static void ConvertXmlAccessPolicyToBinary()
{
string accessPolicyDir = Application.dataPath + "/AccessPolicy";
AccessPolicyUtil.ConvertXmlAccessPolicyToBinaryAccessPolicy($"{accessPolicyDir}/AccessPolicy.xml",
$"{accessPolicyDir}/AccessPolicy.bytes");
}

Verify the legality of AccessPolicy configuration

In practice, it is easy to incorrectly fill in names such as assembly.fullname and type.fullname, resulting in the expected access control policy not being correctly implemented. HybridCLR.Editor.Security.AccessPolicyConfigValidator is used to check the validity of AccessPolicy to avoid this error.

FunctionDescription
ValidateRulesCheck the legality of Rule rules
ValidateTargetsCheck the legality of Target rules

The sample code is as follows:

public static void ValidateAccessPolicy()
{
var reader = new XmlAccessPolicyReader();
reader.LoadXmlFile("Assets/AccessPolicy/AccessPolicy.xml");
List<string> hotUpdateDllNames = SettingsUtil.HotUpdateAssemblyNamesExcludePreserved;
var assemblyCache = new AssemblyCache(MetaUtil.CreateHotUpdateAndAOTAssemblyResolver(EditorUserBuildSettings.activeBuildTarget, hotUpdateDllNames));
var validator = new AccessPolicyConfigValidator(assemblyCache);

var accessPolicy = reader.GetAccessPolicy();
validator.ValidateRules(accessPolicy);
validator.ValidateTargets(accessPolicy, new List<string> { "Tests2" });
}

Pre-verify whether the assembly meets AccessPolicy

Assembly.Load does not check whether there are illegal calls in the assembly when loading the assembly. During the running process, it only checks whether the calling function complies with the AccessPolicy when a function is called for the first time, which causes inconvenience. HybridCLR.Editor.Security.AssemblyValidator is used to offline pre-verify whether all calls in the assembly comply with AccessPolicy.

The sample code is as follows:

public static void ValidateAssembly()
{
var reader = new XmlAccessPolicyReader();
reader.LoadXmlFile("Assets/AccessPolicy/AccessPolicy.xml");
var validator = new AssemblyValidator(reader.GetAccessPolicy());
string test2DllPath = $"{SettingsUtil.GetHotUpdateDllsOutputDirByTarget(EditorUserBuildSettings.activeBuildTarget)}/Tests2.dll";
var mod = ModuleDefMD.Load(test2DllPath);
validator.ValidateAssembly(mod);
}

Set access policy at runtime

The RuntimeApi class provides the LoadAccessPolicy function for setting access policies. This function can be called multiple times during operation to update the access policy.

The sample code is as follows:


void LoadAccessPolicy()
{
byte[] accessPolicyData = File.ReadAllBytes($"{Application.streamingAssetsPath}/AccessPolicy.bin");
RuntimeApi.LoadAccessPolicy(accessPolicyData);
}

- + \ No newline at end of file diff --git a/en/docs/business/advancedencryption.html b/en/docs/business/advancedencryption.html index f645b3a11..9f315028d 100644 --- a/en/docs/business/advancedencryption.html +++ b/en/docs/business/advancedencryption.html @@ -9,14 +9,14 @@ - +

Advanced code hardening

Advanced code hardening uses custom assembly structures and custom instructions to greatly improve App security.

Principle

Advanced code hardening technology improves code security in the following aspects:

  • Use custom randomizable assembly structures. The assembly structure definition itself can be randomized by generating corresponding proprietary code To analyze the corresponding structure, greatly improving the difficulty of cracking
  • Custom transformation of all metadata structures so that they can no longer be read by regular IL decompilation tools (such as ILSpy)
  • Irreversibly convert IL instructions into custom register instruction sets in advance, and the instruction set itself can also be randomized

Other advantages

  • Since it has been converted offline to a custom register instruction set in advance, instruction translation is faster
  • Cooperate with advanced instruction optimization technology to maximize execution efficiency
- + \ No newline at end of file diff --git a/en/docs/business/advancedoptimization.html b/en/docs/business/advancedoptimization.html index f339948bc..408413f29 100644 --- a/en/docs/business/advancedoptimization.html +++ b/en/docs/business/advancedoptimization.html @@ -9,7 +9,7 @@ - + @@ -17,7 +17,7 @@

Offline Instruction Optimization

danger

Offline instruction optimization technology is under development, and currently only standard instruction optimization technology can be used.

Offline Instruction Optimization (OIO) converts original IL instructions into customized register instructions offline. Since there is no compilation performance limit offline, richer compilation optimization technologies can be used, which greatly improves the performance of the interpretation module.

After optimization, the overall instruction execution performance is improved by 100%-1000% (yes, more than 10 times) or even higher, especially the overall improvement of numerical instructions by nearly 300%. And because it has been converted in advance, the loading and instruction translation process is faster and the lag is smaller.

Offline instruction optimization technology supports virtualization technology in code reinforcement solutions, greatly improving code security.

Techonology Detail

Offline instruction optimization technology includes the following optimization technologies:

  • Complete elimination of useless stack instructions. Eliminate all unnecessary stack operations
  • Peephole optimization
  • Constant copy optimization
  • Optimization of local copy propagation
  • Global copy propagation optimization
  • Explain function inline
  • AOT function inline (patented technology)
  • Provide more instinct instructions to greatly improve the performance of common instruction combinations
  • Conditional check elimination technology. Eliminate unnecessary null pointer checks, type cast checks, and array out-of-bounds checks
  • CheckOnce runtime checks dynamically eliminate optimizations. For example, an instruction that accesses a static member variable will no longer check whether the type has been initialized during the second execution.
  • Other optimizations

Performance

TODO.

- + \ No newline at end of file diff --git a/en/docs/business/basicencryption.html b/en/docs/business/basicencryption.html index 44ad84f50..cef6a6cab 100644 --- a/en/docs/business/basicencryption.html +++ b/en/docs/business/basicencryption.html @@ -9,7 +9,7 @@ - + @@ -20,7 +20,7 @@ It is recommended to modify this parameter every time a new main package is released.

MetadtaSeed and key are both dynamic parameters and do not need to be consistent with the main package. This value can be modified every time the encryption hot update dll is updated. It is recommended to modify these values every time or after several versions.

xxEncCodeLength is the length of the encryption instruction. The larger the value, the more complex the encryption. The decryption time is proportional to the length of the encryption instruction. Since the decryption process will bring some overhead, it is recommended to use the default value. if It takes too long to load encrypted hot update assemblies. You can reduce these values appropriately.

Encrypted hot update dll

The HybridCLR.Editor.Encryption.EncryptUtil class is provided to encrypt the dll. The sample code is as follows:

     public static void EncryptDll(string originalDll, string encryptedDll)
{
HybridCLR.Editor.Encryption.EncryptionUtil.EncryptDll(originalDll, encryptedDll, SettingsUtil.EncryptionSettings);
}

For flagship version users, since the default dhao file records the MD5 value of currentDll before encryption, if the dll is encrypted, the dhao file needs to be updated synchronously, otherwise Runtime.LoadDifferentialHybridAssembly will fail to run. For ease of use, we provide the HybridCLR.Editor.DHE.BuildUtil.EncryptDllAndGenerateDHAODatas function separately to complete the encryption and generation of dhao files in one go.

Load hot update dll at runtime

There is no difference from ordinary hot update dll, just use Assembly.Load.

Supplementary metadata dlls can also be encrypted and loaded in the same way as when unencrypted.

- + \ No newline at end of file diff --git a/en/docs/business/basicoptimization.html b/en/docs/business/basicoptimization.html index 8e92edc58..a61ba7ad0 100644 --- a/en/docs/business/basicoptimization.html +++ b/en/docs/business/basicoptimization.html @@ -9,13 +9,13 @@ - +

Standard Interpretation Optimization

tip

Standard interpretation optimization technology is only available in the commercial version.

The standard interpretation optimization greatly improves the performance of interpreted execution using a variety of techniques, benefiting basic instructions (such as variable access and numerical calculations) immensely.

For example, in numerical calculation instructions, the performance has seen a qualitative leap after using standard interpretation optimization technology, with an increase of 280%-735%! Special codes like typeof instructions have seen an increase of over 1000%.

Implementation

The standard interpretation optimization uses the following techniques to enhance interpretation performance.

  • Instruction dispatch optimization
  • Instruction merging
  • Useless instruction elimination
  • Special instinct instructions

Performance Report

The following is the performance report of OnePlus 9R ArmV8 real machine testing, with the test code attached at the end.

AOT Time vs. Commercial Version Time vs. Community Version Time (Lower is better)

data

Commercial Version Time/AOT Time vs. Community Version Time/AOT Time (Lower is better)

The performance of the AOT version is 4.1 - 90 times that of the community version, and 1.30 - 12.9 times that of the commercial version.

data

Commercial Version Performance/Community Version Performance (Higher is better)

The performance of the commercial version is 2.87-7.35 times that of the community version.

data

Commercial Version Time/AOT Time (Lower is better)

The performance of the AOT version is 1.30 - 12.9 times that of the commercial version.

data

Enabling and Disabling Standard Instruction Optimization

Standard optimization is enabled by default. This setting is global and applies to all assemblies, including supplementary metadata assemblies.

You can manually enable or disable this feature using the RuntimeApi.EnableTransformOptimization function.


/// Disable standard instruction optimization
void DisableCodeOptimization()
{
RuntimeApi.EnableTransformOptimization(false);
}
- + \ No newline at end of file diff --git a/en/docs/business/businesscase.html b/en/docs/business/businesscase.html index a89a245bb..a1607b49d 100644 --- a/en/docs/business/businesscase.html +++ b/en/docs/business/businesscase.html @@ -9,13 +9,13 @@ - +

Commercial Project Cases

We have conducted in-depth cooperation with many companies in the industry and solved their problems well. For reasons of commercial confidentiality, we only list a very limited list of business partners who are willing to disclose information.

ProjectGame CompanyIntroductionUltimate EditionProfessional EditionHot Reload Edition
Wonderful FighterThunder GameiOS 11w comments, taptap has 1.69 million followers and 3.57 million downloads.
- + \ No newline at end of file diff --git a/en/docs/business/differentialhybridexecution.html b/en/docs/business/differentialhybridexecution.html index 53576579a..97fca9f8c 100644 --- a/en/docs/business/differentialhybridexecution.html +++ b/en/docs/business/differentialhybridexecution.html @@ -9,13 +9,13 @@ - +

Differential Hybrid Execution (DHE)

HybridCLR introduces the groundbreaking Differential Hybrid Execution (DHE) technology. It allows for arbitrary modifications to AOT DLLs, intelligently running changed or newly added classes and functions in interpreter mode while unchanged classes and functions run in AOT mode, enabling the performance of hot-updated game logic to reach nearly the level of native AOT.

tip

DHE is only available in the Ultimate Edition. Please refer to the Ultimate Edition introduction for details.

Principle

The assemblies marked as DHE are also packaged into the main bundle. After running, the latest hotfix DLL is loaded. During execution, when calling a function from a DHE assembly, if the function has not changed, the native AOT implementation is directly invoked; otherwise, the latest code is executed in interpreter mode. Since in practice, the two versions often do not modify too much code, DHE can basically achieve performance close to native levels.

Features and Advantages

  • Unchanged code performs exactly like native AOT, with performance gains ranging from 3-30 times or higher compared to pure interpretation versions, nearly reaching native performance levels.
  • Arbitrary changes can be made to the code, with almost no intrusions to the codebase and minimal special considerations, similar to the community version.
  • Simple workflow, no need to manually mark which functions have changed like other solutions such as xxxfix; everything is handled automatically by the tool.
  • Lower project transformation costs compared to pure hotfix versions. For example, extern functions can be directly defined in DHE without the need to move to the AOT module.
  • All native code is included in the package, significantly reducing the risk of iOS rejection.

Unsupported Features

  • No code from the corresponding AOT assembly can be executed before loading DHE hotfix code. This means that DHE does not support differential mixing like mscorlib, but supports traditional hotfix assembly differential updates.
  • Due to the first limitation, features such as [InitializeOnLoadMethod] and Script Execution Order settings are not supported in DHE assemblies.
  • DHE scripts cannot be mounted on resources included in the package, including Resources.
  • Extern functions cannot be added through hotfix updates in DHE assemblies.

dhao Files

The dhao file is a core concept of the DHE technology. It contains pre-calculated information about the types and functions that have changed in the latest hotfix DLL, allowing the runtime to determine whether to use the latest interpreted version or directly call the original AOT function when executing a hotfix function. Pre-calculated dhao files are crucial for DHE technology to function properly. Without a dhao file, the original AOT DLL must be carried along, and the cost of calculating function changes is extremely high.

By comparing the latest hotfix DLL with the AOT DLL generated during packaging, the changed types and functions are calculated offline and saved as dhao files. Therefore, for the DHE mechanism to work properly, it depends on the correctness of the dhao file, which in turn depends on accurately providing the latest hotfix DLL and the AOT DLL generated during packaging.

- + \ No newline at end of file diff --git a/en/docs/business/fullgenericsharing.html b/en/docs/business/fullgenericsharing.html index 6e7864e35..3caaa7f34 100644 --- a/en/docs/business/fullgenericsharing.html +++ b/en/docs/business/fullgenericsharing.html @@ -9,7 +9,7 @@ - + @@ -17,7 +17,7 @@

Full Generic Sharing

Although supplementary metadata has completely solved the AOT generic problem, it leads to the need to carry supplementary metadata DLLs with the package or download them during hot updates, thus increasing the size of the package or the time required for hot updates. Loading supplementary metadata not only significantly increases memory usage but also increases startup time. This is a major issue for scenarios with strict requirements on package size and memory, such as WeChat mini-games. Furthermore, generic functions that have been supplemented are executed in interpreted mode, which also reduces runtime performance.

With the introduction of Full Generic Sharing in HybridCLR, there is no longer a need for supplementary metadata, simplifying the workflow and effectively addressing the aforementioned shortcomings of supplementary metadata.

Supported Versions

Supported Unity LTS versions include Unity 2021 and higher.

Principle

The old generic sharing technology could only share generics for class types. Starting from Unity 2021.3.x LTS versions, il2cpp has supported the Full Generic Sharing technology, which means that generic parameters of any type (including value types) can be shared. HybridCLR leverages this mechanism to achieve perfect support for AOT generics without the need for supplementary metadata.

Configuration

danger

Enabling faster (smaller build) will significantly affect the performance of generic functions (15% or even higher), so it is recommended not to enable this option.

If you are using Unity 2021 and there is no memory pressure, it is still recommended to use the supplementary metadata technology to solve the generic problem.

  • The 2020 version does not support Full Generic Sharing.
  • For the 2021 version, the IL2CPP Code Generation option should be set to faster (smaller build).
  • The 2022 version has Full Generic Sharing enabled by default and cannot be disabled. If the IL2CPP Code Generation option is set to faster (smaller build), it can further reduce the package size.
- + \ No newline at end of file diff --git a/en/docs/business/intro.html b/en/docs/business/intro.html index b91c166df..dfafc90a6 100644 --- a/en/docs/business/intro.html +++ b/en/docs/business/intro.html @@ -9,14 +9,14 @@ - +

Introduction

We offer various high-end commercial versions and customizable technical services to meet the needs of game projects in various scenarios.

Commercial Versions

tip

All commercial versions will be long-term supported for Unity 2019-2022 LTS versions, ensuring long-term stable technical support for projects.

Currently, there are three commercial versions, with specific feature comparisons as follows:

  • Professional Edition. Significant improvements in interpreted execution performance (numeric instruction performance is 280%-735% of the community version), optimized metadata memory, support for code encryption, effectively ensuring code security.
  • Ultimate Edition. Includes all features of the Professional Edition, additionally featuring our core DHE technology, greatly improving performance, almost (100% when unchanged) reaching the same level as native AOT.
  • Hot Reload Edition. Includes all features of the Professional Edition, and supports unloading and reloading individual assemblies. Currently, it can unload 100% of the metadata memory of the assembly.
FeatureCommunity EditionProfessional EditionUltimate EditionHot Reload Edition
Interpreted Execution
MonoBehaviour
Supplementary Metadata
Incremental GC
Unity 2019-2022 LTS
DOTS
Full Generic Sharing
Metadata Optimization
Standard Interpretation Performance Optimization
Offline Instruction Optimization
Code Encryption
global-metadata.dat encryption
Hot Reload
Access Control Mechanism
DHE Technology
Technical Support1 year2 years2 years

Pricing

VersionPrice (RMB)Description
Community Edition0Unlimited usage period
Professional EditionContact us via email for business inquiryBuy the right to use for one project permanently, includes 1 year of technical support and 1 year of code updates
Hot Reload EditionContact us via email for business inquiryBuy the right to use for one project permanently, includes 2 years of technical support and 2 years of code updates
Ultimate EditionContact us via email for business inquiryBuy the right to use for one project permanently, includes 2 years of technical support and 2 years of code updates

Trials

tip

Both free and paid trials are only available to enterprise users. Please contact business@code-philosophy.com with your corporate email to get trial support.

All commercial versions support the following trial options:

Trial TypePriceServiceDescription
Free Trial without Full SourceFreeTechnical support is available at a rate of 400 RMB/hourOnly supports building iOS platform targets based on pre-compiled libil2cpp.a, does not provide C++ source code, does not provide tool source code. No trial period limit, but the runtime of the trial version will crash randomly after 30 minutes of startup or after three months of use, please do not use it for formal releases.
Paid Trial with Full Source10% of the total price of the commercial versionEquivalent technical support services to formal paid usersProvides source code and related tools for the trial version of the commercial version, which developers can build and test on their own. If you eventually purchase the formal version, the service fee can be deducted; otherwise, unless there is false advertising or major defects, the trial fee will not be refunded.

Note that free trial versions do not support the following features:

  • DOTS support
  • Incremental GC

Documentation for free trial versions:

Enterprise Technical Support

You can flexibly choose the technical service items needed by the enterprise. If subscribed annually, billing will be based on the service items, otherwise, it will be based on the service duration.

Technical Support Contents

  • Standard response and resolution of bugs, including one-on-one remote assistance and guidance. Most reproducible bugs will be fixed or provided with workarounds within 2-7 days.
  • Solve some special platform compatibility issues.
  • Support for some currently unsupported versions (excluding versions before 2018).
  • Optimization guidance.
  • Other services.

Pricing

Since HybridCLR is easy to use and stable, most companies do not need long-term technical support. Therefore, only timed technical support services are provided. Unused time in a single service can be retained for subsequent use. To save business costs, time-based billing under 2000 RMB does not provide contracts and invoices, please understand.

Service LevelScope of Problem SolvingPrice
StandardProvides technical support for basic usage questions, excluding bug fixes and implementation of unreleased features100 RMB per hour, billed in 15-minute increments (e.g., 15 minutes, 2 hours)
ExpertSolves complex problems, including bug fixes1500 RMB per hour, billed in 30-minute increments (e.g., 30 minutes, 2 hours)

Contact Us

Please use your company's corporate email to email business@code-philosophy.com for inquiries. Emails initiated by personal email accounts such as QQ or 126 will be ignored, please understand.

- + \ No newline at end of file diff --git a/en/docs/business/metadataoptimization.html b/en/docs/business/metadataoptimization.html index 69bfb7720..71369cf73 100644 --- a/en/docs/business/metadataoptimization.html +++ b/en/docs/business/metadataoptimization.html @@ -9,13 +9,13 @@ - +

Metadata Optimization

While executing code, HybridCLR does not significantly increase memory usage, but loading the metadata of assemblies consumes a considerable amount of memory. This may be a problem in scenarios with high memory pressure, such as WeChat Mini Games. To address this, all commercial versions have significantly optimized metadata memory.

Several aspects have been optimized to reduce memory usage:

  • Using full generic sharing technology
  • Optimizing memory usage of loading supplementary metadata assemblies
  • Optimizing memory usage of loading hot update assembly metadata

Additionally, the time taken to load hot update assemblies has been significantly optimized, with commercial versions taking only 30% of the time compared to the community version.

Based on current test results, on a 64-bit platform, commercial versions save approximately {total size of supplementary metadata} * 4 + {total size of hot update assemblies} * 1.8 memory compared to the community version.

For WebGL platforms (including WeChat Mini Games), enabling the faster (smaller) build option will further reduce package size (approximately 1-2 times the size of all AOT DLLs), leading to a significant reduction in memory usage. For most projects, commercial versions can ultimately reduce memory usage by nearly 50-100MB or even more on the WebGL platform.

Full Generic Sharing

Completely eliminates supplementary metadata memory, approximately 4 times the size of the DLL. The downside is that full generic sharing is only supported from 2021 onwards. Enabling full generic sharing can significantly reduce package size (by approximately 30-40% after compilation of managed code).

Optimizing Memory Usage of Supplementary Metadata

We tested the memory consumption of common AOT assemblies after adding supplementary metadata. The memory consumption of the community version is approximately 4 times the size of the DLL; for the commercial version without full generic sharing, it is approximately 1.3 times; with full generic sharing enabled, this becomes 0. The commercial version reduces memory consumption by 67% compared to the community version (100% when full generic sharing is enabled).

Detailed Data:

aot-metadata-data

Memory Consumption:

aot-metadata-memory

Memory Consumption per DLL Size:

aot-metadata-dll-rate

Optimizing Memory Usage of Hot Update Assemblies

We tested the memory consumption of common plugins after loading them in interpreted mode. The memory consumption of the community version is approximately 4.7 times the size of the DLL, while for the commercial version it is 2.9 times. The commercial version reduces memory consumption by 39% compared to the community version.

tip

This data does not include memory consumed by Il2CppClass, MethodInfo, and translated instructions that are lazily initialized at runtime. This part of the memory consumes approximately 2.9-3.5 times the size of the DLL. The final memory consumption of metadata, for the community version, is 7.6-8.2 times, while for the commercial version it is 5.8-6.4 times the size of the DLL. The commercial version reduces memory consumption by approximately 25% compared to the community version.

Detailed Data:

aot-metadata-data

Memory Consumption:

aot-metadata-memory

Memory Consumption per DLL Size:

aot-metadata-dll-rate

- + \ No newline at end of file diff --git a/en/docs/business/pro/commonerrors.html b/en/docs/business/pro/commonerrors.html index f7dd907f9..bb44ef8aa 100644 --- a/en/docs/business/pro/commonerrors.html +++ b/en/docs/business/pro/commonerrors.html @@ -9,13 +9,13 @@ - + - + \ No newline at end of file diff --git a/en/docs/business/pro/freetrial.html b/en/docs/business/pro/freetrial.html index 3733a8300..adbf0d9fd 100644 --- a/en/docs/business/pro/freetrial.html +++ b/en/docs/business/pro/freetrial.html @@ -9,13 +9,13 @@ - +

Free Trial

Compared to the paid trial version and the version after formal purchase, the free trial version includes all the runtime and editor source codes of HybridCLR. However, the free trial version only provides pre-compiled binary libs and tools, and only supports building for the iOS platform target.

Trial Rules

  • Targeted towards enterprise users only.
  • The trial is free and has no trial period limit. However, if technical support is needed during the trial, additional charges will apply.
  • The trial version will randomly crash after 30 minutes of app launch. Please do not use it for formal releases.

Supported Versions

Only specific Unity versions are supported. Nearby minor versions can also be used if there are no compilation errors during building.

  • 2019.4.40f1 To be supported
  • 2020.3.48f1 To be supported
  • 2021.3.31f1
  • 2022.3.11f1

Unsupported Features

tip

The following features are supported in the paid trial or the formal paid version.

  • DOTS
  • Incremental GC
  • Development build options like Profiler, Script Debugging, etc.

Incremental GC and various compilation options are not supported because each compilation parameter requires a separate libil2cpp.a, significantly increasing maintenance costs.

Differences from the Standard Commercial Version

In the trial version, the source code of some modules is removed and replaced with compiled binary programs. The following are the related modules:

  • DllEncryptor DLL encryption module

Installation

The relevant libil2cpp code has been directly included in the package. Open the HybridCLR/Installer menu and click the Install button to complete the installation.

Configuration

Compared to the community version or the formal commercial version, the following settings need to be modified:

  • Disable incremental GC
  • Disable development options
  • The value of the encryption parameter vmSeed cannot be modified and must remain as 0. Other encryption parameters can be modified.

Usage

Apart from minor adjustments in installation and configuration as described above, the rest remains entirely according to the Quick Start documentation.

- + \ No newline at end of file diff --git a/en/docs/business/pro/intro.html b/en/docs/business/pro/intro.html index e1a1c7d86..b4a31acf0 100644 --- a/en/docs/business/pro/intro.html +++ b/en/docs/business/pro/intro.html @@ -9,13 +9,13 @@ - +

Introduction

The Professional Edition offers advanced features not supported in the Community Edition, suitable for scenarios with high memory and package requirements or certain performance pressures, such as WebGL games.

Advantages

  • Supports Full Generic Sharing technology, eliminating the need for additional metadata.
  • Includes Metadata Optimization technology, significantly optimizing metadata memory.
  • Includes Standard Interpretation Optimization, greatly enhancing interpretation performance. For instance, in numerical computation instructions, performance has undergone a qualitative leap with the use of standard interpretation optimization technology, ranging from 280% to 735% of the original performance! For some special codes like the typeof instruction, performance has improved by over 1000%.
  • Supports code encryption for enhanced security.
  • Includes one year of technical support.

Supported Versions

Compatible with all Unity 2019-2022 LTS versions.

- + \ No newline at end of file diff --git a/en/docs/business/pro/quickstart.html b/en/docs/business/pro/quickstart.html index 7982134e2..a847fac2d 100644 --- a/en/docs/business/pro/quickstart.html +++ b/en/docs/business/pro/quickstart.html @@ -9,13 +9,13 @@ - +

Quick Start

Similar to the Quick Start of the Community Edition, this document only highlights the differences.

Installation

  • Extract hybridclr_unity and place it in the project's Packages directory, renaming it to com.code-philosophy.hybridclr.
  • Extract the corresponding il2cpp_plus-{version}.zip based on your Unity version.
  • Extract hybridclr.zip.
  • Place the hybridclr directory from the extracted hybridclr.zip into the libil2cpp directory from the extracted il2cpp-{version}.zip.
  • Open HybridCLR/Installer, enable the Copy libil2cpp from local option, select the libil2cpp directory that was just extracted, and proceed with the installation.

installer

Usage

- + \ No newline at end of file diff --git a/en/docs/business/reload/commonerrors.html b/en/docs/business/reload/commonerrors.html index e0756a088..ffe558278 100644 --- a/en/docs/business/reload/commonerrors.html +++ b/en/docs/business/reload/commonerrors.html @@ -9,13 +9,13 @@ - +

Frequently Asked Questions

Issues with Json Serialization

When the assembly is unloaded, all type metadata is also unloaded. However, almost all commonly used serialization libraries cache reflection information about types. This means that if you use serialization libraries like Unity's JsonUtility or LitJson in your code, they will incorrectly cache reflection information. As a result, if you reload for the second (or third) time and attempt deserialization, errors will occur.

There are several solutions:

  • Modify the code of these deserialization libraries to clear their reflection cache after unloading the assembly. For example, Unity's JsonUtility is natively implemented and cannot clear the cache, so you may need to switch to other Json libraries.
- + \ No newline at end of file diff --git a/en/docs/business/reload/freetrial.html b/en/docs/business/reload/freetrial.html index 1b97dd765..692ab8031 100644 --- a/en/docs/business/reload/freetrial.html +++ b/en/docs/business/reload/freetrial.html @@ -9,13 +9,13 @@ - +

Free Trial

Compared to the paid trial version and the version after formal purchase, the free trial version includes all the runtime and editor source codes of HybridCLR. However, the free trial version only provides pre-compiled binary libs and tools, and only supports building for the iOS platform target.

Trial Rules

  • Targeted towards enterprise users only.
  • The trial is free and has no trial period limit. However, if technical support is needed during the trial, additional charges will apply.
  • The trial version will randomly crash after 30 minutes of app launch. Please do not use it for formal releases.

Supported Versions

Only specific Unity versions are supported. Nearby minor versions can also be used if there are no compilation errors during building.

  • 2019.4.40f1 To be supported
  • 2020.3.48f1 To be supported
  • 2021.3.31f1
  • 2022.3.11f1

Unsupported Features

tip

The following features are supported in the paid trial or the formal paid version.

  • dots
  • Incremental GC
  • Development build options like Profiler, Script Debugging, etc.

The lack of support for Incremental GC and various compilation options is due to the need for separate libil2cpp.a files for each compilation parameter, greatly increasing maintenance costs.

Differences from the Standard Commercial Version

In the trial version, the source code of some modules is removed and replaced with compiled binary programs. The following are the related modules:

  • DllEncryptor DLL encryption module

Installation

The relevant libil2cpp code has been directly included in the package. Open the HybridCLR/Installer menu and click the Install button to complete the installation.

Configuration

Compared to the community version or the formal commercial version, the following settings need to be modified:

  • Disable incremental GC
  • Disable development options
  • The value of the encryption parameter vmSeed cannot be modified and must remain as 0. Other encryption parameters can be modified.

Usage

Apart from minor adjustments in installation and configuration as described above, the rest remains entirely according to the Quick Start documentation.

- + \ No newline at end of file diff --git a/en/docs/business/reload/hotreloadassembly.html b/en/docs/business/reload/hotreloadassembly.html index f8d853fd4..b047d94d8 100644 --- a/en/docs/business/reload/hotreloadassembly.html +++ b/en/docs/business/reload/hotreloadassembly.html @@ -9,13 +9,13 @@ - +
-

Hot Reload Technology

Hot reload technology is used to completely unload or reload an assembly, suitable for small game collections. This solution is only available in the commercial version.

Supported Features

  • Supports unloading assemblies, freeing up 100% of the memory occupied by the assembly.
  • Supports reloading assemblies, allowing code to change arbitrarily or even be completely different (with certain limitations on MonoBehaviour and Scriptable).
  • Supports restricting the set of functions that can be accessed in hot-updated assemblies, suitable for creating sandbox environments in UGC games to prevent malicious player code from causing damage.

Unsupported Features and Special Requirements

  • Ensure that the business code no longer uses objects or functions from the unloaded Assembly and exits all executing old logic.
  • You cannot directly unload a dependent Assembly; you must unload the dependents before the dependencies in reverse dependency order. For example, if A.dll depends on B.dll, you need to unload A.dll first, then B.dll.
  • Related to MonoBehaviour and ScriptableObject:
    • Ensure that overloaded MonoBehaviour event or message functions like Awake and OnEnable are not added or removed (but the function body can change).
    • Ensure that the serialized field names in the script classes with the same name in the old Assembly do not change (the types can change).
    • If the field type is a custom type A (class, struct, or enum) from an unloadable Assembly, it must be marked with the [Serializable] attribute.
    • Field types such as List&lt;A&gt;, where A is a type from an unloadable Assembly, are not supported; replace them with A[].
    • Generic types cannot be inherited, e.g., class MyScript : CommonScript<int>.
  • Some libraries that cache reflection information (most common in serialization-related libraries like LitJson) need to clear cached reflection information after hot reload.
  • Destructors ~XXX() are not supported. Instantiating generic classes with destructors where the generic parameter is a type from the current Assembly is also not allowed.
  • Incompatible with DOTS. Due to the extensive caching of type information and the complexity of implementation, it is difficult to individually clear the cached information.

Incompatible Libraries

  • Jobs in 2022 cache type-related information and require minor modifications to UnityEngine.CoreModule.dll code. Versions earlier than 2022 do not require modifications.
  • Deserialization libraries like LitJson cache reflection information and need to clear the cached reflection information in the library after hot reload. The specific operation depends on the implementation of the library.

Resolving References to Unloaded Objects

Hot reload technology requires that metadata of unloaded assembly U cannot be held in the assembly or global memory that has not been unloaded. This includes, but is not limited to:

  • Instances of types in the unloaded assembly
  • Generic parameters of generic classes or functions that include types from the unloaded assembly
  • Reflection information related to the unloaded assembly, such as Assembly, Type, MethodInfo, PropertyInfo, etc.
  • Delegates pointing to functions in the unloaded assembly
  • Tasks defined in the unloaded assembly
  • Others

Real-world projects can be complex, and it is difficult and impractical for developers to find all illegal references. We have implemented illegal reference checks, and when unloading, logs of all illegal references will be printed. Developers can clear all illegal references based on the printed logs.

- +

Hot Reload Technology

Hot reload technology is used to completely unload or reload an assembly, suitable for small game collections. This solution is only available in the commercial version.

Supported Features

  • Supports unloading assemblies, freeing up 100% of the memory occupied by the assembly.
  • Supports reloading assemblies, allowing code to change arbitrarily or even be completely different (with certain limitations on MonoBehaviour and Scriptable).
  • Supports restricting the set of functions that can be accessed in hot-updated assemblies, suitable for creating sandbox environments in UGC games to prevent malicious player code from causing damage.

Unsupported Features and Special Requirements

  • Ensure that the business code no longer uses objects or functions from the unloaded Assembly and exits all executing old logic.
  • You cannot directly unload a dependent Assembly; you must unload the dependents before the dependencies in reverse dependency order. For example, if A.dll depends on B.dll, you need to unload A.dll first, then B.dll.
  • Related to MonoBehaviour and ScriptableObject:
    • Ensure that overloaded MonoBehaviour event or message functions like Awake and OnEnable are not added or removed (but the function body can change).
    • Ensure that the serialized field names in the script classes with the same name in the old Assembly do not change (the types can change).
    • If the field type is a custom type A (class, struct, or enum) from an unloadable Assembly, it must be marked with the [Serializable] attribute.
    • Field types such as List<A>, where A is a type from an unloadable Assembly, are not supported; replace them with A[].
    • Generic types cannot be inherited, e.g., class MyScript : CommonScript<int>.
  • Some libraries that cache reflection information (most common in serialization-related libraries like LitJson) need to clear cached reflection information after hot reload.
  • Destructors ~XXX() are not supported. Instantiating generic classes with destructors where the generic parameter is a type from the current Assembly is also not allowed.
  • Incompatible with DOTS. Due to the extensive caching of type information and the complexity of implementation, it is difficult to individually clear the cached information.

Incompatible Libraries

  • Jobs in 2022 cache type-related information and require minor modifications to UnityEngine.CoreModule.dll code. Versions earlier than 2022 do not require modifications.
  • Deserialization libraries like LitJson cache reflection information and need to clear the cached reflection information in the library after hot reload. The specific operation depends on the implementation of the library.

Resolving References to Unloaded Objects

Hot reload technology requires that metadata of unloaded assembly U cannot be held in the assembly or global memory that has not been unloaded. This includes, but is not limited to:

  • Instances of types in the unloaded assembly
  • Generic parameters of generic classes or functions that include types from the unloaded assembly
  • Reflection information related to the unloaded assembly, such as Assembly, Type, MethodInfo, PropertyInfo, etc.
  • Delegates pointing to functions in the unloaded assembly
  • Tasks defined in the unloaded assembly
  • Others

Real-world projects can be complex, and it is difficult and impractical for developers to find all illegal references. We have implemented illegal reference checks, and when unloading, logs of all illegal references will be printed. Developers can clear all illegal references based on the printed logs.

+ \ No newline at end of file diff --git a/en/docs/business/reload/intro.html b/en/docs/business/reload/intro.html index dbd9ebb52..286b884e3 100644 --- a/en/docs/business/reload/intro.html +++ b/en/docs/business/reload/intro.html @@ -9,13 +9,13 @@ - +

Introduction

The Hot Reload Special Edition provides support for the innovative Hot Reload Technology. It allows for the complete unloading or reloading of an assembly during runtime, making it particularly suitable for small game collections.

Advantages

  • Includes all features of the Professional Edition
  • Supports Hot Reload Technology
  • Supports Access Control Mechanism, restricting the set of functions that can be accessed in hot-updated assemblies. This is suitable for creating sandbox environments in UGC games to prevent malicious player code from causing damage.
  • Includes two years of technical support

Supported Versions

Compatible with all Unity 2019-2022 LTS versions.

- + \ No newline at end of file diff --git a/en/docs/business/reload/modifydll.html b/en/docs/business/reload/modifydll.html index 0b9d17efc..2208a1f9c 100644 --- a/en/docs/business/reload/modifydll.html +++ b/en/docs/business/reload/modifydll.html @@ -9,7 +9,7 @@ - + @@ -17,7 +17,7 @@

Modify the UnityEngine dll

Since some versions of the dll are not compatible with hot reloading, the code needs to be slightly modified.

Use the dnspy tool

We use dnspy to modify dll files. And dnspy can only run under Win, so even if it is a mac version dll, You also have to copy the corresponding dll to Win and then modify it. Download dnspy, select [Win64 version](https://github.com/dnSpy/dnSpy/releases/download/v6.1.8/dnSpy-net- win64.zip).

The operation of modifying the dll is roughly as follows:

  • Clear all dlls on the left in dnspy
  • open dll
  • Find the function you want to modify ToModifiedType.ToModifiedMethod function, right-click the menu -> Edit Method (c#)..., and the source code editing interface will pop up.
  • If the editor prompts that some dll references are missing, click the button similar to a folder in the lower left corner of the source code editing window to add them.
  • modify the code
  • Click the Compile button in the lower right corner. If it is successful, there will be no prompt, exit the editing interface, and return to the decompilation viewing mode. If that fails, handle compilation errors yourself. Sometimes dnspy will have inexplicable reference errors, exit the source code editing mode, right-click edit method again, and enter again to solve it.
  • Menu File -> Save Module to save the modified dll file. If under Win or Mac, you may encounter permission problems, please handle it accordingly (for example, save it to another location first, and then manually overwrite it)

Modify UnityEngine.CoreModule.dll

caution

Only Unity 2022+ versions need to be modified.

Unity provides a separate set of UnityEngine dlls for each BuildTarget, which are located in {editor_install_dir}/Editor/Data/PlaybackEngines/{platform}/Variations/il2cpp (iOS platform is iOSSupport\Variations\il2cpp\Releasearm64_managed) directory Down, Please replace the relevant dll under each platform according to the platform you need to package.

Since UnityEngine.CoreModule.dll refers to NetStandard 2.1, you need to pull Editor\Data\NetStandard\ref\2.1.0\netstandard.dll into the left assembly explorer of dnspy before compiling.

Original code:

using System;

namespace Unity.Collections.LowLevel.Unsafe
{
// Token: 0x020000A6 RID: 166
internal static partial class BurstRuntime
{
// Token: 0x020000A7 RID: 167
private partial struct HashCode64<T>
{
// Token: 0x06000348 RID: 840 RVA: 0x000063F5 File Offset: 0x000045F5
// Note: this type is marked as 'beforefieldinit'.
static HashCode64()
{
BurstRuntime.HashCode64<T>.Value = BurstRuntime.HashStringWithFNV1A64(typeof(T).AssemblyQualifiedName);
}
}
}
}

Modified code:

using System;

namespace Unity.Collections.LowLevel.Unsafe
{
// Token: 0x020000A6 RID: 166
internal static partial class BurstRuntime
{
// Token: 0x020000A7 RID: 167
private partial struct HashCode64<T>
{
// Token: 0x06000348 RID: 840 RVA: 0x000063F5 File Offset: 0x000045F5
// Note: this type is marked as 'beforefieldinit'.
static HashCode64()
{
BurstRuntime.HashCode64<T>.Value = BurstRuntime.HashStringWithFNV1A64(typeof(T).AssemblyQualifiedName + ":" + typeof(T).GetHashCode().ToString());
}
}
}
}
- + \ No newline at end of file diff --git a/en/docs/business/reload/quickstart.html b/en/docs/business/reload/quickstart.html index a875ee717..9fd644f8f 100644 --- a/en/docs/business/reload/quickstart.html +++ b/en/docs/business/reload/quickstart.html @@ -9,13 +9,13 @@ - +

Quick Start

This is almost identical to the community version of the Quick Start, and this document only describes the differences.

Installation

  • Unzip hybridclr_unity and put it in the project Packages directory, rename it to com.code-philosophy.hybridclr
  • Unzip the corresponding il2cpp_plus-{version}.zip according to your unity version
  • Unzip hybridclr.zip
  • Put the hybridclr directory after unzipping hybridclr.zip into the libil2cpp directory after unzipping il2cpp-{version}.zip
  • Open HybridCLR/Installer, turn on the Copy libil2cpp from local option, select the libil2cpp directory just unzipped, and install it

installer

Use in code

Call RuntimeApi.TryUnloadAssembly or RuntimeApi.ForceUnloadAssembly Unload the assembly and reload it using Assembly.Load. The assembly must be successfully unloaded before it can be loaded again.

There are currently two unloading workflows:

  • TryUnloadAssembly
  • ForceUnloadAssembly

TryUnloadAssembly

Try to unload. If there is a reference to the object in the unloaded assembly in the AppDomain, keep the status quo and return failure, otherwise return success.

The sample code is as follows:


// First load
Assembly ass = Assembly.Load(yyy);

// Execute some code
Type mainType = ass.GetType("Entry");
mainType.GetMethod("Main").Invoke(null, null);

// First unload
// The printObjectReferenceLink parameter is true, which means that when the unloading fails, a detailed reference chain log of illegal objects will be printed, which is convenient for developers to locate where illegal references are maintained.
// It is recommended to set it to true only during the development period and change it to false after the official launch
if (!RuntimeApi.TryUnloadAssembly(ass, true))
{
throw new Exception("unload fail");
}

// Second load
Assembly newAss = Assembly.Load(yyy);

// Execute some code
Type mainType = ass.GetType("Entry");
mainType.GetMethod("Main").Invoke(null, null);

// Second uninstall
if (!RuntimeApi.TryUnloadAssembly(ass, true))
{
throw new Exception("unload fail");
}

ForceUnloadAssembly

Force uninstallation, even if there are references to objects in the uninstalled assembly in the AppDomain. Returning true means there is no problem, and returning false means that an illegal reference was detected during the uninstallation process. If false is returned, it may crash after running for a period of time. Use this operation with caution, and it is recommended to communicate with official technical support in detail.


// Load for the first time
Assembly ass = Assembly.Load(yyy);

// Execute some code
Type mainType = ass.GetType("Entry");
mainType.GetMethod("Main").Invoke(null, null);

// Uninstall for the first time
// The ignoreObjectReferenceValidation parameter is true, which means that illegal object references are not checked during the uninstall process, which can shorten the uninstall time. However, it is recommended to use false regardless of the development period or the official release
// The printObjectReferenceLink parameter is true, which means that when the uninstall fails, a detailed illegal object reference chain log will be printed, which is convenient for developers to locate where the illegal reference is maintained. It is recommended to set it to true only during the development period and change it to false after the official launch
if (!RuntimeApi.ForceUnloadAssembly(ass, false, true))
{
throw new Exception("unload fail");
}

// Second load
Assembly newAss = Assembly.Load(yyy);

// Execute some code
Type mainType = ass.GetType("Entry");
mainType.GetMethod("Main").Invoke(null, null);

// Second uninstall
if (!RuntimeApi.ForceUnloadAssembly(ass, false, true))
{
throw new Exception("unload fail");
}

Notes

  • Async or coroutines can easily implicitly keep references to the uninstalled assembly code in other threads. Please be sure to clean up all asynchronous or coroutine functions before uninstalling
  • UI OnClick or various callback events can easily keep references to the uninstalled assembly, so be sure to clean them up
  • Registering to global events or other heightening can easily accidentally keep references to uninstalled assemblies, so be sure to clean them up.
  • Clean up illegal references in the code according to the illegal reference logs printed during the uninstall process
- + \ No newline at end of file diff --git a/en/docs/business/ultimate/commonerrors.html b/en/docs/business/ultimate/commonerrors.html index 8793f9bc9..52d4aeff6 100644 --- a/en/docs/business/ultimate/commonerrors.html +++ b/en/docs/business/ultimate/commonerrors.html @@ -9,13 +9,13 @@ - +

Frequently Asked Questions

ExecutionEngineException: Could not run the type initializer for origin DHE type 'xxx'

The reason is that the original AOT type replaced by DHE was accidentally created. After using DHE, you cannot create the original types corresponding to the types in the DHE assembly. There are several reasons for this error:

  • DHE code is executed before loading the DHE assembly.
  • Hot-reloaded dhe dll does not match the dhao file, resulting in incorrectly executing original AOT code that should not be executed, creating original AOT types. Only versions 4.5.7 and earlier that do not strictly verify the version of the dll will have this error.
  • When throwing an exception or printing logs, accessing the original functions of the DHE assembly accidentally during the process of obtaining the function call stack frame. This bug exists in version 4.5.8 and earlier.
  • script debugging build option is enabled.
- + \ No newline at end of file diff --git a/en/docs/business/ultimate/freetrial.html b/en/docs/business/ultimate/freetrial.html index 2aaab7b36..9f6479a13 100644 --- a/en/docs/business/ultimate/freetrial.html +++ b/en/docs/business/ultimate/freetrial.html @@ -9,13 +9,13 @@ - +

Free Trial

Compared to the paid trial version and the version after formal purchase, the free trial version only provides pre-compiled binary libs and tools, and only supports building for the iOS platform target.

Trial Rules

  • Targeted towards enterprise users only.
  • The trial is free and has no trial period limit. However, if technical support is needed during the trial, additional charges will apply.
  • The trial version will randomly crash after 30 minutes of app launch. Please do not use it for formal releases.

Supported Versions

Only specific Unity versions are supported.

  • 2019.4.40f1 To be supported
  • 2020.3.48f1 To be supported
  • 2021.3.31f1
  • 2022.3.11f1

Unsupported Features

tip

The following features are supported in the paid trial or the formal paid version.

  • dots
  • Incremental GC
  • Development build options like Profiler, Script Debugging, etc.

The lack of support for Incremental GC and various compilation options is due to the need for separate libil2cpp.a files for each compilation parameter, greatly increasing maintenance costs.

Installation

The relevant libil2cpp code has been directly included in the package. Open the HybridCLR/Installer menu and click the Install button to complete the installation.

Configuration

Compared to the community version or the formal commercial version, the following settings need to be modified:

  • Disable incremental GC
  • Disable development options
  • The value of the encryption parameter vmSeed cannot be modified and must remain as 0. Other encryption parameters can be modified.

Differences from the Standard Commercial Version

In the trial version, the source code of some modules is removed and replaced with compiled binary programs. The following are the related modules:

  • DllEncryptor DLL encryption module
  • DhaoGenerator dhao generation module

Usage

Apart from minor adjustments in installation and configuration as described above, the rest remains entirely according to the Quick Start documentation.

- + \ No newline at end of file diff --git a/en/docs/business/ultimate/injectrules.html b/en/docs/business/ultimate/injectrules.html index fd8d5f95c..242ac9bc7 100644 --- a/en/docs/business/ultimate/injectrules.html +++ b/en/docs/business/ultimate/injectrules.html @@ -9,7 +9,7 @@ - + @@ -23,7 +23,7 @@ achieve this purpose.

tip

Even if a function is marked not to be injected, modifying this function in subsequent hot updates will not cause running errors or execute old logic. It will only cause dirty function infection problems, that is, all functions that directly call this function will be infected. Mark function as dirty.

HybridCLR Settings settings

Fill in the injection policy file path in the InjectRuleFiles field in HybridCLR Settings. The relative path of the file is the project root directory (e.g Assets/InjectRules/DefaultInjectRules.xml).

Allows 0-N configuration policy files to be provided. If there is no configuration policy file, function injection for all DHE assemblies is performed by default.

Configuration rules

The configuration syntax is very similar to link.xml. For a function, if multiple rules match, the last rule takes precedence.

A typical injection policy file is as follows:

<rules>
<assembly fullname="*">
<type fullname="*">
<property name="*"/> All properties are not injected
</type>
</assembly>
<assembly fullname="AssemblyA">
<type fullname="Type1">
<method name="*"/>
</type>
<type fullname="Type2">
<property name="Age*"/>
<property name="Age_3" mode="proxy"/>
<property name="Count" mode="none"/>
<property signature="System.String Name"/>
<method name="Run*"/>
<method name="Run_3" mode="proxy"/>
<method name="Foo"/>
<method signature="System.Int32 Sum(System.Int32,System.Int32)"/>
<method signature="System.Int32 Sum2(System.Int32,System.Int32)"/>
<event name="OnEvent*"/>
<event name="OnEvent_3" mode="proxy"/>
<event name="OnHello"/>
</type>
</assembly>
<assembly fullname="AssemblyB">
<type fullname="*">
<method name="*"/>
</type>
</assembly>
</rules>

rules

The top-level tag is rules, and rules can contain 0-n assembly rules.

NameTypeNullableDescription
assemblychild elementnoassembly rules

assembly

Configure rules for a certain assembly or type of assembly.

NameTypeNullableDescription
fullnamepropertynoThe assembly name without the '.dll' suffix. Support wildcard characters, such as '', 'Unity.', 'MyCustom*' and so on
typechild elementsaretype rules. Can contain 0-N

type

Configure injection rules for a certain type or type. Note that injection rules for generic primitive types are supported, but injection rules for configuring generic instance classes are not supported. For example, you can configure the injection rules of List`1, But the injection rules of List<int> cannot be configured.

  • If a function satisfies multiple rules, the last rule will prevail
  • Property is regarded as two functions: get_{name} and set_{name}, so int Count can also be matched by &lt;method name="get_Count"&gt;
NameTypeNullableDescription
fullnamePropertyNoThe full name of the type. Support wildcard characters, such as '', 'Unity.', 'MyCustom.*.TestType' and so on
methodchild elementisfunction rule
propertychild elementisproperty rule
eventchild elementisevent rule

method

Configure function injection rules.

NameTypeNullableDescription
namepropertynofunction name. Support wildcard characters, such as '', 'Run' and so on
signaturepropertyisa function signature. Supports wildcard characters, such as '', 'System.Int32 (System.Int32)'
modechild elementsareinjection types, valid values ​​are 'none' or 'proxy'. If not filled in or empty, take 'none'

property

Configuration property injection rulesbut. Note that the attribute is treated as two functions: get_{name} and set_{name}, so the getter function get_Count of int Count can also be matched by &lt;method name="get_Count"&gt;.

NameTypeNullableDescription
namepropertynofunction name. Support wildcard characters, such as '', 'Run' and so on
signaturepropertyisa function signature. Supports wildcard characters, such as '*', 'System.Int32 *'
modechild elementsareinjection types, valid values are 'none' or 'proxy'. If not filled in or empty, take 'none'

event

Configure event injection rules. Note that the event is treated as two functions: add_{name} and remove_{name}, so the add function add_OnDone of Action OnDone can also be matched by &lt;method name="add_OnDone"&gt;.

NameTypeNullableDescription
namepropertynofunction name. Support wildcard characters, such as '', 'Run' and so on
signaturepropertyisa function signature. Supports wildcard characters, such as '*', 'Action<System.Int32> On*'
modechild elementsareinjection types, valid values are 'none' or 'proxy'. If not filled in or empty, take 'none'

The injection strategy file needs to be consistent with the App, that is, each independently released App must back up the injection strategy file used at that time. Just like every time you generate a dhao file, you need to use the AOT dll generated when building the App. When generating the dhao file, you must use the injection policy file backed up when building the App. If an error injection strategy file is used, an incorrect dhao file will be generated, which may cause the wrong logic to run or even crash!

- + \ No newline at end of file diff --git a/en/docs/business/ultimate/intro.html b/en/docs/business/ultimate/intro.html index d84044013..5077922b3 100644 --- a/en/docs/business/ultimate/intro.html +++ b/en/docs/business/ultimate/intro.html @@ -9,13 +9,13 @@ - +

Introduction

The Ultimate Edition is primarily aimed at projects with strict performance requirements. Compared to the community edition, the Ultimate Edition has a significant performance improvement, almost reaching native performance levels (100% unchanged), while also offering good optimizations in terms of security and memory.

Supported Versions

Supports all Unity 2019-2022 LTS versions.

Advantages

  • Includes all features of the Professional Edition.
  • Incorporates the innovative DHE technology, where unchanged code performs identically to native code. Compared to the purely interpreted approach of the community edition, DHE offers an astonishing 3-30 times or higher performance boost, achieving nearly native performance levels overall.
  • Includes two years of technical support.

Predictable Effects

The effects of DHE technology are predictable. It's possible to know the final effect without actually running DHE.

Before hot updates, the performance is identical to native code. After hot updates, at the function level, the changed functions are executed in an interpreted manner. When generating the dhao file, all changed functions are also printed out. Therefore, developers can estimate in advance which functions' performance has been affected by the hot update behavior.

Differences from Solutions like InjectFix

Overall, the Ultimate Edition meets the needs of projects that require arbitrary logic hot updates while maintaining native performance, while InjectFix is primarily used for fixing function bugs, with a world of difference between the two. The specific differences are as follows:

SolutionUltimate EditionInjectFix
Code Change LimitationsCan be changed arbitrarilyOnly supports fixing functions and very small-scale code additions (due to many unsupported features and bugs)
Supported C# FeaturesInherits the characteristics of the community version, with almost no code limitationsMany features missing, significant limitations on type inheritance, delegates, generics, reflection, multithreading, async, or simply cannot work properly (similar defects to ILRuntime)
PerformanceUnchanged functions are the same as AOT, changed functions are interpreted, but interpretation performance is extremely efficient (average performance is more than ten times that of InjectFix)Even unchanged functions suffer performance degradation due to instrumentation. Additionally, interpreter performance is extremely inefficient
GCCompletely identical to nativeSignificant additional GC
WorkflowAutomatic tagging, one-click completion, no manual involvement requiredManual tagging, time-consuming, error-prone, and tedious, disaster in multi-version maintenance
Stability LevelExtensively verified in numerous projects, extremely high stabilityMany unsupported features and bugs
- + \ No newline at end of file diff --git a/en/docs/business/ultimate/manual.html b/en/docs/business/ultimate/manual.html index 2c8041029..97cf27d32 100644 --- a/en/docs/business/ultimate/manual.html +++ b/en/docs/business/ultimate/manual.html @@ -9,7 +9,7 @@ - + @@ -24,7 +24,7 @@ In severe cases, it can even cause a crash!

caution

Unless there are special circumstances and you are an experienced expert, do not mark manually. Because the compiler often generates some hidden classes or fields, these class names are not stable. C# code that looks the same on the surface may not actually generate the same code. Forcibly marking it as [Unchanged] will lead to incorrect running logic or even crash.

Used in code

At runtime, after hot update is completed, for each dhe assembly, call RuntimeApi::LoadDifferentialHybridAssembly or RuntimeApi::LoadDifferentialHybridAssemblyUnchecked to load the hot update assembly.

Precautions:

  • Differential hybrid execution assembly should be loaded according to assembly dependency order.
  • If an assembly has not changed, the dhao field can be passed as null, but in this case the AOT dll generated during packaging must be used, and the hot update dll generated through the HybridCLR/CompileDll/xxx command cannot be used.
  • The DHE assembly itself already contains metadata. Even if full generic sharing is not enabled, Do not add metadata to the DHE assembly. If you add it, it will fail. Other non-DHE AOT assemblies can be added as usual. metadata.

RuntimeApi::LoadDifferentialHybridAssembly is a checked workflow. It needs to pass in the md5 of the original dhe dll and the md5 of the current dhe dll, and compare them with the md5 saved in the dhao file. For the originalDllMd5 and currentDllMd5 parameters, the complexity of the workflow is greatly increased.

tip

It is recommended that beginners use the checked workflow in demo projects. After becoming familiar with the workflow, they can use the unchecked workflow in formal projects.

use unchecked RuntimeApi::LoadDifferentialHybridAssembly

Loading DHE assembly

public static string CreateMD5Hash(byte[] bytes)
{
return BitConverter.ToString(new MD5CryptoServiceProvider().ComputeHash(bytes)).Replace("-", "").ToUpperInvariant();
}

///
/// originalDllMd5 is obtained from the `{manifest}` manifest file generated during the build. This manifest file is generated by the developer himself
///
void LoadDifferentialHybridAssembly(string assemblyName, string originalDllMd5)
{
// currentDllMd5 can be generated at runtime or offline and in advance when a hot update package is released.
string currentDllMd5 = CreateMD5Hash(dllBytes);
LoadImageErrorCode err = RuntimeApi.LoadDifferentialHybridAssembly(dllBytes, dhaoBytes, originalDllMd5, currentDllMd5);
if (err == LoadImageErrorCode.OK)
{
Debug.Log($"LoadDifferentialHybridAssembly {assName} OK");
}
else
{
Debug.LogError($"LoadDifferentialHybridAssembly {assName} failed, err={err}");
}
}

use checked RuntimeApi::LoadDifferentialHybridAssemblyUnchecked

danger

When using a unchecked workflow, be sure to ensure the consistency of the original dhe dll, current dhe dll, and dhao files. If they are inconsistent, an operation error may occur, or the process may crash.

Loading DHE assembly

///
/// originalDllMd5 is obtained from the `{manifest}` manifest file generated during the build. This manifest file is generated by the developer himself
///
void LoadDifferentialHybridAssembly(string assemblyName)
{
LoadImageErrorCode err = RuntimeApi.LoadDifferentialHybridAssemblyUnchecked(dllBytes, dhaoBytes);
if (err == LoadImageErrorCode.OK)
{
Debug.Log($"LoadDifferentialHybridAssembly {assName} OK");
}
else
{
Debug.LogError($"LoadDifferentialHybridAssembly {assName} failed, err={err}");
}
}

Configure function injection strategy

tip

In the vast majority of projects, the default full injection strategy has minimal impact on performance. As long as there are no performance issues, you do not need and should not care about this configuration.

In order to avoid indirect dirty function contagion (that is, function A calls function B, if B changes, A will also be marked as changed), a small piece of check jump code is injected into the header of all functions by default. Although it is Very simple if (method->isInterpterImpl) statement, but for short functions like int Age {get; set;}, this insertion may produce an observable performance degradation (even up to 10%).

The function injection strategy is used to optimize this situation. For short functions that do not change, configuring not to inject can improve performance. See the InjectRules document for details.

Fill in the injection policy file path in the InjectRuleFiles field in HybridCLR Settings. The relative path of the file is the project root directory (such as Assets/InjectRules/DefaultInjectRules.xml).

Pack

The files related to construction in DHE technology are dhe dll files and corresponding dhao files.

Non-encrypted workflow

Build the main package

  • Use the trimmed AOT dll generated after building as the dhe dll of the first package (without any changes)
  • Use HybridCLR.Editor.DHE.BuildUtils.GenerateUnchangedDHAODatas to generate the dhao file of the first package

If using a checked workflow, do the following:

  • Generate a {manifest} manifest file for dhe dll that contains at least assemblyName, md5 (it is up to the developer to decide how to implement it), because RuntimeApi.LoadDifferentialHybridAssembly needs to provide the original md5 of dhe dll
  • Add dhe dll, dhao files and {manifest} files to the hot update resource management system

If you use a workflow without validation, do the following:

  • Add dhe dll and dhao files to the hot update resource management system

If you want to carry the dhe dll and dhao files of the first package with the package, please export the project first, then follow the above steps to generate the dhe dll and dhao files, and then add them to the exported project.

Hot update

  • Use HybridCLR/CompileDll/ActivedBuildTarget to generate hot update dll.
  • Use HybridCLR.Editor.DHE.BuildUtils.GenerateDHAODatas to generate the latest hot update dll dhao file
  • Add the latest hot update dll and dhao files to the hot update resource management system
caution

If you package using the development build option, be sure to use HybridCLR/CompileDll/ActivedBuildTarget_Development to compile the hot update dll in Development mode. Otherwise, the comparison result is that almost all functions are judged to have changed.

Encryption workflow

Build the main package

  • Use the cropped AOT dll generated after the build as the original dhe dll
  • Use HybridCLR.Editor.DHE.BuildUtils.EncryptDllAndGenerateUnchangedDHAODatas to generate the dhao file of the first package and the encrypted dhe dll file

If using a checked workflow, do the following:

  • Generate a {manifest} manifest file for dhe dll that contains at least assemblyName, md5 (it is up to the developer to decide how to implement it), because RuntimeApi.LoadDifferentialHybridAssembly needs to provide the original md5 of dhe dll
  • Add the encrypted dhe dll, dhao files and {manifest} files to the hot update resource management system

If you use a workflow without validation, do the following:

  • Add the encrypted dhe dll and dhao files to the hot update resource management system

Hot update

  • Use HybridCLR/CompileDll/ActivedBuildTarget to generate hot update dll.
  • Use HybridCLR.Editor.DHE.BuildUtils.EncryptDllAndGenerateDHAODatas to generate the latest dhe dll encrypted file and the corresponding dhao file
  • Add the encrypted dhe dll and dhao files to the hot update resource management system

Feature not supported

  • Does not support turning on the script debugging build option

Precautions

There are huge differences in the results of calculating dhao caused by external dll

If there is an external dll marked as a DHE assembly, the external dll will be trimmed when packaged, and when calculating the dhao file, the original external dll will be taken, resulting in a huge difference, which is not expected. There are several solutions:

  1. In link.xml <assembly fullname="YourExternDll" preserve="all"/> completely retains the external dll
  2. Instead of using the latest hot update dll to calculate the difference, use the aot dll generated when the latest code is repackaged to calculate the difference.
- + \ No newline at end of file diff --git a/en/docs/business/ultimate/quickstartchecked.html b/en/docs/business/ultimate/quickstartchecked.html index a3287d670..aafe860a7 100644 --- a/en/docs/business/ultimate/quickstartchecked.html +++ b/en/docs/business/ultimate/quickstartchecked.html @@ -9,7 +9,7 @@ - + @@ -20,7 +20,7 @@ However, developers are required to ensure the consistency of aot dll, hot update dll, and dhao files. It is recommended that beginners use the workflow with verification in demo projects. After becoming familiar with the workflow, they can use the workflow without verification in formal projects.

Experience goals

  • Create hot update assembly
  • Load the hot update assembly, execute the hot update code in it, and print Hello, HybridCLR
  • Modify the hot update code to print Hello, World

Prepare environment

Install Unity

  • Install any version 2019.4.x, 2020.3.x, 2021.3.x, 2022.3.x. Some versions have special installation requirements, see Install hybridclr
  • Depending on the operating system you are using, when selecting modules during the installation process, you must select Windows Build Support(IL2CPP) or Mac Build Support(IL2CPP)

select il2cpp modules

  • Windows
    • visual studio 2019 or higher version needs to be installed under Win. The installation must include at least the Game development using Unity and Game development using C++ components.
    • install git -Mac
    • Requires MacOS version >= 12, xcode version >= 13, for example xcode 13.4.1, macos 12.4
    • install git

Initialize Unity hot update project

The process of constructing a hot update project from scratch is lengthy. The code involved in the following steps can refer to the dhe_demo project, whose warehouse address is github.

Create project

Create an empty Unity project.

Create ConsoleToScreen.cs script

This script has no direct effect on demonstrating hot updates. It can print logs to the screen to facilitate locating errors.

Create the Assets/ConsoleToScreen.cs script class with the following code:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ConsoleToScreen : MonoBehaviour
{
const int maxLines = 50;
const int maxLineLength = 120;
private string _logStr = "";

private readonly List<string> _lines = new List<string>();

public int fontSize = 15;

void OnEnable() { Application.logMessageReceived += Log; }
void OnDisable() { Application.logMessageReceived -= Log; }

public void Log(string logString, string stackTrace, LogType type)
{
foreach (var line in logString.Split('\n'))
{
if (line.Length <= maxLineLength)
{
_lines.Add(line);
continue;
}
var lineCount = line.Length / maxLineLength + 1;
for (int i = 0; i < lineCount; i++)
{
if ((i + 1) * maxLineLength <= line.Length)
{
_lines.Add(line.Substring(i * maxLineLength, maxLineLength));
}
else
{
_lines.Add(line.Substring(i * maxLineLength, line.Length - i * maxLineLength));
}
}
}
if (_lines.Count > maxLines)
{
_lines.RemoveRange(0, _lines.Count - maxLines);
}
_logStr = string.Join("\n", _lines);
}

voidOnGUI()
{
GUI.matrix = Matrix4x4.TRS(Vector3.zero, Quaternion.identity,
new Vector3(Screen.width / 1200.0f, Screen.height / 800.0f, 1.0f));
GUI.Label(new Rect(10, 10, 800, 370), _logStr, new GUIStyle() { fontSize = Math.Max(10, fontSize) });
}
}


Create the main scene

  • Create default initial scene main.scene
  • Create an empty GameObject in the scene and hang ConsoleToScreen on it
  • Add the main scene to the packaged scene list in Build Settings

Create HotUpdate hot update module

  • Create Assets/HotUpdate directory
  • Right-click Create/Assembly Definition in the directory and create an assembly module named HotUpdate

Install and configure HybridCLR

Install

  • Unzip hybridclr_unity.zip, place it in the project Packages directory, and rename it com.code-philosophy.hybridclr
  • Unzip the corresponding il2cpp_plus-{version}.zip according to your unity version
  • Unzip hybridclr.zip
  • Place the hybridclr directory after decompression of hybridclr.zip into the libil2cpp directory after decompression of il2cpp-{version}.zip
  • Open HybridCLR/Installer, enable the Copy libil2cpp from local option, select the libil2cpp directory you just decompressed, and install it.
  • Depending on your Unity version:
    • If version >= 2020, replace the ModifiedDlls\{verions}\Unity.IL2CPP.dll file with {proj}\HybridCLRData\LocalIl2CppData-WindowsEditor\il2cpp\build\deploy\netcoreapp3.1\Unity.IL2CPP.dll (Unity 2020) or {proj}\HybridCLRData\LocalIl2CppData-WindowsEditor\il2cpp\build\deploy\Unity.IL2CPP.dll (Unity 2021+). If there is no file corresponding to your version, contact us to make one.
    • If the version is 2019, no operation is required because it has been automatically copied during the Install process

installer

Configure HybridCLR

  • Open menu HybridCLR/Settings
  • Add HotUpdate assembly to differentialHybridAssemblies list

settings

Configure PlayerSettings

  • Scripting Backend switched to IL2CPP
  • Api Compatability Level switched to .Net 4.x (Unity 2019-2020) or .Net Framework (Unity 2021+)

player settings

Create Editor script

Create the BuildTools.cs file in the Assets/Editor directory with the following content:


using HybridCLR.Editor;
using HybridCLR.Editor.DHE;
using HybridCLR.Runtime;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;

public static class BuildTools
{
public const string BackupAOTDllDir = "HybridCLRData/BackupAOT";

public const string EncryptedDllDir = "HybridCLRData/EncryptedDll";

public const string DhaoDir = "HybridCLRData/Dhao";

public const string ManifestFile = "manifest.txt";


/// <summary>
/// Back up the cropped AOT dll generated when building the main package
/// </summary>
[MenuItem("BuildTools/BackupAOTDll")]
public static void BackupAOTDllFromAssemblyPostStrippedDir()
{
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
var backupDir = $"{BackupAOTDllDir}/{target}";
System.IO.Directory.CreateDirectory(backupDir);
var dlls = System.IO.Directory.GetFiles(SettingsUtil.GetAssembliesPostIl2CppStripDir(target));
foreach (var dll in dlls)
{
var fileName = System.IO.Path.GetFileName(dll);
string dstFile = $"{BackupAOTDllDir}/{target}/{fileName}";
System.IO.File.Copy(dll, dstFile, true);
Debug.Log($"BackupAOTDllFromAssemblyPostStrippedDir: {dll} -> {dstFile}");
}
}

/// <summary>
/// Create a dhe manifest file, the format is one 'dll name, md5 of the original dll' per line
/// </summary>
/// <param name="outputDir"></param>
[MenuItem("BuildTools/CreateManifestAtBackupDir")]
public static void CreateManifest()
{
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
string backupDir = $"{BackupAOTDllDir}/{target}";
CreateManifest(backupDir);
}

public static void CreateManifest(string outputDir)
{
Directory.CreateDirectory(outputDir);
var lines = new List<string>();
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
string backupDir = $"{BackupAOTDllDir}/{target}";
foreach (string dheDll in SettingsUtil.DifferentialHybridAssemblyNames)
{
string originalDll = $"{backupDir}/{dheDll}.dll";
string originalDllMd5 = AssemblyOptionDataGenerator.CreateMD5Hash(File.ReadAllBytes(originalDll));
lines.Add($"{dheDll},{originalDllMd5}");
}
string manifestFile = $"{outputDir}/{ManifestFile}";
File.WriteAllBytes(manifestFile, System.Text.Encoding.UTF8.GetBytes(string.Join("\n", lines)));
Debug.Log($"CreateManifest: {manifestFile}");
}

/// <summary>
/// Generate the dhao data corresponding to the first package without any code changes
/// </summary>
[MenuItem("BuildTools/GenerateUnchangedDHAODatas")]
public static void GenerateUnchangedDHAODatas()
{
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
string backupDir = $"{BackupAOTDllDir}/{target}";
string dhaoDir = $"{DhaoDir}/{target}";
BuildUtils.GenerateUnchangedDHAODatas(SettingsUtil.DifferentialHybridAssemblyNames, backupDir, dhaoDir);
}

/// <summary>
/// Generate dhao data of hot update package
/// </summary>
[MenuItem("BuildTools/GenerateDHAODatas")]
public static void GenerateDHAODatas()
{
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
string backupDir = $"{BackupAOTDllDir}/{target}";
string dhaoDir = $"{DhaoDir}/{target}";
string currentDllDir = SettingsUtil.GetHotUpdateDllsOutputDirByTarget(target);
BuildUtils.GenerateDHAODatas(SettingsUtil.DifferentialHybridAssemblyNames, backupDir, currentDllDir, null, null, dhaoDir);
}

/// <summary>
/// Generate the encrypted dll of the first package and the corresponding dhao data without any code changes
/// </summary>
[MenuItem("BuildTools/GenerateUnchangedEncryptedDllAndDhaoDatas")]
public static void GenerateUnchangedEncryptedDllAndDhaoDatas()
{
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
string backupDir = $"{BackupAOTDllDir}/{target}";
string dhaoDir = $"{DhaoDir}/{target}";
string encryptedDllDir = $"{EncryptedDllDir}/{target}";
BuildUtils.EncryptDllAndGenerateUnchangedDHAODatas(SettingsUtil.DifferentialHybridAssemblyNames, backupDir, encryptedDllDir, dhaoDir);
}


/// <summary>
/// Generate the encrypted dll and dhao data of the hot update package
/// </summary>
[MenuItem("BuildTools/GenerateEncryptedDllAndDhaoDatas")]
public static void GenerateEncryptedDllAndDhaoDatas()
{
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
string backupDir = $"{BackupAOTDllDir}/{target}";
string dhaoDir = $"{DhaoDir}/{target}";
string currentDllDir = SettingsUtil.GetHotUpdateDllsOutputDirByTarget(target);
string encryptedDllDir = $"{EncryptedDllDir}/{target}";
BuildUtils.EncryptDllAndGenerateDHAODatas(SettingsUtil.DifferentialHybridAssemblyNames, backupDir, currentDllDir, null, null, encryptedDllDir, dhaoDir);
}

/// <summary>
/// Copy the unmodified first package dll and dhao files to StreamingAssets
/// </summary>
[MenuItem("BuildTools/CopyUnchangedDllAndDhaoFileAndManifestToStreamingAssets")]
public static void CopyUnchangedDllAndDhaoFileToStreamingAssets()
{
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
string streamingAssetsDir = Application.streamingAssetsPath;
Directory.CreateDirectory(streamingAssetsDir);

string manifestFile = $"{BackupAOTDllDir}/{target}/{ManifestFile}";
string dstManifestFile = $"{streamingAssetsDir}/{ManifestFile}";
System.IO.File.Copy(manifestFile, dstManifestFile, true);
Debug.Log($"CopyUnchangedDllAndDhaoFileToStreamingAssets: {manifestFile} -> {dstManifestFile}");

string dllDir = $"{BackupAOTDllDir}/{target}";
string dhaoDir = $"{DhaoDir}/{target}";
foreach (var dll in SettingsUtil.DifferentialHybridAssemblyNames)
{
string srcFile = $"{dllDir}/{dll}.dll";
string dstFile = $"{streamingAssetsDir}/{dll}.dll.bytes";
System.IO.File.Copy(srcFile, dstFile, true);
Debug.Log($"CopyUnchangedDllAndDhaoFileToStreamingAssets: {srcFile} -> {dstFile}");
string dhaoFile = $"{dhaoDir}/{dll}.dhao.bytes";
dstFile = $"{streamingAssetsDir}/{dll}.dhao.bytes";
System.IO.File.Copy(dhaoFile, dstFile, true);
Debug.Log($"CopyUnchangedDllAndDhaoFileToStreamingAssets: {dhaoFile} -> {dstFile}");
}
}

/// <summary>
/// Copy hot update dll and dhao files to StreamingAssets
/// </summary>
[MenuItem("BuildTools/CopyDllAndDhaoFileToStreamingAssets")]
public static void CopyDllAndDhaoFileToStreamingAssets()
{
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
string streamingAssetsDir = Application.streamingAssetsPath;
Directory.CreateDirectory(streamingAssetsDir);

string dllDir = SettingsUtil.GetHotUpdateDllsOutputDirByTarget(target);
string dhaoDir = $"{DhaoDir}/{target}";
foreach (var dll in SettingsUtil.DifferentialHybridAssemblyNames)
{
string srcFile = $"{dllDir}/{dll}.dll";
string dstFile = $"{streamingAssetsDir}/{dll}.dll.bytes";
System.IO.File.Copy(srcFile, dstFile, true);
Debug.Log($"CopyUnchangedDllAndDhaoFileToStreamingAssets: {srcFile} -> {dstFile}");
string dhaoFile = $"{dhaoDir}/{dll}.dhao.bytes";
dstFile = $"{streamingAssetsDir}/{dll}.dhao.bytes";
System.IO.File.Copy(dhaoFile, dstFile, true);
Debug.Log($"CopyUnchangedDllAndDhaoFileToStreamingAssets: {dhaoFile} -> {dstFile}");
}
}
}


Create hot update script

Create the Assets/HotUpdate/Hello.cs file with the following code content

using System.Collections;
using UnityEngine;

public class Hello
{
public static void Run()
{
Debug.Log("Hello, HybridCLR");
}
}

Load hot update assembly

To simplify the demonstration, we do not download HotUpdate.dll through the http server, but directly place HotUpdate.dll in the StreamingAssets directory.

Create the Assets/LoadDll.cs script, then create a GameObject object in the main scene and mount the LoadDll script.

using HybridCLR;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;

public class LoadDll : MonoBehaviour
{

void Start()
{
// In the Editor environment, HotUpdate.dll.bytes has been automatically loaded and does not need to be loaded. Repeated loading will cause problems.
#if !UNITY_EDITOR
var manifests = LoadManifest($"{Application.streamingAssetsPath}/manifest.txt");
Assembly hotUpdateAss = LoadDifferentialHybridAssembly(manifests["HotUpdate"], "HotUpdate");
#else
// No need to load under Editor, directly find the HotUpdate assembly
Assembly hotUpdateAss = System.AppDomain.CurrentDomain.GetAssemblies().First(a => a.GetName().Name == "HotUpdate");
#endif
Type helloType = hotUpdateAss.GetType("Hello");
MethodInfo runMethod = helloType.GetMethod("Run");
runMethod.Invoke(null, null);
}

class Manifest
{
public string AssemblyName { get; set; }

public string OriginalDllMd5 { get; set; }
}

private Dictionary<string, Manifest> LoadManifest(string manifestFile)
{
var manifest = new Dictionary<string, Manifest>();
var lines = File.ReadAllLines(manifestFile, Encoding.UTF8);
foreach (var line in lines)
{
string[] args = line.Split(",");
if (args.Length != 2)
{
Debug.LogError($"manifest file format error, line={line}");
return null;
}
manifest.Add(args[0], new Manifest()
{
AssemblyName = args[0],
OriginalDllMd5 = args[1],
});
}
return manifest;
}


public static string CreateMD5Hash(byte[] bytes)
{
return BitConverter.ToString(new MD5CryptoServiceProvider().ComputeHash(bytes)).Replace("-", "").ToUpperInvariant();
}

private Assembly LoadDifferentialHybridAssembly(Manifest manifest, string assName)
{
byte[] dllBytes = File.ReadAllBytes($"{Application.streamingAssetsPath}/{assName}.dll.bytes");
byte[] dhaoBytes = File.ReadAllBytes($"{Application.streamingAssetsPath}/{assName}.dhao.bytes");
string currentDllMd5 = CreateMD5Hash(dllBytes);
LoadImageErrorCode err = RuntimeApi.LoadDifferentialHybridAssembly(dllBytes, dhaoBytes, manifest.OriginalDllMd5, currentDllMd5);
if (err == LoadImageErrorCode.OK)
{
Debug.Log($"LoadDifferentialHybridAssembly {assName} OK");
return System.AppDomain.CurrentDomain.GetAssemblies().First(a => a.GetName().Name == assName);
}else
{
Debug.LogError($"LoadDifferentialHybridAssembly {assName} failed, err={err}");
return null;
}
}
}

At this point, the creation of the entire hot update project is completed! ! !

Trial run in Editor

Run the main scene and 'Hello,HybridCLR' will be displayed on the screen, indicating that the code is working properly.

Package and run

  • Run menu HybridCLR/Generate/All to perform necessary generation operations. This step cannot be missed!!!
  • Open the Build Settings dialog box, click Build, select the output directory {build}, and execute the build
  • Run BuildTools/BackupAOTDll to back up the trimmed dhe dll. In practice, these dlls should be added to version management for later generation of dhao files. These files will not be modified again.
  • Run BuildTools/CreateManifestAtBackupDir to generate the manifest file of the original dhe dll. In practice, this manifest file should be added to version management and will not be modified again.
  • Run BuildTools/GenerateUnchangedDHAODatas to generate the dhao file of the first package
  • Run BuildTools/CopyUnchangedDllAndDhaoFileAndManifestToStreamingAssets to copy the first package dhe assembly, dhao file, and manifest file to StreamingAssets
  • Copy the Assets/StreamingAssets directory to {build}\dhe_demo2_Data\StreamingAssets
  • Run {build}/Xxx.exe, and the screen will display Hello,HybridCLR, indicating that the hot update code has been successfully executed!

Test hot update

  • Modify the Debug.Log("Hello, HybridCLR"); code in the Run function of Assets/HotUpdate/Hello.cs to Debug.Log("Hello, World");.
  • Run HybridCLR/CompileDll/ActiveBulidTarget to generate hot update dll
  • Run BuildTools/GenerateDHAODatas to generate dhao files
  • Run BuildTools/CopyDllAndDhaoFileToStreamingAssets to copy the hot update dll and dhao files to the StreamingAssets directory
  • Copy the Assets/StreamingAssets directory to {build}\dhe_demo2_Data\StreamingAssets
  • Rerun the program and you will find Hello, World displayed on the screen, indicating that the hot update code has taken effect!

This completes the hot update experience.

- + \ No newline at end of file diff --git a/en/docs/business/ultimate/quickstartunchecked.html b/en/docs/business/ultimate/quickstartunchecked.html index fd32b95bc..ce5019c79 100644 --- a/en/docs/business/ultimate/quickstartunchecked.html +++ b/en/docs/business/ultimate/quickstartunchecked.html @@ -9,7 +9,7 @@ - + @@ -19,7 +19,7 @@ This document describes the workflow without validation.

tip

In practice, the workflow without verification will be much simpler. There is no need to pass the originalDllMd5 and currentDllMd5 parameters, so the process of saving or calculating dll md5 in the workflow is omitted. However, developers are required to ensure the consistency of aot dll, hot update dll, and dhao files. It is recommended that beginners use the workflow with verification in demo projects. After becoming familiar with the workflow, they can use the workflow without verification in formal projects.

Experience goals

  • Create hot update assembly
  • Load the hot update assembly, execute the hot update code in it, and print Hello, HybridCLR
  • Modify the hot update code to print Hello, World

Prepare environment

Install Unity

  • Install any version 2019.4.x, 2020.3.x, 2021.3.x, 2022.3.x. Some versions have special installation requirements, see Install hybridclr
  • Depending on the operating system you are using, when selecting modules during the installation process, you must select Windows Build Support(IL2CPP) or Mac Build Support(IL2CPP)

select il2cpp modules

  • Windows
    • visual studio 2019 or higher version needs to be installed under Win. The installation must include at least the Game development using Unity and Game development using C++ components.
    • install git
  • Mac
    • Requires MacOS version >= 12, xcode version >= 13, for example xcode 13.4.1, macos 12.4
    • install git

Initialize Unity hot update project

The process of constructing a hot update project from scratch is lengthy. The code involved in the following steps can refer to the dhe_demo project, whose warehouse address is github.

Create project

Create an empty Unity project.

Create ConsoleToScreen.cs script

This script has no direct effect on demonstrating hot updates. It can print logs to the screen to facilitate locating errors.

Create the Assets/ConsoleToScreen.cs script class with the following code:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ConsoleToScreen : MonoBehaviour
{
const int maxLines = 50;
const int maxLineLength = 120;
private string _logStr = "";

private readonly List<string> _lines = new List<string>();

public int fontSize = 15;

void OnEnable() { Application.logMessageReceived += Log; }
void OnDisable() { Application.logMessageReceived -= Log; }

public void Log(string logString, string stackTrace, LogType type)
{
foreach (var line in logString.Split('\n'))
{
if (line.Length <= maxLineLength)
{
_lines.Add(line);
continue;
}
var lineCount = line.Length / maxLineLength + 1;
for (int i = 0; i < lineCount; i++)
{
if ((i + 1) * maxLineLength <= line.Length)
{
_lines.Add(line.Substring(i * maxLineLength, maxLineLength));
}
else
{
_lines.Add(line.Substring(i * maxLineLength, line.Length - i * maxLineLength));
}
}
}
if (_lines.Count > maxLines)
{
_lines.RemoveRange(0, _lines.Count - maxLines);
}
_logStr = string.Join("\n", _lines);
}

voidOnGUI()
{
GUI.matrix = Matrix4x4.TRS(Vector3.zero, Quaternion.identity,
new Vector3(Screen.width / 1200.0f, Screen.height / 800.0f, 1.0f));
GUI.Label(new Rect(10, 10, 800, 370), _logStr, new GUIStyle() { fontSize = Math.Max(10, fontSize) });
}
}


Create the main scene

  • Create default initial scene main.scene
  • Create an empty GameObject in the scene and hang ConsoleToScreen on it
  • Add the main scene to the packaged scene list in Build Settings

Create HotUpdate hot update module

  • Create Assets/HotUpdate directory
  • Right-click Create/Assembly Definition in the directory and create an assembly module named HotUpdate

Install and configure HybridCLR

Install

  • Unzip hybridclr_unity.zip, place it in the project Packages directory, and rename it com.code-philosophy.hybridclr
  • Unzip the corresponding il2cpp_plus-{version}.zip according to your unity version
  • Unzip hybridclr.zip
  • Place the hybridclr directory after decompression of hybridclr.zip into the libil2cpp directory after decompression of il2cpp-{version}.zip
  • Open HybridCLR/Installer, enable the Copy libil2cpp from local option, select the libil2cpp directory you just decompressed, and install it.
  • Depending on your Unity version:
    • If version >= 2020, replace the ModifiedDlls\{verions}\Unity.IL2CPP.dll file with {proj}\HybridCLRData\LocalIl2CppData-WindowsEditor\il2cpp\build\deploy\netcoreapp3.1\Unity.IL2CPP.dll (Unity 2020) or {proj}\HybridCLRData\LocalIl2CppData-WindowsEditor\il2cpp\build\deploy\Unity.IL2CPP.dll (Unity 2021+). If there is no file corresponding to your version, contact us to make one.
    • If the version is 2019, no operation is required because it has been automatically copied during the Install process

installer

Configure HybridCLR

  • Open menu HybridCLR/Settings
  • Add HotUpdate assembly to differentialHybridAssemblies list

settings

Configure PlayerSettings

  • Scripting Backend switched to IL2CPP
  • Api Compatability Level switched to .Net 4.x (Unity 2019-2020) or .Net Framework (Unity 2021+)

player settings

Create Editor script

Create the BuildTools.cs file in the Assets/Editor directory with the following content:


using HybridCLR.Editor;
using HybridCLR.Editor.DHE;
using HybridCLR.Runtime;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;

public static class BuildTools
{
public const string BackupAOTDllDir = "HybridCLRData/BackupAOT";

public const string DhaoDir = "HybridCLRData/Dhao";


/// <summary>
/// Back up the cropped AOT dll generated when building the main package
/// </summary>
[MenuItem("BuildTools/BackupAOTDll")]
public static void BackupAOTDllFromAssemblyPostStrippedDir()
{
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
var backupDir = $"{BackupAOTDllDir}/{target}";
System.IO.Directory.CreateDirectory(backupDir);
var dlls = System.IO.Directory.GetFiles(SettingsUtil.GetAssembliesPostIl2CppStripDir(target));
foreach (var dll in dlls)
{
var fileName = System.IO.Path.GetFileName(dll);
string dstFile = $"{BackupAOTDllDir}/{target}/{fileName}";
System.IO.File.Copy(dll, dstFile, true);
Debug.Log($"BackupAOTDllFromAssemblyPostStrippedDir: {dll} -> {dstFile}");
}
}

/// <summary>
/// Generate the dhao data corresponding to the first package without any code changes
/// </summary>
[MenuItem("BuildTools/GenerateUnchangedDHAODatas")]
public static void GenerateUnchangedDHAODatas()
{
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
string backupDir = $"{BackupAOTDllDir}/{target}";
string dhaoDir = $"{DhaoDir}/{target}";
BuildUtils.GenerateUnchangedDHAODatas(SettingsUtil.DifferentialHybridAssemblyNames, backupDir, dhaoDir);
}

/// <summary>
/// Generate dhao data of hot update package
/// </summary>
[MenuItem("BuildTools/GenerateDHAODatas")]
public static void GenerateDHAODatas()
{
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
string backupDir = $"{BackupAOTDllDir}/{target}";
string dhaoDir = $"{DhaoDir}/{target}";
string currentDllDir = SettingsUtil.GetHotUpdateDllsOutputDirByTarget(target);
BuildUtils.GenerateDHAODatas(SettingsUtil.DifferentialHybridAssemblyNames, backupDir, currentDllDir, null, null, dhaoDir);
}

/// <summary>
/// Copy the unmodified first package dll and dhao files to StreamingAssets
/// </summary>
[MenuItem("BuildTools/CopyUnchangedDllAndDhaoFileToStreamingAssets")]
public static void CopyUnchangedDllAndDhaoFileToStreamingAssets()
{
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
string streamingAssetsDir = Application.streamingAssetsPath;
Directory.CreateDirectory(streamingAssetsDir);

string dllDir = $"{BackupAOTDllDir}/{target}";
string dhaoDir = $"{DhaoDir}/{target}";
foreach (var dll in SettingsUtil.DifferentialHybridAssemblyNames)
{
string srcFile = $"{dllDir}/{dll}.dll";
string dstFile = $"{streamingAssetsDir}/{dll}.dll.bytes";
System.IO.File.Copy(srcFile, dstFile, true);
Debug.Log($"CopyUnchangedDllAndDhaoFileToStreamingAssets: {srcFile} -> {dstFile}");
string dhaoFile = $"{dhaoDir}/{dll}.dhao.bytes";
dstFile = $"{streamingAssetsDir}/{dll}.dhao.bytes";
System.IO.File.Copy(dhaoFile, dstFile, true);
Debug.Log($"CopyUnchangedDllAndDhaoFileToStreamingAssets: {dhaoFile} -> {dstFile}");
}
}

/// <summary>
/// Copy hot update dll and dhao files to StreamingAssets
/// </summary>
[MenuItem("BuildTools/CopyDllAndDhaoFileToStreamingAssets")]
public static void CopyDllAndDhaoFileToStreamingAssets()
{
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
string streamingAssetsDir = Application.streamingAssetsPath;
Directory.CreateDirectory(streamingAssetsDir);

string dllDir = SettingsUtil.GetHotUpdateDllsOutputDirByTarget(target);
string dhaoDir = $"{DhaoDir}/{target}";
foreach (var dll in SettingsUtil.DifferentialHybridAssemblyNames)
{
string srcFile = $"{dllDir}/{dll}.dll";
string dstFile = $"{streamingAssetsDir}/{dll}.dll.bytes";
System.IO.File.Copy(srcFile, dstFile, true);
Debug.Log($"CopyUnchangedDllAndDhaoFileToStreamingAssets: {srcFile} -> {dstFile}");
string dhaoFile = $"{dhaoDir}/{dll}.dhao.bytes";
dstFile = $"{streamingAssetsDir}/{dll}.dhao.bytes";
System.IO.File.Copy(dhaoFile, dstFile, true);
Debug.Log($"CopyUnchangedDllAndDhaoFileToStreamingAssets: {dhaoFile} -> {dstFile}");
}
}
}


Create hot update script

Create the Assets/HotUpdate/Hello.cs file with the following code content

using System.Collections;
using UnityEngine;

public class Hello
{
public static void Run()
{
Debug.Log("Hello, HybridCLR");
}
}

Load hot update assembly

To simplify the demonstration, we do not download HotUpdate.dll through the http server, but directly place HotUpdate.dll in the StreamingAssets directory.

Create the Assets/LoadDll.cs script, then create a GameObject object in the main scene and mount the LoadDll script.

using HybridCLR;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;

public class LoadDll : MonoBehaviour
{

void Start()
{
// In the Editor environment, HotUpdate.dll.bytes has been automatically loaded and does not need to be loaded. Repeated loading will cause problems.
#if !UNITY_EDITOR
Assembly hotUpdateAss = LoadDifferentialHybridAssembly("HotUpdate");
#else
// No need to load under Editor, directly find the HotUpdate assembly
Assembly hotUpdateAss = System.AppDomain.CurrentDomain.GetAssemblies().First(a => a.GetName().Name == "HotUpdate");
#endif
Type helloType = hotUpdateAss.GetType("Hello");
MethodInfo runMethod = helloType.GetMethod("Run");
runMethod.Invoke(null, null);
}

private Assembly LoadDifferentialHybridAssembly(string assName)
{
byte[] dllBytes = File.ReadAllBytes($"{Application.streamingAssetsPath}/{assName}.dll.bytes");
byte[] dhaoBytes = File.ReadAllBytes($"{Application.streamingAssetsPath}/{assName}.dhao.bytes");
LoadImageErrorCode err = RuntimeApi.LoadDifferentialHybridAssemblyUnchecked(dllBytes, dhaoBytes);
if (err == LoadImageErrorCode.OK)
{
Debug.Log($"LoadDifferentialHybridAssembly {assName} OK");
return System.AppDomain.CurrentDomain.GetAssemblies().First(a => a.GetName().Name == assName);
}
else
{
Debug.LogError($"LoadDifferentialHybridAssembly {assName} failed, err={err}");
return null;
}
}
}

At this point, the creation of the entire hot update project is completed! ! !

Trial run in Editor

Run the main scene and 'Hello,HybridCLR' will be displayed on the screen, indicating that the code is working properly.

Package and run

  • Run menu HybridCLR/Generate/All to perform necessary generation operations. This step cannot be missed!!!
  • Open the Build Settings dialog box, click Build, select the output directory {build}, and execute the build
  • Run BuildTools/BackupAOTDll to back up the trimmed dhe dll. In practice, these dlls should be added to version management for later generation of dhao files. These files will not be modified again.
  • Run BuildTools/GenerateUnchangedDHAODatas to generate the dhao file of the first package
  • Run BuildTools/CopyUnchangedDllAndDhaoFileToStreamingAssets to copy the first package dhe assembly and dhao files to StreamingAssets
  • Copy the Assets/StreamingAssets directory to {build}\dhe_demo2_Data\StreamingAssets
  • Run {build}/Xxx.exe, and the screen will display Hello,HybridCLR, indicating that the hot update code has been successfully executed!

Test hot update

  • Modify the Debug.Log("Hello, HybridCLR"); code in the Run function of Assets/HotUpdate/Hello.cs to Debug.Log("Hello, World");.
  • Run HybridCLR/CompileDll/ActiveBulidTarget to generate hot update dll
  • Run BuildTools/GenerateDHAODatas to generate dhao files
  • Run BuildTools/CopyDllAndDhaoFileToStreamingAssets to copy the hot update dll and dhao files to the StreamingAssets directory
  • Copy the Assets/StreamingAssets directory to {build}\dhe_demo2_Data\StreamingAssets
  • Rerun the program and you will find Hello, World displayed on the screen, indicating that the hot update code has taken effect!

This completes the hot update experience!

- + \ No newline at end of file diff --git a/en/docs/business/ultimate/workflow.html b/en/docs/business/ultimate/workflow.html index 540b6e7fb..9cb8458f4 100644 --- a/en/docs/business/ultimate/workflow.html +++ b/en/docs/business/ultimate/workflow.html @@ -9,13 +9,13 @@ - +

Simplify dhao workflow

Based on the same version of the original project, the original dll will have slight differences when publishing different platforms, resulting in the need to calculate a separate dhao file for each platform (even for the same platform, due to the instability of code compilation, the generated original dll may also have slight differences), which makes the maintenance of dhao complicated and error-prone. This problem is particularly serious when multiple new and old game packages exist at the same time.

The solution to this problem mainly relies on merging dhao files.

Merging dhao files

Based on the same or similar source code, the original dhe assemblies of game packages released on different platforms have only slight differences, and the dhao files generated during hot updates also have only slight differences.

You can consider merging the dhao files of multiple platforms corresponding to the same dhe assembly, which does not affect the correctness of the operation and has little impact on performance.

We provide the HybridCLR.Editor.DHE.BuildUtil::MergeDHAOFiles function to achieve the goal of merging dhao files.

Note that the workflow with verification cannot use the method of merging dhao files, because the workflow with verification will check the md5 code of the original dll, which is definitely not a match.

- + \ No newline at end of file diff --git a/en/docs/help.html b/en/docs/help.html index 67a42acea..fc4855ff3 100644 --- a/en/docs/help.html +++ b/en/docs/help.html @@ -9,13 +9,13 @@ - + - + \ No newline at end of file diff --git a/en/docs/help/commonerrors.html b/en/docs/help/commonerrors.html index 905165577..c9ce3f6db 100644 --- a/en/docs/help/commonerrors.html +++ b/en/docs/help/commonerrors.html @@ -9,7 +9,7 @@ - + @@ -25,7 +25,7 @@ Fix the crash bug when using Momery Profiler to create a snapshot. Just merge the changes into your current version.

Profiler's BeginSample and EndSample cannot take effect

Because functions such as BeginSample have [Condition] compilation annotations, when compiling the dll in Release mode, these codes will be automatically removed, causing the Profile to become invalid. The solution is to compile the hot update dll in Developemnt mode, the code is as follows. If you are using v3.0.2 and higher, the HybridCLR/CompileDll/ActivedBuildTarget_Development menu command is already included.

     var group = BuildPipeline.GetBuildTargetGroup(target);

ScriptCompilationSettings scriptCompilationSettings = new ScriptCompilationSettings();
scriptCompilationSettings.group = group;
scriptCompilationSettings.target = target;
if(developmentBuild)
{
// The core is this line, which causes the dll to be compiled in Debug mode and retains function calls such as Profiler.BeginSample.
scriptCompilationSettings.options |= ScriptCompilationOptions.DevelopmentBuild;
}
Directory.CreateDirectory(buildDir);
ScriptCompilationResult scriptCompilationResult = PlayerBuildInterface.CompilePlayerScripts(scriptCompilationSettings, buildDir);

There is no response when using the camera on iOS, but no error is reported.

This is caused by WebCamTexture.devices not being retained in AOT. WebCamTexture.devices needs to be referenced manually in AOT.

AVProMovieCapture plugin is not working properly

Due to the implementation of AVProMovieCapture itself, you need to initialize the plug-in first, and then perform operations such as loading HybridCLR.

The EncodeImageAndMetadataIndex function has an IL2CPP_ASSERT assertion failure error.

This is because the hot update dll of your project is too large. There are two solutions:

  • Modify the definition of kMetadataIndexBits in the hybridclr\metadata\MetadataUtil.h file and gradually increase it by 1 until this problem no longer occurs. It is strongly recommended that the value of kMetadataIndexBits not exceed 29, because the maximum number of hot update dlls that can be loaded at this time is 7, and this limit can easily be exceeded.
  • Split the hot update dll into multiple smaller dlls

Crash when calling ResourceCatalogData::GetGUIDFromPath during the execution of AutomaticWorldBootstrap::Initialize at startup

The entities version you are currently using cannot be directly packaged in Player Building. You must install com.unity.platforms and use its separate packaging method, [detailed documentation](https://docs.unity3d.com/Packages/ com.unity.entities@0.51/manual/ecs_building_projects.html).

Job.ScheduleBatch crashes

Hybridclr is incompatible with dots. The commercial version can solve this problem.

Function signature mismatch error occurs when WebGL is running

The WebGL platform uses the faster (smaller) build option by default when packaging, which will enable full generic sharing, and the community version must add metadata before it can work with the full generic sharing mechanism. Solution:

  1. First try to add metadata and add the assembly where the C# code at the top of the function stack is located.
  2. If you still have problems after adding metadata, switch IL2CPP Code Generation in Player Settings to Faster Runtime
  3. If you still have problems, upgrade to the latest hybridclr version
  4. If you still have problems, please contact our technical support

NotSupportNative2Managed bridge function missing exception occurs after using Unity.netcode.runtime

The reason is that NetworkManager.RpcReceiveHandler is internal in Unity.netcode.runtime.dll and is defined as follows

internal delegate void RpcReceiveHandler(NetworkBehaviour behaviour, FastBufferReader reader, __RpcParams parameters);

As a result, the generation tool did not generate a bridge function for it. But Unity is very tricky to generate RpcReceiveHandler for functions marked [ClientRpc] and [ServerRpc] when packaging. Handler function, and references the internal RpcReceiveHandler class! No error was reported. This leads to the problem of missing bridge functions.

The solution is for you to also define a delegate with the same signature in the AOT project.

     // Since __RpcParams is also internal, we have redefined the same type here.
public struct __RpcParams
#pragma warning restore IDE1006 // restore naming rule violation check
{
public ServerRpcParams Server;
public ClientRpcParams Client;
}

public delegate void MyRpcReceiveHandler(NetworkBehaviour behaviour, FastBufferReader reader, __RpcParams parameters);

- + \ No newline at end of file diff --git a/en/docs/help/faq.html b/en/docs/help/faq.html index aeb1e2120..724924050 100644 --- a/en/docs/help/faq.html +++ b/en/docs/help/faq.html @@ -9,13 +9,13 @@ - +

FAQ

What platforms does HybridCLR support?

All platforms supported by il2cpp support

How much will HybridCLR increase the package body

Taking the 2019 version as an example, the libil2cpp.a file of the Android project is exported in release mode, the original version is 12.69M, and the HybridCLR version is 13.97M, which means an increase of about 1.3M.

Why does the package size printed by HybridCLR increase a lot?

HybridCLR itself will only add a few inclusions (1-2M). The package body has increased a lot because you mistakenly reserved too many classes in link.xml, resulting in a sharp increase in the package body. Please refer to Unity's clipping rules for optimization.

Is HybridCLR embedded with mono?

no. HybridCLR supplements il2cpp with a complete register interpreter implemented completely independently.

Are there any restrictions on writing code in HybridCLR?

Few restrictions, see Unsupported Features

Does it support generic classes and generic functions?

Thorough and complete support without any limitations.

Support hot update MonoBehaviour?

fully support. Not only can it be added in the code, but it can also be directly linked to hot update resources. For details, see Using Hot Update MonoBehaviour

Does it support reflection?

Supported, without any restrictions.

How about multithreading support?

Full support. Support Thread, Task, volatile, ThreadStatic, async.

Does it support multiple assemblies?

Support, up to 255. But the dependent dll will not be loaded automatically. You need to manually load hot-updated dlls in the order of dependencies.

Does it support .net standard 2.0?

support. But please note that the main project is packaged with .net standard, while the hot update dll must be packaged with .net 4.x**. For detailed explanation, please refer to Common Errors Documentation

Does it support Unity's DOTS framework?

support. The burst code in the AOT part works fine, but the burst code in the hot update part is executed interpretively. This is obvious.

- + \ No newline at end of file diff --git a/en/docs/help/issue.html b/en/docs/help/issue.html index 6d9dfa097..af5ceade5 100644 --- a/en/docs/help/issue.html +++ b/en/docs/help/issue.html @@ -9,13 +9,13 @@ - +

Issue Report

Before reporting bugs, please confirm that the following steps have been completed:

  • Check out the Common Errors Documentation carefully, most beginner questions are there.
  • At this point, if it is still confirmed that it is a bug, please send it to the technical customer service (QQ1732047670) according to the Feedback Template below.

Bug Feedback

If it is determined to be a bug, please submit the issue according to the following bug feedback template (some larger files such as export projects do not need to be submitted), and then directly feedback the issue to the technical customer service, and attach materials on QQ (such as export projects, etc. ).

Issue Template

  • Unity Editor version. Such as 2020.3.33.
  • operating system. Such as Win10.
  • Wrong BuildTarget. Such as Android 64.
  • The version number of com.code-philosophy.hybridclr. Such as v2.3.1.
  • The version number of the hybridclr repository. Such as v2.2.0.
  • The version number of the il2cpp_plus repository. Such as v2021-2.2.0.
  • Screenshots and log files
  • Recurrence conditions
  • The c# code location of the error (if it can be located)
  • Free users must provide one of the materials that meet the following conditions, otherwise it will be rejected, because bug feedback that does not meet the standards will waste too much of our time, please understand.
    • a reproducible piece of code
    • A minimal reproducible Unity project that requires modifications based on hybridclr_trial. And reproduce immediately after packaging
    • Win 64 reproducible export Debug project (must be started to reproduce) and hot update dll (used to track instructions)
  • Commercial users can provide one of the following materials.
    • The minimal reproducible Unity project, try to modify it on the basis of hybridclr_trial.
    • Win 64 reproducible export Debug project (must be started to reproduce) and hot update dll (used to track instructions)
    • Android (64 or 32) reproducible export Debug project must be able to be successfully packaged directly, and there must be no errors such as missing key store! ! ! It must be built and then run to reproduce.
    • xcode export project. It must be run to reproduce.
- + \ No newline at end of file diff --git a/en/docs/intro.html b/en/docs/intro.html index 500c368b4..49cf33e71 100644 --- a/en/docs/intro.html +++ b/en/docs/intro.html @@ -9,14 +9,14 @@ - +

Introduction

license

logo



HybridCLR is a almost perfect full-platform native c# hot update solution for Unity with complete features, zero cost, high performance, and low memory.

HybridCLR expands the code of il2cpp, making it change from pure AOT runtime to AOT+Interpreter hybrid runtime, and then natively supports dynamic loading of assembly , so that the games packaged based on il2cpp backend can be executed not only on the Android platform, but also on IOS, Consoles and other platforms that limit JIT efficiently in AOT+interpreter hybrid mode, completely supporting hot updates from the bottom layer.

HybridCLR not only supports the traditional fully interpreted execution mode, but also pioneered the Differential Hybrid Execution(DHE) differential hybrid execution technology. That is, you can add, delete, or modify the AOT dll at will, and intelligently make the changed or newly added classes and functions run in interpreter mode, but the unchanged classes and functions run in AOT mode, so that the running performance of the hot-updated game logic basically reaches the original AOT level.

Welcome to embrace modern native C# hot update technology! ! !

Features

  • Features complete. Nearly complete implementation of the ECMA-335 specification, with only a very small number of unsupported features features.
  • Zero learning and use costs. HybridCLR enhances the pure AOT runtime into a complete runtime, making hot update code work seamlessly with AOT code. The script class and the AOT class are in the same runtime, and you can freely write codes such as inheritance, reflection, and multi-threading (volatile, ThreadStatic, Task, async). No need to write any special code, no code generation, almost unlimited.
  • Efficient execution. Implemented an extremely efficient register interpreter, all indicators are significantly better than other hot update schemes. Performance Test Report
  • Memory efficient. The classes defined in the hot update script occupy the same memory space as ordinary c# classes, which is far superior to other hot update solutions. Memory usage report
  • Due to the perfect support for generics, libraries that are not compatible with il2cpp due to AOT generics issues can now run perfectly under il2cpp
  • Support some features not supported by il2cpp, such as makeref, reftype, __refvalue directives
  • The original Differential Hybrid Execution(DHE) makes the running performance of hot update basically reach the level of native AOT.

working principle

Inspired by mono's mixed mode execution technology, HybridCLR provides an additional interpreter module for unity's il2cpp runtime , Transform them from pure AOT runtime to AOT + Interpreter hybrid runtime.

icon

More specifically, HybridCLR does the following:

  • Implemented an efficient metadata (dll) parsing library
  • Modified the metadata management module to realize the dynamic registration of metadata
  • Implemented a compiler from IL instruction set to custom register instruction set
  • Implemented an efficient register interpreter
  • Provide a large number of instinct functions to improve the performance of the interpreter

HybridCLR is a native c# hot update solution. In layman's terms, il2cpp is equivalent to the aot module of mono, and HybridCLR is equivalent to the interpreter module of mono, and the combination of the two becomes a complete mono. HybridCLR makes il2cpp a full-featured runtime, natively (that is, through System.Reflection.Assembly.Load) supports dynamic loading of dlls, thereby supporting hot updates of the ios platform.

Just because HybridCLR is implemented at the native runtime level, the types of the hot update part and the AOT part of the main project are completely equivalent and seamlessly unified. You can call, inherit, reflect, and multi-thread at will, without generating code or writing adapters.

Other hot update solutions are independent vm, and the relationship with il2cpp is essentially equivalent to the relationship of embedding lua in mono. Therefore, the type system is not uniform. In order to allow the hot update type to inherit some AOT types, an adapter needs to be written, and the type in the interpreter cannot be recognized by the type system of the main project. Incomplete features, troublesome development, and low operating efficiency.

Compatibility

  • Supports 2019.4.x, 2020.3.x, 2021.3.x, 2022.3.x, 2023.2.x、6000.x.y versions
  • Support for common platforms. It has stably supported PC (Win32 and Win64), macOS (x86, x64, Arm64), Android (armv7, armv8), iOS (64bit), WebGL, WeChat applet platform, and the remaining platforms are yet to be tested.
  • Tested a large number of common game libraries, and did not find a library that is natively compatible with il2cpp but incompatible after using HybridCLR. As long as the library can work under the il2cpp backend, it can work normally under HybridCLR. Even those libraries that are incompatible with il2cpp due to AOT issues can now run normally because of HybridCLR's ability to expand il2cpp.

Low risk of rejection

tip

HybridCLR is very popular in mainland China, and there are at least hundreds of games using HybridCLR on the App Store and Google Play.

The underlying principle of HybridCLR is still interpretation and execution, and from this point of view there is no essential difference from lua. Therefore, it meets the requirements of App Store and Google Play Store, and there is no special risk of rejection. And because of the high integration of HybridCLR and il2cpp, It is even much safer than the lua scheme, and the probability of rejection is very low.

Stability Status

At present, extremely stable 1.x, 2.x, 3.x, 4.x, 5.x official versions have been released, which are sufficient to meet the stability requirements of large and medium-sized commercial projects. Since the first game was launched on June 7, 2022, only one small bug occurred in the online project, and it was quickly fixed within a few hours.

At present, at least thousands commercial game projects have completed access, and hundreds of them have been launched on both ends. The online projects include MMORPG, heavy card, heavy tower defense and other games.

Most leading companies such as Tencent, NetEase, funplus, Perfect World, Stacked Paper, and ByteDance have already connected to multiple projects and will soon (or already) go online.

About the author

walon : Founder of Code Philosophy (code philosophy)

Graduated from the Department of Physics of Tsinghua University, won the CMO gold medal in 2006, a member of the National Mathematical Olympiad Training Team, and was recommended to Tsinghua University for basic courses. Focus on game technology, good at developing architecture and basic technical facilities.

license

HybridCLR is licensed under the MIT license

- + \ No newline at end of file diff --git a/en/docs/other.html b/en/docs/other.html index 554e907d9..6513dcc6f 100644 --- a/en/docs/other.html +++ b/en/docs/other.html @@ -9,13 +9,13 @@ - + - + \ No newline at end of file diff --git a/en/docs/other/businesscase.html b/en/docs/other/businesscase.html index 53b075b48..32421bef3 100644 --- a/en/docs/other/businesscase.html +++ b/en/docs/other/businesscase.html @@ -9,7 +9,7 @@ - + @@ -20,7 +20,7 @@ Calculated at 25% for apps that do not use hot update technology, among Unity games that use hot update technology, the proportion of using HybridCLR is approximately 26.7%.

Similarly, we counted the data of 326 game apps in the top 500 free list of App Store on February 2, 2024, except for the 326 game apps that are not on the best-selling list. Among them, there are 140 Unity games and 186 non-Unity games. Among Unity games, 13 use HybridCLR hot update, 27 use Lua hot update, and 100 do not use hot update. Among Unity games using hot update technology, the proportion of using HybridCLR is approximately 32.5%.

We can roughly conclude that among the latest and best-performing Unity game apps that use hot update technology, the proportion of using HybridCLR is between 25% and 35%.

Top 200 best-selling projects using HybridCLR

15 games use HybridCLR technology.

appIDbundleIDgame nameranking
996509117com.juzi.balls球球大作战64
1278845241com.yomob.yyzy月圆之夜196
6469103697com.cyou.xxygb西游:笔绘西行24
891616303com.zengame.xzdd指尖四川麻将-主播最爱麻将26
6451241596com.cmge.dpcq.ios斗破苍穹:巅峰对决34
1037198513com.sanguosha.sgsol三国杀OL39
6470348464com.caohua.hymc荒野迷城-废土求生52
6444853360com.xxzyios.xh行侠仗义五千年-国风割草手游73
1508509620com.minigame.skyforce.os孤独战机88
1562260653com.zy.wqmt.cn无期迷途106
6448353442com.ddsw.zombiewaves.zw手机反恐特别行动137
6475003560com.hero.rpg.xj52q星际52区142
6446756603com.leiting.dragonraider飞吧龙骑士-东方火龙145
1561085506com.chillyroom.soulknightprequel元气骑士前传154
6476655022com.csbyios.game传世霸业-正版授权 复古高爆端游移植192

top 500 free projects using HybridCLR

51 games use HybridCLR technology.

appIDbundleIDgame nameranking
1561085506com.chillyroom.soulknightprequel元气骑士前传4
1669193324ioa.chenz.leisure.gn我的休闲时光5
6451466022com.m76cf.kumqffwjv三国吧兄弟-休闲割草解压手游9
1619727250com.shengtiangames.clwg潮灵王国:起源13
6451241596com.cmge.dpcq.ios斗破苍穹:巅峰对决13
6470348464com.caohua.hymc荒野迷城-废土求生19
6446756603com.leiting.dragonraider飞吧龙骑士-东方火龙27
6444853360com.xxzyios.xh行侠仗义五千年-国风割草手游30
6469103697com.cyou.xxygb西游:笔绘西行43
1508509620com.minigame.skyforce.os孤独战机45
1576661186com.xd.t3game火力苏打(T3)72
6448353442com.ddsw.zombiewaves.zw手机反恐特别行动83
1590274853com.Sunborn.SnqxExilium少女前线2:追放93
891616303com.zengame.xzdd指尖四川麻将-主播最爱麻将112
6449481979com.honggame.yzmj.txzr天选之人112
6450107254com.qmjh.qmjhios全民江湖-热血江湖正版手游115
1544895560com.dgames.g15002002.apple宿命回响:弦上的叹息122
6476655022com.csbyios.game传世霸业-正版授权 复古高爆端游移植144
6449188289com.zhaimiao.ygl摇光录:乱世公主148
1546338773badminton.blitz.sports.free.game.cn.ios决战羽毛球 - 联机对战149
6475003560com.hero.rpg.xj52q星际52区160
1434798394com.jys.qipa奇葩战斗家164
1102002812com.zongyi.ndoudizhu斗地主经典版-单机游戏欢乐版棋牌残局173
1663156162com.youzu.shjh.ios山海镜花-归来173
1594550177com.sfgame.zq台球王者-3D真人版175
6452948314com.qulu.yongzhemijing.yzkdk勇者无敌-割草休闲动作游戏186
1583935305com.bkkj.js1朕的江山2:三国策略国战192
1585105852com.feelingtouch.zfsniper.cn僵尸前线3D-末日狙击战争手游196
1507863649com.yongshi.tenojo.ios深空之眼203
1634133454com.da.china飞龙岛历险记209
1523017982com.leiting.mole摩尔庄园212
6447148068com.yofijoy.fcdjios方寸对决212
1629567830com.gwstudio.pixelpeerless.cn勇者秘境218
1562260653com.zy.wqmt.cn无期迷途241
6446361475com.zhsm.sjxh.cn.ios战火使命246
1517370204com.rsg.wdwtApp闪亮的你-娱乐圈养成游戏247
1641223717com.saiyun.wshty我是航天员257
1562937112com.mzzcnew.0414末日之城-策略塔防 卡牌放置258
6443768967com.racoondigi.jqys街球艺术-重新定义街球手游259
1636463825com.leiting.picatown皮卡堂之梦想起源268
1037198513com.sanguosha.sgsol三国杀OL269
1326740391com.setagame.survivordangerzone幸存者危城-末日生存僵尸游戏285
6447829907com.hoolai.qsmy.ios秦时明月:沧海311
6443928984com.hiplay.mergeland.cn糖果精灵传奇-爱丽丝合合仙境320
6450256080com.altgsqj.yhsjMU变态版:永恒世纪358
1673645173com.ra.xkzhanz.ios虚空战争390
1448486874com.bilibili.queji雀姬麻将417
1667162072com.wingjoy.coderustle2不一样传说 2418
6471483492com.saiyun3.wjmc超燃之战-3D割草428
1641048780com.yoozoo.ik.cn战火与永恒451
6469281520com.man4fun.lsz琉生传490

Projects using HybridCLR according to statistics from leading companies

CompanyOnline Project
funplusBingo Aloha
GibbitStrange Combatant
SunbornGirls’ Frontline 2
ExplosionWandering Earth
畅游ハイキュ-!!FLY HIGH
畅游JUMP:群星集结
NetEaseYaotai
BaiduXirang
- + \ No newline at end of file diff --git a/en/docs/other/changelog.html b/en/docs/other/changelog.html index df0f40978..a2a1f8db0 100644 --- a/en/docs/other/changelog.html +++ b/en/docs/other/changelog.html @@ -9,13 +9,13 @@ - +

Changelog

  • 2023.05.19 Update to Unity's latest LTS version 2020.3.48 and 2021.3.25. The last 2020LTS version support has been completed.
  • 2023.04.26 Update to the latest LTS version of Unity 2020.3.47 and 2021.3.23
  • 2023.03.20 com.code-philosophy.hybridclr contains aot assembly list and beautified generic class and function name when generating AOTGenericReference
  • 2023.3.11 Update to the latest LTS version of Unity 2020.3.46 and 2021.3.20
  • 2023.2.4 The 2021 version of the WebGL platform supports mounting scripts on resources
  • 2023.2.4 Complete the final version of DHE
  • 2023.2.3 release version 2.0
  • 2023.1.14 Update to the latest LTS version of Unity 2020.3.43 and 2021.3.16
  • 2022.12.08 support netstandard 2.0
  • 2022.11.28 officially launched multi-branch management hybridclr and il2cpp_plus, and created the official version 1.0 branch at the same time
  • 2022.11.24 Update to the latest LTS version of Unity 2020.3.42 and 2021.3.14
  • 2022.11.21 DHE can correctly handle metadata and generics involved in AOT functions
  • 2022.11.09 Update to the latest LTS version of Unity 2020.3.41 and 2021.3.13
  • 2022.11.07 Open the LTS version and enter the stage of stable maintenance.
  • 2022.10.26 The official main QQ group has reached 3,000 people!
  • 2022.10.26 Support SuperSet metadata mode. The original AOT dll can be loaded directly, simplifying the packaging workflow.
  • 2022.10.19 The WebGL platform runs through all test cases except the platform itself, and supports scripts mounted on resources.
  • 2022.10.08 support profile
  • 2022.9.23 Implemented a complete workflow tool, one-click packaging
  • 2022.8.29 Update 2020.3.33 to the latest 2020.3.38 version, update 2021.3.1 to the latest 2020.3.8 version! The hybridclr_trial installer supports the use of compatible versions, and it is no longer mandatory to install the corresponding version of the branch.
  • 2022.8.27 Support Unity 2019.4.x LTS series version
  • 2022.8.9 officially support macOS intel+silicon platform
  • 2022.7.24 Support MonoPInvokeCallbackAttribute.
  • 2022.7.22 Officially support the Unity 2021 LTS series version.
  • 2022.7.15 Support WebGL platform, run through almost all unit tests (more than 1600 unit tests only 11 failed)
  • 2022.7.14 Support Win 32(x86)
  • 2022.7.13 Cooperative course with UWA launched
  • 2022.7.10 Officially support Android armv7 32-bit version! ! !
  • 2022.7.6 Code Philosophy (code philosophy) was established! ! ! , renamed to HybridCLR
  • On July 4, 2022, the number of github stars exceeded 2000
  • 2022.6.7 Launched the first Android and iOS dual-platform moderate game
  • 2022.5.31 For the first time, a serious RPG card project was run on the three platforms of PC, Android, and iOS in a complete, stable and smooth manner! ! !
  • 2022.5.19 There are two fully running Android projects
  • 2022.5.18 Support Android(armv8) and iOS(64) platforms! ! ! Run all unit tests
  • 2022.4.19 The number of QQ main groups exceeded 1000! ! !
  • 2022.4.16 Completely run a large-scale MMORPG project on the PC platform for the first time
  • 2022.3.23 open source
  • 2022.3.19 Run through the first mini-game 2048
  • 2022.3.16
    • Using the generic sharing mechanism to solve some AOT generic problems
    • Load the luban configuration normally, and run a large-scale code for the first time
  • 2022.3.6
    • Support generic functions (ordinary generic functions and virtual generic functions)
  • 2022.3.3
    • Support for generic classes
    • Support delegate call
  • 2022.2.27 version
    • Full support for exception mechanism
  • 2022.2.25 version
    • Implement a complete register instruction set
  • 2022.1.16 preview 2 version
    • support interface
    • Support generic interface
    • Support for handling value types
    • Implemented a relatively complete IL instruction set support
  • 2022.1.10 release preview 1 version
    • Support object definition and inheritance
    • Support virtual function calls
    • Use the original IL instruction set to support all basic instructions: such as numerical calculation, branch jump
  • 2021.12.19 Start of development
  • 2021.11.2 Created the HybridCLR QQ main group
- + \ No newline at end of file diff --git a/en/docs/other/contactme.html b/en/docs/other/contactme.html index f18ea5b5c..d90293a89 100644 --- a/en/docs/other/contactme.html +++ b/en/docs/other/contactme.html @@ -9,13 +9,13 @@ - + - + \ No newline at end of file diff --git a/en/docs/other/donate.html b/en/docs/other/donate.html index a8d4fcf88..5d73ec311 100644 --- a/en/docs/other/donate.html +++ b/en/docs/other/donate.html @@ -9,13 +9,13 @@ - +

Donate list

Thanks to these friends for their generous sponsorship! ! !

李旭,慷概解囊捐赠 20000

- + \ No newline at end of file diff --git a/en/docs/other/relativepojects.html b/en/docs/other/relativepojects.html index dea0fc477..7e11aa952 100644 --- a/en/docs/other/relativepojects.html +++ b/en/docs/other/relativepojects.html @@ -9,7 +9,7 @@ - + @@ -18,7 +18,7 @@ Author: Dango1992
  • et7_fgui_yooasset_luban_hybridclr. Author: Misty Rain and Blurred Half a Life,Personal Website
  • A HybridCLR hot update framework Deer_GameFramework_HybridCLR derived from the GameFramework framework. Author: AlanDu, Blog
  • Simple and powerful Unity framework TEngine Author: Alex
  • Addressables-based non-aware logic hot update tool Assemblies-Hotfix-Toolkit-Unity Author: Xianyu Gulei
  • Encryption and decryption libraries and small tools, which can be integrated into hybridclr to encrypt/decrypt global-metadata.dat. Hot update assembly and so on. xenctrypt, xFileEncoder.
  • - + \ No newline at end of file diff --git a/en/docs/other/roadmap.html b/en/docs/other/roadmap.html index ec3ed0d16..3208ceb4b 100644 --- a/en/docs/other/roadmap.html +++ b/en/docs/other/roadmap.html @@ -9,13 +9,13 @@ - +

    Subsequent development plan

    • Instruction optimization, the number of compiled instructions is reduced to 1/4-1/2 of the original, and the performance of basic instructions and most object model instructions is improved by 100%-300%. (Currently partially implemented)
    • Support extern function
    - + \ No newline at end of file diff --git a/en/docs/pro.html b/en/docs/pro.html index 7921d6f4d..35e4980fd 100644 --- a/en/docs/pro.html +++ b/en/docs/pro.html @@ -9,13 +9,13 @@ - + - + \ No newline at end of file diff --git a/en/docs/reload.html b/en/docs/reload.html index e2f9f1b08..f1f3b919d 100644 --- a/en/docs/reload.html +++ b/en/docs/reload.html @@ -9,13 +9,13 @@ - +
    - + \ No newline at end of file diff --git a/en/docs/ultimate.html b/en/docs/ultimate.html index 85c7cd6d1..7b7028ff4 100644 --- a/en/docs/ultimate.html +++ b/en/docs/ultimate.html @@ -9,13 +9,13 @@ - +

    Ultimate Edition

    📄️ Simplify dhao workflow

    Based on the same version of the original project, the original dll will have slight differences when publishing different platforms, resulting in the need to calculate a separate dhao file for each platform (even for the same platform, due to the instability of code compilation, the generated original dll may also have slight differences), which makes the maintenance of dhao complicated and error-prone. This problem is particularly serious when multiple new and old game packages exist at the same time.

    - + \ No newline at end of file diff --git a/en/index.html b/en/index.html index 7c05c5d17..d77073f3e 100644 --- a/en/index.html +++ b/en/index.html @@ -9,13 +9,13 @@ - +

    HybridCLR

    Full-featured, zero-cost, high-performance, low-memory Unity full-platform native c# hot update solution

    Easy to use

    Native C# hot update experience, the development workflow is almost the same as traditional Unity C# development, zero learning and use costs.

    High efficiency

    C++ implementation, deep integration with il2cpp, running performance and memory usage indicators are far superior to any other hot update solutions. The pioneering DHE technology makes the running performance of hot updated game logic basically reach the level of native AOT.

    Stable and reliable

    Extremely stable and reliable, enough to meet the stability requirements of large and medium-sized commercial projects. At present, thousands of commercial game projects have been connected, and hundreds of them have been launched on both ends. Most leading companies such as Tencent, Netease, ByteDance, and funplus have already connected.

    - + \ No newline at end of file diff --git a/en/search.html b/en/search.html index 291c39e8a..ad53cb6dd 100644 --- a/en/search.html +++ b/en/search.html @@ -9,13 +9,13 @@ - + - + \ No newline at end of file diff --git a/index.html b/index.html index f8176f6d8..a38ea4771 100644 --- a/index.html +++ b/index.html @@ -9,13 +9,13 @@ - +

    HybridCLR

    特性完整、零成本、高性能、低内存的Unity全平台原生c#热更方案

    易于使用

    原生C#热更新体验,开发工作流与传统Unity C#开发几乎相同,零学习和使用成本。

    实现高效

    C++实现,与il2cpp高级集成,运行性能和内存占用指标都远远优于其他任何热更新方案。开创性的DHE技术让热更新的游戏逻辑的运行性能基本达到原生AOT的水平。

    稳定可靠

    极其稳定可靠,足以满足大中型商业项目的稳定性要求。当前上千个商业游戏项目完成接入,其中有几百款已经双端上线。大多数头部公司如腾讯、网易、字节、funplus都已接入。

    - + \ No newline at end of file diff --git a/search.html b/search.html index 70616e304..7811dcc0c 100644 --- a/search.html +++ b/search.html @@ -9,13 +9,13 @@ - + - + \ No newline at end of file