From a7410934709f096feaf766c4cf2978b927b895d4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 2 Nov 2023 03:22:33 +0000 Subject: [PATCH] deploy: fe6479734bad0d292ef1519efb44527ae23872af --- 404.html | 4 ++-- ...stall-c1f84913c18dfa0406fc90db65085a56.jpg | Bin 25485 -> 0 bytes ...fault-c61d323cdd4133368bc575f35dd7a9ec.jpg | Bin 24395 -> 0 bytes ...19757.15827c36.js => 6ec19757.47c6dd18.js} | 2 +- assets/js/7d20b2b1.463916d8.js | 1 - assets/js/7d20b2b1.a2b63972.js | 1 + ...n.30b08ba4.js => runtime~main.d155d570.js} | 2 +- blog.html | 4 ++-- blog/archive.html | 4 ++-- blog/catelog.html | 4 ++-- blog/instructions.html | 4 ++-- blog/mindexperiment.html | 4 ++-- blog/principle.html | 4 ++-- docs/basic.html | 4 ++-- docs/basic/aotgeneric.html | 4 ++-- docs/basic/architecture.html | 4 ++-- docs/basic/bestpractice.html | 4 ++-- docs/basic/buildpipeline.html | 4 ++-- docs/basic/buildwebgl.html | 4 ++-- docs/basic/codestriping.html | 4 ++-- docs/basic/com.code-philosophy.hybridclr.html | 6 +++--- docs/basic/compileassembly.html | 4 ++-- docs/basic/hotupdateassemblysetting.html | 4 ++-- docs/basic/il2cppbugs.html | 4 ++-- docs/basic/install.html | 6 +++--- docs/basic/memory.html | 4 ++-- docs/basic/methodbridge.html | 4 ++-- docs/basic/migratefromnetstandard.html | 4 ++-- docs/basic/modifyunitydll.html | 4 ++-- docs/basic/monobehaviour.html | 4 ++-- docs/basic/notsupportedfeatures.html | 4 ++-- docs/basic/performance.html | 4 ++-- docs/basic/projectsettings.html | 4 ++-- docs/basic/runhotupdatecodes.html | 4 ++-- docs/basic/sourceinspect.html | 4 ++-- .../supportedplatformanduniyversion.html | 4 ++-- docs/basic/workwithscriptlanguage.html | 4 ++-- docs/beginner.html | 4 ++-- docs/beginner/generic.html | 4 ++-- docs/beginner/monobehaviour.html | 4 ++-- docs/beginner/otherhelp.html | 4 ++-- docs/beginner/quickstart.html | 4 ++-- docs/business.html | 4 ++-- docs/business/advancedcodeoptimization.html | 4 ++-- docs/business/advancedencryption.html | 4 ++-- docs/business/basiccodeoptimization.html | 4 ++-- docs/business/basicencryption.html | 4 ++-- docs/business/businesscase.html | 4 ++-- .../business/differentialhybridexecution.html | 4 ++-- docs/business/fullgenericsharing.html | 4 ++-- docs/business/intro.html | 4 ++-- docs/business/pro/intro.html | 4 ++-- docs/business/pro/quickstart.html | 4 ++-- docs/business/reload/hotreloadassembly.html | 4 ++-- docs/business/reload/intro.html | 4 ++-- docs/business/reload/modifydll.html | 4 ++-- docs/business/reload/quickstart.html | 4 ++-- docs/business/ultimate/intro.html | 4 ++-- docs/business/ultimate/manual.html | 4 ++-- docs/business/ultimate/quickstart.html | 4 ++-- docs/help.html | 4 ++-- docs/help/commonerrors.html | 4 ++-- docs/help/faq.html | 4 ++-- docs/help/issue.html | 4 ++-- docs/intro.html | 4 ++-- docs/other.html | 4 ++-- docs/other/businesscase.html | 4 ++-- docs/other/changelog.html | 4 ++-- docs/other/contactme.html | 4 ++-- docs/other/donate.html | 4 ++-- docs/other/relativepojects.html | 4 ++-- docs/other/roadmap.html | 4 ++-- docs/pro.html | 4 ++-- docs/reload.html | 4 ++-- docs/ultimate.html | 4 ++-- en/404.html | 4 ++-- ...stall-c1f84913c18dfa0406fc90db65085a56.jpg | Bin 25485 -> 0 bytes ...fault-c61d323cdd4133368bc575f35dd7a9ec.jpg | Bin 24395 -> 0 bytes ...f9c4d.6b54584a.js => 50ff9c4d.d672bb77.js} | 2 +- en/assets/js/c225ef2b.259b48ea.js | 1 + en/assets/js/c225ef2b.5cdabdac.js | 1 - ...n.725762af.js => runtime~main.18df0249.js} | 2 +- en/blog.html | 4 ++-- en/blog/archive.html | 4 ++-- en/blog/catelog.html | 4 ++-- en/blog/instructions.html | 4 ++-- en/blog/mindexperiment.html | 4 ++-- en/blog/principle.html | 4 ++-- en/docs/basic.html | 4 ++-- en/docs/basic/aotgeneric.html | 4 ++-- en/docs/basic/architecture.html | 4 ++-- en/docs/basic/bestpractice.html | 4 ++-- en/docs/basic/buildpipeline.html | 4 ++-- en/docs/basic/buildwebgl.html | 4 ++-- en/docs/basic/codestriping.html | 4 ++-- .../basic/com.code-philosophy.hybridclr.html | 6 +++--- en/docs/basic/compileassembly.html | 4 ++-- en/docs/basic/hotupdateassemblysetting.html | 4 ++-- en/docs/basic/il2cppbugs.html | 4 ++-- en/docs/basic/install.html | 6 +++--- en/docs/basic/memory.html | 4 ++-- en/docs/basic/methodbridge.html | 4 ++-- en/docs/basic/migratefromnetstandard.html | 4 ++-- en/docs/basic/modifyunitydll.html | 4 ++-- en/docs/basic/monobehaviour.html | 4 ++-- en/docs/basic/notsupportedfeatures.html | 4 ++-- en/docs/basic/performance.html | 4 ++-- en/docs/basic/projectsettings.html | 4 ++-- en/docs/basic/runhotupdatecodes.html | 4 ++-- en/docs/basic/sourceinspect.html | 4 ++-- .../supportedplatformanduniyversion.html | 4 ++-- en/docs/basic/workwithscriptlanguage.html | 4 ++-- en/docs/beginner.html | 4 ++-- en/docs/beginner/generic.html | 4 ++-- en/docs/beginner/monobehaviour.html | 4 ++-- en/docs/beginner/otherhelp.html | 4 ++-- en/docs/beginner/quickstart.html | 4 ++-- en/docs/business.html | 4 ++-- .../business/advancedcodeoptimization.html | 4 ++-- en/docs/business/advancedencryption.html | 4 ++-- en/docs/business/basiccodeoptimization.html | 4 ++-- en/docs/business/basicencryption.html | 4 ++-- en/docs/business/businesscase.html | 4 ++-- .../business/differentialhybridexecution.html | 4 ++-- en/docs/business/fullgenericsharing.html | 4 ++-- en/docs/business/intro.html | 4 ++-- en/docs/business/pro/intro.html | 4 ++-- en/docs/business/pro/quickstart.html | 4 ++-- .../business/reload/hotreloadassembly.html | 4 ++-- en/docs/business/reload/intro.html | 4 ++-- en/docs/business/reload/modifydll.html | 4 ++-- en/docs/business/reload/quickstart.html | 4 ++-- en/docs/business/ultimate/intro.html | 4 ++-- en/docs/business/ultimate/manual.html | 4 ++-- en/docs/business/ultimate/quickstart.html | 4 ++-- en/docs/help.html | 4 ++-- en/docs/help/commonerrors.html | 4 ++-- en/docs/help/faq.html | 4 ++-- en/docs/help/issue.html | 4 ++-- en/docs/intro.html | 4 ++-- en/docs/other.html | 4 ++-- en/docs/other/businesscase.html | 4 ++-- en/docs/other/changelog.html | 4 ++-- en/docs/other/contactme.html | 4 ++-- en/docs/other/donate.html | 4 ++-- en/docs/other/relativepojects.html | 4 ++-- en/docs/other/roadmap.html | 4 ++-- en/docs/pro.html | 4 ++-- en/docs/reload.html | 4 ++-- en/docs/ultimate.html | 4 ++-- en/img/hybridclr/install.jpg | Bin 25485 -> 8635 bytes en/img/hybridclr/install_default.jpg | Bin 24395 -> 7774 bytes en/index.html | 4 ++-- en/search.html | 4 ++-- img/hybridclr/install.jpg | Bin 25485 -> 8635 bytes img/hybridclr/install_default.jpg | Bin 24395 -> 7774 bytes index.html | 4 ++-- search.html | 4 ++-- 158 files changed, 294 insertions(+), 294 deletions(-) delete mode 100644 assets/images/install-c1f84913c18dfa0406fc90db65085a56.jpg delete mode 100644 assets/images/install_default-c61d323cdd4133368bc575f35dd7a9ec.jpg rename assets/js/{6ec19757.15827c36.js => 6ec19757.47c6dd18.js} (59%) delete mode 100644 assets/js/7d20b2b1.463916d8.js create mode 100644 assets/js/7d20b2b1.a2b63972.js rename assets/js/{runtime~main.30b08ba4.js => runtime~main.d155d570.js} (98%) delete mode 100644 en/assets/images/install-c1f84913c18dfa0406fc90db65085a56.jpg delete mode 100644 en/assets/images/install_default-c61d323cdd4133368bc575f35dd7a9ec.jpg rename en/assets/js/{50ff9c4d.6b54584a.js => 50ff9c4d.d672bb77.js} (55%) create mode 100644 en/assets/js/c225ef2b.259b48ea.js delete mode 100644 en/assets/js/c225ef2b.5cdabdac.js rename en/assets/js/{runtime~main.725762af.js => runtime~main.18df0249.js} (97%) diff --git a/404.html b/404.html index a886684b..8e7022f4 100644 --- a/404.html +++ b/404.html @@ -9,13 +9,13 @@ - +
跳到主要内容

找不到页面

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

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

- + \ No newline at end of file diff --git a/assets/images/install-c1f84913c18dfa0406fc90db65085a56.jpg b/assets/images/install-c1f84913c18dfa0406fc90db65085a56.jpg deleted file mode 100644 index 4a04f1856b5f90ac6d91b590cfa5784d383c3121..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25485 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�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 y?As*Up&9rc)#%qr{i.d(t,{Zo:()=>d,kt:()=>s});var l=i(7294);function n(e,t,i){return t in e?Object.defineProperty(e,t,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[t]=i,e}function a(e,t){var i=Object.keys(e);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);t&&(l=l.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),i.push.apply(i,l)}return i}function p(e){for(var t=1;t=0||(n[i]=e[i]);return n}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(l=0;l=0||Object.prototype.propertyIsEnumerable.call(e,i)&&(n[i]=e[i])}return n}var o=l.createContext({}),c=function(e){var t=l.useContext(o),i=t;return e&&(i="function"==typeof e?e(t):p(p({},t),e)),i},d=function(e){var t=c(e.components);return l.createElement(o.Provider,{value:t},e.children)},m="mdxType",u={inlineCode:"code",wrapper:function(e){var t=e.children;return l.createElement(l.Fragment,{},t)}},k=l.forwardRef((function(e,t){var i=e.components,n=e.mdxType,a=e.originalType,o=e.parentName,d=r(e,["components","mdxType","originalType","parentName"]),m=c(i),k=n,s=m["".concat(o,".").concat(k)]||m[k]||u[k]||a;return i?l.createElement(s,p(p({ref:t},d),{},{components:i})):l.createElement(s,p({ref:t},d))}));function s(e,t){var i=arguments,n=t&&t.mdxType;if("string"==typeof e||n){var a=i.length,p=new Array(a);p[0]=k;var r={};for(var o in t)hasOwnProperty.call(t,o)&&(r[o]=t[o]);r.originalType=e,r[m]="string"==typeof e?e:n,p[1]=r;for(var c=2;c{i.r(t),i.d(t,{assets:()=>o,contentTitle:()=>p,default:()=>u,frontMatter:()=>a,metadata:()=>r,toc:()=>c});var l=i(7462),n=(i(7294),i(3905));const a={},p="\u5b89\u88c5",r={unversionedId:"basic/install",id:"basic/install",title:"\u5b89\u88c5",description:"\u5b89\u88c5\u517c\u5bb9\u7684Unity\u7248\u672c",source:"@site/docs/basic/install.md",sourceDirName:"basic",slug:"/basic/install",permalink:"/docs/basic/install",draft:!1,tags:[],version:"current",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"\u652f\u6301\u7684Unity\u7248\u672c\u548c\u5e73\u53f0",permalink:"/docs/basic/supportedplatformanduniyversion"},next:{title:"\u914d\u7f6e",permalink:"/docs/basic/projectsettings"}},o={},c=[{value:"\u5b89\u88c5\u517c\u5bb9\u7684Unity\u7248\u672c",id:"\u5b89\u88c5\u517c\u5bb9\u7684unity\u7248\u672c",level:2},{value:"\u5b89\u88c5IDE\u53ca\u76f8\u5173\u5de5\u5177",id:"\u5b89\u88c5ide\u53ca\u76f8\u5173\u5de5\u5177",level:2},{value:"\u9009\u62e9 com.code-philosophy.hybridclr \u7248\u672c",id:"\u9009\u62e9-comcode-philosophyhybridclr-\u7248\u672c",level:3},{value:"\u5b89\u88c5 com.code-philosophy.hybridclr package",id:"\u5b89\u88c5-comcode-philosophyhybridclr-package",level:2},{value:"\u4ecegit url\u5b89\u88c5",id:"\u4ecegit-url\u5b89\u88c5",level:3},{value:"\u4ece\u672c\u5730\u6587\u4ef6\u5b89\u88c5",id:"\u4ece\u672c\u5730\u6587\u4ef6\u5b89\u88c5",level:3},{value:"\u66f4\u65b0 com.code-philosophy.hybridclr",id:"\u66f4\u65b0-comcode-philosophyhybridclr",level:2},{value:"\u521d\u59cb\u5316HybridCLR",id:"\u521d\u59cb\u5316hybridclr",level:2},{value:"\u5982\u679c\u4f60\u7684\u7248\u672c >= v2.0.5",id:"\u5982\u679c\u4f60\u7684\u7248\u672c--v205",level:3},{value:"\u5982\u679c\u4f60\u7684\u7248\u672c >= 1.1.20",id:"\u5982\u679c\u4f60\u7684\u7248\u672c--1120",level:3},{value:"\u5982\u679c\u4f60\u7684package\u7248\u672c <= 1.1.19",id:"\u5982\u679c\u4f60\u7684package\u7248\u672c--1119",level:3},{value:"\u5b89\u88c5\u540e\u7684\u7279\u6b8a\u5904\u7406",id:"\u5b89\u88c5\u540e\u7684\u7279\u6b8a\u5904\u7406",level:2},{value:"WebGL\u5e73\u53f0",id:"webgl\u5e73\u53f0",level:3},{value:"Unity 2021",id:"unity-2021",level:3},{value:"Unity 2019",id:"unity-2019",level:3},{value:"\u5728\u975e\u517c\u5bb9\u7684Unity\u7248\u672c\u4e2d\u4f7f\u7528HybridCLR",id:"\u5728\u975e\u517c\u5bb9\u7684unity\u7248\u672c\u4e2d\u4f7f\u7528hybridclr",level:2},{value:"HybridCLR/Installer\u5de5\u4f5c\u539f\u7406",id:"hybridclrinstaller\u5de5\u4f5c\u539f\u7406",level:2},{value:"\u66ff\u6362libil2cpp\u4ee3\u7801",id:"\u66ff\u6362libil2cpp\u4ee3\u7801",level:3},{value:"\u672c\u5730\u5b89\u88c5",id:"\u672c\u5730\u5b89\u88c5",level:3},{value:"\u5168\u5c40\u5b89\u88c5",id:"\u5168\u5c40\u5b89\u88c5",level:3},{value:"\u6ce8\u610f\u4e8b\u9879",id:"\u6ce8\u610f\u4e8b\u9879",level:2}],d={toc:c},m="wrapper";function u(e){let{components:t,...a}=e;return(0,n.kt)(m,(0,l.Z)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,n.kt)("h1",{id:"\u5b89\u88c5"},"\u5b89\u88c5"),(0,n.kt)("h2",{id:"\u5b89\u88c5\u517c\u5bb9\u7684unity\u7248\u672c"},"\u5b89\u88c5\u517c\u5bb9\u7684Unity\u7248\u672c"),(0,n.kt)("p",null,"\u652f\u63012019.4.x\u30012020.3.x\u30012021.3.x\u30012022.3.x \u4e2d\u4efb\u4e00\u7248\u672c\u3002\u63a8\u8350\u5b89\u88c52019.4.40\u30012020.3.26+\u30012021.3.x\u30012022.3.x\u7248\u672c\u3002"),(0,n.kt)("admonition",{type:"tip"},(0,n.kt)("p",{parentName:"admonition"},"\u5982\u679c\u4f60\u7684\u7248\u672c\u4e3a 2019.4.0-2019.4.39\uff0c",(0,n.kt)("strong",{parentName:"p"},"\u9700\u8981\u5148\u5207\u6362\u52302019.4.40\u7248\u672c\u5b8c\u6210HybridCLR\u5b89\u88c5\uff0c\u518d\u5207\u6362\u56de\u5f53\u524d\u7248\u672c"),"\u3002"),(0,n.kt)("p",{parentName:"admonition"},"\u5982\u679c\u4f60\u7684\u7248\u672c\u4e3a 2020.3.0-2020.3.25\uff0c \u5728Installer\u4e2d\u5b8c\u6210\u5b89\u88c5\u540e\uff0c\u4ece2020.3.26+\u4efb\u4e00\u7248\u672c\u7684\u5b89\u88c5\u76ee\u5f55\u590d\u5236",(0,n.kt)("inlineCode",{parentName:"p"},"2020.3.x/Editor/Data/il2cpp/external"),"\u66ff\u6362\n",(0,n.kt)("inlineCode",{parentName:"p"},"{project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp/external"),"\u3002")),(0,n.kt)("p",null,"\u6839\u636e\u4f60\u6253\u5305\u7684\u76ee\u6807\u5e73\u53f0\uff0c\u5b89\u88c5\u8fc7\u7a0b\u4e2d\u9009\u62e9\u5fc5\u8981\u6a21\u5757\u3002\u5982\u679c\u6253\u5305Android\u6216iOS\uff0c\u76f4\u63a5\u9009\u62e9\u76f8\u5e94\u6a21\u5757\u5373\u53ef\u3002\u5982\u679c\u4f60\u60f3\u6253\u5305Standalone\uff0c\u5fc5\u987b\u989d\u5916\u9009\u4e2d ",(0,n.kt)("inlineCode",{parentName:"p"},"Windows Build Support(IL2CPP)"),"\u6216",(0,n.kt)("inlineCode",{parentName:"p"},"Mac Build Support(IL2CPP)"),"\u3002"),(0,n.kt)("p",null,(0,n.kt)("img",{alt:"select il2cpp modules",src:i(8581).Z,width:"721",height:"507"})),(0,n.kt)("h2",{id:"\u5b89\u88c5ide\u53ca\u76f8\u5173\u5de5\u5177"},"\u5b89\u88c5IDE\u53ca\u76f8\u5173\u5de5\u5177"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"Windows",(0,n.kt)("ul",{parentName:"li"},(0,n.kt)("li",{parentName:"ul"},"Win\u4e0b\u9700\u8981\u5b89\u88c5",(0,n.kt)("inlineCode",{parentName:"li"},"visual studio 2019"),"\u6216\u66f4\u9ad8\u7248\u672c\u3002\u5b89\u88c5\u65f6\u81f3\u5c11\u8981\u5305\u542b ",(0,n.kt)("inlineCode",{parentName:"li"},"\u4f7f\u7528Unity\u7684\u6e38\u620f\u5f00\u53d1")," \u548c ",(0,n.kt)("inlineCode",{parentName:"li"},"\u4f7f\u7528c++\u7684\u6e38\u620f\u5f00\u53d1")," \u7ec4\u4ef6\u3002"),(0,n.kt)("li",{parentName:"ul"},"\u5b89\u88c5git"))),(0,n.kt)("li",{parentName:"ul"},"Mac",(0,n.kt)("ul",{parentName:"li"},(0,n.kt)("li",{parentName:"ul"},"\u8981\u6c42MacOS\u7248\u672c >= 12\uff0cxcode\u7248\u672c >= 13\uff0c\u4f8b\u5982",(0,n.kt)("inlineCode",{parentName:"li"},"xcode 13.4.1\uff0c macos 12.4"),"\u3002"),(0,n.kt)("li",{parentName:"ul"},"\u5b89\u88c5 git"),(0,n.kt)("li",{parentName:"ul"},"\u5b89\u88c5cmake")))),(0,n.kt)("h3",{id:"\u9009\u62e9-comcode-philosophyhybridclr-\u7248\u672c"},"\u9009\u62e9 ",(0,n.kt)("inlineCode",{parentName:"h3"},"com.code-philosophy.hybridclr")," \u7248\u672c"),(0,n.kt)("admonition",{type:"tip"},(0,n.kt)("p",{parentName:"admonition"},"v3.0.0 \u4e4b\u524d\u7684\u5305\u540d\u53eb ",(0,n.kt)("inlineCode",{parentName:"p"},"com.focus-creative-games.hybridclr_unity"),"\u3002\u4ece",(0,n.kt)("inlineCode",{parentName:"p"},"v3.0.0"),"\u7248\u672c\u8d77\uff0c\u79fb\u9664\u4e86\u5bf9Unity 2019\u7684\u652f\u6301\uff0c\u4f7f\u75282019\u7684\u5f00\u53d1\u8005\u8bf7\u9009\u62e9",(0,n.kt)("inlineCode",{parentName:"p"},"v2.x.y"),"\u7cfb\u5217\u7248\u672c\u3002")),(0,n.kt)("p",null,"\u5f53\u524d\u5b58\u5728\u8fd9\u4e9b\u7248\u672c\uff1a",(0,n.kt)("inlineCode",{parentName:"p"},"1.0"),"\u5206\u652f\u3001",(0,n.kt)("inlineCode",{parentName:"p"},"v2.x.y"),"\u3001",(0,n.kt)("inlineCode",{parentName:"p"},"v3.x.y"),"\u3001",(0,n.kt)("inlineCode",{parentName:"p"},"v.4.x.y"),"\uff08\u4e5f\u662f\u5f53\u524dmain\u5206\u652f\uff09\u7cfb\u5217\u3002"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("inlineCode",{parentName:"li"},"1.0"),"\u5206\u652f\u8fc7\u4e8e\u4e45\u8fdc\uff0c\u867d\u7136\u5de5\u4f5c\u7a33\u5b9a\uff0c\u4f46Package\u76f8\u5173\u5de5\u4f5c\u6d41\u6bd4\u8f83\u9648\u65e7\uff0c\u4e0d\u5982\u540e\u7eed\u7248\u672c\u4fbf\u6377\uff0c\u800c\u4e14\u5df2\u7ecf\u505c\u6b62\u4e86\u7ef4\u62a4\uff0c\u5f3a\u70c8\u5efa\u8bae",(0,n.kt)("strong",{parentName:"li"},"\u4e0d\u8981"),"\u518d\u4f7f\u7528"),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("inlineCode",{parentName:"li"},"v2.x.y"),"\u7cfb\u5217\u7248\u672c\u5de5\u4f5c\u6d41\u4f18\u5316\u5408\u7406\uff0c\u7ecf\u8fc7\u5927\u91cf\u9879\u76ee\u9a8c\u8bc1\uff0c\u63a8\u8350\u4f7f\u7528Unity 2019\u7248\u672c\u6216\u9a6c\u4e0a\u8981\u4e0a\u7ebf\u7684\u9879\u76ee\u4f7f\u7528"),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("inlineCode",{parentName:"li"},"v3.x.y"),"\u7cfb\u5217 ",(0,n.kt)("strong",{parentName:"li"},"\u79fb\u9664\u4e86\u5bf9Unity 2019\u7684\u652f\u6301"),"\uff0c\u65b0\u589e\u4e86Unity 2022\u7248\u672c\u652f\u6301\u3002\u63a8\u8350\u4f7f\u7528Unity 2020+\u7248\u672c\u6216\u9a6c\u4e0a\u8981\u4e0a\u7ebf\u7684\u9879\u76ee\u4f7f\u7528"),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("inlineCode",{parentName:"li"},"v4.x.y"),"\u7cfb\u5217\u652f\u6301\u589e\u91cf\u5f0fGC\u5e76\u4e14\u5f7b\u5e95\u652f\u6301\u5168\u5e73\u53f0\u3002\u7531\u4e8e\u521a\u521a\u53d1\u5e03\uff0c\u9700\u8981\u51e0\u4e2a\u661f\u671f\u65f6\u95f4\u624d\u80fd\u7a33\u5b9a\uff0c\u63a8\u8350\u5904\u4e8e\u9879\u76ee\u521d\u671f\u6216\u4e2d\u671f\u7684\u9879\u76ee\u4f7f\u7528\u3002")),(0,n.kt)("p",null,"\u8fd9\u4e9b\u7248\u672c\u90fd\u5f88\u7a33\u5b9a\uff0c\u4e0d\u5fc5\u7ea0\u7ed3\u54ea\u4e2a\u66f4\u597d\uff0c\u4e00\u822c\u6765\u8bf4\u8d8a\u65b0\u7684\u7248\u672c\u4f18\u5316\u8d8a\u591a\uff0c\u4f7f\u7528\u4f53\u9a8c\u8d8a\u597d\u3002"),(0,n.kt)("h2",{id:"\u5b89\u88c5-comcode-philosophyhybridclr-package"},"\u5b89\u88c5 ",(0,n.kt)("inlineCode",{parentName:"h2"},"com.code-philosophy.hybridclr")," package"),(0,n.kt)("p",null,"\u4ed3\u5e93\u5730\u5740\u4e3a ",(0,n.kt)("a",{parentName:"p",href:"https://github.com/focus-creative-games/hybridclr_unity"},"github")," ,\u56fd\u5185\u5feb\u901f\u7684\u955c\u50cf\u4ed3\u5e93\u4e3a",(0,n.kt)("a",{parentName:"p",href:"https://gitee.com/focus-creative-games/hybridclr_unity"},"gitee"),"\u3002"),(0,n.kt)("p",null,"\u6709\u51e0\u79cd\u5b89\u88c5\u65b9\u5f0f\uff1a"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"\u4f7f\u7528Package Manager\u4ecegit url\u5b89\u88c5"),(0,n.kt)("li",{parentName:"ul"},"\u672c\u5730\u5b89\u88c5")),(0,n.kt)("h3",{id:"\u4ecegit-url\u5b89\u88c5"},"\u4ecegit url\u5b89\u88c5"),(0,n.kt)("p",null,"\u4e3b\u83dc\u5355\u4e2d\u70b9\u51fb",(0,n.kt)("inlineCode",{parentName:"p"},"Windows/Package Manager"),"\u6253\u5f00\u5305\u7ba1\u7406\u5668\u3002\u5982\u4e0b\u56fe\u6240\u793a\u70b9\u51fb",(0,n.kt)("inlineCode",{parentName:"p"},"Add package from git URL..."),"\uff0c\u586b\u5165",(0,n.kt)("inlineCode",{parentName:"p"},"https://gitee.com/focus-creative-games/hybridclr_unity.git"),"\u6216",(0,n.kt)("inlineCode",{parentName:"p"},"https://github.com/focus-creative-games/hybridclr_unity.git"),"\u3002"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"main\u5206\u652f\u5730\u5740\u4e3a ",(0,n.kt)("inlineCode",{parentName:"li"},"https://gitee.com/focus-creative-games/hybridclr_unity.git")),(0,n.kt)("li",{parentName:"ul"},"\u5176\u4ed6tag\u7248\u672c\u5730\u5740\u4e3a ",(0,n.kt)("inlineCode",{parentName:"li"},"https://gitee.com/focus-creative-games/hybridclr_unity.git#{tag}"))),(0,n.kt)("p",null,"\u60f3\u5b89\u88c5\u67d0\u4e2a\u5206\u652f\u6216\u8005tag\u7248\u672c\uff0c\u8bf7\u5728\u5730\u5740\u540e\u52a0\u4e0a",(0,n.kt)("inlineCode",{parentName:"p"},"#{tag}"),"\uff0c\u5982 ",(0,n.kt)("inlineCode",{parentName:"p"},"https://gitee.com/focus-creative-games/hybridclr_unity.git#v3.0.1"),"\u3002"),(0,n.kt)("p",null,(0,n.kt)("img",{alt:"add package",src:i(5283).Z,width:"808",height:"223"})),(0,n.kt)("p",null,"\u4e0d\u719f\u6089\u4eceurl\u5b89\u88c5package\u7684\u8bf7\u770b",(0,n.kt)("a",{parentName:"p",href:"https://docs.unity3d.com/Manual/upm-ui-giturl.html"},"install from giturl"),"\u3002"),(0,n.kt)("h3",{id:"\u4ece\u672c\u5730\u6587\u4ef6\u5b89\u88c5"},"\u4ece\u672c\u5730\u6587\u4ef6\u5b89\u88c5"),(0,n.kt)("p",null,"\u5c06\u4ed3\u5e93clone\u5230\u672c\u5730\u540e\uff0c\u76ee\u5f55\u6539\u540d\u4e3a",(0,n.kt)("inlineCode",{parentName:"p"},"com.code-philosophy.hybridclr"),"\uff08v3.0.0\u4e4b\u524d\u7684\u7248\u672c\u8bf7\u7528 ",(0,n.kt)("inlineCode",{parentName:"p"},"com.focus-creative-games.hybridclr_unity"),"\uff09\uff0c\u518d\u76f4\u63a5\u79fb\u5230\u9879\u76ee\u7684Packages\u76ee\u5f55\u5373\u53ef\u3002"),(0,n.kt)("h2",{id:"\u66f4\u65b0-comcode-philosophyhybridclr"},"\u66f4\u65b0 com.code-philosophy.hybridclr"),(0,n.kt)("p",null,"\u66f4\u65b0\u5b8ccom.code-philosophy.hybridclr\u540e\u9700\u8981\u91cd\u65b0\u8fd0\u884c",(0,n.kt)("inlineCode",{parentName:"p"},"HybridCLR/Installer"),"\u3002"),(0,n.kt)("h2",{id:"\u521d\u59cb\u5316hybridclr"},"\u521d\u59cb\u5316HybridCLR"),(0,n.kt)("p",null,"\u4e3a\u4e86\u51cf\u5c11package\u81ea\u8eab\u5927\u5c0f\uff0c\u6709\u4e00\u4e9b\u6587\u4ef6\u9700\u8981\u4eceUnity Editor\u7684\u5b89\u88c5\u76ee\u5f55\u590d\u5236\u3002\u56e0\u6b64\u5b89\u88c5\u5b8c\u63d2\u4ef6\u540e\uff0c\u8fd8\u9700\u8981\u4e00\u4e2a\u989d\u5916\u7684\u521d\u59cb\u5316\u8fc7\u7a0b\u3002"),(0,n.kt)("p",null,"\u70b9\u51fb\u83dc\u5355 ",(0,n.kt)("inlineCode",{parentName:"p"},"HybridCLR/Installer..."),"\uff0c\u5f39\u51fa\u5b89\u88c5\u754c\u9762\u3002\u5728\u70b9\u51fb\u5b89\u88c5\u4e4b\u524d\uff0c\u53ef\u80fd\u9700\u8981\u4e00\u4e9b\u8bbe\u7f6e\u3002\u7531\u4e8e\u968f\u7740\u7248\u672c\u53d8\u5316\uff0cInstaller\u4e00\u76f4\u5728\u8c03\u6574\uff0c\u8bf7\u6839\u636e\u4f60\u5f53\u524d\u7248\u672c\u8bfb\u53d6\u4e0b\u9762\u5bf9\u5e94\u7684\u8bf4\u660e\u3002"),(0,n.kt)("h3",{id:"\u5982\u679c\u4f60\u7684\u7248\u672c--v205"},"\u5982\u679c\u4f60\u7684\u7248\u672c >= v2.0.5"),(0,n.kt)("p",null,"com.code-philosophy.hybridclr\u4e2d ",(0,n.kt)("inlineCode",{parentName:"p"},"Data~/hybridclr_version.json")," \u6587\u4ef6\u4e2d\u5df2\u7ecf\u914d\u7f6e\u4e86\u5f53\u524dpackage\u7248\u672c\u5bf9\u5e94\u7684\u517c\u5bb9 hybridclr\u53cail2cpp_plus\u7684\u5206\u652f\u6216\u8005tag\uff0c\nInstaller\u4f1a\u5b89\u88c5\u914d\u7f6e\u4e2d\u6307\u5b9a\u7684\u7248\u672c\uff0c\u4e0d\u518d\u652f\u6301\u81ea\u5b9a\u4e49\u5f85\u5b89\u88c5\u7684\u7248\u672c\u3002"),(0,n.kt)("p",null,"\u914d\u7f6e\u7c7b\u4f3c\u5982\u4e0b\uff1a"),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-json"},'{\n "versions": [\n {\n "unity_version":"2019",\n "hybridclr" : { "branch":"v2.0.1"},\n "il2cpp_plus": { "branch":"v2019-2.0.1"}\n },\n {\n "unity_version":"2020",\n "hybridclr" : { "branch":"v2.0.1"},\n "il2cpp_plus": { "branch":"v2020-2.0.1"}\n },\n {\n "unity_version":"2021",\n "hybridclr" : { "branch":"v2.0.1"},\n "il2cpp_plus": { "branch":"v2021-2.0.1"}\n }\n ]\n}\n')),(0,n.kt)("p",null,"\u5982\u679c\u4f60\u4e00\u5b9a\u8981\u5b89\u88c5\u5176\u4ed6\u7248\u672c\u7684hybridclr\u6216il2cpp_plus\uff0c\u4fee\u6539\u8be5\u914d\u7f6e\u6587\u4ef6\u4e2d\u7684branch\u4e3a\u76ee\u6807\u5206\u652f\u6216\u8005tag\u3002\u7edd\u5927\u591a\u6570\u60c5\u51b5\u4e0b\uff0c\u76f4\u63a5\u70b9\u51fb",(0,n.kt)("inlineCode",{parentName:"p"},"\u5b89\u88c5"),"\u9ed8\u8ba4\u4ece\u8fdc\u7a0b\u4ed3\u5e93\u4e0b\u8f7d\u5b89\u88c5\u5373\u53ef\u3002\u5b89\u88c5\u6210\u529f\u540e\uff0c\u63a7\u5236\u53f0\u4f1a\u6253\u5370",(0,n.kt)("inlineCode",{parentName:"p"},"\u5b89\u88c5\u6210\u529f"),"\u65e5\u5fd7\u3002\u5982\u4e0b\u56fe\u6240\u793a\u3002"),(0,n.kt)("p",null,(0,n.kt)("img",{alt:"install_default",src:i(8648).Z,width:"802",height:"196"})),(0,n.kt)("p",null,"\u4ece\u7248\u672c2.3.1\u8d77\u65b0\u589e\u652f\u6301\u76f4\u63a5\u4ece\u672c\u5730\u81ea\u5df1\u5236\u4f5c\u7684\u5305\u542bhybridclr\u7684libil2cpp\u76ee\u5f55\u590d\u5236\u5b89\u88c5\u3002\u5982\u679c\u4f60\u7f51\u7edc\u4e0d\u597d\uff0c\u6216\u8005\u6ca1\u6709\u5b89\u88c5git\u5bfc\u81f4\u65e0\u6cd5\u4ece\u4ed3\u5e93\u8fdc\u7a0b\u4e0b\u8f7d\u5b89\u88c5\uff0c\u5219\u53ef\u4ee5\u5148\u5c06 ",(0,n.kt)("a",{parentName:"p",href:"https://github.com/focus-creative-games/il2cpp_plus"},"il2cpp_plus"),"\u548c",(0,n.kt)("a",{parentName:"p",href:"https://github.com/focus-creative-games/hybridclr"},"hybridclr"),"\u4e0b\u8f7d\u5230\u672c\u5730\u540e\uff0c\u518d\u6839\u636e\u4e0b\u9762",(0,n.kt)("strong",{parentName:"p"},"\u5b89\u88c5\u539f\u7406"),"\u5c0f\u8282\u7684\u6587\u6863\uff0c\u7531\u8fd9\u4e24\u4e2a\u4ed3\u5e93\u5408\u5e76\u51fa\u542bhybridclr\u7684libil2cpp\u76ee\u5f55\uff0c\u63a5\u7740\u5728",(0,n.kt)("inlineCode",{parentName:"p"},"Installer"),"\u5b89\u88c5\u754c\u9762\u4e2d\u542f\u7528",(0,n.kt)("inlineCode",{parentName:"p"},"\u4ece\u672c\u5730\u590d\u5236libil2cpp"),"\u9009\u9879\uff0c\u9009\u62e9\u4f60\u5236\u4f5c\u7684libil2cpp\u76ee\u5f55\uff0c\u518d\u70b9\u51fb",(0,n.kt)("inlineCode",{parentName:"p"},"\u5b89\u88c5"),"\u6267\u884c\u5b89\u88c5\u3002\u5982\u4e0b\u56fe\u6240\u793a\u3002"),(0,n.kt)("p",null,(0,n.kt)("img",{alt:"install",src:i(5568).Z,width:"814",height:"216"})),(0,n.kt)("h3",{id:"\u5982\u679c\u4f60\u7684\u7248\u672c--1120"},"\u5982\u679c\u4f60\u7684\u7248\u672c >= 1.1.20"),(0,n.kt)("p",null,"com.code-philosophy.hybridclr\u4e2d ",(0,n.kt)("inlineCode",{parentName:"p"},"Data~/hybridclr_version.json")," \u6587\u4ef6\u4e2d\u5df2\u7ecf\u914d\u7f6e\u4e86\u5f53\u524dpackage\u7248\u672c\u5bf9\u5e94\u7684\u517c\u5bb9 hybridclr\u53cail2cpp_plus\u7684\u7248\u672c\uff0c\nInstaller\u4f1a\u5b89\u88c5\u914d\u7f6e\u4e2d\u6307\u5b9a\u7684\u7248\u672c\uff0c\u4e0d\u518d\u652f\u6301\u81ea\u5b9a\u4e49\u5f85\u5b89\u88c5\u7684\u7248\u672c\u3002"),(0,n.kt)("p",null,"\u914d\u7f6e\u7c7b\u4f3c\u5982\u4e0b\uff1a"),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-json"},'{\n "versions": [\n {\n "unity_version":"2019",\n "hybridclr" : { "branch":"main", "hash":"531f98365eebce5d1390175be2b41c41e217d918"},\n "il2cpp_plus": { "branch":"2019-main", "hash":"ebe5190b0404d1857832bd1d52ebec7c3730a01d"}\n },\n {\n "unity_version":"2020",\n "hybridclr" : { "branch":"main", "hash":"531f98365eebce5d1390175be2b41c41e217d918"},\n "il2cpp_plus": { "branch":"2020-main", "hash":"c6cf54285381d0b03a58126e0d39b6e4d11937b7"}\n },\n {\n "unity_version":"2021",\n "hybridclr" : { "branch":"main", "hash":"531f98365eebce5d1390175be2b41c41e217d918"},\n "il2cpp_plus": { "branch":"2021-main", "hash":"99cd1cbbfc1f637460379e81c9a7776cd3e662ad"}\n }\n ]\n}\n\n')),(0,n.kt)("p",null,"\u5982\u679c\u4f60\u60f3\u5b89\u88c5\u5176\u4ed6\u7248\u672c\u7684hybridclr\u6216il2cpp_plus\uff0c\u4fee\u6539\u8be5\u914d\u7f6e\u6587\u4ef6\u4e2d\u7684branch\u548chash\u5373\u53ef\u3002\u76f4\u63a5\u70b9\u51fb",(0,n.kt)("inlineCode",{parentName:"p"},"\u5b89\u88c5"),"\u5b8c\u6210\u5b89\u88c5\u3002\u5b89\u88c5\u6210\u529f\u540e\uff0c\u63a7\u5236\u53f0\u4f1a\u6253\u5370",(0,n.kt)("inlineCode",{parentName:"p"},"\u5b89\u88c5\u6210\u529f"),"\u65e5\u5fd7\u3002"),(0,n.kt)("h3",{id:"\u5982\u679c\u4f60\u7684package\u7248\u672c--1119"},"\u5982\u679c\u4f60\u7684package\u7248\u672c <= 1.1.19"),(0,n.kt)("p",null,"\u586b\u5199\u4f60\u8981\u5b89\u88c5\u7684hybridclr\u548cil2cpp_plus\u4ed3\u5e93\u7684 commit id\u6216branch\u6216tag\u3002\u5982\u679chybridclr\u7684\u7248\u672c\u53f7\u7559\u7a7a\uff0c\u5219\u5b89\u88c5hybridclr\u4ed3\u5e93main\u5206\u652f\u7684\u6700\u65b0\u7248\u672c\u3002\n\u5982\u679cil2cpp_plus\u7684\u7248\u672c\u53f7\u7559\u7a7a\uff0c\u5219\u5b89\u88c5\u76f8\u5e94\u5e74\u5ea6\u7248\u672c\u4e3b\u5206\u652f\uff08\u59822020-main\uff09\u7684\u6700\u65b0\u7248\u672c\u3002"),(0,n.kt)("p",null,(0,n.kt)("strong",{parentName:"p"},"hybridclr_uniyt\u5206\u652f\u3001hybridclr\u4ed3\u5e93\u7684\u5206\u652f\u8ddfil2cpp_plus\u4ed3\u5e93\u5206\u652f\u5fc5\u987b\u5339\u914d"),"\u3002\u5982\u679c\u4f60com.code-philosophy.hybridclr\u4f7f\u7528\u4e86main\u5206\u652f\uff0c\u5219hybridclr\u5fc5\u987b\u4f7f\u7528main\u5206\u652f\uff0cil2cpp_plus\u5fc5\u987b\u4f7f\u7528",(0,n.kt)("inlineCode",{parentName:"p"},"{version}-main"),"\uff0c\u5982\u679c\u4f60hybridclr_unity\u4f7f\u7528\u4e861.0\u5206\u652f\uff0c \u5219hybridclr\u5fc5\u987b\u4f7f\u7528",(0,n.kt)("inlineCode",{parentName:"p"},"1.0"),"\u5206\u652f\uff0cil2cpp_plus\u5fc5\u987b\u4f7f\u7528",(0,n.kt)("inlineCode",{parentName:"p"},"{version}-1.0"),"\u5206\u652f\u3002 \u5982\u679c\u4f60\u4f7f\u7528\u4e86\u67d0\u4e2atag\u7684\u7248\u672c\uff0c\u786e\u4fdd\u8fd9\u4e2atag\u6240\u5c5e\u7684\u5206\u652f\u5339\u914d\u3002"),(0,n.kt)("p",null,"hybridclr\u4ed3\u5e93\u63a8\u8350\u586b\u5199",(0,n.kt)("inlineCode",{parentName:"p"},"1.0"),"\uff0c\u5373\u6bcf\u6b21\u5b89\u88c51.0\u5206\u652f\u7684\u6700\u65b0\u7248\u672c\uff1bil2cpp_plus\u4ed3\u5e93\u63a8\u8350\u586b",(0,n.kt)("inlineCode",{parentName:"p"},"{\u5e74\u5ea6\u7248\u672c}-1.0"),"\uff08\u59822020-1.0\uff09\uff0c\u5373\u6bcf\u6b21\u5b89\u88c5",(0,n.kt)("inlineCode",{parentName:"p"},"{\u5e74\u5ea6\u7248\u672c}-1.0"),"\u5206\u652f\u7684\u6700\u65b0\u7248\u672c\u3002\u5982\u56fe\uff1a"),(0,n.kt)("p",null,(0,n.kt)("img",{alt:"image",src:i(1093).Z,width:"1047",height:"320"})),(0,n.kt)("p",null,"\u76ee\u524d\u5df2\u7ecf\u53d1\u5e03\u4e861.0.1\u7a33\u5b9a\u6b63\u5f0f\u7248\u672c\uff0c\u540c\u6837\u63a8\u8350\u8ffd\u6c42\u7a33\u5b9a\u7684\u9879\u76ee\u4f7f\u7528\u3002com.code-philosophy.hybridclr\u53d6 ",(0,n.kt)("inlineCode",{parentName:"p"},"1.0.1-release"),"\uff0chybridclr \u7248\u672c\u53d6 ",(0,n.kt)("inlineCode",{parentName:"p"},"1.0.1-release"),"\uff0cil2cpp_plus\u7248\u672c\u53d6 ",(0,n.kt)("inlineCode",{parentName:"p"},"{version}-1.0.1-relase"),"\u3002"),(0,n.kt)("p",null,"\u5b8c\u6210\u4ee5\u4e0a\u8bbe\u7f6e\u540e\uff0c\u70b9\u51fb",(0,n.kt)("inlineCode",{parentName:"p"},"install"),"\u6309\u94ae\u5b8c\u6210\u5b89\u88c5\u3002\u5b89\u88c5\u6210\u529f\u540e\uff0c\u63a7\u5236\u53f0\u4f1a\u6253\u5370",(0,n.kt)("inlineCode",{parentName:"p"},"\u5b89\u88c5\u6210\u529f"),"\u65e5\u5fd7\u3002"),(0,n.kt)("p",null,"\u7531\u4e8e\u5b89\u88c5\u8fc7\u7a0b\u9700\u8981\u62c9\u53d6hybridclr\u53cail2cpp_plus\u4ed3\u5e93\uff0c\u6709\u53ef\u80fd\u4f1a\u56e0\u4e3a\u7f51\u7edc\u6545\u969c\u800c\u5931\u8d25\uff0c\u5982\u679c\n\u53d1\u73b0\u5931\u8d25\u65f6 ",(0,n.kt)("inlineCode",{parentName:"p"},"HybridCLRData/hybridclr_repo"),"\u6216",(0,n.kt)("inlineCode",{parentName:"p"},"HybridCLRData/il2cpp_plus_repo"),"\u4e3a\u7a7a\uff0c\u8bf7\u518d\u6b21\u5c1d\u8bd5\u3002"),(0,n.kt)("p",null,"\u6700\u5e38\u89c1\u5931\u8d25\u539f\u56e0\u4e3agit\u672a\u5b89\u88c5\uff0c\u6216\u8005\u5b89\u88c5git\u540e\u672a\u91cd\u542fUnityEditor\u548cUnityHub\u3002\u5982\u679c\u4f60\u786e\u4fe1\u5b89\u88c5\u4e86git\uff0ccmd\u4e2d\u4e5f\u786e\u5b9e\u80fd\u8fd0\u884cgit\uff0c\u5219\u5c1d\u8bd5\u91cd\u542f\u7535\u8111\u3002"),(0,n.kt)("p",null,"\u5982\u679c\u56e0\u4e3a\u5404\u79cd\u7279\u6b8a\u539f\u56e0\u672a\u80fd\u5b8c\u6210\u81ea\u52a8\u5316\u5b89\u88c5\uff0c\u8bf7\u53c2\u7167\u4e0b\u9762\u7684",(0,n.kt)("strong",{parentName:"p"},"\u5b89\u88c5\u539f\u7406"),"\u624b\u52a8\u6a21\u62df\u6574\u4e2a\u5b89\u88c5\u8fc7\u7a0b\u3002"),(0,n.kt)("h2",{id:"\u5b89\u88c5\u540e\u7684\u7279\u6b8a\u5904\u7406"},"\u5b89\u88c5\u540e\u7684\u7279\u6b8a\u5904\u7406"),(0,n.kt)("h3",{id:"webgl\u5e73\u53f0"},"WebGL\u5e73\u53f0"),(0,n.kt)("p",null,"\u7531\u4e8eUnity\u81ea\u8eab\u539f\u56e0\uff0cWebGL\u5e73\u53f0\u5fc5\u987b\u5168\u5c40\u5b89\u88c5\u3002 \u8bf7\u67e5\u9605\u4e0b\u9762\u7ae0\u8282\u7684",(0,n.kt)("inlineCode",{parentName:"p"},"\u5168\u5c40\u5b89\u88c5"),"\u6587\u6863\u3002"),(0,n.kt)("h3",{id:"unity-2021"},"Unity 2021"),(0,n.kt)("admonition",{type:"caution"},(0,n.kt)("p",{parentName:"admonition"},(0,n.kt)("strong",{parentName:"p"},"\u5982\u679c\u4f60\u7684com.code-philosophy.hybridclr\u7248\u672c >= v2.0.1"),"\uff0c\u7531\u4e8e\u5df2\u7ecf\u4f7f\u7528MonoHook\u6280\u672f\u5728\u4e0d\u4fee\u6539UnityEditor.CoreModule.dll\u7684\u60c5\u51b5\u4e0b\u4e5f\u80fd\u590d\u5236\u51fa\u88c1\u526a\u540e\u7684AOT dll\uff0c",(0,n.kt)("strong",{parentName:"p"},"\u4e0d\u9700\u8981"),"\u6267\u884c\u4ee5\u4e0b\u64cd\u4f5c\u3002")),(0,n.kt)("p",null,"\u8865\u5145\u5143\u6570\u636e\u53ca",(0,n.kt)("inlineCode",{parentName:"p"},"HybridCLR/Generate/*"),"\u4e0b\u7684\u90e8\u5206\u547d\u4ee4\u4f9d\u8d56\u88c1\u51cf\u540e\u7684AOT dll\u3002\u4f46Unity 2021\u7248\u672c\uff082019\u30012020\u4e0d\u9700\u8981\uff09\u6253\u5305",(0,n.kt)("inlineCode",{parentName:"p"},"iOS\u5e73\u53f0"),"(\u5176\u4ed6\u5e73\u53f0\u4e0d\u9700\u8981)\u65f6\uff0c\u7531\u4e8eUnity Editor\u672a\u63d0\u4f9b\u516c\u5f00\u63a5\u53e3\u53ef\u4ee5\u590d\u5236\u51fatarget\u4e3aiOS\u65f6\u7684\u88c1\u526a\u540e\u7684AOT dll\uff0c\u6545\u5fc5\u987b\u4f7f\u7528\u4fee\u6539\u540e\u7684UnityEditor.CoreModule.dll\u8986\u76d6Unity\u81ea\u5e26\u7684\u76f8\u5e94\u6587\u4ef6\u3002"),(0,n.kt)("p",null,"\u5177\u4f53\u64cd\u4f5c\u4e3a\u5c06 ",(0,n.kt)("inlineCode",{parentName:"p"},"{package\u76ee\u5f55}/Data~/ModifiedUnityAssemblies/2021.3.x/UnityEditor.CoreModule-{Win,Mac}.dll")," \u8986\u76d6 ",(0,n.kt)("inlineCode",{parentName:"p"},"{Editor\u5b89\u88c5\u76ee\u5f55}/Editor/Data/Managed/UnityEngine/UnityEditor.CoreModule.dll"),"\uff0c\u5177\u4f53\u76f8\u5173\u76ee\u5f55\u6709\u53ef\u80fd\u56e0\u4e3a\u64cd\u4f5c\u7cfb\u7edf\u6216\u8005Unity\u7248\u672c\u800c\u6709\u4e0d\u540c\u3002"),(0,n.kt)("p",null,(0,n.kt)("strong",{parentName:"p"},"\u7531\u4e8e\u6743\u9650\u95ee\u9898\uff0c\u8be5\u64cd\u4f5c\u65e0\u6cd5\u81ea\u52a8\u5b8c\u6210\uff0c\u9700\u8981\u4f60\u624b\u52a8\u6267\u884c\u590d\u5236\u64cd\u4f5c\u3002")),(0,n.kt)("p",null,(0,n.kt)("inlineCode",{parentName:"p"},"UnityEditor.CoreModule.dll")," \u6bcf\u4e2aUnity\u5c0f\u7248\u672c\u90fd\u4e0d\u76f8\u540c\uff0c\u6211\u4eec\u76ee\u524d\u6682\u65f6\u53ea\u63d0\u4f9b\u4e862021.3.1\u7248\u672c\uff0c\u5982\u9700\u5176\u4ed6\u7248\u672c\u8bf7\u81ea\u5df1\u624b\u52a8\u5236\u4f5c\uff0c\u8be6\u60c5\u8bf7\u89c1 ",(0,n.kt)("a",{parentName:"p",href:"/docs/basic/modifyunitydll"},"\u4fee\u6539Unity\u7f16\u8f91\u5668\u76f8\u5173dll"),"\u3002"),(0,n.kt)("h3",{id:"unity-2019"},"Unity 2019"),(0,n.kt)("p",null,"\u4e3a\u4e86\u652f\u63012019\uff0c\u9700\u8981\u4fee\u6539il2cpp\u751f\u6210\u7684\u6e90\u7801\uff0c\u56e0\u6b64\u6211\u4eec\u4fee\u6539\u4e862019\u7248\u672c\u7684il2cpp\u5de5\u5177\u3002\u6545Installer\u7684\u5b89\u88c5\u8fc7\u7a0b\u591a\u4e86\u4e00\u4e2a\u989d\u5916\u6b65\u9aa4\uff1a\u5c06 ",(0,n.kt)("inlineCode",{parentName:"p"},"{package}/Data~/ModifiedUnityAssemblies/2019.4.40/Unity.IL2CPP.dll")," \u590d\u5236\u5230 ",(0,n.kt)("inlineCode",{parentName:"p"},"{project}/HybridCLRData/LocalIl2CppData/il2cpp/build/deploy/net471/Unity.IL2CPP.dll")),(0,n.kt)("p",null,(0,n.kt)("strong",{parentName:"p"},"\u6ce8\u610f\uff0c\u8be5\u64cd\u4f5c\u5728Installer\u5b89\u88c5\u65f6\u81ea\u52a8\u5b8c\u6210\uff0c\u4e0d\u9700\u8981\u624b\u52a8\u64cd\u4f5c\u3002")),(0,n.kt)("admonition",{type:"tip"},(0,n.kt)("p",{parentName:"admonition"},"\u5bf9\u4e8e\u4f7f\u75282019.4.0-2019.4.39\u7248\u672c\u7684\u5f00\u53d1\u8005\uff0c\u8bf7\u5148\u5207\u6362\u52302019.4.40\u7248\u672c\u5b8c\u6210\u5b89\u88c5\uff0c\u518d\u5207\u56de\u4f60\u5f53\u524d\u7248\u672c\u3002")),(0,n.kt)("h2",{id:"\u5728\u975e\u517c\u5bb9\u7684unity\u7248\u672c\u4e2d\u4f7f\u7528hybridclr"},"\u5728\u975e\u517c\u5bb9\u7684Unity\u7248\u672c\u4e2d\u4f7f\u7528HybridCLR"),(0,n.kt)("p",null,"\u7531\u4e8e\u6211\u4eec\u6ca1\u6709\u5b8c\u5168\u6d4b\u8bd5\u6240\u6709Unity\u7248\u672c\uff0c\u5b9e\u9645\u4e0a\u4e00\u4e9b\u4e0d\u5728\u652f\u6301\u5217\u8868\u4e2d\u7684Unity\u7248\u672c\uff0c\u4e5f\u6709\u53ef\u80fd\u80fd\u6b63\u5e38\u4f7f\u7528HybridCLR\u3002\u5b89\u88c5\u65b9\u5f0f\u5982\u4e0b\uff1a"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"\u627e\u4e00\u4e2a\u79bb\u4f60\u7684\u7248\u672c\u6700\u8fd1\u7684\u5728\u652f\u6301\u5217\u8868\u4e2d\u7684\u7248\u672c\uff0c\u4f8b\u5982\u4f60\u7684\u7248\u672c\u53f7\u4e3a 2021.2.20,\u5219\u79bb\u4f60\u6700\u65b0\u7684\u7248\u672c\u4e3a2021.3.0\u3002"),(0,n.kt)("li",{parentName:"ul"},"\u5148\u5c06\u4f60\u7684Unity\u5de5\u7a0b\u5207\u6362\u5230\u8fd9\u4e2a\u6700\u8fd1\u7684\u53d7\u652f\u6301\u7684\u7248\u672c\uff0c\u5b89\u88c5HybridCLR\u3002"),(0,n.kt)("li",{parentName:"ul"},"\u5207\u6362\u56de\u4f60\u7684Unity\u7248\u672c\u3002"),(0,n.kt)("li",{parentName:"ul"},"\u5c1d\u8bd5\u6253\u5305\uff0c\u5982\u679c\u80fd\u987a\u5229\u8fd0\u884c\uff0c\u5219\u8868\u660eHybridCLR\u652f\u6301\u4f60\u8fd9\u4e2a\u7248\u672c\uff0c\u5982\u679c\u6709\u95ee\u9898\uff0c\u90a3\u8fd8\u662f\u5347\u7ea7\u7248\u672c\u5427\u3002")),(0,n.kt)("p",null,"\u5982\u679c\u4f60\u4e00\u5b9a\u8981\u4f7f\u7528\u8be5\u7248\u672c\uff0c\u53ef\u4ee5\u8054\u7cfb\u6211\u4eec\u63d0\u4f9b",(0,n.kt)("a",{parentName:"p",href:"/docs/business/intro"},"\u5546\u4e1a\u6280\u672f\u652f\u6301"),"\u3002"),(0,n.kt)("h2",{id:"hybridclrinstaller\u5de5\u4f5c\u539f\u7406"},(0,n.kt)("inlineCode",{parentName:"h2"},"HybridCLR/Installer"),"\u5de5\u4f5c\u539f\u7406"),(0,n.kt)("p",null,"\u672c\u8282\u53ea\u662f\u4ecb\u7ecd\u539f\u7406\uff0c",(0,n.kt)("strong",{parentName:"p"},"\u5b89\u88c5libil2cpp\u7684\u64cd\u4f5c\u5df2\u7531installer\u5b8c\u6210\uff0c\u5e76\u4e0d\u9700\u8981\u4f60\u624b\u52a8\u64cd\u4f5c"),"\u3002"),(0,n.kt)("p",null,"HybridCLR\u5b89\u88c5\u8fc7\u7a0b\u4e3b\u8981\u5305\u542b\u8fd9\u51e0\u90e8\u5206\uff1a"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"\u5236\u4f5c\u652f\u6301\u70ed\u66f4\u65b0\u7684libil2cpp"),(0,n.kt)("li",{parentName:"ul"},"\u672c\u5730\u6216\u8005\u5168\u5c40\u5b89\u88c5\uff0c\u4f7f\u65b0\u7248\u672clibil2cpp\u751f\u6548"),(0,n.kt)("li",{parentName:"ul"},"\u5bf9Unity Editor\u7684\u5c11\u91cf\u6539\u9020")),(0,n.kt)("h3",{id:"\u66ff\u6362libil2cpp\u4ee3\u7801"},"\u66ff\u6362libil2cpp\u4ee3\u7801"),(0,n.kt)("p",null,"\u539f\u59cb\u7684libil2cpp\u4ee3\u7801\u662fAOT\u8fd0\u884c\u65f6\uff0c\u9700\u8981\u66ff\u6362\u6210\u6539\u9020\u540e\u7684libil2cpp\u624d\u80fd\u652f\u6301\u70ed\u66f4\u65b0\u3002\u6539\u9020\u540e\u7684libil2cpp\u7531\u4e24\u90e8\u5206\u6784\u6210"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"il2cpp_plus"),(0,n.kt)("li",{parentName:"ul"},"hybridclr")),(0,n.kt)("p",null,"il2cpp_plus\u4ed3\u5e93\u4e3a\u5bf9\u539f\u59cblibil2cpp\u4f5c\u4e86\u5c11\u91cf\u4fee\u6539\u4ee5\u652f\u6301\u52a8\u6001",(0,n.kt)("strong",{parentName:"p"},"register"),"\u5143\u6570\u636e\u7684\u7248\u672c\uff08\u6539\u4e86\u51e0\u767e\u884c\u4ee3\u7801\uff09\u3002\u8fd9\u4e2a\u4ed3\u5e93\u4e0e\u539f\u59cblibil2cpp\u4ee3\u7801\u9ad8\u5ea6\n\u76f8\u4f3c\u3002hybridclr\u4e3a\u89e3\u91ca\u5668\u90e8\u5206\u7684\u6838\u5fc3\u4ee3\u7801\uff0c\u5305\u542b\u5143\u6570\u636e\u52a0\u8f7d\u3001\u4ee3\u7801transform(\u7f16\u8bd1)\u3001\u4ee3\u7801\u89e3\u91ca\u6267\u884c\u3002"),(0,n.kt)("p",null,"\u5982\u4e0b\u56fe\u6240\u793a\uff0c\u5c06",(0,n.kt)("inlineCode",{parentName:"p"},"il2cpp_plus/libil2cpp"),"\u76ee\u5f55\u548c",(0,n.kt)("inlineCode",{parentName:"p"},"hybridclr/hybridclr"),"\u76ee\u5f55\u5408\u5e76\uff0c\u5236\u4f5c\u51fa\u6700\u7ec8\u7684\u652f\u6301\u70ed\u66f4\u65b0\u7684libil2cpp\u3002"),(0,n.kt)("p",null,(0,n.kt)("img",{alt:"merge_hybridclr_dir",src:i(7441).Z,width:"811",height:"626"})),(0,n.kt)("h3",{id:"\u672c\u5730\u5b89\u88c5"},"\u672c\u5730\u5b89\u88c5"),(0,n.kt)("p",null,"Unity\u5141\u8bb8\u4f7f\u7528\u73af\u5883\u53d8\u91cf",(0,n.kt)("inlineCode",{parentName:"p"},"UNITY_IL2CPP_PATH"),"\u81ea\u5b9a\u4e49",(0,n.kt)("inlineCode",{parentName:"p"},"il2cpp"),"\u7684\u4f4d\u7f6e\uff0c\u56e0\u6b64\u53ef\u4ee5\u5728\u9879\u76ee\u672c\u5730\u521b\u5efail2cpp\u76ee\u5f55\uff0c\u7528\u6539\u9020\u540e\u7684libil2cpp\u66ff\u6362il2cpp\u76ee\u5f55\u4e0b\u7684libil2cpp\u76ee\u5f55\uff0c\n\u518d\u5c06",(0,n.kt)("inlineCode",{parentName:"p"},"UNITY_IL2CPP_PATH"),"\u73af\u5883\u53d8\u91cf\u6307\u5411\u8be5\u76ee\u5f55\u3002\u5927\u81f4\u8fc7\u7a0b\u5982\u4e0b\uff1a"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"\u4eceEditor\u5b89\u88c5\u76ee\u5f55\u590d\u5236il2cpp\u76ee\u5f55\u5230",(0,n.kt)("inlineCode",{parentName:"li"},"{project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp")),(0,n.kt)("li",{parentName:"ul"},"\u4ececlone il2cpp_plus\u548chybridclr\u4ed3\u5e93\uff0c\u5236\u4f5c\u51fa\u6700\u7ec8\u7684libil2cpp\u76ee\u5f55"),(0,n.kt)("li",{parentName:"ul"},"\u5c06\u6700\u7ec8\u7684libil2cpp\u76ee\u5f55\u66ff\u6362 ",(0,n.kt)("inlineCode",{parentName:"li"},"{project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp/libil2cpp")),(0,n.kt)("li",{parentName:"ul"},"\u4eceEditor\u5b89\u88c5\u76ee\u5f55\u590d\u5236 ",(0,n.kt)("inlineCode",{parentName:"li"},"MonoBleedingEdge")," \u76ee\u5f55\u5230 ",(0,n.kt)("inlineCode",{parentName:"li"},"{project}/HybridCLRData/LocalIl2CppData-{platform}/MonoBleedingEdge")),(0,n.kt)("li",{parentName:"ul"},"\u5176\u4ed6\u5904\u7406\u3002\u59822019\u7248\u672c\u5c06 ",(0,n.kt)("inlineCode",{parentName:"li"},"{package}/Data~/ModifiedUnityAssemblies/2019.4.40/Unity.IL2CPP.dll")," \u590d\u5236\u5230 ",(0,n.kt)("inlineCode",{parentName:"li"},"{project}/HybridCLRData/LocalIl2CppData/il2cpp/build/deploy/net471/Unity.IL2CPP.dll"))),(0,n.kt)("admonition",{type:"tip"},(0,n.kt)("p",{parentName:"admonition"},"com.code-philosophy.hybridclr \u5305\u4fee\u6539\u4e86\u672cUnityEditor",(0,n.kt)("strong",{parentName:"p"},"\u8fdb\u7a0b\u5185"),"\u7684\u73af\u5883\u53d8\u91cf",(0,n.kt)("inlineCode",{parentName:"p"},"UNITY_IL2CPP_PATH"),"\uff0c\u5e76\u4e0d\u4f1a\u5f71\u54cd\u5176\u4ed6Unity\u9879\u76ee\u3002")),(0,n.kt)("p",null,"\u521b\u5efa\u4e0a\u5c42",(0,n.kt)("inlineCode",{parentName:"p"},"LocalIl2CppData-{platform}"),"\u76ee\u5f55\uff0c\u800c\u4e0d\u662f\u53ea\u521b\u5efail2cpp\u662f\u56e0\u4e3a\u5b9e\u6d4b\u53d1\u73b0\u4ec5\u4ec5\u6307\u5b9ail2cpp\u76ee\u5f55\u4f4d\u7f6e\u662f\u4e0d\u591f\u7684\uff0c\u6253\u5305\u65f6Unity\u9690\u542b\u5047\u8bbe\u4e86il2cpp\u540c\u7ea7\u6709\u4e00\u4e2a",(0,n.kt)("inlineCode",{parentName:"p"},"MonoBleedingEdge"),"\u76ee\u5f55\uff0c\u6240\u4ee5\u521b\u5efa\u4e86\u4e0a\u7ea7\u76ee\u5f55\uff0c\u5c06il2cpp\u53caMonoBleedingEdge\u76ee\u5f55\u90fd\u590d\u5236\u8fc7\u6765\u3002"),(0,n.kt)("p",null,"\u56e0\u4e3a\u4e0d\u540c\u5e73\u53f0Editor\u81ea\u5e26\u7684il2cpp\u76ee\u5f55\u7565\u6709\u4e0d\u540c\uff0cLocalIl2CppData\u8981\u533a\u5206platform\u3002"),(0,n.kt)("h3",{id:"\u5168\u5c40\u5b89\u88c5"},"\u5168\u5c40\u5b89\u88c5"),(0,n.kt)("p",null,"\u5168\u5c40\u5b89\u88c5\u9700\u8981\u66ff\u6362\uff08\u6216\u94fe\u63a5\uff09Editor\u5b89\u88c5\u76ee\u5f55\u7684libil2cpp\u76ee\u5f55(Win\u4e0b\u4e3a{editor}/Data/il2cpp/libil2cpp\uff0cMac\u7c7b\u4f3c)\u4e3a\u6539\u9020\u540e\u7684libil2cpp\uff0c\u53ca\u989d\u5916\u66ff\u6362\u4e00\u4e9b\u4fee\u6539\u7684\u6587\u4ef6\uff08\u59822019\u8fd8\u9700\u8981\u4fee\u6539Unity.IL2CPP.dll\uff09\u3002\u6709\u51e0\u4e2a\u7f3a\u9677\uff1a"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"\u56e0\u4e3a\u76ee\u5f55\u6743\u9650\u539f\u56e0\uff0c\u53ef\u80fd\u65e0\u6cd5\u81ea\u52a8\u5b8c\u6210"),(0,n.kt)("li",{parentName:"ul"},"\u4f1a\u5f71\u54cd\u5176\u4ed6\u4e0d\u4f7f\u7528hybridclr\u7684\u9879\u76ee"),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("inlineCode",{parentName:"li"},"HybridCLR/Generate/xxxx"),"\u64cd\u4f5c\u9700\u8981\u4fee\u6539libil2cpp\u76ee\u5f55\u4e0b\u7684\u6587\u4ef6\uff0c\u6709\u53ef\u80fd\u76ee\u5f55\u6743\u9650\u7684\u539f\u56e0\u800c\u5931\u8d25\u3002")),(0,n.kt)("p",null,"\u4f7f\u7528",(0,n.kt)("inlineCode",{parentName:"p"},"HybridCLR/Installer"),"\u5b8c\u6210\u5b89\u88c5\u540e\uff0c\u5728",(0,n.kt)("inlineCode",{parentName:"p"},"HybridCLR/Settings"),"\u4e2d\u5f00\u542f ",(0,n.kt)("inlineCode",{parentName:"p"},"useGlobalIl2Cpp")," \u9009\u9879\u6765\u542f\u52a8\u5168\u5c40\u5b89\u88c5\uff0c\u6b64\u65f6\u4f1a\u6e05\u9664\u73af\u5883\u53d8\u91cf",(0,n.kt)("inlineCode",{parentName:"p"},"UNITY_IL2CPP_PATH"),"\u3002"),(0,n.kt)("p",null,"\u5982\u679c\u4f60\u4f7f\u7528\u66ff\u6362\u76ee\u5f55\u7684\u65b9\u5f0f\u8fdb\u884c\u5168\u5c40\u5b89\u88c5\uff0c\u5e76\u4e14\u4f60\u7684com.code-philosophy.hybridclr\u7248\u672c >= 2.1.0\uff0c\u5219",(0,n.kt)("strong",{parentName:"p"},"\u7b2c\u4e00\u6b21"),"\u8986\u76d6libil2cpp\u524d\uff0c\u8bf7\u5148\u8fd0\u884c",(0,n.kt)("inlineCode",{parentName:"p"},"HybridCLR/Generate/Il2cppDef"),"\uff08\u53ea\u6b64\u4e00\u6b21\uff0c\u540e\u9762\u4e0d\u518d\u9700\u8981\uff0c\u9664\u975e\u4f60\u5207\u6362\u4e86\u9879\u76eeUnity\u7248\u672c\uff09\u4ee5\u751f\u6210\u6b63\u786e\u7684\u7248\u672c\u5b8f\uff0c\u518d\u8986\u76d6\u539f\u59cb\u7684libil2cpp\u76ee\u5f55\u3002",(0,n.kt)("strong",{parentName:"p"},"\u7b26\u53f7\u94fe\u63a5\u5b89\u88c5\u65b9\u5f0f\u6216\u8005com.code-philosophy.hybridclr\u7248\u672c\u4f4e\u4e8e2.1.0\u4e0d\u9700\u8981\u6267\u884c\u6b64\u64cd\u4f5c\uff0c\u76f4\u63a5\u8986\u76d6\u539f\u59cb\u7684libil2cpp\u76ee\u5f55\u5373\u53ef"),"\u3002"),(0,n.kt)("p",null,"\u7531\u4e8e\u6743\u9650\u539f\u56e0\uff0c\u5373\u4f7f\u662f\u5168\u5c40\u5b89\u88c5\uff0c",(0,n.kt)("inlineCode",{parentName:"p"},"Generate/xxx"),"\u547d\u4ee4\u4fee\u6539\u7684\u662f\u672c\u5730",(0,n.kt)("inlineCode",{parentName:"p"},"{project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp/libil2cpp"),"\u4e0b\u7684\u6587\u4ef6\u3002",(0,n.kt)("strong",{parentName:"p"},"\u8bf7\u6bcf\u6b21generate\u540e\u90fd\u5c06\u672c\u5730libil2cpp\u76ee\u5f55\u8986\u76d6\u5168\u5c40\u5b89\u88c5\u76ee\u5f55"),"\u3002"),(0,n.kt)("p",null,"\u6bcf\u6b21\u66ff\u6362libil2cpp\u76ee\u5f55\u975e\u5e38\u9ebb\u70e6\uff0c\u63a8\u8350\u4f7f\u7528\u94fe\u63a5\u5b89\u88c5\u76ee\u5f55\u7684libil2cpp\u76ee\u5f55\u5230\u672c\u5730libil2cpp\u76ee\u5f55\u65b9\u5f0f\u3002\u65b9\u6cd5\u5982\u4e0b\uff1a"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"Win\u5e73\u53f0\u3002\u4ee5\u7ba1\u7406\u5458\u6743\u9650\u6253\u5f00\u547d\u4ee4\u884c\u7a97\u53e3\uff0c\u5220\u9664\u6216\u8005\u91cd\u547d\u540d\u539flibil2cpp\uff0c\u7136\u540e\u8fd0\u884c ",(0,n.kt)("inlineCode",{parentName:"li"},'mklink /D "" "{project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp/libil2cpp"'),"\u3002"),(0,n.kt)("li",{parentName:"ul"},"Linux\u6216\u8005Mac\u5e73\u53f0\u3002\u4ee5\u7ba1\u7406\u5458\u6743\u9650\u6253\u5f00\u547d\u4ee4\u884c\u7a97\u53e3\uff0c\u5220\u9664\u6216\u8005\u91cd\u547d\u540d\u539flibil2cpp\uff0c\u7136\u540e\u8fd0\u884c ",(0,n.kt)("inlineCode",{parentName:"li"},'ln -s "{project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp/libil2cpp" "" '),"\u3002")),(0,n.kt)("p",null,"\u5bf9\u4e8e2019\u7248\u672c\u66ff\u6362 Unity.IL2CPP.dll\uff0c\u4e5f\u4f7f\u7528\u7c7b\u4f3c\u4e0a\u9762\u7684\u66ff\u6362\u6216\u8005\u8f6f\u94fe\u63a5\u7684\u65b9\u5f0f\u3002"),(0,n.kt)("h2",{id:"\u6ce8\u610f\u4e8b\u9879"},"\u6ce8\u610f\u4e8b\u9879"),(0,n.kt)("p",null,"\u7531\u4e8e Unity \u7684\u7f13\u5b58\u673a\u5236\uff0c\u66f4\u65b0 HybridCLR \u540e\uff0c\u4e00\u5b9a\u8981\u6e05\u9664 Library\\Il2cppBuildCache \u76ee\u5f55\uff0c\u4e0d\u7136\u6253\u5305\u65f6\u4e0d\u4f1a\u4f7f\u7528\u6700\u65b0\u7684\u4ee3\u7801\u3002\u5982\u679c\u4f60\u4f7f\u7528Installer\u6765\u81ea\u52a8\u5b89\u88c5\u6216\u8005\u66f4\u65b0HybridCLR\uff0c\u5b83\u4f1a\u81ea\u52a8\u6e05\u9664\u8fd9\u4e9b\u76ee\u5f55\uff0c\u4e0d\u9700\u8981\u4f60\u989d\u5916\u64cd\u4f5c\u3002"))}u.isMDXComponent=!0},5568:(e,t,i)=>{i.d(t,{Z:()=>l});const l=i.p+"assets/images/install-c1f84913c18dfa0406fc90db65085a56.jpg"},8648:(e,t,i)=>{i.d(t,{Z:()=>l});const l=i.p+"assets/images/install_default-c61d323cdd4133368bc575f35dd7a9ec.jpg"},5283:(e,t,i)=>{i.d(t,{Z:()=>l});const l=i.p+"assets/images/install_hybridclrunity_package-9a53b1ee8f7ffd8a700ed1f977ca74e3.jpg"},1093:(e,t,i)=>{i.d(t,{Z:()=>l});const l=i.p+"assets/images/install_version-bafc326c19c3b969342179d820ead842.jpg"},7441:(e,t,i)=>{i.d(t,{Z:()=>l});const l=i.p+"assets/images/merge_hybridclr_dir-04680fdb60dccd43bfd2593b4affd10e.jpg"},8581:(e,t,i)=>{i.d(t,{Z:()=>l});const l=i.p+"assets/images/select_il2cpp_modules-d895c3fb5390e04b53e40ada2b422239.jpg"}}]); \ No newline at end of file +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[220],{3905:(A,l,i)=>{i.d(l,{Zo:()=>c,kt:()=>y});var e=i(7294);function n(A,l,i){return l in A?Object.defineProperty(A,l,{value:i,enumerable:!0,configurable:!0,writable:!0}):A[l]=i,A}function t(A,l){var i=Object.keys(A);if(Object.getOwnPropertySymbols){var e=Object.getOwnPropertySymbols(A);l&&(e=e.filter((function(l){return Object.getOwnPropertyDescriptor(A,l).enumerable}))),i.push.apply(i,e)}return i}function a(A){for(var l=1;l=0||(n[i]=A[i]);return n}(A,l);if(Object.getOwnPropertySymbols){var t=Object.getOwnPropertySymbols(A);for(e=0;e=0||Object.prototype.propertyIsEnumerable.call(A,i)&&(n[i]=A[i])}return n}var r=e.createContext({}),o=function(A){var l=e.useContext(r),i=l;return A&&(i="function"==typeof A?A(l):a(a({},l),A)),i},c=function(A){var l=o(A.components);return e.createElement(r.Provider,{value:l},A.children)},d="mdxType",P={inlineCode:"code",wrapper:function(A){var l=A.children;return e.createElement(e.Fragment,{},l)}},m=e.forwardRef((function(A,l){var i=A.components,n=A.mdxType,t=A.originalType,r=A.parentName,c=p(A,["components","mdxType","originalType","parentName"]),d=o(i),m=n,y=d["".concat(r,".").concat(m)]||d[m]||P[m]||t;return i?e.createElement(y,a(a({ref:l},c),{},{components:i})):e.createElement(y,a({ref:l},c))}));function y(A,l){var i=arguments,n=l&&l.mdxType;if("string"==typeof A||n){var t=i.length,a=new Array(t);a[0]=m;var p={};for(var r in l)hasOwnProperty.call(l,r)&&(p[r]=l[r]);p.originalType=A,p[d]="string"==typeof A?A:n,a[1]=p;for(var o=2;o{i.r(l),i.d(l,{assets:()=>r,contentTitle:()=>a,default:()=>P,frontMatter:()=>t,metadata:()=>p,toc:()=>o});var e=i(7462),n=(i(7294),i(3905));const t={},a="\u5b89\u88c5",p={unversionedId:"basic/install",id:"basic/install",title:"\u5b89\u88c5",description:"\u5b89\u88c5\u517c\u5bb9\u7684Unity\u7248\u672c",source:"@site/docs/basic/install.md",sourceDirName:"basic",slug:"/basic/install",permalink:"/docs/basic/install",draft:!1,tags:[],version:"current",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"\u652f\u6301\u7684Unity\u7248\u672c\u548c\u5e73\u53f0",permalink:"/docs/basic/supportedplatformanduniyversion"},next:{title:"\u914d\u7f6e",permalink:"/docs/basic/projectsettings"}},r={},o=[{value:"\u5b89\u88c5\u517c\u5bb9\u7684Unity\u7248\u672c",id:"\u5b89\u88c5\u517c\u5bb9\u7684unity\u7248\u672c",level:2},{value:"\u5b89\u88c5IDE\u53ca\u76f8\u5173\u5de5\u5177",id:"\u5b89\u88c5ide\u53ca\u76f8\u5173\u5de5\u5177",level:2},{value:"\u9009\u62e9 com.code-philosophy.hybridclr \u7248\u672c",id:"\u9009\u62e9-comcode-philosophyhybridclr-\u7248\u672c",level:3},{value:"\u5b89\u88c5 com.code-philosophy.hybridclr package",id:"\u5b89\u88c5-comcode-philosophyhybridclr-package",level:2},{value:"\u4ecegit url\u5b89\u88c5",id:"\u4ecegit-url\u5b89\u88c5",level:3},{value:"\u4ece\u672c\u5730\u6587\u4ef6\u5b89\u88c5",id:"\u4ece\u672c\u5730\u6587\u4ef6\u5b89\u88c5",level:3},{value:"\u66f4\u65b0 com.code-philosophy.hybridclr",id:"\u66f4\u65b0-comcode-philosophyhybridclr",level:2},{value:"\u521d\u59cb\u5316HybridCLR",id:"\u521d\u59cb\u5316hybridclr",level:2},{value:"\u5982\u679c\u4f60\u7684\u7248\u672c >= v2.0.5",id:"\u5982\u679c\u4f60\u7684\u7248\u672c--v205",level:3},{value:"\u5982\u679c\u4f60\u7684\u7248\u672c >= 1.1.20",id:"\u5982\u679c\u4f60\u7684\u7248\u672c--1120",level:3},{value:"\u5982\u679c\u4f60\u7684package\u7248\u672c <= 1.1.19",id:"\u5982\u679c\u4f60\u7684package\u7248\u672c--1119",level:3},{value:"\u5b89\u88c5\u540e\u7684\u7279\u6b8a\u5904\u7406",id:"\u5b89\u88c5\u540e\u7684\u7279\u6b8a\u5904\u7406",level:2},{value:"WebGL\u5e73\u53f0",id:"webgl\u5e73\u53f0",level:3},{value:"Unity 2021",id:"unity-2021",level:3},{value:"Unity 2019",id:"unity-2019",level:3},{value:"\u5728\u975e\u517c\u5bb9\u7684Unity\u7248\u672c\u4e2d\u4f7f\u7528HybridCLR",id:"\u5728\u975e\u517c\u5bb9\u7684unity\u7248\u672c\u4e2d\u4f7f\u7528hybridclr",level:2},{value:"HybridCLR/Installer\u5de5\u4f5c\u539f\u7406",id:"hybridclrinstaller\u5de5\u4f5c\u539f\u7406",level:2},{value:"\u66ff\u6362libil2cpp\u4ee3\u7801",id:"\u66ff\u6362libil2cpp\u4ee3\u7801",level:3},{value:"\u672c\u5730\u5b89\u88c5",id:"\u672c\u5730\u5b89\u88c5",level:3},{value:"\u5168\u5c40\u5b89\u88c5",id:"\u5168\u5c40\u5b89\u88c5",level:3},{value:"\u6ce8\u610f\u4e8b\u9879",id:"\u6ce8\u610f\u4e8b\u9879",level:2}],c={toc:o},d="wrapper";function P(A){let{components:l,...t}=A;return(0,n.kt)(d,(0,e.Z)({},c,t,{components:l,mdxType:"MDXLayout"}),(0,n.kt)("h1",{id:"\u5b89\u88c5"},"\u5b89\u88c5"),(0,n.kt)("h2",{id:"\u5b89\u88c5\u517c\u5bb9\u7684unity\u7248\u672c"},"\u5b89\u88c5\u517c\u5bb9\u7684Unity\u7248\u672c"),(0,n.kt)("p",null,"\u652f\u63012019.4.x\u30012020.3.x\u30012021.3.x\u30012022.3.x \u4e2d\u4efb\u4e00\u7248\u672c\u3002\u63a8\u8350\u5b89\u88c52019.4.40\u30012020.3.26+\u30012021.3.x\u30012022.3.x\u7248\u672c\u3002"),(0,n.kt)("admonition",{type:"tip"},(0,n.kt)("p",{parentName:"admonition"},"\u5982\u679c\u4f60\u7684\u7248\u672c\u4e3a 2019.4.0-2019.4.39\uff0c",(0,n.kt)("strong",{parentName:"p"},"\u9700\u8981\u5148\u5207\u6362\u52302019.4.40\u7248\u672c\u5b8c\u6210HybridCLR\u5b89\u88c5\uff0c\u518d\u5207\u6362\u56de\u5f53\u524d\u7248\u672c"),"\u3002"),(0,n.kt)("p",{parentName:"admonition"},"\u5982\u679c\u4f60\u7684\u7248\u672c\u4e3a 2020.3.0-2020.3.25\uff0c \u5728Installer\u4e2d\u5b8c\u6210\u5b89\u88c5\u540e\uff0c\u4ece2020.3.26+\u4efb\u4e00\u7248\u672c\u7684\u5b89\u88c5\u76ee\u5f55\u590d\u5236",(0,n.kt)("inlineCode",{parentName:"p"},"2020.3.x/Editor/Data/il2cpp/external"),"\u66ff\u6362\n",(0,n.kt)("inlineCode",{parentName:"p"},"{project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp/external"),"\u3002")),(0,n.kt)("p",null,"\u6839\u636e\u4f60\u6253\u5305\u7684\u76ee\u6807\u5e73\u53f0\uff0c\u5b89\u88c5\u8fc7\u7a0b\u4e2d\u9009\u62e9\u5fc5\u8981\u6a21\u5757\u3002\u5982\u679c\u6253\u5305Android\u6216iOS\uff0c\u76f4\u63a5\u9009\u62e9\u76f8\u5e94\u6a21\u5757\u5373\u53ef\u3002\u5982\u679c\u4f60\u60f3\u6253\u5305Standalone\uff0c\u5fc5\u987b\u989d\u5916\u9009\u4e2d ",(0,n.kt)("inlineCode",{parentName:"p"},"Windows Build Support(IL2CPP)"),"\u6216",(0,n.kt)("inlineCode",{parentName:"p"},"Mac Build Support(IL2CPP)"),"\u3002"),(0,n.kt)("p",null,(0,n.kt)("img",{alt:"select il2cpp modules",src:i(8581).Z,width:"721",height:"507"})),(0,n.kt)("h2",{id:"\u5b89\u88c5ide\u53ca\u76f8\u5173\u5de5\u5177"},"\u5b89\u88c5IDE\u53ca\u76f8\u5173\u5de5\u5177"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"Windows",(0,n.kt)("ul",{parentName:"li"},(0,n.kt)("li",{parentName:"ul"},"Win\u4e0b\u9700\u8981\u5b89\u88c5",(0,n.kt)("inlineCode",{parentName:"li"},"visual studio 2019"),"\u6216\u66f4\u9ad8\u7248\u672c\u3002\u5b89\u88c5\u65f6\u81f3\u5c11\u8981\u5305\u542b ",(0,n.kt)("inlineCode",{parentName:"li"},"\u4f7f\u7528Unity\u7684\u6e38\u620f\u5f00\u53d1")," \u548c ",(0,n.kt)("inlineCode",{parentName:"li"},"\u4f7f\u7528c++\u7684\u6e38\u620f\u5f00\u53d1")," \u7ec4\u4ef6\u3002"),(0,n.kt)("li",{parentName:"ul"},"\u5b89\u88c5git"))),(0,n.kt)("li",{parentName:"ul"},"Mac",(0,n.kt)("ul",{parentName:"li"},(0,n.kt)("li",{parentName:"ul"},"\u8981\u6c42MacOS\u7248\u672c >= 12\uff0cxcode\u7248\u672c >= 13\uff0c\u4f8b\u5982",(0,n.kt)("inlineCode",{parentName:"li"},"xcode 13.4.1\uff0c macos 12.4"),"\u3002"),(0,n.kt)("li",{parentName:"ul"},"\u5b89\u88c5 git"),(0,n.kt)("li",{parentName:"ul"},"\u5b89\u88c5cmake")))),(0,n.kt)("h3",{id:"\u9009\u62e9-comcode-philosophyhybridclr-\u7248\u672c"},"\u9009\u62e9 ",(0,n.kt)("inlineCode",{parentName:"h3"},"com.code-philosophy.hybridclr")," \u7248\u672c"),(0,n.kt)("admonition",{type:"tip"},(0,n.kt)("p",{parentName:"admonition"},"v3.0.0 \u4e4b\u524d\u7684\u5305\u540d\u53eb ",(0,n.kt)("inlineCode",{parentName:"p"},"com.focus-creative-games.hybridclr_unity"),"\u3002\u4ece",(0,n.kt)("inlineCode",{parentName:"p"},"v3.0.0"),"\u7248\u672c\u8d77\uff0c\u79fb\u9664\u4e86\u5bf9Unity 2019\u7684\u652f\u6301\uff0c\u4f7f\u75282019\u7684\u5f00\u53d1\u8005\u8bf7\u9009\u62e9",(0,n.kt)("inlineCode",{parentName:"p"},"v2.x.y"),"\u7cfb\u5217\u7248\u672c\u3002")),(0,n.kt)("p",null,"\u5f53\u524d\u5b58\u5728\u8fd9\u4e9b\u7248\u672c\uff1a",(0,n.kt)("inlineCode",{parentName:"p"},"1.0"),"\u5206\u652f\u3001",(0,n.kt)("inlineCode",{parentName:"p"},"v2.x.y"),"\u3001",(0,n.kt)("inlineCode",{parentName:"p"},"v3.x.y"),"\u3001",(0,n.kt)("inlineCode",{parentName:"p"},"v.4.x.y"),"\uff08\u4e5f\u662f\u5f53\u524dmain\u5206\u652f\uff09\u7cfb\u5217\u3002"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("inlineCode",{parentName:"li"},"1.0"),"\u5206\u652f\u8fc7\u4e8e\u4e45\u8fdc\uff0c\u867d\u7136\u5de5\u4f5c\u7a33\u5b9a\uff0c\u4f46Package\u76f8\u5173\u5de5\u4f5c\u6d41\u6bd4\u8f83\u9648\u65e7\uff0c\u4e0d\u5982\u540e\u7eed\u7248\u672c\u4fbf\u6377\uff0c\u800c\u4e14\u5df2\u7ecf\u505c\u6b62\u4e86\u7ef4\u62a4\uff0c\u5f3a\u70c8\u5efa\u8bae",(0,n.kt)("strong",{parentName:"li"},"\u4e0d\u8981"),"\u518d\u4f7f\u7528"),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("inlineCode",{parentName:"li"},"v2.x.y"),"\u7cfb\u5217\u7248\u672c\u5de5\u4f5c\u6d41\u4f18\u5316\u5408\u7406\uff0c\u7ecf\u8fc7\u5927\u91cf\u9879\u76ee\u9a8c\u8bc1\uff0c\u63a8\u8350\u4f7f\u7528Unity 2019\u7248\u672c\u6216\u9a6c\u4e0a\u8981\u4e0a\u7ebf\u7684\u9879\u76ee\u4f7f\u7528"),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("inlineCode",{parentName:"li"},"v3.x.y"),"\u7cfb\u5217 ",(0,n.kt)("strong",{parentName:"li"},"\u79fb\u9664\u4e86\u5bf9Unity 2019\u7684\u652f\u6301"),"\uff0c\u65b0\u589e\u4e86Unity 2022\u7248\u672c\u652f\u6301\u3002\u63a8\u8350\u4f7f\u7528Unity 2020+\u7248\u672c\u6216\u9a6c\u4e0a\u8981\u4e0a\u7ebf\u7684\u9879\u76ee\u4f7f\u7528"),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("inlineCode",{parentName:"li"},"v4.x.y"),"\u7cfb\u5217\u652f\u6301\u589e\u91cf\u5f0fGC\u5e76\u4e14\u5f7b\u5e95\u652f\u6301\u5168\u5e73\u53f0\u3002\u7531\u4e8e\u521a\u521a\u53d1\u5e03\uff0c\u9700\u8981\u51e0\u4e2a\u661f\u671f\u65f6\u95f4\u624d\u80fd\u7a33\u5b9a\uff0c\u63a8\u8350\u5904\u4e8e\u9879\u76ee\u521d\u671f\u6216\u4e2d\u671f\u7684\u9879\u76ee\u4f7f\u7528\u3002")),(0,n.kt)("p",null,"\u8fd9\u4e9b\u7248\u672c\u90fd\u5f88\u7a33\u5b9a\uff0c\u4e0d\u5fc5\u7ea0\u7ed3\u54ea\u4e2a\u66f4\u597d\uff0c\u4e00\u822c\u6765\u8bf4\u8d8a\u65b0\u7684\u7248\u672c\u4f18\u5316\u8d8a\u591a\uff0c\u4f7f\u7528\u4f53\u9a8c\u8d8a\u597d\u3002"),(0,n.kt)("h2",{id:"\u5b89\u88c5-comcode-philosophyhybridclr-package"},"\u5b89\u88c5 ",(0,n.kt)("inlineCode",{parentName:"h2"},"com.code-philosophy.hybridclr")," package"),(0,n.kt)("p",null,"\u4ed3\u5e93\u5730\u5740\u4e3a ",(0,n.kt)("a",{parentName:"p",href:"https://github.com/focus-creative-games/hybridclr_unity"},"github")," ,\u56fd\u5185\u5feb\u901f\u7684\u955c\u50cf\u4ed3\u5e93\u4e3a",(0,n.kt)("a",{parentName:"p",href:"https://gitee.com/focus-creative-games/hybridclr_unity"},"gitee"),"\u3002"),(0,n.kt)("p",null,"\u6709\u51e0\u79cd\u5b89\u88c5\u65b9\u5f0f\uff1a"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"\u4f7f\u7528Package Manager\u4ecegit url\u5b89\u88c5"),(0,n.kt)("li",{parentName:"ul"},"\u672c\u5730\u5b89\u88c5")),(0,n.kt)("h3",{id:"\u4ecegit-url\u5b89\u88c5"},"\u4ecegit url\u5b89\u88c5"),(0,n.kt)("p",null,"\u4e3b\u83dc\u5355\u4e2d\u70b9\u51fb",(0,n.kt)("inlineCode",{parentName:"p"},"Windows/Package Manager"),"\u6253\u5f00\u5305\u7ba1\u7406\u5668\u3002\u5982\u4e0b\u56fe\u6240\u793a\u70b9\u51fb",(0,n.kt)("inlineCode",{parentName:"p"},"Add package from git URL..."),"\uff0c\u586b\u5165",(0,n.kt)("inlineCode",{parentName:"p"},"https://gitee.com/focus-creative-games/hybridclr_unity.git"),"\u6216",(0,n.kt)("inlineCode",{parentName:"p"},"https://github.com/focus-creative-games/hybridclr_unity.git"),"\u3002"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"main\u5206\u652f\u5730\u5740\u4e3a ",(0,n.kt)("inlineCode",{parentName:"li"},"https://gitee.com/focus-creative-games/hybridclr_unity.git")),(0,n.kt)("li",{parentName:"ul"},"\u5176\u4ed6tag\u7248\u672c\u5730\u5740\u4e3a ",(0,n.kt)("inlineCode",{parentName:"li"},"https://gitee.com/focus-creative-games/hybridclr_unity.git#{tag}"))),(0,n.kt)("p",null,"\u60f3\u5b89\u88c5\u67d0\u4e2a\u5206\u652f\u6216\u8005tag\u7248\u672c\uff0c\u8bf7\u5728\u5730\u5740\u540e\u52a0\u4e0a",(0,n.kt)("inlineCode",{parentName:"p"},"#{tag}"),"\uff0c\u5982 ",(0,n.kt)("inlineCode",{parentName:"p"},"https://gitee.com/focus-creative-games/hybridclr_unity.git#v3.0.1"),"\u3002"),(0,n.kt)("p",null,(0,n.kt)("img",{alt:"add package",src:i(5283).Z,width:"808",height:"223"})),(0,n.kt)("p",null,"\u4e0d\u719f\u6089\u4eceurl\u5b89\u88c5package\u7684\u8bf7\u770b",(0,n.kt)("a",{parentName:"p",href:"https://docs.unity3d.com/Manual/upm-ui-giturl.html"},"install from giturl"),"\u3002"),(0,n.kt)("h3",{id:"\u4ece\u672c\u5730\u6587\u4ef6\u5b89\u88c5"},"\u4ece\u672c\u5730\u6587\u4ef6\u5b89\u88c5"),(0,n.kt)("p",null,"\u5c06\u4ed3\u5e93clone\u5230\u672c\u5730\u540e\uff0c\u76ee\u5f55\u6539\u540d\u4e3a",(0,n.kt)("inlineCode",{parentName:"p"},"com.code-philosophy.hybridclr"),"\uff08v3.0.0\u4e4b\u524d\u7684\u7248\u672c\u8bf7\u7528 ",(0,n.kt)("inlineCode",{parentName:"p"},"com.focus-creative-games.hybridclr_unity"),"\uff09\uff0c\u518d\u76f4\u63a5\u79fb\u5230\u9879\u76ee\u7684Packages\u76ee\u5f55\u5373\u53ef\u3002"),(0,n.kt)("h2",{id:"\u66f4\u65b0-comcode-philosophyhybridclr"},"\u66f4\u65b0 com.code-philosophy.hybridclr"),(0,n.kt)("p",null,"\u66f4\u65b0\u5b8ccom.code-philosophy.hybridclr\u540e\u9700\u8981\u91cd\u65b0\u8fd0\u884c",(0,n.kt)("inlineCode",{parentName:"p"},"HybridCLR/Installer"),"\u3002"),(0,n.kt)("h2",{id:"\u521d\u59cb\u5316hybridclr"},"\u521d\u59cb\u5316HybridCLR"),(0,n.kt)("p",null,"\u4e3a\u4e86\u51cf\u5c11package\u81ea\u8eab\u5927\u5c0f\uff0c\u6709\u4e00\u4e9b\u6587\u4ef6\u9700\u8981\u4eceUnity Editor\u7684\u5b89\u88c5\u76ee\u5f55\u590d\u5236\u3002\u56e0\u6b64\u5b89\u88c5\u5b8c\u63d2\u4ef6\u540e\uff0c\u8fd8\u9700\u8981\u4e00\u4e2a\u989d\u5916\u7684\u521d\u59cb\u5316\u8fc7\u7a0b\u3002"),(0,n.kt)("p",null,"\u70b9\u51fb\u83dc\u5355 ",(0,n.kt)("inlineCode",{parentName:"p"},"HybridCLR/Installer..."),"\uff0c\u5f39\u51fa\u5b89\u88c5\u754c\u9762\u3002\u5728\u70b9\u51fb\u5b89\u88c5\u4e4b\u524d\uff0c\u53ef\u80fd\u9700\u8981\u4e00\u4e9b\u8bbe\u7f6e\u3002\u7531\u4e8e\u968f\u7740\u7248\u672c\u53d8\u5316\uff0cInstaller\u4e00\u76f4\u5728\u8c03\u6574\uff0c\u8bf7\u6839\u636e\u4f60\u5f53\u524d\u7248\u672c\u8bfb\u53d6\u4e0b\u9762\u5bf9\u5e94\u7684\u8bf4\u660e\u3002"),(0,n.kt)("h3",{id:"\u5982\u679c\u4f60\u7684\u7248\u672c--v205"},"\u5982\u679c\u4f60\u7684\u7248\u672c >= v2.0.5"),(0,n.kt)("p",null,"com.code-philosophy.hybridclr\u4e2d ",(0,n.kt)("inlineCode",{parentName:"p"},"Data~/hybridclr_version.json")," \u6587\u4ef6\u4e2d\u5df2\u7ecf\u914d\u7f6e\u4e86\u5f53\u524dpackage\u7248\u672c\u5bf9\u5e94\u7684\u517c\u5bb9 hybridclr\u53cail2cpp_plus\u7684\u5206\u652f\u6216\u8005tag\uff0c\nInstaller\u4f1a\u5b89\u88c5\u914d\u7f6e\u4e2d\u6307\u5b9a\u7684\u7248\u672c\uff0c\u4e0d\u518d\u652f\u6301\u81ea\u5b9a\u4e49\u5f85\u5b89\u88c5\u7684\u7248\u672c\u3002"),(0,n.kt)("p",null,"\u914d\u7f6e\u7c7b\u4f3c\u5982\u4e0b\uff1a"),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-json"},'{\n "versions": [\n {\n "unity_version":"2019",\n "hybridclr" : { "branch":"v2.0.1"},\n "il2cpp_plus": { "branch":"v2019-2.0.1"}\n },\n {\n "unity_version":"2020",\n "hybridclr" : { "branch":"v2.0.1"},\n "il2cpp_plus": { "branch":"v2020-2.0.1"}\n },\n {\n "unity_version":"2021",\n "hybridclr" : { "branch":"v2.0.1"},\n "il2cpp_plus": { "branch":"v2021-2.0.1"}\n }\n ]\n}\n')),(0,n.kt)("p",null,"\u5982\u679c\u4f60\u4e00\u5b9a\u8981\u5b89\u88c5\u5176\u4ed6\u7248\u672c\u7684hybridclr\u6216il2cpp_plus\uff0c\u4fee\u6539\u8be5\u914d\u7f6e\u6587\u4ef6\u4e2d\u7684branch\u4e3a\u76ee\u6807\u5206\u652f\u6216\u8005tag\u3002\u7edd\u5927\u591a\u6570\u60c5\u51b5\u4e0b\uff0c\u76f4\u63a5\u70b9\u51fb",(0,n.kt)("inlineCode",{parentName:"p"},"\u5b89\u88c5"),"\u9ed8\u8ba4\u4ece\u8fdc\u7a0b\u4ed3\u5e93\u4e0b\u8f7d\u5b89\u88c5\u5373\u53ef\u3002\u5b89\u88c5\u6210\u529f\u540e\uff0c\u63a7\u5236\u53f0\u4f1a\u6253\u5370",(0,n.kt)("inlineCode",{parentName:"p"},"\u5b89\u88c5\u6210\u529f"),"\u65e5\u5fd7\u3002\u5982\u4e0b\u56fe\u6240\u793a\u3002"),(0,n.kt)("p",null,(0,n.kt)("img",{alt:"install_default",src:i(8648).Z,width:"803",height:"221"})),(0,n.kt)("p",null,"\u4ece\u7248\u672c2.3.1\u8d77\u65b0\u589e\u652f\u6301\u76f4\u63a5\u4ece\u672c\u5730\u81ea\u5df1\u5236\u4f5c\u7684\u5305\u542bhybridclr\u7684libil2cpp\u76ee\u5f55\u590d\u5236\u5b89\u88c5\u3002\u5982\u679c\u4f60\u7f51\u7edc\u4e0d\u597d\uff0c\u6216\u8005\u6ca1\u6709\u5b89\u88c5git\u5bfc\u81f4\u65e0\u6cd5\u4ece\u4ed3\u5e93\u8fdc\u7a0b\u4e0b\u8f7d\u5b89\u88c5\uff0c\u5219\u53ef\u4ee5\u5148\u5c06 ",(0,n.kt)("a",{parentName:"p",href:"https://github.com/focus-creative-games/il2cpp_plus"},"il2cpp_plus"),"\u548c",(0,n.kt)("a",{parentName:"p",href:"https://github.com/focus-creative-games/hybridclr"},"hybridclr"),"\u4e0b\u8f7d\u5230\u672c\u5730\u540e\uff0c\u518d\u6839\u636e\u4e0b\u9762",(0,n.kt)("strong",{parentName:"p"},"\u5b89\u88c5\u539f\u7406"),"\u5c0f\u8282\u7684\u6587\u6863\uff0c\u7531\u8fd9\u4e24\u4e2a\u4ed3\u5e93\u5408\u5e76\u51fa\u542bhybridclr\u7684libil2cpp\u76ee\u5f55\uff0c\u63a5\u7740\u5728",(0,n.kt)("inlineCode",{parentName:"p"},"Installer"),"\u5b89\u88c5\u754c\u9762\u4e2d\u542f\u7528",(0,n.kt)("inlineCode",{parentName:"p"},"\u4ece\u672c\u5730\u590d\u5236libil2cpp"),"\u9009\u9879\uff0c\u9009\u62e9\u4f60\u5236\u4f5c\u7684libil2cpp\u76ee\u5f55\uff0c\u518d\u70b9\u51fb",(0,n.kt)("inlineCode",{parentName:"p"},"\u5b89\u88c5"),"\u6267\u884c\u5b89\u88c5\u3002\u5982\u4e0b\u56fe\u6240\u793a\u3002"),(0,n.kt)("p",null,(0,n.kt)("img",{alt:"install",src:i(5568).Z,width:"802",height:"195"})),(0,n.kt)("h3",{id:"\u5982\u679c\u4f60\u7684\u7248\u672c--1120"},"\u5982\u679c\u4f60\u7684\u7248\u672c >= 1.1.20"),(0,n.kt)("p",null,"com.code-philosophy.hybridclr\u4e2d ",(0,n.kt)("inlineCode",{parentName:"p"},"Data~/hybridclr_version.json")," \u6587\u4ef6\u4e2d\u5df2\u7ecf\u914d\u7f6e\u4e86\u5f53\u524dpackage\u7248\u672c\u5bf9\u5e94\u7684\u517c\u5bb9 hybridclr\u53cail2cpp_plus\u7684\u7248\u672c\uff0c\nInstaller\u4f1a\u5b89\u88c5\u914d\u7f6e\u4e2d\u6307\u5b9a\u7684\u7248\u672c\uff0c\u4e0d\u518d\u652f\u6301\u81ea\u5b9a\u4e49\u5f85\u5b89\u88c5\u7684\u7248\u672c\u3002"),(0,n.kt)("p",null,"\u914d\u7f6e\u7c7b\u4f3c\u5982\u4e0b\uff1a"),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-json"},'{\n "versions": [\n {\n "unity_version":"2019",\n "hybridclr" : { "branch":"main", "hash":"531f98365eebce5d1390175be2b41c41e217d918"},\n "il2cpp_plus": { "branch":"2019-main", "hash":"ebe5190b0404d1857832bd1d52ebec7c3730a01d"}\n },\n {\n "unity_version":"2020",\n "hybridclr" : { "branch":"main", "hash":"531f98365eebce5d1390175be2b41c41e217d918"},\n "il2cpp_plus": { "branch":"2020-main", "hash":"c6cf54285381d0b03a58126e0d39b6e4d11937b7"}\n },\n {\n "unity_version":"2021",\n "hybridclr" : { "branch":"main", "hash":"531f98365eebce5d1390175be2b41c41e217d918"},\n "il2cpp_plus": { "branch":"2021-main", "hash":"99cd1cbbfc1f637460379e81c9a7776cd3e662ad"}\n }\n ]\n}\n\n')),(0,n.kt)("p",null,"\u5982\u679c\u4f60\u60f3\u5b89\u88c5\u5176\u4ed6\u7248\u672c\u7684hybridclr\u6216il2cpp_plus\uff0c\u4fee\u6539\u8be5\u914d\u7f6e\u6587\u4ef6\u4e2d\u7684branch\u548chash\u5373\u53ef\u3002\u76f4\u63a5\u70b9\u51fb",(0,n.kt)("inlineCode",{parentName:"p"},"\u5b89\u88c5"),"\u5b8c\u6210\u5b89\u88c5\u3002\u5b89\u88c5\u6210\u529f\u540e\uff0c\u63a7\u5236\u53f0\u4f1a\u6253\u5370",(0,n.kt)("inlineCode",{parentName:"p"},"\u5b89\u88c5\u6210\u529f"),"\u65e5\u5fd7\u3002"),(0,n.kt)("h3",{id:"\u5982\u679c\u4f60\u7684package\u7248\u672c--1119"},"\u5982\u679c\u4f60\u7684package\u7248\u672c <= 1.1.19"),(0,n.kt)("p",null,"\u586b\u5199\u4f60\u8981\u5b89\u88c5\u7684hybridclr\u548cil2cpp_plus\u4ed3\u5e93\u7684 commit id\u6216branch\u6216tag\u3002\u5982\u679chybridclr\u7684\u7248\u672c\u53f7\u7559\u7a7a\uff0c\u5219\u5b89\u88c5hybridclr\u4ed3\u5e93main\u5206\u652f\u7684\u6700\u65b0\u7248\u672c\u3002\n\u5982\u679cil2cpp_plus\u7684\u7248\u672c\u53f7\u7559\u7a7a\uff0c\u5219\u5b89\u88c5\u76f8\u5e94\u5e74\u5ea6\u7248\u672c\u4e3b\u5206\u652f\uff08\u59822020-main\uff09\u7684\u6700\u65b0\u7248\u672c\u3002"),(0,n.kt)("p",null,(0,n.kt)("strong",{parentName:"p"},"hybridclr_uniyt\u5206\u652f\u3001hybridclr\u4ed3\u5e93\u7684\u5206\u652f\u8ddfil2cpp_plus\u4ed3\u5e93\u5206\u652f\u5fc5\u987b\u5339\u914d"),"\u3002\u5982\u679c\u4f60com.code-philosophy.hybridclr\u4f7f\u7528\u4e86main\u5206\u652f\uff0c\u5219hybridclr\u5fc5\u987b\u4f7f\u7528main\u5206\u652f\uff0cil2cpp_plus\u5fc5\u987b\u4f7f\u7528",(0,n.kt)("inlineCode",{parentName:"p"},"{version}-main"),"\uff0c\u5982\u679c\u4f60hybridclr_unity\u4f7f\u7528\u4e861.0\u5206\u652f\uff0c \u5219hybridclr\u5fc5\u987b\u4f7f\u7528",(0,n.kt)("inlineCode",{parentName:"p"},"1.0"),"\u5206\u652f\uff0cil2cpp_plus\u5fc5\u987b\u4f7f\u7528",(0,n.kt)("inlineCode",{parentName:"p"},"{version}-1.0"),"\u5206\u652f\u3002 \u5982\u679c\u4f60\u4f7f\u7528\u4e86\u67d0\u4e2atag\u7684\u7248\u672c\uff0c\u786e\u4fdd\u8fd9\u4e2atag\u6240\u5c5e\u7684\u5206\u652f\u5339\u914d\u3002"),(0,n.kt)("p",null,"hybridclr\u4ed3\u5e93\u63a8\u8350\u586b\u5199",(0,n.kt)("inlineCode",{parentName:"p"},"1.0"),"\uff0c\u5373\u6bcf\u6b21\u5b89\u88c51.0\u5206\u652f\u7684\u6700\u65b0\u7248\u672c\uff1bil2cpp_plus\u4ed3\u5e93\u63a8\u8350\u586b",(0,n.kt)("inlineCode",{parentName:"p"},"{\u5e74\u5ea6\u7248\u672c}-1.0"),"\uff08\u59822020-1.0\uff09\uff0c\u5373\u6bcf\u6b21\u5b89\u88c5",(0,n.kt)("inlineCode",{parentName:"p"},"{\u5e74\u5ea6\u7248\u672c}-1.0"),"\u5206\u652f\u7684\u6700\u65b0\u7248\u672c\u3002\u5982\u56fe\uff1a"),(0,n.kt)("p",null,(0,n.kt)("img",{alt:"image",src:i(1093).Z,width:"1047",height:"320"})),(0,n.kt)("p",null,"\u76ee\u524d\u5df2\u7ecf\u53d1\u5e03\u4e861.0.1\u7a33\u5b9a\u6b63\u5f0f\u7248\u672c\uff0c\u540c\u6837\u63a8\u8350\u8ffd\u6c42\u7a33\u5b9a\u7684\u9879\u76ee\u4f7f\u7528\u3002com.code-philosophy.hybridclr\u53d6 ",(0,n.kt)("inlineCode",{parentName:"p"},"1.0.1-release"),"\uff0chybridclr \u7248\u672c\u53d6 ",(0,n.kt)("inlineCode",{parentName:"p"},"1.0.1-release"),"\uff0cil2cpp_plus\u7248\u672c\u53d6 ",(0,n.kt)("inlineCode",{parentName:"p"},"{version}-1.0.1-relase"),"\u3002"),(0,n.kt)("p",null,"\u5b8c\u6210\u4ee5\u4e0a\u8bbe\u7f6e\u540e\uff0c\u70b9\u51fb",(0,n.kt)("inlineCode",{parentName:"p"},"install"),"\u6309\u94ae\u5b8c\u6210\u5b89\u88c5\u3002\u5b89\u88c5\u6210\u529f\u540e\uff0c\u63a7\u5236\u53f0\u4f1a\u6253\u5370",(0,n.kt)("inlineCode",{parentName:"p"},"\u5b89\u88c5\u6210\u529f"),"\u65e5\u5fd7\u3002"),(0,n.kt)("p",null,"\u7531\u4e8e\u5b89\u88c5\u8fc7\u7a0b\u9700\u8981\u62c9\u53d6hybridclr\u53cail2cpp_plus\u4ed3\u5e93\uff0c\u6709\u53ef\u80fd\u4f1a\u56e0\u4e3a\u7f51\u7edc\u6545\u969c\u800c\u5931\u8d25\uff0c\u5982\u679c\n\u53d1\u73b0\u5931\u8d25\u65f6 ",(0,n.kt)("inlineCode",{parentName:"p"},"HybridCLRData/hybridclr_repo"),"\u6216",(0,n.kt)("inlineCode",{parentName:"p"},"HybridCLRData/il2cpp_plus_repo"),"\u4e3a\u7a7a\uff0c\u8bf7\u518d\u6b21\u5c1d\u8bd5\u3002"),(0,n.kt)("p",null,"\u6700\u5e38\u89c1\u5931\u8d25\u539f\u56e0\u4e3agit\u672a\u5b89\u88c5\uff0c\u6216\u8005\u5b89\u88c5git\u540e\u672a\u91cd\u542fUnityEditor\u548cUnityHub\u3002\u5982\u679c\u4f60\u786e\u4fe1\u5b89\u88c5\u4e86git\uff0ccmd\u4e2d\u4e5f\u786e\u5b9e\u80fd\u8fd0\u884cgit\uff0c\u5219\u5c1d\u8bd5\u91cd\u542f\u7535\u8111\u3002"),(0,n.kt)("p",null,"\u5982\u679c\u56e0\u4e3a\u5404\u79cd\u7279\u6b8a\u539f\u56e0\u672a\u80fd\u5b8c\u6210\u81ea\u52a8\u5316\u5b89\u88c5\uff0c\u8bf7\u53c2\u7167\u4e0b\u9762\u7684",(0,n.kt)("strong",{parentName:"p"},"\u5b89\u88c5\u539f\u7406"),"\u624b\u52a8\u6a21\u62df\u6574\u4e2a\u5b89\u88c5\u8fc7\u7a0b\u3002"),(0,n.kt)("h2",{id:"\u5b89\u88c5\u540e\u7684\u7279\u6b8a\u5904\u7406"},"\u5b89\u88c5\u540e\u7684\u7279\u6b8a\u5904\u7406"),(0,n.kt)("h3",{id:"webgl\u5e73\u53f0"},"WebGL\u5e73\u53f0"),(0,n.kt)("p",null,"\u7531\u4e8eUnity\u81ea\u8eab\u539f\u56e0\uff0cWebGL\u5e73\u53f0\u5fc5\u987b\u5168\u5c40\u5b89\u88c5\u3002 \u8bf7\u67e5\u9605\u4e0b\u9762\u7ae0\u8282\u7684",(0,n.kt)("inlineCode",{parentName:"p"},"\u5168\u5c40\u5b89\u88c5"),"\u6587\u6863\u3002"),(0,n.kt)("h3",{id:"unity-2021"},"Unity 2021"),(0,n.kt)("admonition",{type:"caution"},(0,n.kt)("p",{parentName:"admonition"},(0,n.kt)("strong",{parentName:"p"},"\u5982\u679c\u4f60\u7684com.code-philosophy.hybridclr\u7248\u672c >= v2.0.1"),"\uff0c\u7531\u4e8e\u5df2\u7ecf\u4f7f\u7528MonoHook\u6280\u672f\u5728\u4e0d\u4fee\u6539UnityEditor.CoreModule.dll\u7684\u60c5\u51b5\u4e0b\u4e5f\u80fd\u590d\u5236\u51fa\u88c1\u526a\u540e\u7684AOT dll\uff0c",(0,n.kt)("strong",{parentName:"p"},"\u4e0d\u9700\u8981"),"\u6267\u884c\u4ee5\u4e0b\u64cd\u4f5c\u3002")),(0,n.kt)("p",null,"\u8865\u5145\u5143\u6570\u636e\u53ca",(0,n.kt)("inlineCode",{parentName:"p"},"HybridCLR/Generate/*"),"\u4e0b\u7684\u90e8\u5206\u547d\u4ee4\u4f9d\u8d56\u88c1\u51cf\u540e\u7684AOT dll\u3002\u4f46Unity 2021\u7248\u672c\uff082019\u30012020\u4e0d\u9700\u8981\uff09\u6253\u5305",(0,n.kt)("inlineCode",{parentName:"p"},"iOS\u5e73\u53f0"),"(\u5176\u4ed6\u5e73\u53f0\u4e0d\u9700\u8981)\u65f6\uff0c\u7531\u4e8eUnity Editor\u672a\u63d0\u4f9b\u516c\u5f00\u63a5\u53e3\u53ef\u4ee5\u590d\u5236\u51fatarget\u4e3aiOS\u65f6\u7684\u88c1\u526a\u540e\u7684AOT dll\uff0c\u6545\u5fc5\u987b\u4f7f\u7528\u4fee\u6539\u540e\u7684UnityEditor.CoreModule.dll\u8986\u76d6Unity\u81ea\u5e26\u7684\u76f8\u5e94\u6587\u4ef6\u3002"),(0,n.kt)("p",null,"\u5177\u4f53\u64cd\u4f5c\u4e3a\u5c06 ",(0,n.kt)("inlineCode",{parentName:"p"},"{package\u76ee\u5f55}/Data~/ModifiedUnityAssemblies/2021.3.x/UnityEditor.CoreModule-{Win,Mac}.dll")," \u8986\u76d6 ",(0,n.kt)("inlineCode",{parentName:"p"},"{Editor\u5b89\u88c5\u76ee\u5f55}/Editor/Data/Managed/UnityEngine/UnityEditor.CoreModule.dll"),"\uff0c\u5177\u4f53\u76f8\u5173\u76ee\u5f55\u6709\u53ef\u80fd\u56e0\u4e3a\u64cd\u4f5c\u7cfb\u7edf\u6216\u8005Unity\u7248\u672c\u800c\u6709\u4e0d\u540c\u3002"),(0,n.kt)("p",null,(0,n.kt)("strong",{parentName:"p"},"\u7531\u4e8e\u6743\u9650\u95ee\u9898\uff0c\u8be5\u64cd\u4f5c\u65e0\u6cd5\u81ea\u52a8\u5b8c\u6210\uff0c\u9700\u8981\u4f60\u624b\u52a8\u6267\u884c\u590d\u5236\u64cd\u4f5c\u3002")),(0,n.kt)("p",null,(0,n.kt)("inlineCode",{parentName:"p"},"UnityEditor.CoreModule.dll")," \u6bcf\u4e2aUnity\u5c0f\u7248\u672c\u90fd\u4e0d\u76f8\u540c\uff0c\u6211\u4eec\u76ee\u524d\u6682\u65f6\u53ea\u63d0\u4f9b\u4e862021.3.1\u7248\u672c\uff0c\u5982\u9700\u5176\u4ed6\u7248\u672c\u8bf7\u81ea\u5df1\u624b\u52a8\u5236\u4f5c\uff0c\u8be6\u60c5\u8bf7\u89c1 ",(0,n.kt)("a",{parentName:"p",href:"/docs/basic/modifyunitydll"},"\u4fee\u6539Unity\u7f16\u8f91\u5668\u76f8\u5173dll"),"\u3002"),(0,n.kt)("h3",{id:"unity-2019"},"Unity 2019"),(0,n.kt)("p",null,"\u4e3a\u4e86\u652f\u63012019\uff0c\u9700\u8981\u4fee\u6539il2cpp\u751f\u6210\u7684\u6e90\u7801\uff0c\u56e0\u6b64\u6211\u4eec\u4fee\u6539\u4e862019\u7248\u672c\u7684il2cpp\u5de5\u5177\u3002\u6545Installer\u7684\u5b89\u88c5\u8fc7\u7a0b\u591a\u4e86\u4e00\u4e2a\u989d\u5916\u6b65\u9aa4\uff1a\u5c06 ",(0,n.kt)("inlineCode",{parentName:"p"},"{package}/Data~/ModifiedUnityAssemblies/2019.4.40/Unity.IL2CPP.dll")," \u590d\u5236\u5230 ",(0,n.kt)("inlineCode",{parentName:"p"},"{project}/HybridCLRData/LocalIl2CppData/il2cpp/build/deploy/net471/Unity.IL2CPP.dll")),(0,n.kt)("p",null,(0,n.kt)("strong",{parentName:"p"},"\u6ce8\u610f\uff0c\u8be5\u64cd\u4f5c\u5728Installer\u5b89\u88c5\u65f6\u81ea\u52a8\u5b8c\u6210\uff0c\u4e0d\u9700\u8981\u624b\u52a8\u64cd\u4f5c\u3002")),(0,n.kt)("admonition",{type:"tip"},(0,n.kt)("p",{parentName:"admonition"},"\u5bf9\u4e8e\u4f7f\u75282019.4.0-2019.4.39\u7248\u672c\u7684\u5f00\u53d1\u8005\uff0c\u8bf7\u5148\u5207\u6362\u52302019.4.40\u7248\u672c\u5b8c\u6210\u5b89\u88c5\uff0c\u518d\u5207\u56de\u4f60\u5f53\u524d\u7248\u672c\u3002")),(0,n.kt)("h2",{id:"\u5728\u975e\u517c\u5bb9\u7684unity\u7248\u672c\u4e2d\u4f7f\u7528hybridclr"},"\u5728\u975e\u517c\u5bb9\u7684Unity\u7248\u672c\u4e2d\u4f7f\u7528HybridCLR"),(0,n.kt)("p",null,"\u7531\u4e8e\u6211\u4eec\u6ca1\u6709\u5b8c\u5168\u6d4b\u8bd5\u6240\u6709Unity\u7248\u672c\uff0c\u5b9e\u9645\u4e0a\u4e00\u4e9b\u4e0d\u5728\u652f\u6301\u5217\u8868\u4e2d\u7684Unity\u7248\u672c\uff0c\u4e5f\u6709\u53ef\u80fd\u80fd\u6b63\u5e38\u4f7f\u7528HybridCLR\u3002\u5b89\u88c5\u65b9\u5f0f\u5982\u4e0b\uff1a"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"\u627e\u4e00\u4e2a\u79bb\u4f60\u7684\u7248\u672c\u6700\u8fd1\u7684\u5728\u652f\u6301\u5217\u8868\u4e2d\u7684\u7248\u672c\uff0c\u4f8b\u5982\u4f60\u7684\u7248\u672c\u53f7\u4e3a 2021.2.20,\u5219\u79bb\u4f60\u6700\u65b0\u7684\u7248\u672c\u4e3a2021.3.0\u3002"),(0,n.kt)("li",{parentName:"ul"},"\u5148\u5c06\u4f60\u7684Unity\u5de5\u7a0b\u5207\u6362\u5230\u8fd9\u4e2a\u6700\u8fd1\u7684\u53d7\u652f\u6301\u7684\u7248\u672c\uff0c\u5b89\u88c5HybridCLR\u3002"),(0,n.kt)("li",{parentName:"ul"},"\u5207\u6362\u56de\u4f60\u7684Unity\u7248\u672c\u3002"),(0,n.kt)("li",{parentName:"ul"},"\u5c1d\u8bd5\u6253\u5305\uff0c\u5982\u679c\u80fd\u987a\u5229\u8fd0\u884c\uff0c\u5219\u8868\u660eHybridCLR\u652f\u6301\u4f60\u8fd9\u4e2a\u7248\u672c\uff0c\u5982\u679c\u6709\u95ee\u9898\uff0c\u90a3\u8fd8\u662f\u5347\u7ea7\u7248\u672c\u5427\u3002")),(0,n.kt)("p",null,"\u5982\u679c\u4f60\u4e00\u5b9a\u8981\u4f7f\u7528\u8be5\u7248\u672c\uff0c\u53ef\u4ee5\u8054\u7cfb\u6211\u4eec\u63d0\u4f9b",(0,n.kt)("a",{parentName:"p",href:"/docs/business/intro"},"\u5546\u4e1a\u6280\u672f\u652f\u6301"),"\u3002"),(0,n.kt)("h2",{id:"hybridclrinstaller\u5de5\u4f5c\u539f\u7406"},(0,n.kt)("inlineCode",{parentName:"h2"},"HybridCLR/Installer"),"\u5de5\u4f5c\u539f\u7406"),(0,n.kt)("p",null,"\u672c\u8282\u53ea\u662f\u4ecb\u7ecd\u539f\u7406\uff0c",(0,n.kt)("strong",{parentName:"p"},"\u5b89\u88c5libil2cpp\u7684\u64cd\u4f5c\u5df2\u7531installer\u5b8c\u6210\uff0c\u5e76\u4e0d\u9700\u8981\u4f60\u624b\u52a8\u64cd\u4f5c"),"\u3002"),(0,n.kt)("p",null,"HybridCLR\u5b89\u88c5\u8fc7\u7a0b\u4e3b\u8981\u5305\u542b\u8fd9\u51e0\u90e8\u5206\uff1a"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"\u5236\u4f5c\u652f\u6301\u70ed\u66f4\u65b0\u7684libil2cpp"),(0,n.kt)("li",{parentName:"ul"},"\u672c\u5730\u6216\u8005\u5168\u5c40\u5b89\u88c5\uff0c\u4f7f\u65b0\u7248\u672clibil2cpp\u751f\u6548"),(0,n.kt)("li",{parentName:"ul"},"\u5bf9Unity Editor\u7684\u5c11\u91cf\u6539\u9020")),(0,n.kt)("h3",{id:"\u66ff\u6362libil2cpp\u4ee3\u7801"},"\u66ff\u6362libil2cpp\u4ee3\u7801"),(0,n.kt)("p",null,"\u539f\u59cb\u7684libil2cpp\u4ee3\u7801\u662fAOT\u8fd0\u884c\u65f6\uff0c\u9700\u8981\u66ff\u6362\u6210\u6539\u9020\u540e\u7684libil2cpp\u624d\u80fd\u652f\u6301\u70ed\u66f4\u65b0\u3002\u6539\u9020\u540e\u7684libil2cpp\u7531\u4e24\u90e8\u5206\u6784\u6210"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"il2cpp_plus"),(0,n.kt)("li",{parentName:"ul"},"hybridclr")),(0,n.kt)("p",null,"il2cpp_plus\u4ed3\u5e93\u4e3a\u5bf9\u539f\u59cblibil2cpp\u4f5c\u4e86\u5c11\u91cf\u4fee\u6539\u4ee5\u652f\u6301\u52a8\u6001",(0,n.kt)("strong",{parentName:"p"},"register"),"\u5143\u6570\u636e\u7684\u7248\u672c\uff08\u6539\u4e86\u51e0\u767e\u884c\u4ee3\u7801\uff09\u3002\u8fd9\u4e2a\u4ed3\u5e93\u4e0e\u539f\u59cblibil2cpp\u4ee3\u7801\u9ad8\u5ea6\n\u76f8\u4f3c\u3002hybridclr\u4e3a\u89e3\u91ca\u5668\u90e8\u5206\u7684\u6838\u5fc3\u4ee3\u7801\uff0c\u5305\u542b\u5143\u6570\u636e\u52a0\u8f7d\u3001\u4ee3\u7801transform(\u7f16\u8bd1)\u3001\u4ee3\u7801\u89e3\u91ca\u6267\u884c\u3002"),(0,n.kt)("p",null,"\u5982\u4e0b\u56fe\u6240\u793a\uff0c\u5c06",(0,n.kt)("inlineCode",{parentName:"p"},"il2cpp_plus/libil2cpp"),"\u76ee\u5f55\u548c",(0,n.kt)("inlineCode",{parentName:"p"},"hybridclr/hybridclr"),"\u76ee\u5f55\u5408\u5e76\uff0c\u5236\u4f5c\u51fa\u6700\u7ec8\u7684\u652f\u6301\u70ed\u66f4\u65b0\u7684libil2cpp\u3002"),(0,n.kt)("p",null,(0,n.kt)("img",{alt:"merge_hybridclr_dir",src:i(7441).Z,width:"811",height:"626"})),(0,n.kt)("h3",{id:"\u672c\u5730\u5b89\u88c5"},"\u672c\u5730\u5b89\u88c5"),(0,n.kt)("p",null,"Unity\u5141\u8bb8\u4f7f\u7528\u73af\u5883\u53d8\u91cf",(0,n.kt)("inlineCode",{parentName:"p"},"UNITY_IL2CPP_PATH"),"\u81ea\u5b9a\u4e49",(0,n.kt)("inlineCode",{parentName:"p"},"il2cpp"),"\u7684\u4f4d\u7f6e\uff0c\u56e0\u6b64\u53ef\u4ee5\u5728\u9879\u76ee\u672c\u5730\u521b\u5efail2cpp\u76ee\u5f55\uff0c\u7528\u6539\u9020\u540e\u7684libil2cpp\u66ff\u6362il2cpp\u76ee\u5f55\u4e0b\u7684libil2cpp\u76ee\u5f55\uff0c\n\u518d\u5c06",(0,n.kt)("inlineCode",{parentName:"p"},"UNITY_IL2CPP_PATH"),"\u73af\u5883\u53d8\u91cf\u6307\u5411\u8be5\u76ee\u5f55\u3002\u5927\u81f4\u8fc7\u7a0b\u5982\u4e0b\uff1a"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"\u4eceEditor\u5b89\u88c5\u76ee\u5f55\u590d\u5236il2cpp\u76ee\u5f55\u5230",(0,n.kt)("inlineCode",{parentName:"li"},"{project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp")),(0,n.kt)("li",{parentName:"ul"},"\u4ececlone il2cpp_plus\u548chybridclr\u4ed3\u5e93\uff0c\u5236\u4f5c\u51fa\u6700\u7ec8\u7684libil2cpp\u76ee\u5f55"),(0,n.kt)("li",{parentName:"ul"},"\u5c06\u6700\u7ec8\u7684libil2cpp\u76ee\u5f55\u66ff\u6362 ",(0,n.kt)("inlineCode",{parentName:"li"},"{project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp/libil2cpp")),(0,n.kt)("li",{parentName:"ul"},"\u4eceEditor\u5b89\u88c5\u76ee\u5f55\u590d\u5236 ",(0,n.kt)("inlineCode",{parentName:"li"},"MonoBleedingEdge")," \u76ee\u5f55\u5230 ",(0,n.kt)("inlineCode",{parentName:"li"},"{project}/HybridCLRData/LocalIl2CppData-{platform}/MonoBleedingEdge")),(0,n.kt)("li",{parentName:"ul"},"\u5176\u4ed6\u5904\u7406\u3002\u59822019\u7248\u672c\u5c06 ",(0,n.kt)("inlineCode",{parentName:"li"},"{package}/Data~/ModifiedUnityAssemblies/2019.4.40/Unity.IL2CPP.dll")," \u590d\u5236\u5230 ",(0,n.kt)("inlineCode",{parentName:"li"},"{project}/HybridCLRData/LocalIl2CppData/il2cpp/build/deploy/net471/Unity.IL2CPP.dll"))),(0,n.kt)("admonition",{type:"tip"},(0,n.kt)("p",{parentName:"admonition"},"com.code-philosophy.hybridclr \u5305\u4fee\u6539\u4e86\u672cUnityEditor",(0,n.kt)("strong",{parentName:"p"},"\u8fdb\u7a0b\u5185"),"\u7684\u73af\u5883\u53d8\u91cf",(0,n.kt)("inlineCode",{parentName:"p"},"UNITY_IL2CPP_PATH"),"\uff0c\u5e76\u4e0d\u4f1a\u5f71\u54cd\u5176\u4ed6Unity\u9879\u76ee\u3002")),(0,n.kt)("p",null,"\u521b\u5efa\u4e0a\u5c42",(0,n.kt)("inlineCode",{parentName:"p"},"LocalIl2CppData-{platform}"),"\u76ee\u5f55\uff0c\u800c\u4e0d\u662f\u53ea\u521b\u5efail2cpp\u662f\u56e0\u4e3a\u5b9e\u6d4b\u53d1\u73b0\u4ec5\u4ec5\u6307\u5b9ail2cpp\u76ee\u5f55\u4f4d\u7f6e\u662f\u4e0d\u591f\u7684\uff0c\u6253\u5305\u65f6Unity\u9690\u542b\u5047\u8bbe\u4e86il2cpp\u540c\u7ea7\u6709\u4e00\u4e2a",(0,n.kt)("inlineCode",{parentName:"p"},"MonoBleedingEdge"),"\u76ee\u5f55\uff0c\u6240\u4ee5\u521b\u5efa\u4e86\u4e0a\u7ea7\u76ee\u5f55\uff0c\u5c06il2cpp\u53caMonoBleedingEdge\u76ee\u5f55\u90fd\u590d\u5236\u8fc7\u6765\u3002"),(0,n.kt)("p",null,"\u56e0\u4e3a\u4e0d\u540c\u5e73\u53f0Editor\u81ea\u5e26\u7684il2cpp\u76ee\u5f55\u7565\u6709\u4e0d\u540c\uff0cLocalIl2CppData\u8981\u533a\u5206platform\u3002"),(0,n.kt)("h3",{id:"\u5168\u5c40\u5b89\u88c5"},"\u5168\u5c40\u5b89\u88c5"),(0,n.kt)("p",null,"\u5168\u5c40\u5b89\u88c5\u9700\u8981\u66ff\u6362\uff08\u6216\u94fe\u63a5\uff09Editor\u5b89\u88c5\u76ee\u5f55\u7684libil2cpp\u76ee\u5f55(Win\u4e0b\u4e3a{editor}/Data/il2cpp/libil2cpp\uff0cMac\u7c7b\u4f3c)\u4e3a\u6539\u9020\u540e\u7684libil2cpp\uff0c\u53ca\u989d\u5916\u66ff\u6362\u4e00\u4e9b\u4fee\u6539\u7684\u6587\u4ef6\uff08\u59822019\u8fd8\u9700\u8981\u4fee\u6539Unity.IL2CPP.dll\uff09\u3002\u6709\u51e0\u4e2a\u7f3a\u9677\uff1a"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"\u56e0\u4e3a\u76ee\u5f55\u6743\u9650\u539f\u56e0\uff0c\u53ef\u80fd\u65e0\u6cd5\u81ea\u52a8\u5b8c\u6210"),(0,n.kt)("li",{parentName:"ul"},"\u4f1a\u5f71\u54cd\u5176\u4ed6\u4e0d\u4f7f\u7528hybridclr\u7684\u9879\u76ee"),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("inlineCode",{parentName:"li"},"HybridCLR/Generate/xxxx"),"\u64cd\u4f5c\u9700\u8981\u4fee\u6539libil2cpp\u76ee\u5f55\u4e0b\u7684\u6587\u4ef6\uff0c\u6709\u53ef\u80fd\u76ee\u5f55\u6743\u9650\u7684\u539f\u56e0\u800c\u5931\u8d25\u3002")),(0,n.kt)("p",null,"\u4f7f\u7528",(0,n.kt)("inlineCode",{parentName:"p"},"HybridCLR/Installer"),"\u5b8c\u6210\u5b89\u88c5\u540e\uff0c\u5728",(0,n.kt)("inlineCode",{parentName:"p"},"HybridCLR/Settings"),"\u4e2d\u5f00\u542f ",(0,n.kt)("inlineCode",{parentName:"p"},"useGlobalIl2Cpp")," \u9009\u9879\u6765\u542f\u52a8\u5168\u5c40\u5b89\u88c5\uff0c\u6b64\u65f6\u4f1a\u6e05\u9664\u73af\u5883\u53d8\u91cf",(0,n.kt)("inlineCode",{parentName:"p"},"UNITY_IL2CPP_PATH"),"\u3002"),(0,n.kt)("p",null,"\u5982\u679c\u4f60\u4f7f\u7528\u66ff\u6362\u76ee\u5f55\u7684\u65b9\u5f0f\u8fdb\u884c\u5168\u5c40\u5b89\u88c5\uff0c\u5e76\u4e14\u4f60\u7684com.code-philosophy.hybridclr\u7248\u672c >= 2.1.0\uff0c\u5219",(0,n.kt)("strong",{parentName:"p"},"\u7b2c\u4e00\u6b21"),"\u8986\u76d6libil2cpp\u524d\uff0c\u8bf7\u5148\u8fd0\u884c",(0,n.kt)("inlineCode",{parentName:"p"},"HybridCLR/Generate/Il2cppDef"),"\uff08\u53ea\u6b64\u4e00\u6b21\uff0c\u540e\u9762\u4e0d\u518d\u9700\u8981\uff0c\u9664\u975e\u4f60\u5207\u6362\u4e86\u9879\u76eeUnity\u7248\u672c\uff09\u4ee5\u751f\u6210\u6b63\u786e\u7684\u7248\u672c\u5b8f\uff0c\u518d\u8986\u76d6\u539f\u59cb\u7684libil2cpp\u76ee\u5f55\u3002",(0,n.kt)("strong",{parentName:"p"},"\u7b26\u53f7\u94fe\u63a5\u5b89\u88c5\u65b9\u5f0f\u6216\u8005com.code-philosophy.hybridclr\u7248\u672c\u4f4e\u4e8e2.1.0\u4e0d\u9700\u8981\u6267\u884c\u6b64\u64cd\u4f5c\uff0c\u76f4\u63a5\u8986\u76d6\u539f\u59cb\u7684libil2cpp\u76ee\u5f55\u5373\u53ef"),"\u3002"),(0,n.kt)("p",null,"\u7531\u4e8e\u6743\u9650\u539f\u56e0\uff0c\u5373\u4f7f\u662f\u5168\u5c40\u5b89\u88c5\uff0c",(0,n.kt)("inlineCode",{parentName:"p"},"Generate/xxx"),"\u547d\u4ee4\u4fee\u6539\u7684\u662f\u672c\u5730",(0,n.kt)("inlineCode",{parentName:"p"},"{project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp/libil2cpp"),"\u4e0b\u7684\u6587\u4ef6\u3002",(0,n.kt)("strong",{parentName:"p"},"\u8bf7\u6bcf\u6b21generate\u540e\u90fd\u5c06\u672c\u5730libil2cpp\u76ee\u5f55\u8986\u76d6\u5168\u5c40\u5b89\u88c5\u76ee\u5f55"),"\u3002"),(0,n.kt)("p",null,"\u6bcf\u6b21\u66ff\u6362libil2cpp\u76ee\u5f55\u975e\u5e38\u9ebb\u70e6\uff0c\u63a8\u8350\u4f7f\u7528\u94fe\u63a5\u5b89\u88c5\u76ee\u5f55\u7684libil2cpp\u76ee\u5f55\u5230\u672c\u5730libil2cpp\u76ee\u5f55\u65b9\u5f0f\u3002\u65b9\u6cd5\u5982\u4e0b\uff1a"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"Win\u5e73\u53f0\u3002\u4ee5\u7ba1\u7406\u5458\u6743\u9650\u6253\u5f00\u547d\u4ee4\u884c\u7a97\u53e3\uff0c\u5220\u9664\u6216\u8005\u91cd\u547d\u540d\u539flibil2cpp\uff0c\u7136\u540e\u8fd0\u884c ",(0,n.kt)("inlineCode",{parentName:"li"},'mklink /D "" "{project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp/libil2cpp"'),"\u3002"),(0,n.kt)("li",{parentName:"ul"},"Linux\u6216\u8005Mac\u5e73\u53f0\u3002\u4ee5\u7ba1\u7406\u5458\u6743\u9650\u6253\u5f00\u547d\u4ee4\u884c\u7a97\u53e3\uff0c\u5220\u9664\u6216\u8005\u91cd\u547d\u540d\u539flibil2cpp\uff0c\u7136\u540e\u8fd0\u884c ",(0,n.kt)("inlineCode",{parentName:"li"},'ln -s "{project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp/libil2cpp" "" '),"\u3002")),(0,n.kt)("p",null,"\u5bf9\u4e8e2019\u7248\u672c\u66ff\u6362 Unity.IL2CPP.dll\uff0c\u4e5f\u4f7f\u7528\u7c7b\u4f3c\u4e0a\u9762\u7684\u66ff\u6362\u6216\u8005\u8f6f\u94fe\u63a5\u7684\u65b9\u5f0f\u3002"),(0,n.kt)("h2",{id:"\u6ce8\u610f\u4e8b\u9879"},"\u6ce8\u610f\u4e8b\u9879"),(0,n.kt)("p",null,"\u7531\u4e8e Unity \u7684\u7f13\u5b58\u673a\u5236\uff0c\u66f4\u65b0 HybridCLR \u540e\uff0c\u4e00\u5b9a\u8981\u6e05\u9664 Library\\Il2cppBuildCache \u76ee\u5f55\uff0c\u4e0d\u7136\u6253\u5305\u65f6\u4e0d\u4f1a\u4f7f\u7528\u6700\u65b0\u7684\u4ee3\u7801\u3002\u5982\u679c\u4f60\u4f7f\u7528Installer\u6765\u81ea\u52a8\u5b89\u88c5\u6216\u8005\u66f4\u65b0HybridCLR\uff0c\u5b83\u4f1a\u81ea\u52a8\u6e05\u9664\u8fd9\u4e9b\u76ee\u5f55\uff0c\u4e0d\u9700\u8981\u4f60\u989d\u5916\u64cd\u4f5c\u3002"))}P.isMDXComponent=!0},5568:(A,l,i)=>{i.d(l,{Z:()=>e});const e="data:image/jpeg;base64,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88888888888888888888888888888888888888oAQAsIA0Q8gk888888888888888888888888888888888888888oAU888888888888888888888888888888888888888888888888oApU/wA+9HItHYfPPPPPPJUMsEYEYEMAAMIAAAEYIFMMMMBAGDBAFAPLPPLHPPPPDPPPPPPPLL/vvvvvvjb/AM+8/wD+vMMMMMMMc888884A8www8w0888888888888888888888888888888888888888888oAsAQgwAE888888888888888888888888888888888888888888oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8QAJhEAAgEDAwMEAwAAAAAAAAAAAAFhEZHRceHxIUHwEDAxgECBof/aAAgBAwEBPxBudWiFEKIUQohRCiFCSXRfSpqI6RV9hO+wukVZX3RXT49LVVRrqevsLiuiXY1r7GpfYU19jWua1zWua1zUvsal9jWvsa19hN1ab/eDxt5PG3k8beTxt5Ene8nxyrX+U/Jg9ipknBIOQeTkHk5B5OQeTkHk5B5OQeRpVXGSCQSLkgkXJBOJAzqm5+h3/8QAHxEAAwEAAgIDAQAAAAAAAAAAAAFhESExMEFAUYBx/9oACAECAQE/EEpYmVZVlWVZVlWVY23y/wAVLrxi2NE+/sat5Gu4POcmW+GY3s79mzBPh/HgSLyKqqqZVKlSpc2dfJ9W+BJiEiaJomiaJomiaG70iRIkSJEiRI4RPwd//8QAKhABAAIBAwIFBQEBAQEAAAAAAQARITFh8EFREHGRsdFggaHB8SAwUOH/2gAIAQEAAT8QSwBjotXP4itQLijC3uE+0QuwcGEaSW6oNRha3qj7SuqyCMWGB1ymkB5UEBhdPaJkiiCl9gZWzURtWyXnVdtYeISl5drsXtLMFGowFt6ifacISlZethRpqXXBPRgi3qh946S+4MI0k4QnCEAKtp03fo7Ln9aqTg+3pHC/uHYaC6LzsOZM3iLGue06xWn9xs092c96iUeN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"},8648:(A,l,i)=>{i.d(l,{Z:()=>e});const e="data:image/jpeg;base64,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AvMUOgzyUGdvgJCyHaJ5IPt/3QnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmEBZUFEEMBNjCbGENv0iMLSqTJZ5VroJQYAYgKGg1g0QtofNYlXbKOeWqwAfmbGE2MJsYQYiAQBf40O9fqMNR8dXf6jDUfHd3gmKABVU/aH1P2h9T9ofU/aH1P2h9T9ofU/aH1P2h9T9ofU/aH1P2h9QGKQJVX0sNR8afWsfm/6H8D2ZJaiQGMvMK0mMcI3G43nROCACNEkdIFV6o3+6gC/k/ipBZkeQVqCJTPqAKpKT9ySf4fi/6P0sNR8afWsGDAlgiPgWncP8ncP8ncP8gRhigMdDx+QA/qNhIKuEhBIGolmAOS7F0OhGxRBBU0gIUUgJoIAVEGQS64oABQWAncP8ncP8ncP8hgxBiyHg35+lhqPjobwlololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololvjsb/Ursb/Ursb/Urub/AFK6m/1K6m/1K6m/1K//2Q=="},5283:(A,l,i)=>{i.d(l,{Z:()=>e});const e=i.p+"assets/images/install_hybridclrunity_package-9a53b1ee8f7ffd8a700ed1f977ca74e3.jpg"},1093:(A,l,i)=>{i.d(l,{Z:()=>e});const e=i.p+"assets/images/install_version-bafc326c19c3b969342179d820ead842.jpg"},7441:(A,l,i)=>{i.d(l,{Z:()=>e});const e=i.p+"assets/images/merge_hybridclr_dir-04680fdb60dccd43bfd2593b4affd10e.jpg"},8581:(A,l,i)=>{i.d(l,{Z:()=>e});const e=i.p+"assets/images/select_il2cpp_modules-d895c3fb5390e04b53e40ada2b422239.jpg"}}]); \ No newline at end of file diff --git a/assets/js/7d20b2b1.463916d8.js b/assets/js/7d20b2b1.463916d8.js deleted file mode 100644 index 85bc7a2a..00000000 --- a/assets/js/7d20b2b1.463916d8.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[3423],{3905:(e,l,t)=>{t.d(l,{Zo:()=>s,kt:()=>k});var n=t(7294);function i(e,l,t){return l in e?Object.defineProperty(e,l,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[l]=t,e}function a(e,l){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);l&&(n=n.filter((function(l){return Object.getOwnPropertyDescriptor(e,l).enumerable}))),t.push.apply(t,n)}return t}function r(e){for(var l=1;l=0||(i[t]=e[t]);return i}(e,l);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(i[t]=e[t])}return i}var o=n.createContext({}),d=function(e){var l=n.useContext(o),t=l;return e&&(t="function"==typeof e?e(l):r(r({},l),e)),t},s=function(e){var l=d(e.components);return n.createElement(o.Provider,{value:l},e.children)},m="mdxType",u={inlineCode:"code",wrapper:function(e){var l=e.children;return n.createElement(n.Fragment,{},l)}},c=n.forwardRef((function(e,l){var t=e.components,i=e.mdxType,a=e.originalType,o=e.parentName,s=p(e,["components","mdxType","originalType","parentName"]),m=d(t),c=i,k=m["".concat(o,".").concat(c)]||m[c]||u[c]||a;return t?n.createElement(k,r(r({ref:l},s),{},{components:t})):n.createElement(k,r({ref:l},s))}));function k(e,l){var t=arguments,i=l&&l.mdxType;if("string"==typeof e||i){var a=t.length,r=new Array(a);r[0]=c;var p={};for(var o in l)hasOwnProperty.call(l,o)&&(p[o]=l[o]);p.originalType=e,p[m]="string"==typeof e?e:i,r[1]=p;for(var d=2;d{t.r(l),t.d(l,{assets:()=>o,contentTitle:()=>r,default:()=>u,frontMatter:()=>a,metadata:()=>p,toc:()=>d});var n=t(7462),i=(t(7294),t(3905));const a={},r="hybridclr Package\u624b\u518c",p={unversionedId:"basic/com.code-philosophy.hybridclr",id:"basic/com.code-philosophy.hybridclr",title:"hybridclr Package\u624b\u518c",description:"com.code-philosophy.hybridclr\u662f\u4e00\u4e2aUnity package\uff0c\u5b83\u63d0\u4f9b\u4e86HybridCLR\u6240\u9700\u7684Editor\u5de5\u4f5c\u6d41\u5de5\u5177\u811a\u672c\u53caRuntime\u811a\u672c\u3002\u501f\u52a9",source:"@site/docs/basic/com.code-philosophy.hybridclr.md",sourceDirName:"basic",slug:"/basic/com.code-philosophy.hybridclr",permalink:"/docs/basic/com.code-philosophy.hybridclr",draft:!1,tags:[],version:"current",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"\u4e0d\u652f\u6301\u7684\u7279\u6027",permalink:"/docs/basic/notsupportedfeatures"},next:{title:"\u6700\u4f73\u5b9e\u8df5",permalink:"/docs/basic/bestpractice"}},o={},d=[{value:"HybridCLR\u83dc\u5355\u4ecb\u7ecd",id:"hybridclr\u83dc\u5355\u4ecb\u7ecd",level:2},{value:"Installer...",id:"installer",level:3},{value:"Compile Dll",id:"compile-dll",level:3},{value:"Generate",id:"generate",level:3},{value:"Generate/Il2CppDef",id:"generateil2cppdef",level:3},{value:"Generate/LinkXml",id:"generatelinkxml",level:3},{value:"Generate/AotDlls",id:"generateaotdlls",level:3},{value:"Generate/MethodBridge",id:"generatemethodbridge",level:3},{value:"Generate/AOTGenericReference",id:"generateaotgenericreference",level:3},{value:"Generate/ReversePInvokeWrapper",id:"generatereversepinvokewrapper",level:3},{value:"Generate/All",id:"generateall",level:3},{value:"HybridCLR \u914d\u7f6e",id:"hybridclr-\u914d\u7f6e",level:2},{value:"enable",id:"enable",level:3},{value:"useGlobalIl2cpp",id:"useglobalil2cpp",level:3},{value:"hybridclrRepoURL",id:"hybridclrrepourl",level:3},{value:"il2cppPlusRepoURL",id:"il2cppplusrepourl",level:3},{value:"hotUpdateAssemblyDefinitions",id:"hotupdateassemblydefinitions",level:3},{value:"hotUpdateAssemblies",id:"hotupdateassemblies",level:3},{value:"preserveHotUpdateAssemblies",id:"preservehotupdateassemblies",level:3},{value:"hotUpdateDllCompileOutputRootDir",id:"hotupdatedllcompileoutputrootdir",level:3},{value:"externalHotUpdateAssemblyDirs",id:"externalhotupdateassemblydirs",level:3},{value:"strippedAOTDllOutputRootDir",id:"strippedaotdlloutputrootdir",level:3},{value:"patchAOTAssemblies",id:"patchaotassemblies",level:3},{value:"outputLinkFile",id:"outputlinkfile",level:3},{value:"outputAOTGenericReferenceFile",id:"outputaotgenericreferencefile",level:3},{value:"maxGenericReferenceIteration",id:"maxgenericreferenceiteration",level:3},{value:"maxMethodBridgeGenericIteration",id:"maxmethodbridgegenericiteration",level:3},{value:"Build Pipeline\u76f8\u5173\u811a\u672c",id:"build-pipeline\u76f8\u5173\u811a\u672c",level:2},{value:"\u68c0\u67e5\u548c\u4fee\u590d\u8bbe\u7f6e",id:"\u68c0\u67e5\u548c\u4fee\u590d\u8bbe\u7f6e",level:3},{value:"\u6253\u5305\u65f6\u81ea\u52a8\u6392\u9664\u70ed\u66f4\u65b0assembly",id:"\u6253\u5305\u65f6\u81ea\u52a8\u6392\u9664\u70ed\u66f4\u65b0assembly",level:3},{value:"\u6253\u5305\u65f6\u5c06\u70ed\u66f4\u65b0dll\u540d\u6dfb\u52a0\u5230assembly\u5217\u8868",id:"\u6253\u5305\u65f6\u5c06\u70ed\u66f4\u65b0dll\u540d\u6dfb\u52a0\u5230assembly\u5217\u8868",level:3},{value:"\u5907\u4efd\u88c1\u526a\u540e\u7684AOT dll",id:"\u5907\u4efd\u88c1\u526a\u540e\u7684aot-dll",level:3},{value:"iOSBuild\u811a\u672c",id:"iosbuild\u811a\u672c",level:2},{value:"Runtime\u76f8\u5173\u811a\u672c",id:"runtime\u76f8\u5173\u811a\u672c",level:2},{value:"LoadImageErrorCode",id:"loadimageerrorcode",level:3},{value:"\u5143\u6570\u636e\u6a21\u5f0f HomologousImageMode",id:"\u5143\u6570\u636e\u6a21\u5f0f-homologousimagemode",level:3},{value:"HomologousImageMode::Consistent \u6a21\u5f0f",id:"homologousimagemodeconsistent-\u6a21\u5f0f",level:4},{value:"HomologousImageMode::SuperSet \u6a21\u5f0f",id:"homologousimagemodesuperset-\u6a21\u5f0f",level:4},{value:"RuntimeApi",id:"runtimeapi",level:3},{value:"ReversePInvokeWrapperGenerationAttribute",id:"reversepinvokewrappergenerationattribute",level:3}],s={toc:d},m="wrapper";function u(e){let{components:l,...a}=e;return(0,i.kt)(m,(0,n.Z)({},s,a,{components:l,mdxType:"MDXLayout"}),(0,i.kt)("h1",{id:"hybridclr-package\u624b\u518c"},"hybridclr Package\u624b\u518c"),(0,i.kt)("p",null,(0,i.kt)("inlineCode",{parentName:"p"},"com.code-philosophy.hybridclr"),"\u662f\u4e00\u4e2aUnity package\uff0c\u5b83\u63d0\u4f9b\u4e86HybridCLR\u6240\u9700\u7684Editor\u5de5\u4f5c\u6d41\u5de5\u5177\u811a\u672c\u53caRuntime\u811a\u672c\u3002\u501f\u52a9\ncom.code-philosophy.hybridclr\u63d0\u4f9b\u7684\u5de5\u4f5c\u6d41\u5de5\u5177\uff0c\u6253\u5305\u4e00\u4e2a\u652f\u6301HybridCLR\u70ed\u66f4\u65b0\u529f\u80fd\u7684App\u53d8\u5f97\u975e\u5e38\u7b80\u5355\u3002hybridclr_unity\u5305\u4e3b\u8981\u5305\u542b\u4ee5\u4e0b\u5185\u5bb9\uff1a"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Editor\u76f8\u5173\u811a\u672c"),(0,i.kt)("li",{parentName:"ul"},"Runtime\u76f8\u5173\u811a\u672c"),(0,i.kt)("li",{parentName:"ul"},"iOSBuild\u811a\u672c")),(0,i.kt)("admonition",{type:"caution"},(0,i.kt)("p",{parentName:"admonition"},"v3.0.0 \u4e4b\u524d\u7684\u5305\u540d\u53eb ",(0,i.kt)("inlineCode",{parentName:"p"},"com.focus-creative-games.hybridclr_unity"),"\u3002")),(0,i.kt)("h2",{id:"hybridclr\u83dc\u5355\u4ecb\u7ecd"},"HybridCLR\u83dc\u5355\u4ecb\u7ecd"),(0,i.kt)("p",null,"\u4ee5\u4e0b\u5b50\u83dc\u5355\u5747\u5728\u83dc\u5355\u680f\u7684",(0,i.kt)("inlineCode",{parentName:"p"},"HybridCLR"),"\u83dc\u5355\u4e0b\uff0c\u51fa\u4e8e\u7b80\u5316\uff0c\u6211\u4eec\u4e0b\u9762\u63d0\u5230\u5b50\u83dc\u5355\u65f6\u4e0d\u518d\u5305\u542bHybridCLR\u3002"),(0,i.kt)("h3",{id:"installer"},"Installer..."),(0,i.kt)("p",null,"\u8be6\u7ec6\u6587\u6863\u89c1",(0,i.kt)("a",{parentName:"p",href:"/docs/basic/install"},"\u5b89\u88c5HybridCLR"),"\u3002"),(0,i.kt)("p",null,"Installer\u662f\u4e00\u4e2a\u65b9\u4fbf\u7684\u5b89\u88c5\u5668\uff0c\u5e2e\u52a9\u6b63\u786e\u8bbe\u7f6e\u672c\u5730il2cpp\u76ee\u5f55\uff0c\u5176\u4e2d\u5305\u542b\u66ff\u6362",(0,i.kt)("inlineCode",{parentName:"p"},"HybridCLRData/LocalIl2CppData-{platform}/il2cpp/libil2cpp"),"\u76ee\u5f55\u4e3aHybridCLR\u4fee\u6539\u7248\u672c\u3002"),(0,i.kt)("p",null,"\u5b89\u88c5\u754c\u9762\u4e2d ",(0,i.kt)("inlineCode",{parentName:"p"},"\u5b89\u88c5\u72b6\u6001\uff1a\u5df2\u5b89\u88c5|\u672a\u5b89\u88c5")," \u6307\u793a\u662f\u5426\u5b8c\u6210HybridCLR\u521d\u59cb\u5316\u3002\u70b9\u51fb\u5b89\u88c5\uff0c\u5982\u6210\u529f\uff0c\u5219\u6700\u540e\u4f1a\u663e\u793a",(0,i.kt)("inlineCode",{parentName:"p"},"\u5b89\u88c5\u6210\u529f"),"\u65e5\u5fd7\uff0c\u5e76\u4e14\u5b89\u88c5\u72b6\u6001\u5207\u6362\u4e3a",(0,i.kt)("inlineCode",{parentName:"p"},"\u5df2\u5b89\u88c5"),"\uff0c\u5426\u5219\u8bf7\u68c0\u67e5\u9519\u8bef\u65e5\u5fd7\u3002"),(0,i.kt)("admonition",{type:"tip"},(0,i.kt)("p",{parentName:"admonition"},"\u5982\u679c\u5df2\u7ecf\u5b89\u88c5HybridCLR\uff0c\u70b9\u51fb\u5b89\u88c5\u6309\u94ae\u4f1a\u5b89\u88c5\u6700\u65b0\u7684HybridCLR\u7248\u672c\u7684libil2cpp\u3002")),(0,i.kt)("p",null,"com.code-philosophy.hybridclr\u4e2d ",(0,i.kt)("inlineCode",{parentName:"p"},"Data~/hybridclr_version.json")," \u6587\u4ef6\u4e2d\u5df2\u7ecf\u914d\u7f6e\u4e86\u5f53\u524dpackage\u7248\u672c\u5bf9\u5e94\u7684\u517c\u5bb9 hybridclr\u53cail2cpp_plus\u7684\u5206\u652f\u6216\u8005tag\uff0c\nInstaller\u4f1a\u5b89\u88c5\u914d\u7f6e\u4e2d\u6307\u5b9a\u7684\u7248\u672c\uff0c\u4e0d\u518d\u652f\u6301\u81ea\u5b9a\u4e49\u5f85\u5b89\u88c5\u7684\u7248\u672c\u3002"),(0,i.kt)("p",null,"\u914d\u7f6e\u7c7b\u4f3c\u5982\u4e0b\uff1a"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-json"},'{\n "versions": [\n {\n "unity_version":"2019",\n "hybridclr" : { "branch":"v2.0.1"},\n "il2cpp_plus": { "branch":"v2019-2.0.1"}\n },\n {\n "unity_version":"2020",\n "hybridclr" : { "branch":"v2.0.1"},\n "il2cpp_plus": { "branch":"v2020-2.0.1"}\n },\n {\n "unity_version":"2021",\n "hybridclr" : { "branch":"v2.0.1"},\n "il2cpp_plus": { "branch":"v2021-2.0.1"}\n },\n {\n "unity_version":"2022",\n "hybridclr" : { "branch":"v2.0.1"},\n "il2cpp_plus": { "branch":"v2020-2.0.1"}\n }\n ]\n}\n')),(0,i.kt)("p",null,"\u5982\u679c\u4f60\u4e00\u5b9a\u8981\u5b89\u88c5\u5176\u4ed6\u7248\u672c\u7684hybridclr\u6216il2cpp_plus\uff0c\u4fee\u6539\u8be5\u914d\u7f6e\u6587\u4ef6\u4e2d\u7684branch\u4e3a\u76ee\u6807\u5206\u652f\u6216\u8005tag\u3002"),(0,i.kt)("p",null,(0,i.kt)("img",{alt:"install_default",src:t(8648).Z,width:"802",height:"196"})),(0,i.kt)("p",null,"\u4ece\u7248\u672c2.3.1\u8d77\u65b0\u589e\u652f\u6301\u76f4\u63a5\u4ece\u672c\u5730\u81ea\u5df1\u5236\u4f5c\u7684\u5305\u542bhybridclr\u7684libil2cpp\u76ee\u5f55\u590d\u5236\u5b89\u88c5\u3002\u5982\u679c\u4f60\u7f51\u7edc\u4e0d\u597d\uff0c\u6216\u8005\u6ca1\u6709\u5b89\u88c5git\u5bfc\u81f4\u65e0\u6cd5\u4ece\u4ed3\u5e93\u8fdc\u7a0b\u4e0b\u8f7d\u5b89\u88c5\uff0c\u5219\u53ef\u4ee5\u5148\u5c06 ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/focus-creative-games/il2cpp_plus"},"il2cpp_plus"),"\u548c",(0,i.kt)("a",{parentName:"p",href:"https://github.com/focus-creative-games/hybridclr"},"hybridclr"),"\u4e0b\u8f7d\u5230\u672c\u5730\u540e\uff0c\u518d\u6839\u636e\u4e0b\u9762",(0,i.kt)("strong",{parentName:"p"},"\u5b89\u88c5\u539f\u7406"),"\u5c0f\u8282\u7684\u6587\u6863\uff0c\u7531\u8fd9\u4e24\u4e2a\u4ed3\u5e93\u5408\u5e76\u51fa\u542bhybridclr\u7684libil2cpp\u76ee\u5f55\uff0c\u63a5\u7740\u5728",(0,i.kt)("inlineCode",{parentName:"p"},"Installer"),"\u5b89\u88c5\u754c\u9762\u4e2d\u542f\u7528",(0,i.kt)("inlineCode",{parentName:"p"},"\u4ece\u672c\u5730\u590d\u5236libil2cpp"),"\u9009\u9879\uff0c\u9009\u62e9\u4f60\u5236\u4f5c\u7684libil2cpp\u76ee\u5f55\uff0c\u518d\u70b9\u51fb",(0,i.kt)("inlineCode",{parentName:"p"},"\u5b89\u88c5"),"\u6267\u884c\u5b89\u88c5\u3002\u5982\u4e0b\u56fe\u6240\u793a\u3002"),(0,i.kt)("p",null,(0,i.kt)("img",{alt:"install",src:t(5568).Z,width:"814",height:"216"})),(0,i.kt)("h3",{id:"compile-dll"},"Compile Dll"),(0,i.kt)("p",null,"\u5bf9\u4e8e\u6bcf\u4e2atarget\uff0c\u5fc5\u987b\u4f7f\u7528\u76ee\u6807\u5e73\u53f0\u7f16\u8bd1\u5f00\u5173\u4e0b\u7f16\u8bd1\u51fa\u7684\u70ed\u66f4\u65b0dll\uff0c\u5426\u5219\u4f1a\u51fa\u73b0\u70ed\u66f4\u65b0\u4ee3\u7801\u4e0eAOT\u4e3b\u5305\u6216\u8005\u70ed\u66f4\u65b0\u8d44\u6e90\u7684\u4ee3\u7801\u4fe1\u606f\u4e0d\u5339\u914d\u7684\u60c5\u51b5\u3002"),(0,i.kt)("p",null,"com.code-philosophy.hybridclr\u7684",(0,i.kt)("inlineCode",{parentName:"p"},"HybridCLR.Editor"),"\u7a0b\u5e8f\u96c6\u63d0\u4f9b\u4e86",(0,i.kt)("inlineCode",{parentName:"p"},"HybridCLR.Editor.Commands.CompileDllCommand.CompileDll(BuildTarget target)"),"\u63a5\u53e3\uff0c\n\u65b9\u4fbf\u5f00\u53d1\u8005\u7075\u6d3b\u5730\u81ea\u884c\u7f16\u8bd1\u70ed\u66f4\u65b0dll\u3002\u7f16\u8bd1\u5b8c\u6210\u540e\u7684\u70ed\u66f4\u65b0dll\u653e\u5230 ",(0,i.kt)("inlineCode",{parentName:"p"},"{project}/HybridCLRData/HotUpdateDlls/{platform}")," \u76ee\u5f55\u4e0b\u3002"),(0,i.kt)("h3",{id:"generate"},"Generate"),(0,i.kt)("p",null,"Generate\u4e0b\u5305\u542b\u6253\u5305\u65f6\u9700\u8981\u7684\u751f\u6210\u547d\u4ee4\u3002"),(0,i.kt)("h3",{id:"generateil2cppdef"},"Generate/Il2CppDef"),(0,i.kt)("p",null,"hybridclr\u4ee3\u7801\u8981\u517c\u5bb9\u591a\u4e2aUnity\u7248\u672c\uff0c\u9700\u8981\u5f53\u524dUnity\u7248\u672c\u76f8\u5173\u5b8f\u5b9a\u4e49\u3002",(0,i.kt)("inlineCode",{parentName:"p"},"Generate/Il2CppDef"),"\u547d\u4ee4\u751f\u6210\u4e86\u76f8\u5173\u7248\u672c\u5b8f\u53ca\u5176\u4ed6\u5fc5\u987b\u7684\u4ee3\u7801\uff0c\u751f\u6210\u7684\u4ee3\u7801\u7c7b\u4f3c\u5982\u4e0b\u3002"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-cpp"},"// hybridclr/generated/UnityVersion.h\n\n#define HYBRIDCLR_UNITY_VERSION 2020333\n#define HYBRIDCLR_UNITY_2020 1\n#define HYBRIDCLR_UNITY_2019_OR_NEW 1\n#define HYBRIDCLR_UNITY_2020_OR_NEW 1\n")),(0,i.kt)("h3",{id:"generatelinkxml"},"Generate/LinkXml"),(0,i.kt)("p",null,"\u626b\u63cf\u70ed\u66f4\u65b0dll\u5f15\u7528\u7684AOT\u7c7b\u578b\uff0c\u751f\u6210link.xml\uff0c\u907f\u514d\u70ed\u66f4\u65b0\u811a\u672c\u7528\u5230\u7684AOT\u7c7b\u578b\u6216\u51fd\u6570\u88ab\u88c1\u526a\u3002\u8f93\u51fa\u7684\u6587\u4ef6\u8def\u5f84\u5728 HybridCLRSettings.asset\u4e2d",(0,i.kt)("inlineCode",{parentName:"p"},"OuputLinkXml"),"\u5b57\u6bb5\u4e2d\u6307\u5b9a\uff0c\u9ed8\u8ba4\u4e3a",(0,i.kt)("inlineCode",{parentName:"p"},"LinkGenerator/link.xml"),"\u3002"),(0,i.kt)("p",null,"\u66f4\u5177\u4f53\u7684\u88c1\u526a\u76f8\u5173\u4ecb\u7ecd\u8bf7\u770b",(0,i.kt)("a",{parentName:"p",href:"/docs/basic/codestriping"},"\u4ee3\u7801\u88c1\u526a\u539f\u7406\u53ca\u89e3\u51b3\u529e\u6cd5"),"\u3002"),(0,i.kt)("h3",{id:"generateaotdlls"},"Generate/AotDlls"),(0,i.kt)("p",null,"\u751f\u6210\u88c1\u526a\u540e\u7684AOT dlls\u3002\u811a\u672c\u901a\u8fc7\u5728\u4e00\u4e2a\u4e34\u65f6\u76ee\u5f55\u5bfc\u51fa\u5de5\u7a0b\uff0c\u5b9e\u73b0\u751f\u6210\u88c1\u526a\u540e\u7684AOT dlls\u7684\u76ee\u6807\u3002\u751f\u6210AOT dlls\u4f9d\u8d56\u4e8e",(0,i.kt)("inlineCode",{parentName:"p"},"Generate/LinkXml"),"\u548c",(0,i.kt)("inlineCode",{parentName:"p"},"Generate/Il2CppDef"),"\u3002\n\u5982\u679c\u4f60\u6ca1\u6709\u7528 ",(0,i.kt)("inlineCode",{parentName:"p"},"HybridCLR/Generate/All")," \u8fd9\u6837\u7684\u4e00\u952e\u751f\u6210\u547d\u4ee4\uff0c\u8bf7\u4f9d\u6b21\u8fd0\u884c\u4ee5\u4e0b\u547d\u4ee4\uff1a"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"HybridCLR/Generate/Il2CppDef")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"HybridCLR/Generate/LinkXml")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"HybridCLR/Generate/AotDlls"))),(0,i.kt)("h3",{id:"generatemethodbridge"},"Generate/MethodBridge"),(0,i.kt)("p",null,"\u6839\u636e\u5f53\u524d\u7684AOT dll\u96c6\u626b\u63cf\u751f\u6210\u6865\u63a5\u51fd\u6570\u6587\u4ef6\u3002\u76f8\u5173\u6587\u6863\u8bf7\u770b",(0,i.kt)("a",{parentName:"p",href:"/docs/basic/methodbridge"},"\u6865\u63a5\u51fd\u6570"),"\u3002"),(0,i.kt)("p",null,"\u751f\u6210\u6865\u63a5\u51fd\u6570\u4f9d\u8d56AOT dlls\u548c\u70ed\u66f4\u65b0dlls\u3002\u5982\u679c\u4f60\u6ca1\u6709\u7528 ",(0,i.kt)("inlineCode",{parentName:"p"},"HybridCLR/Generate/All")," \u8fd9\u6837\u7684\u4e00\u952e\u751f\u6210\u547d\u4ee4\uff0c\u8bf7\u4f9d\u6b21\u8fd0\u884c\u4ee5\u4e0b\u547d\u4ee4\uff1a"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"HybridCLR/Generate/Il2CppDef")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"HybridCLR/Generate/LinkXml")," (\u9690\u542b\u8c03\u7528\u4e86 ",(0,i.kt)("inlineCode",{parentName:"li"},"HybridCLR/CompileDll/ActiveBuildTarget"),")"),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"HybridCLR/Generate/AotDlls")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"HybridCLR/Generate/MethodBridge"))),(0,i.kt)("h3",{id:"generateaotgenericreference"},"Generate/AOTGenericReference"),(0,i.kt)("p",null,"\u6839\u636e\u5f53\u524d\u70ed\u66f4\u65b0dll\u626b\u63cf\u51fa\u6240\u6709\u4ea7\u751f\u7684AOT\u6cdb\u578b\u7c7b\u578b\u53ca\u51fd\u6570\u7684\u5b9e\u4f8b\u5316\uff0c\u5e76\u751f\u6210\u4e00\u4e2a",(0,i.kt)("strong",{parentName:"p"},"\u542f\u53d1\u7684"),"\u6cdb\u578b\u5b9e\u4f8b\u5316\u6587\u4ef6",(0,i.kt)("inlineCode",{parentName:"p"},"AOTGenericReferences.cs"),"\u3002\n\u7531\u4e8e\u5c06\u626b\u63cf\u51fa\u7684\u6cdb\u578b\u7c7b\u578b\u53ca\u51fd\u6570\u8f6c\u6362\u4e3a\u5bf9\u5e94\u7684\u4ee3\u7801\u5f15\u7528\u6bd4\u8f83\u9ebb\u70e6\uff0c\u751f\u6210\u7684\u6240\u6709\u6cdb\u578b\u5b9e\u4f8b\u5316\u4ee3\u7801\u90fd\u662f",(0,i.kt)("strong",{parentName:"p"},"\u6ce8\u91ca\u4ee3\u7801"),"\u3002"),(0,i.kt)("p",null,(0,i.kt)("inlineCode",{parentName:"p"},"AOTGenericReferences.cs"),"\u6587\u4ef6\u4e2d\u8fd8\u5305\u542b\u4e86\u5e94\u8be5\u8865\u5145\u5143\u6570\u636e\u7684assembly\u5217\u8868\uff0c\u7c7b\u4f3c\u5982\u4e0b\uff0c\u65b9\u4fbf\u5f00\u53d1\u8005\u4e0d\u9700\u8981\u8fd0\u884c\u6e38\u620f\u4e5f\u80fd\u5feb\u901f\u77e5\u9053\u5e94\u8be5\u8865\u5145\u54ea\u4e9b\u5143\u6570\u636e\u3002"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-csharp"}," // {{ AOT assemblies\n // Main.dll\n // System.Core.dll\n // UnityEngine.CoreModule.dll\n // mscorlib.dll\n // }}\n")),(0,i.kt)("p",null,"\u8bf7\u5728\u5176\u4ed6\u6587\u4ef6\u4e2d\u6dfb\u52a0\u6cdb\u578b\u7c7b\u578b\u53ca\u51fd\u6570\u7684\u5b9e\u4f8b\u5316\u5f15\u7528\uff0c\u56e0\u4e3a\u8fd9\u4e2a\u8f93\u51fa\u6587\u4ef6\u6bcf\u6b21\u91cd\u65b0\u751f\u6210\u540e\u4f1a\u88ab\u8986\u76d6\u3002\n\u8fd9\u4e2a\u6cdb\u578b\u5b9e\u4f8b\u5316\u6587\u6863\u53ea\u8d77\u5230\u542f\u53d1\u4f5c\u7528\uff0c\u544a\u8bc9\u4f60\u53ef\u4ee5aot\u6cdb\u578b\u5b9e\u4f8b\u5316\u54ea\u4e9b\u7c7b\u548c\u51fd\u6570\u3002\n\u66f4\u5177\u4f53\u7684AOT\u6cdb\u578b\u76f8\u5173\u6587\u6863\u8bf7\u770b",(0,i.kt)("a",{parentName:"p",href:"/docs/basic/aotgeneric"},"AOT\u6cdb\u578b\u4ecb\u7ecd"),"\u3002"),(0,i.kt)("admonition",{type:"tip"},(0,i.kt)("p",{parentName:"admonition"},"\u4f7f\u7528\u8865\u5145\u5143\u6570\u636e\u673a\u5236\u540e\uff0c",(0,i.kt)("strong",{parentName:"p"},"\u4e0d\u4f5c\u4efb\u4f55\u5904\u7406"),"\u4e5f\u4e0d\u5f71\u54cd\u6b63\u5e38\u8fd0\u884c\u3002\u4f46\u5982\u679c\u624b\u52a8\u5bf9aot\u6cdb\u578b\u5b9e\u4f8b\u5316\uff0c\u53ef\u4ee5\u63d0\u5347\u6027\u80fd\u3002\u5efa\u8bae\u662f\u5bf9\u4e8e\u5c11\u91cf\u6027\u80fd\u654f\u611f\u7684\u7c7b\u6216\u51fd\u6570\u624b\u52a8\u6cdb\u578b\u5b9e\u4f8b\u5316\u5373\u53ef\uff0c\u5982",(0,i.kt)("inlineCode",{parentName:"p"},"Dictionary"),"\u4e4b\u7c7b\u3002")),(0,i.kt)("p",null,"\u7531\u5f00\u53d1\u8005\u81ea\u5df1\u914c\u60c5\u8f6c\u6362\u4e3a\u6b63\u786e\u7684\u5b9e\u4f8b\u5316\u5f15\u7528\uff08",(0,i.kt)("strong",{parentName:"p"},"\u8fd9\u4e2a\u64cd\u4f5c\u662f\u53ef\u9009\u7684\uff0c\u53ef\u4ee5\u5b8c\u5168\u4e0d\u5904\u7406\u6216\u53ea\u5904\u7406\u4e00\u90e8\u5206"),"\uff09\uff0c\u5373\u5728AOT\u4ee3\u7801\u4e2d\u5b9e\u4f8b\u5316\u8fd9\u6ce8\u91ca\u4e2d\u7684\u6cdb\u578b\u7c7b\u6216\u6cdb\u578b\u51fd\u6570\u3002\u65b9\u6cd5\u5927\u81f4\u5982\u4e0b\uff1a"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-csharp"},"\n // System.Collections.Generics.List`1.ctor\n new List();\n\n // System.Byte[] Array.Empty`1()\n Array.Empty();\n\n")),(0,i.kt)("h3",{id:"generatereversepinvokewrapper"},"Generate/ReversePInvokeWrapper"),(0,i.kt)("p",null,"\u4e3a\u6807\u8bb0\u4e86",(0,i.kt)("inlineCode",{parentName:"p"},"[MonoPInvokeCallback]"),"\u6ce8\u89e3\u7684\u70ed\u66f4\u65b0C#\u9759\u6001\u51fd\u6570\u751f\u6210ReversePInvokeWrapper\u51fd\u6570\u3002\u5177\u4f53\u7684MonoPInvokeCallback\u4ecb\u7ecd\u8bf7\u770b\u6587\u6863",(0,i.kt)("a",{parentName:"p",href:"/docs/basic/workwithscriptlanguage"},"MonoPInvokeCallback\u652f\u6301")),(0,i.kt)("h3",{id:"generateall"},"Generate/All"),(0,i.kt)("p",null,"\u4e00\u952e\u6267\u884c\u6253\u5305\u524d\u5fc5\u987b\u7684\u751f\u6210\u64cd\u4f5c\u3002"),(0,i.kt)("h2",{id:"hybridclr-\u914d\u7f6e"},"HybridCLR \u914d\u7f6e"),(0,i.kt)("p",null,"\u70b9\u51fb\u83dc\u5355 ",(0,i.kt)("inlineCode",{parentName:"p"},"HybridCLR/Settings")," \u6253\u5f00\u914d\u7f6e\u754c\u9762\u3002\u4e0b\u9762\u662f\u5b57\u6bb5\u8be6\u7ec6\u8bf4\u660e\u3002"),(0,i.kt)("h3",{id:"enable"},"enable"),(0,i.kt)("p",null,"\u662f\u5426\u5f00\u542fHybridCLR\u70ed\u66f4\u3002\u9ed8\u8ba4true\u3002\u5982\u679c\u4e3afalse\uff0c\u5219\u6253\u5305\u4e0d\u518d\u5305\u542bHybridCLR\u529f\u80fd\u3002"),(0,i.kt)("admonition",{type:"caution"},(0,i.kt)("p",{parentName:"admonition"},"\u5982\u679c\u7981\u7528HybridCLR\uff0c\u8bf7\u540c\u65f6\u4e5f\u79fb\u9664\u4e3b\u5de5\u7a0b\u4e2d\u5bf9HybridCLR.Runtime\u7a0b\u5e8f\u96c6\u7684\u5f15\u7528\uff0c\u5426\u5219\u6253\u5305\u65f6\u4f1a\u51fa\u73b0",(0,i.kt)("inlineCode",{parentName:"p"},"RuntimeApi::LoadMetadataForAOTAssembly"),"\u4e4b\u7c7b\u7b26\u53f7\u4e22\u5931\u7684\u9519\u8bef\u3002")),(0,i.kt)("h3",{id:"useglobalil2cpp"},"useGlobalIl2cpp"),(0,i.kt)("p",null,"\u662f\u5426\u4f7f\u7528\u5168\u5c40\u5b89\u88c5\u4f4d\u7f6e\uff0c\u5373editor\u5b89\u88c5\u4f4d\u7f6e\u4e0b\u7684il2cpp\u76ee\u5f55\u3002\u9ed8\u8ba4false\u3002\u4e00\u822c\u53ea\u6709\u6253\u5305WebGL\u65f6\u624d\u9700\u8981",(0,i.kt)("inlineCode",{parentName:"p"},"useGlobalIl2cpp=true"),"\u3002"),(0,i.kt)("p",null,"\u6ce8\u610f\uff0c\u5c31\u7b97 ",(0,i.kt)("inlineCode",{parentName:"p"},"useGlobalIl2Cpp=true"),"\uff0c\u5b89\u88c5\u65f6\u4ecd\u7136\u4f1a\u590d\u5236il2cpp\u5230HybridCLRData\u76ee\u5f55\u3002\u5728\u590d\u5236\u524d\u9700\u8981\u5148\u8fd0\u884c",(0,i.kt)("inlineCode",{parentName:"p"},"HybridCLR/Generate/Il2CppDef"),"\u751f\u6210\u7248\u672c\u5b8f\uff0c\n\u518d\u624b\u52a8\u5c06 ",(0,i.kt)("inlineCode",{parentName:"p"},"{project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp/libil2cpp"),"\u76ee\u5f55\u66ff\u6362 editor\u5b89\u88c5\u76ee\u5f55\u4e0b\u7684\u5bf9\u5e94\u76ee\u5f55\u3002\n\u53e6\u5916\u6bcf\u6b21\u8fd0\u884c",(0,i.kt)("inlineCode",{parentName:"p"},"HybridCLR/Generate/*"),"\u6267\u884c\u751f\u6210\u64cd\u4f5c\uff0c\u8f93\u51fa\u76ee\u5f55\u4ecd\u7136\u662f\u672c\u5730\u76ee\u5f55\uff0c\u9700\u8981\u81ea\u5df1\u624b\u52a8\u590d\u5236\u66ff\u6362\u5168\u5c40\u5b89\u88c5\u4f4d\u7f6e\u7684libil2cpp\u76ee\u5f55\u3002"),(0,i.kt)("h3",{id:"hybridclrrepourl"},"hybridclrRepoURL"),(0,i.kt)("p",null,"hybridclr\u4ed3\u5e93\u7684\u5730\u5740\uff0c\u9ed8\u8ba4\u503c\u4e3a ",(0,i.kt)("inlineCode",{parentName:"p"},"https://gitee.com/focus-creative-games/hybridclr"),"\u3002Installer\u5b89\u88c5\u65f6\u4ece\u6b64\u5730\u5740clone hybridclr\u4ed3\u5e93\u4ee3\u7801\u3002"),(0,i.kt)("h3",{id:"il2cppplusrepourl"},"il2cppPlusRepoURL"),(0,i.kt)("p",null,"il2cpp_plus \u4ed3\u5e93\u7684\u5730\u5740\uff0c\u9ed8\u8ba4\u503c\u4e3a ",(0,i.kt)("inlineCode",{parentName:"p"},"https://gitee.com/focus-creative-games/il2cpp_plus"),"\u3002Installer\u5b89\u88c5\u65f6\u4ece\u6b64\u5730\u5740clone il2cpp_plus\u4ed3\u5e93\u4ee3\u7801\u3002"),(0,i.kt)("h3",{id:"hotupdateassemblydefinitions"},"hotUpdateAssemblyDefinitions"),(0,i.kt)("p",null,"\u4ee5assembly definition(asmdef) \u5f62\u5f0f\u5b9a\u4e49\u7684\u70ed\u66f4\u65b0\u6a21\u5757\u5217\u8868\uff0c\u5b83\u4e0e\u4e0b\u9762\u7684",(0,i.kt)("inlineCode",{parentName:"p"},"hotUpdateAssemblies"),"\u662f\u7b49\u6548\u7684\uff0c\u53ea\u4e0d\u8fc7\u7f16\u8f91\u5668\u4e0b\u62d6\u5165asmdef\u6a21\u5757\u6bd4\u8f83\u65b9\u4fbf\uff0c\u4e5f\u4e0d\u5bb9\u6613\u5931\u8bef\u5199\u9519\u540d\u79f0\u3002"),(0,i.kt)("admonition",{type:"caution"},(0,i.kt)("p",{parentName:"admonition"},(0,i.kt)("inlineCode",{parentName:"p"},"hotUpdateAssemblyDefinitions"),"\u548c",(0,i.kt)("inlineCode",{parentName:"p"},"hotUpdateAssemblies"),"\u5408\u5e76\u540e\u6784\u6210\u6700\u7ec8\u7684\u70ed\u66f4\u65b0dll\u5217\u8868\u3002\u540c\u4e00\u4e2aassembly\u4e0d\u8981\u5728\u4e24\u4e2a\u5217\u8868\u4e2d\u540c\u65f6\u51fa\u73b0\uff0c\u4f1a\u62a5\u9519\uff01")),(0,i.kt)("h3",{id:"hotupdateassemblies"},"hotUpdateAssemblies"),(0,i.kt)("p",null,"\u6709\u4e00\u4e9bassembly\u4ee5dll\u5f62\u5f0f\u5b58\u5728\uff0c\u4f8b\u5982\u4f60\u5728\u5916\u90e8\u5de5\u7a0b\u4e2d\u521b\u5efa\u7684\u70ed\u66f4\u65b0dll\uff0c\u53c8\u5982\u4f60\u76f4\u63a5\u4f7f\u7528Assembly-CSharp\u4f5c\u4e3a\u4f60\u7684\u70ed\u66f4\u65b0dll\u3002\u7531\u4e8e\u6ca1\u6709\u5bf9\u5e94\u7684asmdef\u6587\u4ef6\uff0c\u53ea\u80fd\u4ee5dll\u540d\u79f0\u5f62\u5f0f\u624b\u52a8\u914d\u7f6e\u3002\n\u586b\u5199assembly\u540d\u79f0\u65f6\u4e0d\u8981\u5305\u542b'.dll'\u540e\u7f00\uff0c\u50cf",(0,i.kt)("inlineCode",{parentName:"p"},"Main"),"\u3001",(0,i.kt)("inlineCode",{parentName:"p"},"Assembly-CSharp"),"\u8fd9\u6837\u5373\u53ef\u3002asmdef\u5f62\u5f0f\u7684assembly\uff0c\u4f60\u4e5f\u53ef\u4ee5\u9009\u62e9\u4e0d\u52a0\u5230",(0,i.kt)("inlineCode",{parentName:"p"},"hotUpdateAssemblyDefinitions"),"\uff0c\n\u800c\u662f\u52a0\u5230",(0,i.kt)("inlineCode",{parentName:"p"},"hotUpdateAssemblies"),"\u3002\u4e0d\u8fc7\u8fd9\u6837\u4e0d\u5982\u76f4\u63a5\u62d6\u5165\u5217\u8868\u65b9\u4fbf\uff0c\u4f60\u81ea\u5df1\u914c\u60c5\u9009\u62e9\u3002"),(0,i.kt)("p",null,(0,i.kt)("inlineCode",{parentName:"p"},"hotUpdateAssemblyDefinitions"),"\u548c",(0,i.kt)("inlineCode",{parentName:"p"},"hotUpdateAssemblies"),"\u5408\u5e76\u540e\u6784\u6210\u6700\u7ec8\u7684\u70ed\u66f4\u65b0dll\u5217\u8868\u3002\u540c\u4e00\u4e2aassembly\u4e0d\u8981\u5728\u4e24\u4e2a\u5217\u8868\u4e2d\u540c\u65f6\u51fa\u73b0\uff0c\u4f1a\u62a5\u9519\uff01"),(0,i.kt)("h3",{id:"preservehotupdateassemblies"},"preserveHotUpdateAssemblies"),(0,i.kt)("p",null,"\u9884\u7559\u7684\u70ed\u66f4\u65b0dll\u540d\u5b57\u5217\u8868\u3002\u6709\u65f6\u5019\u60f3\u5728\u5c06\u6765\u65b0\u589e\u4e00\u4e9b\u70ed\u66f4\u65b0dll\uff0c\u5e76\u4e14\u671f\u671b\u8fd9\u4e9b\u65b0\u7684\u70ed\u66f4\u65b0dll\u7684\u811a\u672c\u80fd\u591f\u6302\u8f7d\u5230\u8d44\u6e90\u4e0a\uff0c\u5982\u679c\u76f4\u63a5\u5c06\u70ed\u66f4\u65b0dll\u540d\u52a0\u5230 hotUpdateAssemblies\u5219\u4f1a\u62a5assembly\u4e0d\u5b58\u5728\u7684\u9519\u8bef\u3002\npreserveHotUpdateAssemblies\u5b57\u6bb5\u7528\u6765\u6ee1\u8db3\u8fd9\u79cd\u9700\u6c42\u3002\u6253\u5305\u65f6\u4e0d\u68c0\u67e5\u8fd9\u4e9bdll\u7684\u6709\u6548\u6027\uff0c\u5e76\u4e14\u4f1a\u5c06\u5b83\u4eec\u6dfb\u52a0\u5230scriptingassemblies.json\u4e4b\u7c7b\u7684assembly\u5217\u8868\u6587\u4ef6\u4e2d\u3002\n\u586b\u5199assembly\u540d\u79f0\u65f6\u4e0d\u8981\u5305\u542b",(0,i.kt)("inlineCode",{parentName:"p"},".dll"),"\u540e\u7f00\uff0c\u50cf",(0,i.kt)("inlineCode",{parentName:"p"},"Assembly-CSharp"),"\u8fd9\u6837\u5373\u53ef\u3002"),(0,i.kt)("h3",{id:"hotupdatedllcompileoutputrootdir"},"hotUpdateDllCompileOutputRootDir"),(0,i.kt)("p",null,"\u7f16\u8bd1\u540e\u7684\u70ed\u66f4\u65b0dll\u7684\u8f93\u51fa\u6839\u76ee\u5f55\u3002\u6700\u7ec8\u8f93\u51fa\u76ee\u5f55\u5728\u8be5\u76ee\u5f55\u7684\u5e73\u53f0\u5b50\u76ee\u5f55\u4e0b\uff0c\u5373 ",(0,i.kt)("inlineCode",{parentName:"p"},"${hotUpdateDllCompileOutputRootDir}/{platform}"),"\u3002"),(0,i.kt)("h3",{id:"externalhotupdateassemblydirs"},"externalHotUpdateAssemblyDirs"),(0,i.kt)("p",null,"\u81ea\u5b9a\u4e49\u5916\u90e8\u70ed\u66f4\u65b0dll\u7684\u641c\u7d22\u8def\u5f84\u3002\u6709\u4e00\u4e9b\u6846\u67b6\u6216\u9879\u76ee\u7684\u70ed\u66f4\u65b0\u9879\u76ee\u653e\u5230Unity\u5916\u90e8\uff0c\u7f16\u8bd1\u51fa\u7684dll\u4e5f\u5728\u5916\u90e8\u3002\u8fd9\u4e2a\u53c2\u6570\u63d0\u4f9b\u4e86\u4e00\u4e2a\u70ed\u66f4\u65b0dll\n\u7684\u641c\u7d22\u8def\u5f84\uff0c\u8fd9\u6837\u4e0d\u9700\u8981\u6bcf\u6b21\u5c06\u5916\u90e8dll\u590d\u5236\u5230\u5de5\u7a0b\u91cc\u6216\u8005\u590d\u5236\u5230 hotUpdateAssemblies \u76ee\u5f55\u4e86\u3002"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"\u6309\u641c\u7d22\u8def\u5f84\u7684\u987a\u5e8f\u641c\u7d22\uff0c\u6392\u5728\u8d8a\u524d\u7684\u4f18\u5148\u7ea7\u8d8a\u9ad8\u3002"),(0,i.kt)("li",{parentName:"ul"},"\u641c\u7d22\u8def\u5f84\u5fc5\u987b\u662f\u76f8\u5bf9\u4f4d\u7f6e\uff0c\u76f8\u5bf9\u4e8e\u9879\u76ee\u6839\u76ee\u5f55\uff08\u5373Assets\u7684",(0,i.kt)("strong",{parentName:"li"},"\u4e0a\u7ea7\u76ee\u5f55"),"\uff09\u3002\u5373\u586b",(0,i.kt)("inlineCode",{parentName:"li"},"mydir"),"\uff0c\u641c\u7d22",(0,i.kt)("inlineCode",{parentName:"li"},"{proj}/mydir"),"\u3002"),(0,i.kt)("li",{parentName:"ul"},"\u6bcf\u4e2a\u8def\u5f84",(0,i.kt)("inlineCode",{parentName:"li"},"dir"),"\uff0c\u4f1a\u5148\u5c1d\u8bd5\u641c\u7d22",(0,i.kt)("inlineCode",{parentName:"li"},"{dir}/{platform}"),"\uff0c\u518d\u5c1d\u8bd5\u641c\u7d22",(0,i.kt)("inlineCode",{parentName:"li"},"{dir}"),"\u3002\u8fd9\u6837\u505a\u4e3a\u4e86\u517c\u987e\u5e73\u53f0\u7279\u6b8a\u6027\u53ca\u901a\u7528\u6027\u3002")),(0,i.kt)("p",null,"\u4e0b\u9762\u5c55\u793a\u4e00\u4e2a\u4f7f\u7528\u793a\u4f8b\u3002\u4f60\u6709\u4e00\u4e2a\u5916\u90e8dll\uff0c\u5b83\u7684\u4f4d\u7f6e\u4e3a ",(0,i.kt)("inlineCode",{parentName:"p"},"{proj}/MyDir1/MyDir2/Foo.dll"),"\uff0c\u5219\u4f60\u5e94\u8be5\uff1a"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"\u5728 hotUpdateAssemblies \u6dfb\u52a0 ",(0,i.kt)("inlineCode",{parentName:"li"},"Foo")),(0,i.kt)("li",{parentName:"ul"},"\u5728 externalHotUpdateAssemblyDirs \u91cc\u6dfb\u52a0\u76ee\u5f55 ",(0,i.kt)("inlineCode",{parentName:"li"},"MyDir1/Mydir2"))),(0,i.kt)("h3",{id:"strippedaotdlloutputrootdir"},"strippedAOTDllOutputRootDir"),(0,i.kt)("p",null,"\u88c1\u526a\u540e\u7684AOT dll\u7684\u6682\u5b58\u76ee\u5f55\u3002\u6700\u7ec8\u76ee\u5f55\u5728\u8be5\u76ee\u5f55\u7684\u5e73\u53f0\u5b50\u76ee\u5f55\u4e0b\uff0c\u5373 ",(0,i.kt)("inlineCode",{parentName:"p"},"${strippedAOTDllOutputRootDir}/{platform}"),"\u3002"),(0,i.kt)("h3",{id:"patchaotassemblies"},"patchAOTAssemblies"),(0,i.kt)("p",null,"\u8865\u5145\u5143\u6570\u636eAOT dll\u5217\u8868\u3002",(0,i.kt)("strong",{parentName:"p"},"package\u672c\u8eab\u6ca1\u6709\u7528\u5230\u8fd9\u4e2a\u914d\u7f6e\u9879"),"\u3002\u5b83\u63d0\u4f9b\u4e86\u4e00\u4e2a\u914d\u7f6eAOT dll\u5217\u8868\u7684\u5730\u65b9\uff0c\u65b9\u4fbf\u5f00\u53d1\u8005\u5728\u81ea\u5df1\u7684\u6253\u5305\u6d41\u7a0b\u4e2d\u4f7f\u7528\uff0c\u8fd9\u6837\u5c31\u4e0d\u7528\u5f00\u53d1\u8005\u5355\u72ec\u518d\u5b9a\u4e49\u4e00\u4e2a\u8865\u5145\u5143\u6570\u636eAOT dll\u914d\u7f6e\u811a\u672c\u4e86\u3002\n\u586b\u5199assembly\u540d\u79f0\u65f6\u4e0d\u8981\u5305\u542b'.dll'\u540e\u7f00\uff0c\u50cf",(0,i.kt)("inlineCode",{parentName:"p"},"Main"),"\u3001",(0,i.kt)("inlineCode",{parentName:"p"},"Assembly-CSharp"),"\u8fd9\u6837\u5373\u53ef\u3002"),(0,i.kt)("h3",{id:"outputlinkfile"},"outputLinkFile"),(0,i.kt)("p",null,"\u8fd0\u884c\u83dc\u5355",(0,i.kt)("inlineCode",{parentName:"p"},"HybridCLR/Generate/LinkXml"),"\u547d\u4ee4\u65f6\uff0c\u8f93\u51fa\u7684link.xml\u6587\u4ef6\u8def\u5f84\u3002"),(0,i.kt)("admonition",{type:"caution"},(0,i.kt)("p",{parentName:"admonition"},"\u5343\u4e07\u4e0d\u8981\u6307\u5411 ",(0,i.kt)("inlineCode",{parentName:"p"},"Assets/link.xml"),"\uff0c\u90a3\u4e2alink.xml\u4e00\u822c\u7528\u6765\u624b\u52a8\u9884\u7559AOT\u7c7b\u578b\uff0c\u800c\u8fd9\u4e2a\u81ea\u52a8\u8f93\u51fa\u7684link.xml\u6bcf\u6b21\u90fd\u4f1a\u8986\u76d6\u3002")),(0,i.kt)("h3",{id:"outputaotgenericreferencefile"},"outputAOTGenericReferenceFile"),(0,i.kt)("p",null,"\u8fd0\u884c\u83dc\u5355",(0,i.kt)("inlineCode",{parentName:"p"},"HybridCLR/Generate/AOTGenericReference"),"\u65f6\u8f93\u51fa\u7684AOT\u6cdb\u578b\u5b9e\u4f8b\u5316\u96c6\u5408\u6587\u4ef6\u7684\u8def\u5f84\u3002"),(0,i.kt)("h3",{id:"maxgenericreferenceiteration"},"maxGenericReferenceIteration"),(0,i.kt)("p",null,"\u8fd0\u884c\u83dc\u5355",(0,i.kt)("inlineCode",{parentName:"p"},"HybridCLR/Generate/AOTGenericReference"),"\u65f6\uff0c\u751f\u6210\u5de5\u5177\u9012\u5f52\u5206\u6790AOT\u6cdb\u578b\u5b9e\u4f8b\u5316\u7684\u8fed\u4ee3\u6b21\u6570\u3002"),(0,i.kt)("p",null,"\u56e0\u4e3a\u6cdb\u578b\u51fd\u6570\u4e2d\u53ef\u80fd\u4f1a\u95f4\u63a5\u4f7f\u7528\u4e86\u65b0\u7684\u6cdb\u578b\u7c7b\u548c\u6cdb\u578b\u51fd\u6570\uff0c\u56e0\u6b64\u9700\u8981\u591a\u8f6e\u8fed\u4ee3\u624d\u80fd\u5206\u6790\u51fa\u6240\u6709\u7684\u6cdb\u578b\u5b9e\u4f8b\u5316\uff0c",(0,i.kt)("inlineCode",{parentName:"p"},"maxGenericReferenceIteration"),"\u53c2\u6570\u7528\u4e8e\u63a7\u5236\u8fed\u4ee3\u6b21\u6570\u3002\u8fd9\u4e2a\u53c2\u6570\u4e00\u822c10\u4ee5\u5185\u5c31\u591f\u4e86\uff0c\u4f60\u901a\u8fc7\u89c2\u5bdf\u65e5\u5fd7\n\u80fd\u770b\u5230\u51e0\u8f6e\u8fed\u4ee3\u540e\u8ba1\u7b97\u7ec8\u6b62\uff0c\u5982\u679c\u8fed\u4ee3\u7ec8\u6b62\u65f6\u8fd8\u6709\u5927\u91cf\u6cdb\u578b\u672a\u8ba1\u7b97\u8fed\u4ee3\uff0c\u53ef\u4ee5\u9002\u5f53\u589e\u52a0\u8fd9\u4e2a\u503c\u3002"),(0,i.kt)("p",null,"\u4e3a\u4ec0\u4e48\u4e0d\u53cd\u590d\u8fed\u4ee3\u76f4\u81f3\u8ba1\u7b97\u51fa\u6240\u6709\u6cdb\u578b\u5b9e\u4f8b\u5316\u5462\uff1f\u56e0\u4e3a\u6709\u53ef\u80fd\u51fa\u73b0\u6c38\u8fdc\u65e0\u6cd5\u8ba1\u7b97\u5b8c\u7684\u60c5\u51b5\u3002\u5982\u4e0b\u4ee3\u7801\uff0cAOT.Show()\n\u7531\u4e8e\u9012\u5f52\u6cdb\u578b\u5b9e\u4f8b\u5316\uff0c\u6c38\u8fdc\u4e5f\u65e0\u6cd5\u8ba1\u7b97\u5b8c\u3002"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-csharp"},"\n struct AOT\n {\n\n public void Show()\n {\n var a = new AOT>();\n a.Show();\n }\n }\n\n")),(0,i.kt)("h3",{id:"maxmethodbridgegenericiteration"},"maxMethodBridgeGenericIteration"),(0,i.kt)("p",null,"\u8fd0\u884c\u83dc\u5355",(0,i.kt)("inlineCode",{parentName:"p"},"HybridCLR/Generate/MethodBridge"),"\u65f6\uff0c\u751f\u6210\u5de5\u5177\u9012\u5f52\u5206\u6790AOT\u6cdb\u578b\u5b9e\u4f8b\u5316\u7684\u8fed\u4ee3\u6b21\u6570\u3002\u542b\u4e49\u4e0e",(0,i.kt)("inlineCode",{parentName:"p"},"maxGenericReferenceIteration"),"\u76f8\u4f3c\u3002"),(0,i.kt)("h2",{id:"build-pipeline\u76f8\u5173\u811a\u672c"},"Build Pipeline\u76f8\u5173\u811a\u672c"),(0,i.kt)("p",null,"\u4e3b\u8981\u5305\u542b\u4ee5\u4e0b\u529f\u80fd\uff1a"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"\u68c0\u67e5\u548c\u4fee\u590d\u8bbe\u7f6e"),(0,i.kt)("li",{parentName:"ul"},"\u6253\u5305\u65f6\u81ea\u52a8\u6392\u9664\u70ed\u66f4\u65b0assembly"),(0,i.kt)("li",{parentName:"ul"},"\u6253\u5305\u65f6\u5c06\u70ed\u66f4\u65b0dll\u540d\u6dfb\u52a0\u5230assembly\u5217\u8868"),(0,i.kt)("li",{parentName:"ul"},"\u5907\u4efd\u88c1\u526a\u540e\u7684AOT dll")),(0,i.kt)("h3",{id:"\u68c0\u67e5\u548c\u4fee\u590d\u8bbe\u7f6e"},"\u68c0\u67e5\u548c\u4fee\u590d\u8bbe\u7f6e"),(0,i.kt)("p",null,"\u5c5e\u4e8e\u6253\u5305\u5de5\u4f5c\u6d41\u7684\u4e00\u90e8\u5206\uff0c\u76f8\u5173\u4ee3\u7801\u5728 ",(0,i.kt)("inlineCode",{parentName:"p"},"Editor/BuildProcessors/CheckSettings.cs"),"\u4e2d\u3002\u5305\u542b\u4ee5\u4e0b\u64cd\u4f5c\uff1a"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"\u6839\u636e\u662f\u5426\u5f00\u542fHybridCLR\uff0c\u8bbe\u7f6e\u6216\u8005\u6e05\u9664UNITY_IL2CPP_PATH\u73af\u5883\u53d8\u91cf\u3002\u811a\u672c\u4e2d\u4fee\u6539\u7684UNITY_IL2CPP_PATH\u73af\u5883\u53d8\u91cf\u662f\u672c\u8fdb\u7a0b\u7684\u73af\u5883\u53d8\u91cf\uff0c\u4e0d\u7528\u62c5\u5fc3\u5e72\u6270\u4e86\u5176\u4ed6\u9879\u76ee\u3002"),(0,i.kt)("li",{parentName:"ul"},"\u5982\u679c\u4f4e\u4e8e\uff08\u4e0d\u542b\uff09v4.0.0\u7248\u672c\uff0c\u4f1a\u68c0\u67e5\u5e76\u81ea\u52a8\u5173\u95ed\u589e\u91cf\u5f0fGC(Use Incremental GC) \u9009\u9879"),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"Scripting Backend")," \u5207\u6362\u4e3a ",(0,i.kt)("inlineCode",{parentName:"li"},"il2cpp"),", WebGL\u5e73\u53f0\u4e0d\u7528\u8bbe\u7f6e\u6b64\u9009\u9879\u3002",(0,i.kt)("strong",{parentName:"li"},"\u81ea",(0,i.kt)("inlineCode",{parentName:"strong"},"v2.4.0"),"\u8d77\uff0c\u4f1a\u81ea\u52a8\u8bbe\u7f6e\u6b64\u9009\u9879\uff0c\u53ef\u4ee5\u4e0d\u7528\u624b\u52a8\u6267\u884c\u6b64\u64cd\u4f5c"),"\u3002"),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"Api Compatability Level")," \u5207\u6362\u4e3a ",(0,i.kt)("inlineCode",{parentName:"li"},".NetFramework 4"),"(Unity 2019\u30012020) \u6216 ",(0,i.kt)("inlineCode",{parentName:"li"},".Net Framework"),"\uff08Unity 2021+\uff09"),(0,i.kt)("li",{parentName:"ul"},"\u5982\u679cHybridCLRSettings\u91cc\u672a\u8bbe\u7f6e\u4efb\u4f55\u70ed\u66f4\u65b0assembly\uff0c\u63d0\u793a\u9519\u8bef\u3002")),(0,i.kt)("h3",{id:"\u6253\u5305\u65f6\u81ea\u52a8\u6392\u9664\u70ed\u66f4\u65b0assembly"},"\u6253\u5305\u65f6\u81ea\u52a8\u6392\u9664\u70ed\u66f4\u65b0assembly"),(0,i.kt)("p",null,"\u5c5e\u4e8e\u6253\u5305\u5de5\u4f5c\u6d41\u7684\u4e00\u90e8\u5206\uff0c\u76f8\u5173\u4ee3\u7801\u5728 ",(0,i.kt)("inlineCode",{parentName:"p"},"Editor/BuildProcessors/FilterHotFixAssemblies.cs"),"\u4e2d\u3002"),(0,i.kt)("p",null,"\u5f88\u663e\u7136\uff0c\u70ed\u66f4\u65b0assembly\u4e0d\u5e94\u8be5\u88abil2cpp\u5904\u7406\u5e76\u4e14\u7f16\u8bd1\u5230\u6700\u7ec8\u7684\u5305\u4f53\u91cc\u3002\u6211\u4eec\u5904\u7406\u4e86",(0,i.kt)("inlineCode",{parentName:"p"},"IFilterBuildAssemblies"),"\u56de\u8c03\uff0c\n\u5c06\u70ed\u66f4\u65b0dll\u4ecebuild assemblies\u5217\u8868\u79fb\u9664\u3002\u811a\u672c\u4e2d\u4f1a\u989d\u5916\u68c0\u67e5\u662f\u5426\u5199\u9519assembly\u540d\u5b57\uff0c\u4ee5\u53ca\u662f\u5426\u5931\u8bef\u914d\u7f6e\u4e86\u91cd\u590d\u7684assembly\u3002"),(0,i.kt)("h3",{id:"\u6253\u5305\u65f6\u5c06\u70ed\u66f4\u65b0dll\u540d\u6dfb\u52a0\u5230assembly\u5217\u8868"},"\u6253\u5305\u65f6\u5c06\u70ed\u66f4\u65b0dll\u540d\u6dfb\u52a0\u5230assembly\u5217\u8868"),(0,i.kt)("p",null,"\u5c5e\u4e8e\u6253\u5305\u5de5\u4f5c\u6d41\u7684\u4e00\u90e8\u5206\uff0c\u76f8\u5173\u4ee3\u7801\u5728 ",(0,i.kt)("inlineCode",{parentName:"p"},"Editor/BuildProcessors/PatchScriptingAssemblyList.cs"),"\u4e2d\u3002"),(0,i.kt)("p",null,"\u5de5\u5177\u5728\u6253\u5305\u65f6\uff0c\u4f1a\u81ea\u52a8\u5c06\u70ed\u66f4\u65b0assembly\u7684dll\u540d\u52a0\u5165assembly\u5217\u8868\u914d\u7f6e\u6587\u4ef6\u3002\u70ed\u66f4\u65b0MonoBehaviour\u811a\u672c\u6240\u5728\u7684assembly\u7684dll\u540d\u5fc5\u987b\u6dfb\u52a0\u5230assembly\u5217\u8868\u914d\u7f6e\u6587\u4ef6\uff0c\nUnity\u7684\u8d44\u6e90\u7ba1\u7406\u7cfb\u7edf\u624d\u80fd\u6b63\u786e\u8bc6\u522b\u548c\u8fd8\u539f\u70ed\u66f4\u65b0\u811a\u672c\u3002\u66f4\u8be6\u7ec6\u7684\u539f\u7406\u4ecb\u7ecd\u8bf7\u770b ",(0,i.kt)("a",{parentName:"p",href:"/docs/basic/monobehaviour"},"\u4f7f\u7528\u70ed\u66f4\u65b0MonoBehaviour")," \u3002"),(0,i.kt)("h3",{id:"\u5907\u4efd\u88c1\u526a\u540e\u7684aot-dll"},"\u5907\u4efd\u88c1\u526a\u540e\u7684AOT dll"),(0,i.kt)("p",null,"\u5c5e\u4e8e\u6253\u5305\u5de5\u4f5c\u6d41\u7684\u4e00\u90e8\u5206\uff0c\u76f8\u5173\u4ee3\u7801\u5728 ",(0,i.kt)("inlineCode",{parentName:"p"},"Editor/BuildProcessors/CopyStrippedAOTAssemblies.cs"),"\u4e2d\u3002"),(0,i.kt)("p",null,"\u5f53\u8865\u5145\u5143\u6570\u636e\u6a21\u5f0f\u4e3a",(0,i.kt)("inlineCode",{parentName:"p"},"HomologousImageMode::Consistent"),"\u65f6\uff0c\u9700\u8981\u4f7f\u7528\u6253\u5305\u65f6\u751f\u6210\u7684\u88c1\u526a\u540e\u7684AOT dll\u3002\u56e0\u6b64\u4f1a\u81ea\u52a8\u5c06\u6253\u5305\u8fc7\u7a0b\u4e2d\u751f\u6210\u7684\u88c1\u526a\u540e\u7684AOT dll\n\u590d\u5236\u5230 ",(0,i.kt)("inlineCode",{parentName:"p"},"{project}/HybridCLRData/AssembliesPostIl2CppStrip/{platform}"),"\u76ee\u5f55\uff0c\u65b9\u4fbf\u5c06\u6765\u5904\u7406\u3002\u5f53\u6570\u636e\u6a21\u5f0f\u4e3a",(0,i.kt)("inlineCode",{parentName:"p"},"HomologousImageMode::SuperSet"),"\u65f6\uff0c\n\u53ef\u4ee5\u76f4\u63a5\u4f7f\u7528\u539f\u59cb\u7684aot dll\u3002\u8fd9\u4e2a\u4f18\u70b9\u662f\u5de5\u4f5c\u6d41\u4e0a\u4fbf\u5229\u4e00\u4e9b\uff0c\u4e0d\u7528\u6bcf\u6b21\u6253\u5305\u540e\u66f4\u65b0aot dll\uff0c\u7f3a\u70b9\u662f\u591a\u5360\u4e86\u5185\u5b58\uff0c\u540c\u65f6\u5927\u5e45\u589e\u52a0\u4e86\u88c1\u526adll\u7684\u5927\u5c0f\uff0c\u8bf7\u4f7f\u7528\u8005\u81ea\u5df1\u6743\u8861\u4f7f\u7528\u539f\u59cb\u8fd8\u662f\u88c1\u526a\u540e\u7684aot dll\u3002"),(0,i.kt)("h2",{id:"iosbuild\u811a\u672c"},"iOSBuild\u811a\u672c"),(0,i.kt)("p",null,"package\u4e2d ",(0,i.kt)("inlineCode",{parentName:"p"},"Editor/Data~/iOSBuild")," \u5305\u542b\u4e86\u7f16\u8bd1iOS\u7248\u672clibil2cpp.a\u6240\u9700\u7684\u811a\u672c\u3002\u5728\u8fd0\u884c",(0,i.kt)("inlineCode",{parentName:"p"},"HybridCLR/Installer..."),"\u83dc\u5355\u547d\u4ee4\u6210\u529f\u521d\u59cb\u5316HybridCLR\u540e\uff0c\u4f1a\u81ea\u52a8\u590d\u5236\u5230",(0,i.kt)("inlineCode",{parentName:"p"},"{project}/HybridCLRData/iOSBuild"),"\u76ee\u5f55\u3002\n",(0,i.kt)("strong",{parentName:"p"},"\u540e\u7eed\u64cd\u4f5c\u5fc5\u987b\u5728",(0,i.kt)("inlineCode",{parentName:"strong"},"{project}/HybridCLRData/iOSBuild"),"\u76ee\u5f55\u8fdb\u884c"),"\u3002\u7f16\u8bd1libil2cpp.a\u7684\u5177\u4f53\u64cd\u4f5c\u8bf7\u770b\u6587\u6863 ",(0,i.kt)("a",{parentName:"p",href:"/docs/basic/buildpipeline"},"iOS\u5e73\u53f0\u6253\u5305"),"\u3002"),(0,i.kt)("h2",{id:"runtime\u76f8\u5173\u811a\u672c"},"Runtime\u76f8\u5173\u811a\u672c"),(0,i.kt)("p",null,"\u5305\u542b\u8fd0\u884c\u65f6\u7528\u5230\u7684\u7c7b\u3002"),(0,i.kt)("h3",{id:"loadimageerrorcode"},"LoadImageErrorCode"),(0,i.kt)("p",null,"\u52a0\u8f7d\u70ed\u66f4\u65b0dll\u7684\u9519\u8bef\u7801\u3002"),(0,i.kt)("h3",{id:"\u5143\u6570\u636e\u6a21\u5f0f-homologousimagemode"},"\u5143\u6570\u636e\u6a21\u5f0f HomologousImageMode"),(0,i.kt)("p",null,"\u76ee\u524d\u652f\u6301\u4e24\u79cd\u5143\u6570\u636e\u6a21\u5f0f\uff1a"),(0,i.kt)("h4",{id:"homologousimagemodeconsistent-\u6a21\u5f0f"},(0,i.kt)("inlineCode",{parentName:"h4"},"HomologousImageMode::Consistent")," \u6a21\u5f0f"),(0,i.kt)("p",null,"\u5373\u8865\u5145\u7684dll\u4e0e\u6253\u5305\u65f6\u88c1\u526a\u540e\u7684dll\u7cbe\u786e\u4e00\u81f4\u3002\u56e0\u6b64\u5fc5\u987b\u4f7f\u7528build\u8fc7\u7a0b\u4e2d\u751f\u6210\u7684\u88c1\u526a\u540e\u7684dll\uff0c\u5219\u4e0d\u80fd\u76f4\u63a5\u590d\u5236\u539f\u59cbdll\u3002\u6211\u4eec\u5728",(0,i.kt)("inlineCode",{parentName:"p"},"HybridCLR.BuildProcessors.CopyStrippedAOTAssemblies"),"\u91cc\u6dfb\u52a0\u4e86\u5904\u7406\u4ee3\u7801\uff0c\u5728\u6253\u5305\u65f6\u81ea\u52a8\u5c06\u8fd9\u4e9b\u88c1\u526a\u540e\u7684dll\u590d\u5236\u5230 ",(0,i.kt)("inlineCode",{parentName:"p"},"{project}/HybridCLRData/AssembliesPostIl2CppStrip/{target}")," \u76ee\u5f55\u3002"),(0,i.kt)("h4",{id:"homologousimagemodesuperset-\u6a21\u5f0f"},(0,i.kt)("inlineCode",{parentName:"h4"},"HomologousImageMode::SuperSet")," \u6a21\u5f0f"),(0,i.kt)("p",null,"\u5373\u8865\u5145\u7684dll\u662f\u6253\u5305\u65f6\u88c1\u526a\u540e\u7684dll\u7684\u8d85\u96c6\uff0c\u5305\u542b\u4e86\u88c1\u526adll\u7684\u6240\u6709\u5143\u6570\u636e\u3002\u4e00\u4e2a\u6700\u7b80\u5355\u6613\u5f97\u7684\u8d85\u96c6dll\u4e3a\u539f\u59cbaot dll\uff0c\u8fd9\u4e5f\u662f\u63a8\u8350\u4f7f\u7528\u7684\u8d85\u96c6dll\u3002"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"\u539f\u59cbUnityEngine\u76f8\u5173AOT dll\u5728Unity\u5b89\u88c5\u76ee\u5f55\u7684PlayBackEngines\u5b50\u76ee\u5f55\u4e0b"),(0,i.kt)("li",{parentName:"ul"},"\u539f\u59cb\u7684.net\u6838\u5fc3AOT dll\u5982mscorlib.dll\u5728Unity\u5b89\u88c5\u76ee\u5f55\u7684 ",(0,i.kt)("inlineCode",{parentName:"li"},"unityaot{xxx}")," \u76ee\u5f55\u4e0b\u30022019-2020\u7edf\u4e00\u4e3aunityaot\u76ee\u5f55\uff0c2021\u8d77\u62c6\u5206\u6210\u591a\u4e2a\u76ee\u5f55\uff0c\u5982\u679c\u6253\u5305android\u53d6unityaot-linux\u3001\u5982\u679c\u6253\u5305iOS\u53d6unityaot-macos\u3002"),(0,i.kt)("li",{parentName:"ul"},"\u63d2\u4ef6\u7684AOT dll\u4e3a\u5de5\u7a0b\u76ee\u5f55\u4e2d\u7684\u76f8\u5e94\u5e73\u53f0\u7684\u539f\u59cbdll\u3002\u5982\u679c\u662f\u6e90\u7801\u5f62\u5f0f\uff0c\u5219\u4e3a\u7f16\u8bd1\u597d\u7684dll\uff0c\u53d6",(0,i.kt)("inlineCode",{parentName:"li"},"HybridCLR/HotUpdateDlls/{platform}"),"\u76ee\u5f55\u4e0b\u7684\u76f8\u5e94dll\u5373\u53ef")),(0,i.kt)("p",null,"\u4ee5Unity 2020.3.33\u7248\u672cWin\u4e0b\u7684Win64\u76ee\u6807\u4e3a\u4f8b\uff1a"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"mscorlib.dll\u5728 ",(0,i.kt)("inlineCode",{parentName:"li"},"{editor}/Editor/Data/MonoBleedingEdge/lib/mono/unityaot")),(0,i.kt)("li",{parentName:"ul"},"UnityEngine.CoreModule.dll \u5728 ",(0,i.kt)("inlineCode",{parentName:"li"},"{editor}/Editor/Data/Playbackengines/windowsstandalonesupport/Variations/il2cpp/Managed")),(0,i.kt)("li",{parentName:"ul"},"protobuf-net.dll \u4e3a\u4f60\u7684\u5de5\u7a0b\u4e2d\u7684\u539f\u59cb",(0,i.kt)("inlineCode",{parentName:"li"},"protobuf-net.dll")),(0,i.kt)("li",{parentName:"ul"},"\u4f60\u7684AOT\u6a21\u5757Main\u5bf9\u5e94\u7684AOT dll\u4e3a ",(0,i.kt)("inlineCode",{parentName:"li"},"HybridCLR/HotUpdateDlls/{platform}/Main.dll"))),(0,i.kt)("p",null,(0,i.kt)("inlineCode",{parentName:"p"},"SuerSet"),"\u6a21\u5f0f\u4e5f\u53ef\u4ee5\u4f7f\u7528",(0,i.kt)("inlineCode",{parentName:"p"},"Consistent"),"\u6a21\u5f0f\u7684\u88c1\u51cf\u540e\u7684dll\uff0c\u56e0\u4e3a\u81ea\u5df1\u663e\u7136\u5305\u542b\u81ea\u8eab\u7684\u6240\u6709\u5143\u6570\u636e\u3002"),(0,i.kt)("h3",{id:"runtimeapi"},"RuntimeApi"),(0,i.kt)("p",null,"\u5e95\u5c42\u7684\u64cd\u4f5cHybridCLR\u7684\u5de5\u5177\u7c7b\u3002\u6bd4\u8f83\u5e38\u7528\u7684\u6709:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"LoadImageErrorCode LoadMetadataForAOTAssembly(byte[] dllBytes, HomologousImageMode mode)")," \u7528\u4e8e\u52a0\u8f7d\u8865\u5145\u5143\u6570\u636eassembly\u3002")),(0,i.kt)("h3",{id:"reversepinvokewrappergenerationattribute"},"ReversePInvokeWrapperGenerationAttribute"),(0,i.kt)("p",null,"\u5982\u679c\u9879\u76ee\u4e2d\u7528\u4e8exlua\u4e4b\u7c7b\u7684\u811a\u672c\u8bed\u8a00\uff0c\u5bf9\u4e8e\u8981\u6ce8\u518c\u5230lua\u4e2d\u7684C#\u51fd\u6570\uff0c\u90fd\u9700\u8981\u6dfb\u52a0",(0,i.kt)("inlineCode",{parentName:"p"},"[MonoPInvokeCallback]"),"\u6ce8\u89e3\u3002\u8fd9\u6837\u53ef\u4ee5\u4e3a\u8fd9\u4e9bC#\u51fd\u6570\u8fd4\u56de\u4e00\u4e2a\u5bf9\u5e94\u7684c++\n\u51fd\u6570\u6307\u9488\uff0c\u7528\u4e8e\u6ce8\u518c\u5230\u811a\u672c\u8bed\u8a00\u91cc\u3002HybridCLR\u652f\u6301\u5c06\u70ed\u66f4\u65b0C#\u4ee3\u7801\u6ce8\u518c\u5230lua\u4e2d\uff0c\u4f46\u5fc5\u987b\u63d0\u524d\u751f\u6210\u4e0e",(0,i.kt)("inlineCode",{parentName:"p"},"[MonoPInvokeCallback]"),"\u5bf9\u5e94\u7684C++\u6869\u51fd\u6570\uff0c\u624d\u53ef\u80fd\u4e3a\u6bcf\u4e2aC#\u51fd\u6570\u8fd4\u56de\u4e00\u4e2a\u76f8\u5e94\u7684C++\u51fd\u6570\u6307\u9488\u3002\n\u811a\u672c\u63d0\u4f9b\u4e86\u81ea\u52a8\u751f\u6210\u6869\u51fd\u6570\u7684\u529f\u80fd\u3002\u8be6\u7ec6\u8bf7\u89c1 ",(0,i.kt)("a",{parentName:"p",href:"/docs/basic/workwithscriptlanguage"},"MonoPInvokeCallback\u652f\u6301")," \u53ca ",(0,i.kt)("a",{parentName:"p",href:"/docs/basic/workwithscriptlanguage"},"HybridCLR+lua/js/python")," \u6587\u6863"),(0,i.kt)("p",null,"\u6bcf\u4e2a\u5e26 ",(0,i.kt)("inlineCode",{parentName:"p"},"[MonoPInvokeCallback]")," \u7279\u6027\u7684\u51fd\u6570\u90fd\u9700\u8981\u4e00\u4e2a\u552f\u4e00\u5bf9\u5e94\u7684wrapper\u51fd\u6570\u3002\u8fd9\u4e9bwrapper\u51fd\u6570\u5fc5\u987b\u662f\u6253\u5305\u65f6\u9884\u5148\u751f\u6210\uff0c\u4e0d\u53ef\u53d8\u5316\u3002\n\u56e0\u6b64\u5982\u679c\u540e\u7eed\u70ed\u66f4\u65b0\u65b0\u589e\u4e86 \u5e26 ",(0,i.kt)("inlineCode",{parentName:"p"},"[MonoPInvokeCallback]")," \u7279\u6027\u7684\u51fd\u6570\uff0c\u5219\u4f1a\u53d1\u751fwrapper\u51fd\u6570\u4e0d\u8db3\u7684\u60c5\u51b5\u3002ReversePInvokeWrapperGenerationAttribute\n\u7528\u4e8e\u4e3a\u5f53\u524d\u6dfb\u52a0\u4e86 ",(0,i.kt)("inlineCode",{parentName:"p"},"[MonoPInvokeCallback]")," \u7279\u6027\u7684\u51fd\u6570\u9884\u7559\u6307\u5b9a\u6570\u91cf\u7684wrapper\u51fd\u6570\u3002\u5728\u5982\u4e0b\u793a\u4f8b\u4e2d\uff0c\u4e3aLuaFunction\u7b7e\u540d\u7684\u51fd\u6570\u9884\u7559\u4e8610\u4e2awrapper\u51fd\u6570\u3002"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-csharp"}," delegate int LuaFunction(IntPtr luaState);\n\n public class MonoPInvokeWrapperPreserves\n {\n [ReversePInvokeWrapperGeneration(10)]\n [MonoPInvokeCallback(typeof(LuaFunction))]\n public static int LuaCallback(IntPtr luaState)\n {\n return 0;\n }\n\n [MonoPInvokeCallback(typeof(Func))]\n public static int Sum(int a, int b)\n {\n return a + b;\n }\n\n [MonoPInvokeCallback(typeof(Func))]\n public static int Sum2(int a, int b)\n {\n return a + b;\n }\n\n [MonoPInvokeCallback(typeof(Func))]\n public static int Sum3()\n {\n return 0;\n }\n }\n")))}u.isMDXComponent=!0},5568:(e,l,t)=>{t.d(l,{Z:()=>n});const n=t.p+"assets/images/install-c1f84913c18dfa0406fc90db65085a56.jpg"},8648:(e,l,t)=>{t.d(l,{Z:()=>n});const n=t.p+"assets/images/install_default-c61d323cdd4133368bc575f35dd7a9ec.jpg"}}]); \ No newline at end of file diff --git a/assets/js/7d20b2b1.a2b63972.js b/assets/js/7d20b2b1.a2b63972.js new file mode 100644 index 00000000..22bb2906 --- /dev/null +++ b/assets/js/7d20b2b1.a2b63972.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[3423],{3905:(A,e,l)=>{l.d(e,{Zo:()=>d,kt:()=>P});var i=l(7294);function n(A,e,l){return e in A?Object.defineProperty(A,e,{value:l,enumerable:!0,configurable:!0,writable:!0}):A[e]=l,A}function t(A,e){var l=Object.keys(A);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(A);e&&(i=i.filter((function(e){return Object.getOwnPropertyDescriptor(A,e).enumerable}))),l.push.apply(l,i)}return l}function a(A){for(var e=1;e=0||(n[l]=A[l]);return n}(A,e);if(Object.getOwnPropertySymbols){var t=Object.getOwnPropertySymbols(A);for(i=0;i=0||Object.prototype.propertyIsEnumerable.call(A,l)&&(n[l]=A[l])}return n}var r=i.createContext({}),p=function(A){var e=i.useContext(r),l=e;return A&&(l="function"==typeof A?A(e):a(a({},e),A)),l},d=function(A){var e=p(A.components);return i.createElement(r.Provider,{value:e},A.children)},m="mdxType",s={inlineCode:"code",wrapper:function(A){var e=A.children;return i.createElement(i.Fragment,{},e)}},c=i.forwardRef((function(A,e){var l=A.components,n=A.mdxType,t=A.originalType,r=A.parentName,d=o(A,["components","mdxType","originalType","parentName"]),m=p(l),c=n,P=m["".concat(r,".").concat(c)]||m[c]||s[c]||t;return l?i.createElement(P,a(a({ref:e},d),{},{components:l})):i.createElement(P,a({ref:e},d))}));function P(A,e){var l=arguments,n=e&&e.mdxType;if("string"==typeof A||n){var t=l.length,a=new Array(t);a[0]=c;var o={};for(var r in e)hasOwnProperty.call(e,r)&&(o[r]=e[r]);o.originalType=A,o[m]="string"==typeof A?A:n,a[1]=o;for(var p=2;p{l.r(e),l.d(e,{assets:()=>r,contentTitle:()=>a,default:()=>s,frontMatter:()=>t,metadata:()=>o,toc:()=>p});var i=l(7462),n=(l(7294),l(3905));const t={},a="hybridclr Package\u624b\u518c",o={unversionedId:"basic/com.code-philosophy.hybridclr",id:"basic/com.code-philosophy.hybridclr",title:"hybridclr Package\u624b\u518c",description:"com.code-philosophy.hybridclr\u662f\u4e00\u4e2aUnity package\uff0c\u5b83\u63d0\u4f9b\u4e86HybridCLR\u6240\u9700\u7684Editor\u5de5\u4f5c\u6d41\u5de5\u5177\u811a\u672c\u53caRuntime\u811a\u672c\u3002\u501f\u52a9",source:"@site/docs/basic/com.code-philosophy.hybridclr.md",sourceDirName:"basic",slug:"/basic/com.code-philosophy.hybridclr",permalink:"/docs/basic/com.code-philosophy.hybridclr",draft:!1,tags:[],version:"current",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"\u4e0d\u652f\u6301\u7684\u7279\u6027",permalink:"/docs/basic/notsupportedfeatures"},next:{title:"\u6700\u4f73\u5b9e\u8df5",permalink:"/docs/basic/bestpractice"}},r={},p=[{value:"HybridCLR\u83dc\u5355\u4ecb\u7ecd",id:"hybridclr\u83dc\u5355\u4ecb\u7ecd",level:2},{value:"Installer...",id:"installer",level:3},{value:"Compile Dll",id:"compile-dll",level:3},{value:"Generate",id:"generate",level:3},{value:"Generate/Il2CppDef",id:"generateil2cppdef",level:3},{value:"Generate/LinkXml",id:"generatelinkxml",level:3},{value:"Generate/AotDlls",id:"generateaotdlls",level:3},{value:"Generate/MethodBridge",id:"generatemethodbridge",level:3},{value:"Generate/AOTGenericReference",id:"generateaotgenericreference",level:3},{value:"Generate/ReversePInvokeWrapper",id:"generatereversepinvokewrapper",level:3},{value:"Generate/All",id:"generateall",level:3},{value:"HybridCLR \u914d\u7f6e",id:"hybridclr-\u914d\u7f6e",level:2},{value:"enable",id:"enable",level:3},{value:"useGlobalIl2cpp",id:"useglobalil2cpp",level:3},{value:"hybridclrRepoURL",id:"hybridclrrepourl",level:3},{value:"il2cppPlusRepoURL",id:"il2cppplusrepourl",level:3},{value:"hotUpdateAssemblyDefinitions",id:"hotupdateassemblydefinitions",level:3},{value:"hotUpdateAssemblies",id:"hotupdateassemblies",level:3},{value:"preserveHotUpdateAssemblies",id:"preservehotupdateassemblies",level:3},{value:"hotUpdateDllCompileOutputRootDir",id:"hotupdatedllcompileoutputrootdir",level:3},{value:"externalHotUpdateAssemblyDirs",id:"externalhotupdateassemblydirs",level:3},{value:"strippedAOTDllOutputRootDir",id:"strippedaotdlloutputrootdir",level:3},{value:"patchAOTAssemblies",id:"patchaotassemblies",level:3},{value:"outputLinkFile",id:"outputlinkfile",level:3},{value:"outputAOTGenericReferenceFile",id:"outputaotgenericreferencefile",level:3},{value:"maxGenericReferenceIteration",id:"maxgenericreferenceiteration",level:3},{value:"maxMethodBridgeGenericIteration",id:"maxmethodbridgegenericiteration",level:3},{value:"Build Pipeline\u76f8\u5173\u811a\u672c",id:"build-pipeline\u76f8\u5173\u811a\u672c",level:2},{value:"\u68c0\u67e5\u548c\u4fee\u590d\u8bbe\u7f6e",id:"\u68c0\u67e5\u548c\u4fee\u590d\u8bbe\u7f6e",level:3},{value:"\u6253\u5305\u65f6\u81ea\u52a8\u6392\u9664\u70ed\u66f4\u65b0assembly",id:"\u6253\u5305\u65f6\u81ea\u52a8\u6392\u9664\u70ed\u66f4\u65b0assembly",level:3},{value:"\u6253\u5305\u65f6\u5c06\u70ed\u66f4\u65b0dll\u540d\u6dfb\u52a0\u5230assembly\u5217\u8868",id:"\u6253\u5305\u65f6\u5c06\u70ed\u66f4\u65b0dll\u540d\u6dfb\u52a0\u5230assembly\u5217\u8868",level:3},{value:"\u5907\u4efd\u88c1\u526a\u540e\u7684AOT dll",id:"\u5907\u4efd\u88c1\u526a\u540e\u7684aot-dll",level:3},{value:"iOSBuild\u811a\u672c",id:"iosbuild\u811a\u672c",level:2},{value:"Runtime\u76f8\u5173\u811a\u672c",id:"runtime\u76f8\u5173\u811a\u672c",level:2},{value:"LoadImageErrorCode",id:"loadimageerrorcode",level:3},{value:"\u5143\u6570\u636e\u6a21\u5f0f HomologousImageMode",id:"\u5143\u6570\u636e\u6a21\u5f0f-homologousimagemode",level:3},{value:"HomologousImageMode::Consistent \u6a21\u5f0f",id:"homologousimagemodeconsistent-\u6a21\u5f0f",level:4},{value:"HomologousImageMode::SuperSet \u6a21\u5f0f",id:"homologousimagemodesuperset-\u6a21\u5f0f",level:4},{value:"RuntimeApi",id:"runtimeapi",level:3},{value:"ReversePInvokeWrapperGenerationAttribute",id:"reversepinvokewrappergenerationattribute",level:3}],d={toc:p},m="wrapper";function s(A){let{components:e,...t}=A;return(0,n.kt)(m,(0,i.Z)({},d,t,{components:e,mdxType:"MDXLayout"}),(0,n.kt)("h1",{id:"hybridclr-package\u624b\u518c"},"hybridclr Package\u624b\u518c"),(0,n.kt)("p",null,(0,n.kt)("inlineCode",{parentName:"p"},"com.code-philosophy.hybridclr"),"\u662f\u4e00\u4e2aUnity package\uff0c\u5b83\u63d0\u4f9b\u4e86HybridCLR\u6240\u9700\u7684Editor\u5de5\u4f5c\u6d41\u5de5\u5177\u811a\u672c\u53caRuntime\u811a\u672c\u3002\u501f\u52a9\ncom.code-philosophy.hybridclr\u63d0\u4f9b\u7684\u5de5\u4f5c\u6d41\u5de5\u5177\uff0c\u6253\u5305\u4e00\u4e2a\u652f\u6301HybridCLR\u70ed\u66f4\u65b0\u529f\u80fd\u7684App\u53d8\u5f97\u975e\u5e38\u7b80\u5355\u3002hybridclr_unity\u5305\u4e3b\u8981\u5305\u542b\u4ee5\u4e0b\u5185\u5bb9\uff1a"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"Editor\u76f8\u5173\u811a\u672c"),(0,n.kt)("li",{parentName:"ul"},"Runtime\u76f8\u5173\u811a\u672c"),(0,n.kt)("li",{parentName:"ul"},"iOSBuild\u811a\u672c")),(0,n.kt)("admonition",{type:"caution"},(0,n.kt)("p",{parentName:"admonition"},"v3.0.0 \u4e4b\u524d\u7684\u5305\u540d\u53eb ",(0,n.kt)("inlineCode",{parentName:"p"},"com.focus-creative-games.hybridclr_unity"),"\u3002")),(0,n.kt)("h2",{id:"hybridclr\u83dc\u5355\u4ecb\u7ecd"},"HybridCLR\u83dc\u5355\u4ecb\u7ecd"),(0,n.kt)("p",null,"\u4ee5\u4e0b\u5b50\u83dc\u5355\u5747\u5728\u83dc\u5355\u680f\u7684",(0,n.kt)("inlineCode",{parentName:"p"},"HybridCLR"),"\u83dc\u5355\u4e0b\uff0c\u51fa\u4e8e\u7b80\u5316\uff0c\u6211\u4eec\u4e0b\u9762\u63d0\u5230\u5b50\u83dc\u5355\u65f6\u4e0d\u518d\u5305\u542bHybridCLR\u3002"),(0,n.kt)("h3",{id:"installer"},"Installer..."),(0,n.kt)("p",null,"\u8be6\u7ec6\u6587\u6863\u89c1",(0,n.kt)("a",{parentName:"p",href:"/docs/basic/install"},"\u5b89\u88c5HybridCLR"),"\u3002"),(0,n.kt)("p",null,"Installer\u662f\u4e00\u4e2a\u65b9\u4fbf\u7684\u5b89\u88c5\u5668\uff0c\u5e2e\u52a9\u6b63\u786e\u8bbe\u7f6e\u672c\u5730il2cpp\u76ee\u5f55\uff0c\u5176\u4e2d\u5305\u542b\u66ff\u6362",(0,n.kt)("inlineCode",{parentName:"p"},"HybridCLRData/LocalIl2CppData-{platform}/il2cpp/libil2cpp"),"\u76ee\u5f55\u4e3aHybridCLR\u4fee\u6539\u7248\u672c\u3002"),(0,n.kt)("p",null,"\u5b89\u88c5\u754c\u9762\u4e2d ",(0,n.kt)("inlineCode",{parentName:"p"},"\u5b89\u88c5\u72b6\u6001\uff1a\u5df2\u5b89\u88c5|\u672a\u5b89\u88c5")," \u6307\u793a\u662f\u5426\u5b8c\u6210HybridCLR\u521d\u59cb\u5316\u3002\u70b9\u51fb\u5b89\u88c5\uff0c\u5982\u6210\u529f\uff0c\u5219\u6700\u540e\u4f1a\u663e\u793a",(0,n.kt)("inlineCode",{parentName:"p"},"\u5b89\u88c5\u6210\u529f"),"\u65e5\u5fd7\uff0c\u5e76\u4e14\u5b89\u88c5\u72b6\u6001\u5207\u6362\u4e3a",(0,n.kt)("inlineCode",{parentName:"p"},"\u5df2\u5b89\u88c5"),"\uff0c\u5426\u5219\u8bf7\u68c0\u67e5\u9519\u8bef\u65e5\u5fd7\u3002"),(0,n.kt)("admonition",{type:"tip"},(0,n.kt)("p",{parentName:"admonition"},"\u5982\u679c\u5df2\u7ecf\u5b89\u88c5HybridCLR\uff0c\u70b9\u51fb\u5b89\u88c5\u6309\u94ae\u4f1a\u5b89\u88c5\u6700\u65b0\u7684HybridCLR\u7248\u672c\u7684libil2cpp\u3002")),(0,n.kt)("p",null,"com.code-philosophy.hybridclr\u4e2d ",(0,n.kt)("inlineCode",{parentName:"p"},"Data~/hybridclr_version.json")," \u6587\u4ef6\u4e2d\u5df2\u7ecf\u914d\u7f6e\u4e86\u5f53\u524dpackage\u7248\u672c\u5bf9\u5e94\u7684\u517c\u5bb9 hybridclr\u53cail2cpp_plus\u7684\u5206\u652f\u6216\u8005tag\uff0c\nInstaller\u4f1a\u5b89\u88c5\u914d\u7f6e\u4e2d\u6307\u5b9a\u7684\u7248\u672c\uff0c\u4e0d\u518d\u652f\u6301\u81ea\u5b9a\u4e49\u5f85\u5b89\u88c5\u7684\u7248\u672c\u3002"),(0,n.kt)("p",null,"\u914d\u7f6e\u7c7b\u4f3c\u5982\u4e0b\uff1a"),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-json"},'{\n "versions": [\n {\n "unity_version":"2019",\n "hybridclr" : { "branch":"v2.0.1"},\n "il2cpp_plus": { "branch":"v2019-2.0.1"}\n },\n {\n "unity_version":"2020",\n "hybridclr" : { "branch":"v2.0.1"},\n "il2cpp_plus": { "branch":"v2020-2.0.1"}\n },\n {\n "unity_version":"2021",\n "hybridclr" : { "branch":"v2.0.1"},\n "il2cpp_plus": { "branch":"v2021-2.0.1"}\n },\n {\n "unity_version":"2022",\n "hybridclr" : { "branch":"v2.0.1"},\n "il2cpp_plus": { "branch":"v2020-2.0.1"}\n }\n ]\n}\n')),(0,n.kt)("p",null,"\u5982\u679c\u4f60\u4e00\u5b9a\u8981\u5b89\u88c5\u5176\u4ed6\u7248\u672c\u7684hybridclr\u6216il2cpp_plus\uff0c\u4fee\u6539\u8be5\u914d\u7f6e\u6587\u4ef6\u4e2d\u7684branch\u4e3a\u76ee\u6807\u5206\u652f\u6216\u8005tag\u3002"),(0,n.kt)("p",null,(0,n.kt)("img",{alt:"install_default",src:l(8648).Z,width:"803",height:"221"})),(0,n.kt)("p",null,"\u4ece\u7248\u672c2.3.1\u8d77\u65b0\u589e\u652f\u6301\u76f4\u63a5\u4ece\u672c\u5730\u81ea\u5df1\u5236\u4f5c\u7684\u5305\u542bhybridclr\u7684libil2cpp\u76ee\u5f55\u590d\u5236\u5b89\u88c5\u3002\u5982\u679c\u4f60\u7f51\u7edc\u4e0d\u597d\uff0c\u6216\u8005\u6ca1\u6709\u5b89\u88c5git\u5bfc\u81f4\u65e0\u6cd5\u4ece\u4ed3\u5e93\u8fdc\u7a0b\u4e0b\u8f7d\u5b89\u88c5\uff0c\u5219\u53ef\u4ee5\u5148\u5c06 ",(0,n.kt)("a",{parentName:"p",href:"https://github.com/focus-creative-games/il2cpp_plus"},"il2cpp_plus"),"\u548c",(0,n.kt)("a",{parentName:"p",href:"https://github.com/focus-creative-games/hybridclr"},"hybridclr"),"\u4e0b\u8f7d\u5230\u672c\u5730\u540e\uff0c\u518d\u6839\u636e\u4e0b\u9762",(0,n.kt)("strong",{parentName:"p"},"\u5b89\u88c5\u539f\u7406"),"\u5c0f\u8282\u7684\u6587\u6863\uff0c\u7531\u8fd9\u4e24\u4e2a\u4ed3\u5e93\u5408\u5e76\u51fa\u542bhybridclr\u7684libil2cpp\u76ee\u5f55\uff0c\u63a5\u7740\u5728",(0,n.kt)("inlineCode",{parentName:"p"},"Installer"),"\u5b89\u88c5\u754c\u9762\u4e2d\u542f\u7528",(0,n.kt)("inlineCode",{parentName:"p"},"\u4ece\u672c\u5730\u590d\u5236libil2cpp"),"\u9009\u9879\uff0c\u9009\u62e9\u4f60\u5236\u4f5c\u7684libil2cpp\u76ee\u5f55\uff0c\u518d\u70b9\u51fb",(0,n.kt)("inlineCode",{parentName:"p"},"\u5b89\u88c5"),"\u6267\u884c\u5b89\u88c5\u3002\u5982\u4e0b\u56fe\u6240\u793a\u3002"),(0,n.kt)("p",null,(0,n.kt)("img",{alt:"install",src:l(5568).Z,width:"802",height:"195"})),(0,n.kt)("h3",{id:"compile-dll"},"Compile Dll"),(0,n.kt)("p",null,"\u5bf9\u4e8e\u6bcf\u4e2atarget\uff0c\u5fc5\u987b\u4f7f\u7528\u76ee\u6807\u5e73\u53f0\u7f16\u8bd1\u5f00\u5173\u4e0b\u7f16\u8bd1\u51fa\u7684\u70ed\u66f4\u65b0dll\uff0c\u5426\u5219\u4f1a\u51fa\u73b0\u70ed\u66f4\u65b0\u4ee3\u7801\u4e0eAOT\u4e3b\u5305\u6216\u8005\u70ed\u66f4\u65b0\u8d44\u6e90\u7684\u4ee3\u7801\u4fe1\u606f\u4e0d\u5339\u914d\u7684\u60c5\u51b5\u3002"),(0,n.kt)("p",null,"com.code-philosophy.hybridclr\u7684",(0,n.kt)("inlineCode",{parentName:"p"},"HybridCLR.Editor"),"\u7a0b\u5e8f\u96c6\u63d0\u4f9b\u4e86",(0,n.kt)("inlineCode",{parentName:"p"},"HybridCLR.Editor.Commands.CompileDllCommand.CompileDll(BuildTarget target)"),"\u63a5\u53e3\uff0c\n\u65b9\u4fbf\u5f00\u53d1\u8005\u7075\u6d3b\u5730\u81ea\u884c\u7f16\u8bd1\u70ed\u66f4\u65b0dll\u3002\u7f16\u8bd1\u5b8c\u6210\u540e\u7684\u70ed\u66f4\u65b0dll\u653e\u5230 ",(0,n.kt)("inlineCode",{parentName:"p"},"{project}/HybridCLRData/HotUpdateDlls/{platform}")," \u76ee\u5f55\u4e0b\u3002"),(0,n.kt)("h3",{id:"generate"},"Generate"),(0,n.kt)("p",null,"Generate\u4e0b\u5305\u542b\u6253\u5305\u65f6\u9700\u8981\u7684\u751f\u6210\u547d\u4ee4\u3002"),(0,n.kt)("h3",{id:"generateil2cppdef"},"Generate/Il2CppDef"),(0,n.kt)("p",null,"hybridclr\u4ee3\u7801\u8981\u517c\u5bb9\u591a\u4e2aUnity\u7248\u672c\uff0c\u9700\u8981\u5f53\u524dUnity\u7248\u672c\u76f8\u5173\u5b8f\u5b9a\u4e49\u3002",(0,n.kt)("inlineCode",{parentName:"p"},"Generate/Il2CppDef"),"\u547d\u4ee4\u751f\u6210\u4e86\u76f8\u5173\u7248\u672c\u5b8f\u53ca\u5176\u4ed6\u5fc5\u987b\u7684\u4ee3\u7801\uff0c\u751f\u6210\u7684\u4ee3\u7801\u7c7b\u4f3c\u5982\u4e0b\u3002"),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-cpp"},"// hybridclr/generated/UnityVersion.h\n\n#define HYBRIDCLR_UNITY_VERSION 2020333\n#define HYBRIDCLR_UNITY_2020 1\n#define HYBRIDCLR_UNITY_2019_OR_NEW 1\n#define HYBRIDCLR_UNITY_2020_OR_NEW 1\n")),(0,n.kt)("h3",{id:"generatelinkxml"},"Generate/LinkXml"),(0,n.kt)("p",null,"\u626b\u63cf\u70ed\u66f4\u65b0dll\u5f15\u7528\u7684AOT\u7c7b\u578b\uff0c\u751f\u6210link.xml\uff0c\u907f\u514d\u70ed\u66f4\u65b0\u811a\u672c\u7528\u5230\u7684AOT\u7c7b\u578b\u6216\u51fd\u6570\u88ab\u88c1\u526a\u3002\u8f93\u51fa\u7684\u6587\u4ef6\u8def\u5f84\u5728 HybridCLRSettings.asset\u4e2d",(0,n.kt)("inlineCode",{parentName:"p"},"OuputLinkXml"),"\u5b57\u6bb5\u4e2d\u6307\u5b9a\uff0c\u9ed8\u8ba4\u4e3a",(0,n.kt)("inlineCode",{parentName:"p"},"LinkGenerator/link.xml"),"\u3002"),(0,n.kt)("p",null,"\u66f4\u5177\u4f53\u7684\u88c1\u526a\u76f8\u5173\u4ecb\u7ecd\u8bf7\u770b",(0,n.kt)("a",{parentName:"p",href:"/docs/basic/codestriping"},"\u4ee3\u7801\u88c1\u526a\u539f\u7406\u53ca\u89e3\u51b3\u529e\u6cd5"),"\u3002"),(0,n.kt)("h3",{id:"generateaotdlls"},"Generate/AotDlls"),(0,n.kt)("p",null,"\u751f\u6210\u88c1\u526a\u540e\u7684AOT dlls\u3002\u811a\u672c\u901a\u8fc7\u5728\u4e00\u4e2a\u4e34\u65f6\u76ee\u5f55\u5bfc\u51fa\u5de5\u7a0b\uff0c\u5b9e\u73b0\u751f\u6210\u88c1\u526a\u540e\u7684AOT dlls\u7684\u76ee\u6807\u3002\u751f\u6210AOT dlls\u4f9d\u8d56\u4e8e",(0,n.kt)("inlineCode",{parentName:"p"},"Generate/LinkXml"),"\u548c",(0,n.kt)("inlineCode",{parentName:"p"},"Generate/Il2CppDef"),"\u3002\n\u5982\u679c\u4f60\u6ca1\u6709\u7528 ",(0,n.kt)("inlineCode",{parentName:"p"},"HybridCLR/Generate/All")," \u8fd9\u6837\u7684\u4e00\u952e\u751f\u6210\u547d\u4ee4\uff0c\u8bf7\u4f9d\u6b21\u8fd0\u884c\u4ee5\u4e0b\u547d\u4ee4\uff1a"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("inlineCode",{parentName:"li"},"HybridCLR/Generate/Il2CppDef")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("inlineCode",{parentName:"li"},"HybridCLR/Generate/LinkXml")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("inlineCode",{parentName:"li"},"HybridCLR/Generate/AotDlls"))),(0,n.kt)("h3",{id:"generatemethodbridge"},"Generate/MethodBridge"),(0,n.kt)("p",null,"\u6839\u636e\u5f53\u524d\u7684AOT dll\u96c6\u626b\u63cf\u751f\u6210\u6865\u63a5\u51fd\u6570\u6587\u4ef6\u3002\u76f8\u5173\u6587\u6863\u8bf7\u770b",(0,n.kt)("a",{parentName:"p",href:"/docs/basic/methodbridge"},"\u6865\u63a5\u51fd\u6570"),"\u3002"),(0,n.kt)("p",null,"\u751f\u6210\u6865\u63a5\u51fd\u6570\u4f9d\u8d56AOT dlls\u548c\u70ed\u66f4\u65b0dlls\u3002\u5982\u679c\u4f60\u6ca1\u6709\u7528 ",(0,n.kt)("inlineCode",{parentName:"p"},"HybridCLR/Generate/All")," \u8fd9\u6837\u7684\u4e00\u952e\u751f\u6210\u547d\u4ee4\uff0c\u8bf7\u4f9d\u6b21\u8fd0\u884c\u4ee5\u4e0b\u547d\u4ee4\uff1a"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("inlineCode",{parentName:"li"},"HybridCLR/Generate/Il2CppDef")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("inlineCode",{parentName:"li"},"HybridCLR/Generate/LinkXml")," (\u9690\u542b\u8c03\u7528\u4e86 ",(0,n.kt)("inlineCode",{parentName:"li"},"HybridCLR/CompileDll/ActiveBuildTarget"),")"),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("inlineCode",{parentName:"li"},"HybridCLR/Generate/AotDlls")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("inlineCode",{parentName:"li"},"HybridCLR/Generate/MethodBridge"))),(0,n.kt)("h3",{id:"generateaotgenericreference"},"Generate/AOTGenericReference"),(0,n.kt)("p",null,"\u6839\u636e\u5f53\u524d\u70ed\u66f4\u65b0dll\u626b\u63cf\u51fa\u6240\u6709\u4ea7\u751f\u7684AOT\u6cdb\u578b\u7c7b\u578b\u53ca\u51fd\u6570\u7684\u5b9e\u4f8b\u5316\uff0c\u5e76\u751f\u6210\u4e00\u4e2a",(0,n.kt)("strong",{parentName:"p"},"\u542f\u53d1\u7684"),"\u6cdb\u578b\u5b9e\u4f8b\u5316\u6587\u4ef6",(0,n.kt)("inlineCode",{parentName:"p"},"AOTGenericReferences.cs"),"\u3002\n\u7531\u4e8e\u5c06\u626b\u63cf\u51fa\u7684\u6cdb\u578b\u7c7b\u578b\u53ca\u51fd\u6570\u8f6c\u6362\u4e3a\u5bf9\u5e94\u7684\u4ee3\u7801\u5f15\u7528\u6bd4\u8f83\u9ebb\u70e6\uff0c\u751f\u6210\u7684\u6240\u6709\u6cdb\u578b\u5b9e\u4f8b\u5316\u4ee3\u7801\u90fd\u662f",(0,n.kt)("strong",{parentName:"p"},"\u6ce8\u91ca\u4ee3\u7801"),"\u3002"),(0,n.kt)("p",null,(0,n.kt)("inlineCode",{parentName:"p"},"AOTGenericReferences.cs"),"\u6587\u4ef6\u4e2d\u8fd8\u5305\u542b\u4e86\u5e94\u8be5\u8865\u5145\u5143\u6570\u636e\u7684assembly\u5217\u8868\uff0c\u7c7b\u4f3c\u5982\u4e0b\uff0c\u65b9\u4fbf\u5f00\u53d1\u8005\u4e0d\u9700\u8981\u8fd0\u884c\u6e38\u620f\u4e5f\u80fd\u5feb\u901f\u77e5\u9053\u5e94\u8be5\u8865\u5145\u54ea\u4e9b\u5143\u6570\u636e\u3002"),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-csharp"}," // {{ AOT assemblies\n // Main.dll\n // System.Core.dll\n // UnityEngine.CoreModule.dll\n // mscorlib.dll\n // }}\n")),(0,n.kt)("p",null,"\u8bf7\u5728\u5176\u4ed6\u6587\u4ef6\u4e2d\u6dfb\u52a0\u6cdb\u578b\u7c7b\u578b\u53ca\u51fd\u6570\u7684\u5b9e\u4f8b\u5316\u5f15\u7528\uff0c\u56e0\u4e3a\u8fd9\u4e2a\u8f93\u51fa\u6587\u4ef6\u6bcf\u6b21\u91cd\u65b0\u751f\u6210\u540e\u4f1a\u88ab\u8986\u76d6\u3002\n\u8fd9\u4e2a\u6cdb\u578b\u5b9e\u4f8b\u5316\u6587\u6863\u53ea\u8d77\u5230\u542f\u53d1\u4f5c\u7528\uff0c\u544a\u8bc9\u4f60\u53ef\u4ee5aot\u6cdb\u578b\u5b9e\u4f8b\u5316\u54ea\u4e9b\u7c7b\u548c\u51fd\u6570\u3002\n\u66f4\u5177\u4f53\u7684AOT\u6cdb\u578b\u76f8\u5173\u6587\u6863\u8bf7\u770b",(0,n.kt)("a",{parentName:"p",href:"/docs/basic/aotgeneric"},"AOT\u6cdb\u578b\u4ecb\u7ecd"),"\u3002"),(0,n.kt)("admonition",{type:"tip"},(0,n.kt)("p",{parentName:"admonition"},"\u4f7f\u7528\u8865\u5145\u5143\u6570\u636e\u673a\u5236\u540e\uff0c",(0,n.kt)("strong",{parentName:"p"},"\u4e0d\u4f5c\u4efb\u4f55\u5904\u7406"),"\u4e5f\u4e0d\u5f71\u54cd\u6b63\u5e38\u8fd0\u884c\u3002\u4f46\u5982\u679c\u624b\u52a8\u5bf9aot\u6cdb\u578b\u5b9e\u4f8b\u5316\uff0c\u53ef\u4ee5\u63d0\u5347\u6027\u80fd\u3002\u5efa\u8bae\u662f\u5bf9\u4e8e\u5c11\u91cf\u6027\u80fd\u654f\u611f\u7684\u7c7b\u6216\u51fd\u6570\u624b\u52a8\u6cdb\u578b\u5b9e\u4f8b\u5316\u5373\u53ef\uff0c\u5982",(0,n.kt)("inlineCode",{parentName:"p"},"Dictionary"),"\u4e4b\u7c7b\u3002")),(0,n.kt)("p",null,"\u7531\u5f00\u53d1\u8005\u81ea\u5df1\u914c\u60c5\u8f6c\u6362\u4e3a\u6b63\u786e\u7684\u5b9e\u4f8b\u5316\u5f15\u7528\uff08",(0,n.kt)("strong",{parentName:"p"},"\u8fd9\u4e2a\u64cd\u4f5c\u662f\u53ef\u9009\u7684\uff0c\u53ef\u4ee5\u5b8c\u5168\u4e0d\u5904\u7406\u6216\u53ea\u5904\u7406\u4e00\u90e8\u5206"),"\uff09\uff0c\u5373\u5728AOT\u4ee3\u7801\u4e2d\u5b9e\u4f8b\u5316\u8fd9\u6ce8\u91ca\u4e2d\u7684\u6cdb\u578b\u7c7b\u6216\u6cdb\u578b\u51fd\u6570\u3002\u65b9\u6cd5\u5927\u81f4\u5982\u4e0b\uff1a"),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-csharp"},"\n // System.Collections.Generics.List`1.ctor\n new List();\n\n // System.Byte[] Array.Empty`1()\n Array.Empty();\n\n")),(0,n.kt)("h3",{id:"generatereversepinvokewrapper"},"Generate/ReversePInvokeWrapper"),(0,n.kt)("p",null,"\u4e3a\u6807\u8bb0\u4e86",(0,n.kt)("inlineCode",{parentName:"p"},"[MonoPInvokeCallback]"),"\u6ce8\u89e3\u7684\u70ed\u66f4\u65b0C#\u9759\u6001\u51fd\u6570\u751f\u6210ReversePInvokeWrapper\u51fd\u6570\u3002\u5177\u4f53\u7684MonoPInvokeCallback\u4ecb\u7ecd\u8bf7\u770b\u6587\u6863",(0,n.kt)("a",{parentName:"p",href:"/docs/basic/workwithscriptlanguage"},"MonoPInvokeCallback\u652f\u6301")),(0,n.kt)("h3",{id:"generateall"},"Generate/All"),(0,n.kt)("p",null,"\u4e00\u952e\u6267\u884c\u6253\u5305\u524d\u5fc5\u987b\u7684\u751f\u6210\u64cd\u4f5c\u3002"),(0,n.kt)("h2",{id:"hybridclr-\u914d\u7f6e"},"HybridCLR \u914d\u7f6e"),(0,n.kt)("p",null,"\u70b9\u51fb\u83dc\u5355 ",(0,n.kt)("inlineCode",{parentName:"p"},"HybridCLR/Settings")," \u6253\u5f00\u914d\u7f6e\u754c\u9762\u3002\u4e0b\u9762\u662f\u5b57\u6bb5\u8be6\u7ec6\u8bf4\u660e\u3002"),(0,n.kt)("h3",{id:"enable"},"enable"),(0,n.kt)("p",null,"\u662f\u5426\u5f00\u542fHybridCLR\u70ed\u66f4\u3002\u9ed8\u8ba4true\u3002\u5982\u679c\u4e3afalse\uff0c\u5219\u6253\u5305\u4e0d\u518d\u5305\u542bHybridCLR\u529f\u80fd\u3002"),(0,n.kt)("admonition",{type:"caution"},(0,n.kt)("p",{parentName:"admonition"},"\u5982\u679c\u7981\u7528HybridCLR\uff0c\u8bf7\u540c\u65f6\u4e5f\u79fb\u9664\u4e3b\u5de5\u7a0b\u4e2d\u5bf9HybridCLR.Runtime\u7a0b\u5e8f\u96c6\u7684\u5f15\u7528\uff0c\u5426\u5219\u6253\u5305\u65f6\u4f1a\u51fa\u73b0",(0,n.kt)("inlineCode",{parentName:"p"},"RuntimeApi::LoadMetadataForAOTAssembly"),"\u4e4b\u7c7b\u7b26\u53f7\u4e22\u5931\u7684\u9519\u8bef\u3002")),(0,n.kt)("h3",{id:"useglobalil2cpp"},"useGlobalIl2cpp"),(0,n.kt)("p",null,"\u662f\u5426\u4f7f\u7528\u5168\u5c40\u5b89\u88c5\u4f4d\u7f6e\uff0c\u5373editor\u5b89\u88c5\u4f4d\u7f6e\u4e0b\u7684il2cpp\u76ee\u5f55\u3002\u9ed8\u8ba4false\u3002\u4e00\u822c\u53ea\u6709\u6253\u5305WebGL\u65f6\u624d\u9700\u8981",(0,n.kt)("inlineCode",{parentName:"p"},"useGlobalIl2cpp=true"),"\u3002"),(0,n.kt)("p",null,"\u6ce8\u610f\uff0c\u5c31\u7b97 ",(0,n.kt)("inlineCode",{parentName:"p"},"useGlobalIl2Cpp=true"),"\uff0c\u5b89\u88c5\u65f6\u4ecd\u7136\u4f1a\u590d\u5236il2cpp\u5230HybridCLRData\u76ee\u5f55\u3002\u5728\u590d\u5236\u524d\u9700\u8981\u5148\u8fd0\u884c",(0,n.kt)("inlineCode",{parentName:"p"},"HybridCLR/Generate/Il2CppDef"),"\u751f\u6210\u7248\u672c\u5b8f\uff0c\n\u518d\u624b\u52a8\u5c06 ",(0,n.kt)("inlineCode",{parentName:"p"},"{project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp/libil2cpp"),"\u76ee\u5f55\u66ff\u6362 editor\u5b89\u88c5\u76ee\u5f55\u4e0b\u7684\u5bf9\u5e94\u76ee\u5f55\u3002\n\u53e6\u5916\u6bcf\u6b21\u8fd0\u884c",(0,n.kt)("inlineCode",{parentName:"p"},"HybridCLR/Generate/*"),"\u6267\u884c\u751f\u6210\u64cd\u4f5c\uff0c\u8f93\u51fa\u76ee\u5f55\u4ecd\u7136\u662f\u672c\u5730\u76ee\u5f55\uff0c\u9700\u8981\u81ea\u5df1\u624b\u52a8\u590d\u5236\u66ff\u6362\u5168\u5c40\u5b89\u88c5\u4f4d\u7f6e\u7684libil2cpp\u76ee\u5f55\u3002"),(0,n.kt)("h3",{id:"hybridclrrepourl"},"hybridclrRepoURL"),(0,n.kt)("p",null,"hybridclr\u4ed3\u5e93\u7684\u5730\u5740\uff0c\u9ed8\u8ba4\u503c\u4e3a ",(0,n.kt)("inlineCode",{parentName:"p"},"https://gitee.com/focus-creative-games/hybridclr"),"\u3002Installer\u5b89\u88c5\u65f6\u4ece\u6b64\u5730\u5740clone hybridclr\u4ed3\u5e93\u4ee3\u7801\u3002"),(0,n.kt)("h3",{id:"il2cppplusrepourl"},"il2cppPlusRepoURL"),(0,n.kt)("p",null,"il2cpp_plus \u4ed3\u5e93\u7684\u5730\u5740\uff0c\u9ed8\u8ba4\u503c\u4e3a ",(0,n.kt)("inlineCode",{parentName:"p"},"https://gitee.com/focus-creative-games/il2cpp_plus"),"\u3002Installer\u5b89\u88c5\u65f6\u4ece\u6b64\u5730\u5740clone il2cpp_plus\u4ed3\u5e93\u4ee3\u7801\u3002"),(0,n.kt)("h3",{id:"hotupdateassemblydefinitions"},"hotUpdateAssemblyDefinitions"),(0,n.kt)("p",null,"\u4ee5assembly definition(asmdef) \u5f62\u5f0f\u5b9a\u4e49\u7684\u70ed\u66f4\u65b0\u6a21\u5757\u5217\u8868\uff0c\u5b83\u4e0e\u4e0b\u9762\u7684",(0,n.kt)("inlineCode",{parentName:"p"},"hotUpdateAssemblies"),"\u662f\u7b49\u6548\u7684\uff0c\u53ea\u4e0d\u8fc7\u7f16\u8f91\u5668\u4e0b\u62d6\u5165asmdef\u6a21\u5757\u6bd4\u8f83\u65b9\u4fbf\uff0c\u4e5f\u4e0d\u5bb9\u6613\u5931\u8bef\u5199\u9519\u540d\u79f0\u3002"),(0,n.kt)("admonition",{type:"caution"},(0,n.kt)("p",{parentName:"admonition"},(0,n.kt)("inlineCode",{parentName:"p"},"hotUpdateAssemblyDefinitions"),"\u548c",(0,n.kt)("inlineCode",{parentName:"p"},"hotUpdateAssemblies"),"\u5408\u5e76\u540e\u6784\u6210\u6700\u7ec8\u7684\u70ed\u66f4\u65b0dll\u5217\u8868\u3002\u540c\u4e00\u4e2aassembly\u4e0d\u8981\u5728\u4e24\u4e2a\u5217\u8868\u4e2d\u540c\u65f6\u51fa\u73b0\uff0c\u4f1a\u62a5\u9519\uff01")),(0,n.kt)("h3",{id:"hotupdateassemblies"},"hotUpdateAssemblies"),(0,n.kt)("p",null,"\u6709\u4e00\u4e9bassembly\u4ee5dll\u5f62\u5f0f\u5b58\u5728\uff0c\u4f8b\u5982\u4f60\u5728\u5916\u90e8\u5de5\u7a0b\u4e2d\u521b\u5efa\u7684\u70ed\u66f4\u65b0dll\uff0c\u53c8\u5982\u4f60\u76f4\u63a5\u4f7f\u7528Assembly-CSharp\u4f5c\u4e3a\u4f60\u7684\u70ed\u66f4\u65b0dll\u3002\u7531\u4e8e\u6ca1\u6709\u5bf9\u5e94\u7684asmdef\u6587\u4ef6\uff0c\u53ea\u80fd\u4ee5dll\u540d\u79f0\u5f62\u5f0f\u624b\u52a8\u914d\u7f6e\u3002\n\u586b\u5199assembly\u540d\u79f0\u65f6\u4e0d\u8981\u5305\u542b'.dll'\u540e\u7f00\uff0c\u50cf",(0,n.kt)("inlineCode",{parentName:"p"},"Main"),"\u3001",(0,n.kt)("inlineCode",{parentName:"p"},"Assembly-CSharp"),"\u8fd9\u6837\u5373\u53ef\u3002asmdef\u5f62\u5f0f\u7684assembly\uff0c\u4f60\u4e5f\u53ef\u4ee5\u9009\u62e9\u4e0d\u52a0\u5230",(0,n.kt)("inlineCode",{parentName:"p"},"hotUpdateAssemblyDefinitions"),"\uff0c\n\u800c\u662f\u52a0\u5230",(0,n.kt)("inlineCode",{parentName:"p"},"hotUpdateAssemblies"),"\u3002\u4e0d\u8fc7\u8fd9\u6837\u4e0d\u5982\u76f4\u63a5\u62d6\u5165\u5217\u8868\u65b9\u4fbf\uff0c\u4f60\u81ea\u5df1\u914c\u60c5\u9009\u62e9\u3002"),(0,n.kt)("p",null,(0,n.kt)("inlineCode",{parentName:"p"},"hotUpdateAssemblyDefinitions"),"\u548c",(0,n.kt)("inlineCode",{parentName:"p"},"hotUpdateAssemblies"),"\u5408\u5e76\u540e\u6784\u6210\u6700\u7ec8\u7684\u70ed\u66f4\u65b0dll\u5217\u8868\u3002\u540c\u4e00\u4e2aassembly\u4e0d\u8981\u5728\u4e24\u4e2a\u5217\u8868\u4e2d\u540c\u65f6\u51fa\u73b0\uff0c\u4f1a\u62a5\u9519\uff01"),(0,n.kt)("h3",{id:"preservehotupdateassemblies"},"preserveHotUpdateAssemblies"),(0,n.kt)("p",null,"\u9884\u7559\u7684\u70ed\u66f4\u65b0dll\u540d\u5b57\u5217\u8868\u3002\u6709\u65f6\u5019\u60f3\u5728\u5c06\u6765\u65b0\u589e\u4e00\u4e9b\u70ed\u66f4\u65b0dll\uff0c\u5e76\u4e14\u671f\u671b\u8fd9\u4e9b\u65b0\u7684\u70ed\u66f4\u65b0dll\u7684\u811a\u672c\u80fd\u591f\u6302\u8f7d\u5230\u8d44\u6e90\u4e0a\uff0c\u5982\u679c\u76f4\u63a5\u5c06\u70ed\u66f4\u65b0dll\u540d\u52a0\u5230 hotUpdateAssemblies\u5219\u4f1a\u62a5assembly\u4e0d\u5b58\u5728\u7684\u9519\u8bef\u3002\npreserveHotUpdateAssemblies\u5b57\u6bb5\u7528\u6765\u6ee1\u8db3\u8fd9\u79cd\u9700\u6c42\u3002\u6253\u5305\u65f6\u4e0d\u68c0\u67e5\u8fd9\u4e9bdll\u7684\u6709\u6548\u6027\uff0c\u5e76\u4e14\u4f1a\u5c06\u5b83\u4eec\u6dfb\u52a0\u5230scriptingassemblies.json\u4e4b\u7c7b\u7684assembly\u5217\u8868\u6587\u4ef6\u4e2d\u3002\n\u586b\u5199assembly\u540d\u79f0\u65f6\u4e0d\u8981\u5305\u542b",(0,n.kt)("inlineCode",{parentName:"p"},".dll"),"\u540e\u7f00\uff0c\u50cf",(0,n.kt)("inlineCode",{parentName:"p"},"Assembly-CSharp"),"\u8fd9\u6837\u5373\u53ef\u3002"),(0,n.kt)("h3",{id:"hotupdatedllcompileoutputrootdir"},"hotUpdateDllCompileOutputRootDir"),(0,n.kt)("p",null,"\u7f16\u8bd1\u540e\u7684\u70ed\u66f4\u65b0dll\u7684\u8f93\u51fa\u6839\u76ee\u5f55\u3002\u6700\u7ec8\u8f93\u51fa\u76ee\u5f55\u5728\u8be5\u76ee\u5f55\u7684\u5e73\u53f0\u5b50\u76ee\u5f55\u4e0b\uff0c\u5373 ",(0,n.kt)("inlineCode",{parentName:"p"},"${hotUpdateDllCompileOutputRootDir}/{platform}"),"\u3002"),(0,n.kt)("h3",{id:"externalhotupdateassemblydirs"},"externalHotUpdateAssemblyDirs"),(0,n.kt)("p",null,"\u81ea\u5b9a\u4e49\u5916\u90e8\u70ed\u66f4\u65b0dll\u7684\u641c\u7d22\u8def\u5f84\u3002\u6709\u4e00\u4e9b\u6846\u67b6\u6216\u9879\u76ee\u7684\u70ed\u66f4\u65b0\u9879\u76ee\u653e\u5230Unity\u5916\u90e8\uff0c\u7f16\u8bd1\u51fa\u7684dll\u4e5f\u5728\u5916\u90e8\u3002\u8fd9\u4e2a\u53c2\u6570\u63d0\u4f9b\u4e86\u4e00\u4e2a\u70ed\u66f4\u65b0dll\n\u7684\u641c\u7d22\u8def\u5f84\uff0c\u8fd9\u6837\u4e0d\u9700\u8981\u6bcf\u6b21\u5c06\u5916\u90e8dll\u590d\u5236\u5230\u5de5\u7a0b\u91cc\u6216\u8005\u590d\u5236\u5230 hotUpdateAssemblies \u76ee\u5f55\u4e86\u3002"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"\u6309\u641c\u7d22\u8def\u5f84\u7684\u987a\u5e8f\u641c\u7d22\uff0c\u6392\u5728\u8d8a\u524d\u7684\u4f18\u5148\u7ea7\u8d8a\u9ad8\u3002"),(0,n.kt)("li",{parentName:"ul"},"\u641c\u7d22\u8def\u5f84\u5fc5\u987b\u662f\u76f8\u5bf9\u4f4d\u7f6e\uff0c\u76f8\u5bf9\u4e8e\u9879\u76ee\u6839\u76ee\u5f55\uff08\u5373Assets\u7684",(0,n.kt)("strong",{parentName:"li"},"\u4e0a\u7ea7\u76ee\u5f55"),"\uff09\u3002\u5373\u586b",(0,n.kt)("inlineCode",{parentName:"li"},"mydir"),"\uff0c\u641c\u7d22",(0,n.kt)("inlineCode",{parentName:"li"},"{proj}/mydir"),"\u3002"),(0,n.kt)("li",{parentName:"ul"},"\u6bcf\u4e2a\u8def\u5f84",(0,n.kt)("inlineCode",{parentName:"li"},"dir"),"\uff0c\u4f1a\u5148\u5c1d\u8bd5\u641c\u7d22",(0,n.kt)("inlineCode",{parentName:"li"},"{dir}/{platform}"),"\uff0c\u518d\u5c1d\u8bd5\u641c\u7d22",(0,n.kt)("inlineCode",{parentName:"li"},"{dir}"),"\u3002\u8fd9\u6837\u505a\u4e3a\u4e86\u517c\u987e\u5e73\u53f0\u7279\u6b8a\u6027\u53ca\u901a\u7528\u6027\u3002")),(0,n.kt)("p",null,"\u4e0b\u9762\u5c55\u793a\u4e00\u4e2a\u4f7f\u7528\u793a\u4f8b\u3002\u4f60\u6709\u4e00\u4e2a\u5916\u90e8dll\uff0c\u5b83\u7684\u4f4d\u7f6e\u4e3a ",(0,n.kt)("inlineCode",{parentName:"p"},"{proj}/MyDir1/MyDir2/Foo.dll"),"\uff0c\u5219\u4f60\u5e94\u8be5\uff1a"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"\u5728 hotUpdateAssemblies \u6dfb\u52a0 ",(0,n.kt)("inlineCode",{parentName:"li"},"Foo")),(0,n.kt)("li",{parentName:"ul"},"\u5728 externalHotUpdateAssemblyDirs \u91cc\u6dfb\u52a0\u76ee\u5f55 ",(0,n.kt)("inlineCode",{parentName:"li"},"MyDir1/Mydir2"))),(0,n.kt)("h3",{id:"strippedaotdlloutputrootdir"},"strippedAOTDllOutputRootDir"),(0,n.kt)("p",null,"\u88c1\u526a\u540e\u7684AOT dll\u7684\u6682\u5b58\u76ee\u5f55\u3002\u6700\u7ec8\u76ee\u5f55\u5728\u8be5\u76ee\u5f55\u7684\u5e73\u53f0\u5b50\u76ee\u5f55\u4e0b\uff0c\u5373 ",(0,n.kt)("inlineCode",{parentName:"p"},"${strippedAOTDllOutputRootDir}/{platform}"),"\u3002"),(0,n.kt)("h3",{id:"patchaotassemblies"},"patchAOTAssemblies"),(0,n.kt)("p",null,"\u8865\u5145\u5143\u6570\u636eAOT dll\u5217\u8868\u3002",(0,n.kt)("strong",{parentName:"p"},"package\u672c\u8eab\u6ca1\u6709\u7528\u5230\u8fd9\u4e2a\u914d\u7f6e\u9879"),"\u3002\u5b83\u63d0\u4f9b\u4e86\u4e00\u4e2a\u914d\u7f6eAOT dll\u5217\u8868\u7684\u5730\u65b9\uff0c\u65b9\u4fbf\u5f00\u53d1\u8005\u5728\u81ea\u5df1\u7684\u6253\u5305\u6d41\u7a0b\u4e2d\u4f7f\u7528\uff0c\u8fd9\u6837\u5c31\u4e0d\u7528\u5f00\u53d1\u8005\u5355\u72ec\u518d\u5b9a\u4e49\u4e00\u4e2a\u8865\u5145\u5143\u6570\u636eAOT dll\u914d\u7f6e\u811a\u672c\u4e86\u3002\n\u586b\u5199assembly\u540d\u79f0\u65f6\u4e0d\u8981\u5305\u542b'.dll'\u540e\u7f00\uff0c\u50cf",(0,n.kt)("inlineCode",{parentName:"p"},"Main"),"\u3001",(0,n.kt)("inlineCode",{parentName:"p"},"Assembly-CSharp"),"\u8fd9\u6837\u5373\u53ef\u3002"),(0,n.kt)("h3",{id:"outputlinkfile"},"outputLinkFile"),(0,n.kt)("p",null,"\u8fd0\u884c\u83dc\u5355",(0,n.kt)("inlineCode",{parentName:"p"},"HybridCLR/Generate/LinkXml"),"\u547d\u4ee4\u65f6\uff0c\u8f93\u51fa\u7684link.xml\u6587\u4ef6\u8def\u5f84\u3002"),(0,n.kt)("admonition",{type:"caution"},(0,n.kt)("p",{parentName:"admonition"},"\u5343\u4e07\u4e0d\u8981\u6307\u5411 ",(0,n.kt)("inlineCode",{parentName:"p"},"Assets/link.xml"),"\uff0c\u90a3\u4e2alink.xml\u4e00\u822c\u7528\u6765\u624b\u52a8\u9884\u7559AOT\u7c7b\u578b\uff0c\u800c\u8fd9\u4e2a\u81ea\u52a8\u8f93\u51fa\u7684link.xml\u6bcf\u6b21\u90fd\u4f1a\u8986\u76d6\u3002")),(0,n.kt)("h3",{id:"outputaotgenericreferencefile"},"outputAOTGenericReferenceFile"),(0,n.kt)("p",null,"\u8fd0\u884c\u83dc\u5355",(0,n.kt)("inlineCode",{parentName:"p"},"HybridCLR/Generate/AOTGenericReference"),"\u65f6\u8f93\u51fa\u7684AOT\u6cdb\u578b\u5b9e\u4f8b\u5316\u96c6\u5408\u6587\u4ef6\u7684\u8def\u5f84\u3002"),(0,n.kt)("h3",{id:"maxgenericreferenceiteration"},"maxGenericReferenceIteration"),(0,n.kt)("p",null,"\u8fd0\u884c\u83dc\u5355",(0,n.kt)("inlineCode",{parentName:"p"},"HybridCLR/Generate/AOTGenericReference"),"\u65f6\uff0c\u751f\u6210\u5de5\u5177\u9012\u5f52\u5206\u6790AOT\u6cdb\u578b\u5b9e\u4f8b\u5316\u7684\u8fed\u4ee3\u6b21\u6570\u3002"),(0,n.kt)("p",null,"\u56e0\u4e3a\u6cdb\u578b\u51fd\u6570\u4e2d\u53ef\u80fd\u4f1a\u95f4\u63a5\u4f7f\u7528\u4e86\u65b0\u7684\u6cdb\u578b\u7c7b\u548c\u6cdb\u578b\u51fd\u6570\uff0c\u56e0\u6b64\u9700\u8981\u591a\u8f6e\u8fed\u4ee3\u624d\u80fd\u5206\u6790\u51fa\u6240\u6709\u7684\u6cdb\u578b\u5b9e\u4f8b\u5316\uff0c",(0,n.kt)("inlineCode",{parentName:"p"},"maxGenericReferenceIteration"),"\u53c2\u6570\u7528\u4e8e\u63a7\u5236\u8fed\u4ee3\u6b21\u6570\u3002\u8fd9\u4e2a\u53c2\u6570\u4e00\u822c10\u4ee5\u5185\u5c31\u591f\u4e86\uff0c\u4f60\u901a\u8fc7\u89c2\u5bdf\u65e5\u5fd7\n\u80fd\u770b\u5230\u51e0\u8f6e\u8fed\u4ee3\u540e\u8ba1\u7b97\u7ec8\u6b62\uff0c\u5982\u679c\u8fed\u4ee3\u7ec8\u6b62\u65f6\u8fd8\u6709\u5927\u91cf\u6cdb\u578b\u672a\u8ba1\u7b97\u8fed\u4ee3\uff0c\u53ef\u4ee5\u9002\u5f53\u589e\u52a0\u8fd9\u4e2a\u503c\u3002"),(0,n.kt)("p",null,"\u4e3a\u4ec0\u4e48\u4e0d\u53cd\u590d\u8fed\u4ee3\u76f4\u81f3\u8ba1\u7b97\u51fa\u6240\u6709\u6cdb\u578b\u5b9e\u4f8b\u5316\u5462\uff1f\u56e0\u4e3a\u6709\u53ef\u80fd\u51fa\u73b0\u6c38\u8fdc\u65e0\u6cd5\u8ba1\u7b97\u5b8c\u7684\u60c5\u51b5\u3002\u5982\u4e0b\u4ee3\u7801\uff0cAOT.Show()\n\u7531\u4e8e\u9012\u5f52\u6cdb\u578b\u5b9e\u4f8b\u5316\uff0c\u6c38\u8fdc\u4e5f\u65e0\u6cd5\u8ba1\u7b97\u5b8c\u3002"),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-csharp"},"\n struct AOT\n {\n\n public void Show()\n {\n var a = new AOT>();\n a.Show();\n }\n }\n\n")),(0,n.kt)("h3",{id:"maxmethodbridgegenericiteration"},"maxMethodBridgeGenericIteration"),(0,n.kt)("p",null,"\u8fd0\u884c\u83dc\u5355",(0,n.kt)("inlineCode",{parentName:"p"},"HybridCLR/Generate/MethodBridge"),"\u65f6\uff0c\u751f\u6210\u5de5\u5177\u9012\u5f52\u5206\u6790AOT\u6cdb\u578b\u5b9e\u4f8b\u5316\u7684\u8fed\u4ee3\u6b21\u6570\u3002\u542b\u4e49\u4e0e",(0,n.kt)("inlineCode",{parentName:"p"},"maxGenericReferenceIteration"),"\u76f8\u4f3c\u3002"),(0,n.kt)("h2",{id:"build-pipeline\u76f8\u5173\u811a\u672c"},"Build Pipeline\u76f8\u5173\u811a\u672c"),(0,n.kt)("p",null,"\u4e3b\u8981\u5305\u542b\u4ee5\u4e0b\u529f\u80fd\uff1a"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"\u68c0\u67e5\u548c\u4fee\u590d\u8bbe\u7f6e"),(0,n.kt)("li",{parentName:"ul"},"\u6253\u5305\u65f6\u81ea\u52a8\u6392\u9664\u70ed\u66f4\u65b0assembly"),(0,n.kt)("li",{parentName:"ul"},"\u6253\u5305\u65f6\u5c06\u70ed\u66f4\u65b0dll\u540d\u6dfb\u52a0\u5230assembly\u5217\u8868"),(0,n.kt)("li",{parentName:"ul"},"\u5907\u4efd\u88c1\u526a\u540e\u7684AOT dll")),(0,n.kt)("h3",{id:"\u68c0\u67e5\u548c\u4fee\u590d\u8bbe\u7f6e"},"\u68c0\u67e5\u548c\u4fee\u590d\u8bbe\u7f6e"),(0,n.kt)("p",null,"\u5c5e\u4e8e\u6253\u5305\u5de5\u4f5c\u6d41\u7684\u4e00\u90e8\u5206\uff0c\u76f8\u5173\u4ee3\u7801\u5728 ",(0,n.kt)("inlineCode",{parentName:"p"},"Editor/BuildProcessors/CheckSettings.cs"),"\u4e2d\u3002\u5305\u542b\u4ee5\u4e0b\u64cd\u4f5c\uff1a"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"\u6839\u636e\u662f\u5426\u5f00\u542fHybridCLR\uff0c\u8bbe\u7f6e\u6216\u8005\u6e05\u9664UNITY_IL2CPP_PATH\u73af\u5883\u53d8\u91cf\u3002\u811a\u672c\u4e2d\u4fee\u6539\u7684UNITY_IL2CPP_PATH\u73af\u5883\u53d8\u91cf\u662f\u672c\u8fdb\u7a0b\u7684\u73af\u5883\u53d8\u91cf\uff0c\u4e0d\u7528\u62c5\u5fc3\u5e72\u6270\u4e86\u5176\u4ed6\u9879\u76ee\u3002"),(0,n.kt)("li",{parentName:"ul"},"\u5982\u679c\u4f4e\u4e8e\uff08\u4e0d\u542b\uff09v4.0.0\u7248\u672c\uff0c\u4f1a\u68c0\u67e5\u5e76\u81ea\u52a8\u5173\u95ed\u589e\u91cf\u5f0fGC(Use Incremental GC) \u9009\u9879"),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("inlineCode",{parentName:"li"},"Scripting Backend")," \u5207\u6362\u4e3a ",(0,n.kt)("inlineCode",{parentName:"li"},"il2cpp"),", WebGL\u5e73\u53f0\u4e0d\u7528\u8bbe\u7f6e\u6b64\u9009\u9879\u3002",(0,n.kt)("strong",{parentName:"li"},"\u81ea",(0,n.kt)("inlineCode",{parentName:"strong"},"v2.4.0"),"\u8d77\uff0c\u4f1a\u81ea\u52a8\u8bbe\u7f6e\u6b64\u9009\u9879\uff0c\u53ef\u4ee5\u4e0d\u7528\u624b\u52a8\u6267\u884c\u6b64\u64cd\u4f5c"),"\u3002"),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("inlineCode",{parentName:"li"},"Api Compatability Level")," \u5207\u6362\u4e3a ",(0,n.kt)("inlineCode",{parentName:"li"},".NetFramework 4"),"(Unity 2019\u30012020) \u6216 ",(0,n.kt)("inlineCode",{parentName:"li"},".Net Framework"),"\uff08Unity 2021+\uff09"),(0,n.kt)("li",{parentName:"ul"},"\u5982\u679cHybridCLRSettings\u91cc\u672a\u8bbe\u7f6e\u4efb\u4f55\u70ed\u66f4\u65b0assembly\uff0c\u63d0\u793a\u9519\u8bef\u3002")),(0,n.kt)("h3",{id:"\u6253\u5305\u65f6\u81ea\u52a8\u6392\u9664\u70ed\u66f4\u65b0assembly"},"\u6253\u5305\u65f6\u81ea\u52a8\u6392\u9664\u70ed\u66f4\u65b0assembly"),(0,n.kt)("p",null,"\u5c5e\u4e8e\u6253\u5305\u5de5\u4f5c\u6d41\u7684\u4e00\u90e8\u5206\uff0c\u76f8\u5173\u4ee3\u7801\u5728 ",(0,n.kt)("inlineCode",{parentName:"p"},"Editor/BuildProcessors/FilterHotFixAssemblies.cs"),"\u4e2d\u3002"),(0,n.kt)("p",null,"\u5f88\u663e\u7136\uff0c\u70ed\u66f4\u65b0assembly\u4e0d\u5e94\u8be5\u88abil2cpp\u5904\u7406\u5e76\u4e14\u7f16\u8bd1\u5230\u6700\u7ec8\u7684\u5305\u4f53\u91cc\u3002\u6211\u4eec\u5904\u7406\u4e86",(0,n.kt)("inlineCode",{parentName:"p"},"IFilterBuildAssemblies"),"\u56de\u8c03\uff0c\n\u5c06\u70ed\u66f4\u65b0dll\u4ecebuild assemblies\u5217\u8868\u79fb\u9664\u3002\u811a\u672c\u4e2d\u4f1a\u989d\u5916\u68c0\u67e5\u662f\u5426\u5199\u9519assembly\u540d\u5b57\uff0c\u4ee5\u53ca\u662f\u5426\u5931\u8bef\u914d\u7f6e\u4e86\u91cd\u590d\u7684assembly\u3002"),(0,n.kt)("h3",{id:"\u6253\u5305\u65f6\u5c06\u70ed\u66f4\u65b0dll\u540d\u6dfb\u52a0\u5230assembly\u5217\u8868"},"\u6253\u5305\u65f6\u5c06\u70ed\u66f4\u65b0dll\u540d\u6dfb\u52a0\u5230assembly\u5217\u8868"),(0,n.kt)("p",null,"\u5c5e\u4e8e\u6253\u5305\u5de5\u4f5c\u6d41\u7684\u4e00\u90e8\u5206\uff0c\u76f8\u5173\u4ee3\u7801\u5728 ",(0,n.kt)("inlineCode",{parentName:"p"},"Editor/BuildProcessors/PatchScriptingAssemblyList.cs"),"\u4e2d\u3002"),(0,n.kt)("p",null,"\u5de5\u5177\u5728\u6253\u5305\u65f6\uff0c\u4f1a\u81ea\u52a8\u5c06\u70ed\u66f4\u65b0assembly\u7684dll\u540d\u52a0\u5165assembly\u5217\u8868\u914d\u7f6e\u6587\u4ef6\u3002\u70ed\u66f4\u65b0MonoBehaviour\u811a\u672c\u6240\u5728\u7684assembly\u7684dll\u540d\u5fc5\u987b\u6dfb\u52a0\u5230assembly\u5217\u8868\u914d\u7f6e\u6587\u4ef6\uff0c\nUnity\u7684\u8d44\u6e90\u7ba1\u7406\u7cfb\u7edf\u624d\u80fd\u6b63\u786e\u8bc6\u522b\u548c\u8fd8\u539f\u70ed\u66f4\u65b0\u811a\u672c\u3002\u66f4\u8be6\u7ec6\u7684\u539f\u7406\u4ecb\u7ecd\u8bf7\u770b ",(0,n.kt)("a",{parentName:"p",href:"/docs/basic/monobehaviour"},"\u4f7f\u7528\u70ed\u66f4\u65b0MonoBehaviour")," \u3002"),(0,n.kt)("h3",{id:"\u5907\u4efd\u88c1\u526a\u540e\u7684aot-dll"},"\u5907\u4efd\u88c1\u526a\u540e\u7684AOT dll"),(0,n.kt)("p",null,"\u5c5e\u4e8e\u6253\u5305\u5de5\u4f5c\u6d41\u7684\u4e00\u90e8\u5206\uff0c\u76f8\u5173\u4ee3\u7801\u5728 ",(0,n.kt)("inlineCode",{parentName:"p"},"Editor/BuildProcessors/CopyStrippedAOTAssemblies.cs"),"\u4e2d\u3002"),(0,n.kt)("p",null,"\u5f53\u8865\u5145\u5143\u6570\u636e\u6a21\u5f0f\u4e3a",(0,n.kt)("inlineCode",{parentName:"p"},"HomologousImageMode::Consistent"),"\u65f6\uff0c\u9700\u8981\u4f7f\u7528\u6253\u5305\u65f6\u751f\u6210\u7684\u88c1\u526a\u540e\u7684AOT dll\u3002\u56e0\u6b64\u4f1a\u81ea\u52a8\u5c06\u6253\u5305\u8fc7\u7a0b\u4e2d\u751f\u6210\u7684\u88c1\u526a\u540e\u7684AOT dll\n\u590d\u5236\u5230 ",(0,n.kt)("inlineCode",{parentName:"p"},"{project}/HybridCLRData/AssembliesPostIl2CppStrip/{platform}"),"\u76ee\u5f55\uff0c\u65b9\u4fbf\u5c06\u6765\u5904\u7406\u3002\u5f53\u6570\u636e\u6a21\u5f0f\u4e3a",(0,n.kt)("inlineCode",{parentName:"p"},"HomologousImageMode::SuperSet"),"\u65f6\uff0c\n\u53ef\u4ee5\u76f4\u63a5\u4f7f\u7528\u539f\u59cb\u7684aot dll\u3002\u8fd9\u4e2a\u4f18\u70b9\u662f\u5de5\u4f5c\u6d41\u4e0a\u4fbf\u5229\u4e00\u4e9b\uff0c\u4e0d\u7528\u6bcf\u6b21\u6253\u5305\u540e\u66f4\u65b0aot dll\uff0c\u7f3a\u70b9\u662f\u591a\u5360\u4e86\u5185\u5b58\uff0c\u540c\u65f6\u5927\u5e45\u589e\u52a0\u4e86\u88c1\u526adll\u7684\u5927\u5c0f\uff0c\u8bf7\u4f7f\u7528\u8005\u81ea\u5df1\u6743\u8861\u4f7f\u7528\u539f\u59cb\u8fd8\u662f\u88c1\u526a\u540e\u7684aot dll\u3002"),(0,n.kt)("h2",{id:"iosbuild\u811a\u672c"},"iOSBuild\u811a\u672c"),(0,n.kt)("p",null,"package\u4e2d ",(0,n.kt)("inlineCode",{parentName:"p"},"Editor/Data~/iOSBuild")," \u5305\u542b\u4e86\u7f16\u8bd1iOS\u7248\u672clibil2cpp.a\u6240\u9700\u7684\u811a\u672c\u3002\u5728\u8fd0\u884c",(0,n.kt)("inlineCode",{parentName:"p"},"HybridCLR/Installer..."),"\u83dc\u5355\u547d\u4ee4\u6210\u529f\u521d\u59cb\u5316HybridCLR\u540e\uff0c\u4f1a\u81ea\u52a8\u590d\u5236\u5230",(0,n.kt)("inlineCode",{parentName:"p"},"{project}/HybridCLRData/iOSBuild"),"\u76ee\u5f55\u3002\n",(0,n.kt)("strong",{parentName:"p"},"\u540e\u7eed\u64cd\u4f5c\u5fc5\u987b\u5728",(0,n.kt)("inlineCode",{parentName:"strong"},"{project}/HybridCLRData/iOSBuild"),"\u76ee\u5f55\u8fdb\u884c"),"\u3002\u7f16\u8bd1libil2cpp.a\u7684\u5177\u4f53\u64cd\u4f5c\u8bf7\u770b\u6587\u6863 ",(0,n.kt)("a",{parentName:"p",href:"/docs/basic/buildpipeline"},"iOS\u5e73\u53f0\u6253\u5305"),"\u3002"),(0,n.kt)("h2",{id:"runtime\u76f8\u5173\u811a\u672c"},"Runtime\u76f8\u5173\u811a\u672c"),(0,n.kt)("p",null,"\u5305\u542b\u8fd0\u884c\u65f6\u7528\u5230\u7684\u7c7b\u3002"),(0,n.kt)("h3",{id:"loadimageerrorcode"},"LoadImageErrorCode"),(0,n.kt)("p",null,"\u52a0\u8f7d\u70ed\u66f4\u65b0dll\u7684\u9519\u8bef\u7801\u3002"),(0,n.kt)("h3",{id:"\u5143\u6570\u636e\u6a21\u5f0f-homologousimagemode"},"\u5143\u6570\u636e\u6a21\u5f0f HomologousImageMode"),(0,n.kt)("p",null,"\u76ee\u524d\u652f\u6301\u4e24\u79cd\u5143\u6570\u636e\u6a21\u5f0f\uff1a"),(0,n.kt)("h4",{id:"homologousimagemodeconsistent-\u6a21\u5f0f"},(0,n.kt)("inlineCode",{parentName:"h4"},"HomologousImageMode::Consistent")," \u6a21\u5f0f"),(0,n.kt)("p",null,"\u5373\u8865\u5145\u7684dll\u4e0e\u6253\u5305\u65f6\u88c1\u526a\u540e\u7684dll\u7cbe\u786e\u4e00\u81f4\u3002\u56e0\u6b64\u5fc5\u987b\u4f7f\u7528build\u8fc7\u7a0b\u4e2d\u751f\u6210\u7684\u88c1\u526a\u540e\u7684dll\uff0c\u5219\u4e0d\u80fd\u76f4\u63a5\u590d\u5236\u539f\u59cbdll\u3002\u6211\u4eec\u5728",(0,n.kt)("inlineCode",{parentName:"p"},"HybridCLR.BuildProcessors.CopyStrippedAOTAssemblies"),"\u91cc\u6dfb\u52a0\u4e86\u5904\u7406\u4ee3\u7801\uff0c\u5728\u6253\u5305\u65f6\u81ea\u52a8\u5c06\u8fd9\u4e9b\u88c1\u526a\u540e\u7684dll\u590d\u5236\u5230 ",(0,n.kt)("inlineCode",{parentName:"p"},"{project}/HybridCLRData/AssembliesPostIl2CppStrip/{target}")," \u76ee\u5f55\u3002"),(0,n.kt)("h4",{id:"homologousimagemodesuperset-\u6a21\u5f0f"},(0,n.kt)("inlineCode",{parentName:"h4"},"HomologousImageMode::SuperSet")," \u6a21\u5f0f"),(0,n.kt)("p",null,"\u5373\u8865\u5145\u7684dll\u662f\u6253\u5305\u65f6\u88c1\u526a\u540e\u7684dll\u7684\u8d85\u96c6\uff0c\u5305\u542b\u4e86\u88c1\u526adll\u7684\u6240\u6709\u5143\u6570\u636e\u3002\u4e00\u4e2a\u6700\u7b80\u5355\u6613\u5f97\u7684\u8d85\u96c6dll\u4e3a\u539f\u59cbaot dll\uff0c\u8fd9\u4e5f\u662f\u63a8\u8350\u4f7f\u7528\u7684\u8d85\u96c6dll\u3002"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"\u539f\u59cbUnityEngine\u76f8\u5173AOT dll\u5728Unity\u5b89\u88c5\u76ee\u5f55\u7684PlayBackEngines\u5b50\u76ee\u5f55\u4e0b"),(0,n.kt)("li",{parentName:"ul"},"\u539f\u59cb\u7684.net\u6838\u5fc3AOT dll\u5982mscorlib.dll\u5728Unity\u5b89\u88c5\u76ee\u5f55\u7684 ",(0,n.kt)("inlineCode",{parentName:"li"},"unityaot{xxx}")," \u76ee\u5f55\u4e0b\u30022019-2020\u7edf\u4e00\u4e3aunityaot\u76ee\u5f55\uff0c2021\u8d77\u62c6\u5206\u6210\u591a\u4e2a\u76ee\u5f55\uff0c\u5982\u679c\u6253\u5305android\u53d6unityaot-linux\u3001\u5982\u679c\u6253\u5305iOS\u53d6unityaot-macos\u3002"),(0,n.kt)("li",{parentName:"ul"},"\u63d2\u4ef6\u7684AOT dll\u4e3a\u5de5\u7a0b\u76ee\u5f55\u4e2d\u7684\u76f8\u5e94\u5e73\u53f0\u7684\u539f\u59cbdll\u3002\u5982\u679c\u662f\u6e90\u7801\u5f62\u5f0f\uff0c\u5219\u4e3a\u7f16\u8bd1\u597d\u7684dll\uff0c\u53d6",(0,n.kt)("inlineCode",{parentName:"li"},"HybridCLR/HotUpdateDlls/{platform}"),"\u76ee\u5f55\u4e0b\u7684\u76f8\u5e94dll\u5373\u53ef")),(0,n.kt)("p",null,"\u4ee5Unity 2020.3.33\u7248\u672cWin\u4e0b\u7684Win64\u76ee\u6807\u4e3a\u4f8b\uff1a"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"mscorlib.dll\u5728 ",(0,n.kt)("inlineCode",{parentName:"li"},"{editor}/Editor/Data/MonoBleedingEdge/lib/mono/unityaot")),(0,n.kt)("li",{parentName:"ul"},"UnityEngine.CoreModule.dll \u5728 ",(0,n.kt)("inlineCode",{parentName:"li"},"{editor}/Editor/Data/Playbackengines/windowsstandalonesupport/Variations/il2cpp/Managed")),(0,n.kt)("li",{parentName:"ul"},"protobuf-net.dll \u4e3a\u4f60\u7684\u5de5\u7a0b\u4e2d\u7684\u539f\u59cb",(0,n.kt)("inlineCode",{parentName:"li"},"protobuf-net.dll")),(0,n.kt)("li",{parentName:"ul"},"\u4f60\u7684AOT\u6a21\u5757Main\u5bf9\u5e94\u7684AOT dll\u4e3a ",(0,n.kt)("inlineCode",{parentName:"li"},"HybridCLR/HotUpdateDlls/{platform}/Main.dll"))),(0,n.kt)("p",null,(0,n.kt)("inlineCode",{parentName:"p"},"SuerSet"),"\u6a21\u5f0f\u4e5f\u53ef\u4ee5\u4f7f\u7528",(0,n.kt)("inlineCode",{parentName:"p"},"Consistent"),"\u6a21\u5f0f\u7684\u88c1\u51cf\u540e\u7684dll\uff0c\u56e0\u4e3a\u81ea\u5df1\u663e\u7136\u5305\u542b\u81ea\u8eab\u7684\u6240\u6709\u5143\u6570\u636e\u3002"),(0,n.kt)("h3",{id:"runtimeapi"},"RuntimeApi"),(0,n.kt)("p",null,"\u5e95\u5c42\u7684\u64cd\u4f5cHybridCLR\u7684\u5de5\u5177\u7c7b\u3002\u6bd4\u8f83\u5e38\u7528\u7684\u6709:"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("inlineCode",{parentName:"li"},"LoadImageErrorCode LoadMetadataForAOTAssembly(byte[] dllBytes, HomologousImageMode mode)")," \u7528\u4e8e\u52a0\u8f7d\u8865\u5145\u5143\u6570\u636eassembly\u3002")),(0,n.kt)("h3",{id:"reversepinvokewrappergenerationattribute"},"ReversePInvokeWrapperGenerationAttribute"),(0,n.kt)("p",null,"\u5982\u679c\u9879\u76ee\u4e2d\u7528\u4e8exlua\u4e4b\u7c7b\u7684\u811a\u672c\u8bed\u8a00\uff0c\u5bf9\u4e8e\u8981\u6ce8\u518c\u5230lua\u4e2d\u7684C#\u51fd\u6570\uff0c\u90fd\u9700\u8981\u6dfb\u52a0",(0,n.kt)("inlineCode",{parentName:"p"},"[MonoPInvokeCallback]"),"\u6ce8\u89e3\u3002\u8fd9\u6837\u53ef\u4ee5\u4e3a\u8fd9\u4e9bC#\u51fd\u6570\u8fd4\u56de\u4e00\u4e2a\u5bf9\u5e94\u7684c++\n\u51fd\u6570\u6307\u9488\uff0c\u7528\u4e8e\u6ce8\u518c\u5230\u811a\u672c\u8bed\u8a00\u91cc\u3002HybridCLR\u652f\u6301\u5c06\u70ed\u66f4\u65b0C#\u4ee3\u7801\u6ce8\u518c\u5230lua\u4e2d\uff0c\u4f46\u5fc5\u987b\u63d0\u524d\u751f\u6210\u4e0e",(0,n.kt)("inlineCode",{parentName:"p"},"[MonoPInvokeCallback]"),"\u5bf9\u5e94\u7684C++\u6869\u51fd\u6570\uff0c\u624d\u53ef\u80fd\u4e3a\u6bcf\u4e2aC#\u51fd\u6570\u8fd4\u56de\u4e00\u4e2a\u76f8\u5e94\u7684C++\u51fd\u6570\u6307\u9488\u3002\n\u811a\u672c\u63d0\u4f9b\u4e86\u81ea\u52a8\u751f\u6210\u6869\u51fd\u6570\u7684\u529f\u80fd\u3002\u8be6\u7ec6\u8bf7\u89c1 ",(0,n.kt)("a",{parentName:"p",href:"/docs/basic/workwithscriptlanguage"},"MonoPInvokeCallback\u652f\u6301")," \u53ca ",(0,n.kt)("a",{parentName:"p",href:"/docs/basic/workwithscriptlanguage"},"HybridCLR+lua/js/python")," \u6587\u6863"),(0,n.kt)("p",null,"\u6bcf\u4e2a\u5e26 ",(0,n.kt)("inlineCode",{parentName:"p"},"[MonoPInvokeCallback]")," \u7279\u6027\u7684\u51fd\u6570\u90fd\u9700\u8981\u4e00\u4e2a\u552f\u4e00\u5bf9\u5e94\u7684wrapper\u51fd\u6570\u3002\u8fd9\u4e9bwrapper\u51fd\u6570\u5fc5\u987b\u662f\u6253\u5305\u65f6\u9884\u5148\u751f\u6210\uff0c\u4e0d\u53ef\u53d8\u5316\u3002\n\u56e0\u6b64\u5982\u679c\u540e\u7eed\u70ed\u66f4\u65b0\u65b0\u589e\u4e86 \u5e26 ",(0,n.kt)("inlineCode",{parentName:"p"},"[MonoPInvokeCallback]")," \u7279\u6027\u7684\u51fd\u6570\uff0c\u5219\u4f1a\u53d1\u751fwrapper\u51fd\u6570\u4e0d\u8db3\u7684\u60c5\u51b5\u3002ReversePInvokeWrapperGenerationAttribute\n\u7528\u4e8e\u4e3a\u5f53\u524d\u6dfb\u52a0\u4e86 ",(0,n.kt)("inlineCode",{parentName:"p"},"[MonoPInvokeCallback]")," \u7279\u6027\u7684\u51fd\u6570\u9884\u7559\u6307\u5b9a\u6570\u91cf\u7684wrapper\u51fd\u6570\u3002\u5728\u5982\u4e0b\u793a\u4f8b\u4e2d\uff0c\u4e3aLuaFunction\u7b7e\u540d\u7684\u51fd\u6570\u9884\u7559\u4e8610\u4e2awrapper\u51fd\u6570\u3002"),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-csharp"}," delegate int LuaFunction(IntPtr luaState);\n\n public class MonoPInvokeWrapperPreserves\n {\n [ReversePInvokeWrapperGeneration(10)]\n [MonoPInvokeCallback(typeof(LuaFunction))]\n public static int LuaCallback(IntPtr luaState)\n {\n return 0;\n }\n\n [MonoPInvokeCallback(typeof(Func))]\n public static int Sum(int a, int b)\n {\n return a + b;\n }\n\n [MonoPInvokeCallback(typeof(Func))]\n public static int Sum2(int a, int b)\n {\n return a + b;\n }\n\n [MonoPInvokeCallback(typeof(Func))]\n public static int Sum3()\n {\n return 0;\n }\n }\n")))}s.isMDXComponent=!0},5568:(A,e,l)=>{l.d(e,{Z:()=>i});const i="data:image/jpeg;base64,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88888888888888888888888888888888888888oAQAsIA0Q8gk888888888888888888888888888888888888888oAU888888888888888888888888888888888888888888888888oApU/wA+9HItHYfPPPPPPJUMsEYEYEMAAMIAAAEYIFMMMMBAGDBAFAPLPPLHPPPPDPPPPPPPLL/vvvvvvjb/AM+8/wD+vMMMMMMMc888884A8www8w0888888888888888888888888888888888888888888oAsAQgwAE888888888888888888888888888888888888888888oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8QAJhEAAgEDAwMEAwAAAAAAAAAAAAFhEZHRceHxIUHwEDAxgECBof/aAAgBAwEBPxBudWiFEKIUQohRCiFCSXRfSpqI6RV9hO+wukVZX3RXT49LVVRrqevsLiuiXY1r7GpfYU19jWua1zWua1zUvsal9jWvsa19hN1ab/eDxt5PG3k8beTxt5Ene8nxyrX+U/Jg9ipknBIOQeTkHk5B5OQeTkHk5B5OQeRpVXGSCQSLkgkXJBOJAzqm5+h3/8QAHxEAAwEAAgIDAQAAAAAAAAAAAAFhESExMEFAUYBx/9oACAECAQE/EEpYmVZVlWVZVlWVY23y/wAVLrxi2NE+/sat5Gu4POcmW+GY3s79mzBPh/HgSLyKqqqZVKlSpc2dfJ9W+BJiEiaJomiaJomiaG70iRIkSJEiRI4RPwd//8QAKhABAAIBAwIFBQEBAQEAAAAAAQARITFh8EFREHGRsdFggaHB8SAwUOH/2gAIAQEAAT8QSwBjotXP4itQLijC3uE+0QuwcGEaSW6oNRha3qj7SuqyCMWGB1ymkB5UEBhdPaJkiiCl9gZWzURtWyXnVdtYeISl5drsXtLMFGowFt6ifacISlZethRpqXXBPRgi3qh946S+4MI0k4QnCEAKtp03fo7Ln9aqTg+3pHC/uHYaC6LzsOZM3iLGue06xWn9xs092c96iUeN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"},8648:(A,e,l)=>{l.d(e,{Z:()=>i});const i="data:image/jpeg;base64,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AvMUOgzyUGdvgJCyHaJ5IPt/3QnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmEBZUFEEMBNjCbGENv0iMLSqTJZ5VroJQYAYgKGg1g0QtofNYlXbKOeWqwAfmbGE2MJsYQYiAQBf40O9fqMNR8dXf6jDUfHd3gmKABVU/aH1P2h9T9ofU/aH1P2h9T9ofU/aH1P2h9T9ofU/aH1P2h9QGKQJVX0sNR8afWsfm/6H8D2ZJaiQGMvMK0mMcI3G43nROCACNEkdIFV6o3+6gC/k/ipBZkeQVqCJTPqAKpKT9ySf4fi/6P0sNR8afWsGDAlgiPgWncP8ncP8ncP8gRhigMdDx+QA/qNhIKuEhBIGolmAOS7F0OhGxRBBU0gIUUgJoIAVEGQS64oABQWAncP8ncP8ncP8hgxBiyHg35+lhqPjobwlololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololvjsb/Ursb/Ursb/Urub/AFK6m/1K6m/1K6m/1K//2Q=="}}]); \ No newline at end of file diff --git a/assets/js/runtime~main.30b08ba4.js b/assets/js/runtime~main.d155d570.js similarity index 98% rename from assets/js/runtime~main.30b08ba4.js rename to assets/js/runtime~main.d155d570.js index d73bf05b..6b5c6829 100644 --- a/assets/js/runtime~main.30b08ba4.js +++ b/assets/js/runtime~main.d155d570.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/"+({12:"f160c361",53:"935f2afb",90:"2efe1410",220:"6ec19757",434:"90b957e4",533:"b2b675dd",685:"05f46752",688:"e044ccdf",852:"6704bb9d",964:"733c4d41",1176:"84b73551",1198:"72413e93",1433:"ff8c06e1",1477:"b2f554cd",1634:"e6335e6f",1744:"7bef7309",1752:"dd53d751",1977:"099d81ac",1998:"6d0a6812",2e3:"90e3b8d9",2034:"21ad55e6",2182:"f739fd9f",2306:"48d46c19",2365:"a7626ec9",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",3777:"303a7ab0",3892:"0f4b3ece",4103:"e9ab53df",4130:"dd933416",4195:"c4f5d8e4",4369:"9e92f087",4475:"bacda3a9",4569:"39b1bd06",5041:"ebee79fe",5080:"88236a13",5153:"c9aab52f",5183:"032c34c3",5367:"26b576d2",5649:"5dd67a5f",5650:"5148d8fe",5659:"27b4bb7f",5746:"5a96aca1",5936:"1566bc1f",6103:"ccc49370",6290:"1d92ca72",6333:"41bb1898",6468:"4dfc0651",6729:"bdd7c4d4",6848:"f33e1a49",7020:"ba76a366",7040:"fbd8196d",7065:"80680481",7087:"1b21ecc3",7203:"f4f82255",7404:"742b5987",7589:"0ccd1bc3",7681:"a99908d5",7884:"c71319a4",7918:"17896441",7920:"1a4e3797",7991:"7faaab83",8052:"b7e34b9a",8063:"f93d3a31",8787:"c55163c5",9106:"3d345fd1",9124:"c4ad3b7e",9451:"355d470d",9462:"9b588bbf",9514:"1be78505",9671:"0e384e19",9817:"14eb3368",9822:"3d291b3d",9888:"026413ce"}[e]||e)+"."+{12:"3b57c09e",53:"d90132d1",90:"e2232209",220:"15827c36",434:"01d83772",533:"d838adc3",685:"a462ac2d",688:"91697c54",852:"349bb8ca",964:"f7189ca4",1176:"19d7f58c",1198:"a2d62704",1426:"f6ed8f65",1433:"d1e528c8",1477:"65b4b0f1",1634:"690dff03",1744:"12476551",1752:"9d2d5e36",1977:"2873fc9c",1998:"742d95ee",2e3:"cea902da",2034:"f05e7d12",2182:"ad078e06",2306:"4d90ead8",2365:"1a7ed67a",2535:"9d9ea10e",2616:"8ff4f04a",2815:"0525f1e5",2828:"cbb0c6af",2838:"5892eafb",2857:"e6128cda",2965:"fe48b928",3089:"d1467cbe",3131:"a8704c94",3170:"fef9d017",3423:"463916d8",3503:"64d89e78",3608:"e989768d",3764:"3d52885e",3777:"f657297a",3892:"08855efb",4103:"b15d6b20",4130:"9f3bde5c",4195:"e054211d",4369:"356f50d5",4475:"8c2e86cf",4569:"e57052c7",4972:"3d0f496c",5041:"3e77bb41",5080:"ce7b4e42",5153:"4a39de02",5183:"3d592d84",5367:"467ebaf3",5649:"9ad910cb",5650:"8911351c",5659:"86c2844f",5746:"f49f1414",5936:"88c9b8e5",6048:"779f8c90",6103:"1d3911bc",6186:"170d1bc9",6290:"3e88330e",6333:"1eb39c27",6468:"c5633afd",6729:"2f59ebfd",6848:"ab097966",6945:"94f4a660",7020:"2330e1fc",7040:"28169402",7065:"e23c508d",7087:"e4808fe0",7203:"cbedbc3b",7404:"5809c436",7589:"3ae65c80",7681:"bbfc0c56",7884:"34b74eaa",7918:"d5cb46e3",7920:"bd81094a",7991:"152c94b7",8052:"022da9e7",8063:"93bf2927",8787:"ec56e041",8894:"91734414",9106:"04cb8255",9124:"fedac576",9451:"594f8519",9462:"4798c258",9514:"d5cf2d0b",9671:"266370e9",9817:"0f68630a",9822:"94f115f8",9888:"dd84d40b"}[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="/",r.gca=function(e){return e={17896441:"7918",80680481:"7065",f160c361:"12","935f2afb":"53","2efe1410":"90","6ec19757":"220","90b957e4":"434",b2b675dd:"533","05f46752":"685",e044ccdf:"688","6704bb9d":"852","733c4d41":"964","84b73551":"1176","72413e93":"1198",ff8c06e1:"1433",b2f554cd:"1477",e6335e6f:"1634","7bef7309":"1744",dd53d751:"1752","099d81ac":"1977","6d0a6812":"1998","90e3b8d9":"2000","21ad55e6":"2034",f739fd9f:"2182","48d46c19":"2306",a7626ec9:"2365","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","303a7ab0":"3777","0f4b3ece":"3892",e9ab53df:"4103",dd933416:"4130",c4f5d8e4:"4195","9e92f087":"4369",bacda3a9:"4475","39b1bd06":"4569",ebee79fe:"5041","88236a13":"5080",c9aab52f:"5153","032c34c3":"5183","26b576d2":"5367","5dd67a5f":"5649","5148d8fe":"5650","27b4bb7f":"5659","5a96aca1":"5746","1566bc1f":"5936",ccc49370:"6103","1d92ca72":"6290","41bb1898":"6333","4dfc0651":"6468",bdd7c4d4:"6729",f33e1a49:"6848",ba76a366:"7020",fbd8196d:"7040","1b21ecc3":"7087",f4f82255:"7203","742b5987":"7404","0ccd1bc3":"7589",a99908d5:"7681",c71319a4:"7884","1a4e3797":"7920","7faaab83":"7991",b7e34b9a:"8052",f93d3a31:"8063",c55163c5:"8787","3d345fd1":"9106",c4ad3b7e:"9124","355d470d":"9451","9b588bbf":"9462","1be78505":"9514","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,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/"+({12:"f160c361",53:"935f2afb",90:"2efe1410",220:"6ec19757",434:"90b957e4",533:"b2b675dd",685:"05f46752",688:"e044ccdf",852:"6704bb9d",964:"733c4d41",1176:"84b73551",1198:"72413e93",1433:"ff8c06e1",1477:"b2f554cd",1634:"e6335e6f",1744:"7bef7309",1752:"dd53d751",1977:"099d81ac",1998:"6d0a6812",2e3:"90e3b8d9",2034:"21ad55e6",2182:"f739fd9f",2306:"48d46c19",2365:"a7626ec9",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",3777:"303a7ab0",3892:"0f4b3ece",4103:"e9ab53df",4130:"dd933416",4195:"c4f5d8e4",4369:"9e92f087",4475:"bacda3a9",4569:"39b1bd06",5041:"ebee79fe",5080:"88236a13",5153:"c9aab52f",5183:"032c34c3",5367:"26b576d2",5649:"5dd67a5f",5650:"5148d8fe",5659:"27b4bb7f",5746:"5a96aca1",5936:"1566bc1f",6103:"ccc49370",6290:"1d92ca72",6333:"41bb1898",6468:"4dfc0651",6729:"bdd7c4d4",6848:"f33e1a49",7020:"ba76a366",7040:"fbd8196d",7065:"80680481",7087:"1b21ecc3",7203:"f4f82255",7404:"742b5987",7589:"0ccd1bc3",7681:"a99908d5",7884:"c71319a4",7918:"17896441",7920:"1a4e3797",7991:"7faaab83",8052:"b7e34b9a",8063:"f93d3a31",8787:"c55163c5",9106:"3d345fd1",9124:"c4ad3b7e",9451:"355d470d",9462:"9b588bbf",9514:"1be78505",9671:"0e384e19",9817:"14eb3368",9822:"3d291b3d",9888:"026413ce"}[e]||e)+"."+{12:"3b57c09e",53:"d90132d1",90:"e2232209",220:"47c6dd18",434:"01d83772",533:"d838adc3",685:"a462ac2d",688:"91697c54",852:"349bb8ca",964:"f7189ca4",1176:"19d7f58c",1198:"a2d62704",1426:"f6ed8f65",1433:"d1e528c8",1477:"65b4b0f1",1634:"690dff03",1744:"12476551",1752:"9d2d5e36",1977:"2873fc9c",1998:"742d95ee",2e3:"cea902da",2034:"f05e7d12",2182:"ad078e06",2306:"4d90ead8",2365:"1a7ed67a",2535:"9d9ea10e",2616:"8ff4f04a",2815:"0525f1e5",2828:"cbb0c6af",2838:"5892eafb",2857:"e6128cda",2965:"fe48b928",3089:"d1467cbe",3131:"a8704c94",3170:"fef9d017",3423:"a2b63972",3503:"64d89e78",3608:"e989768d",3764:"3d52885e",3777:"f657297a",3892:"08855efb",4103:"b15d6b20",4130:"9f3bde5c",4195:"e054211d",4369:"356f50d5",4475:"8c2e86cf",4569:"e57052c7",4972:"3d0f496c",5041:"3e77bb41",5080:"ce7b4e42",5153:"4a39de02",5183:"3d592d84",5367:"467ebaf3",5649:"9ad910cb",5650:"8911351c",5659:"86c2844f",5746:"f49f1414",5936:"88c9b8e5",6048:"779f8c90",6103:"1d3911bc",6186:"170d1bc9",6290:"3e88330e",6333:"1eb39c27",6468:"c5633afd",6729:"2f59ebfd",6848:"ab097966",6945:"94f4a660",7020:"2330e1fc",7040:"28169402",7065:"e23c508d",7087:"e4808fe0",7203:"cbedbc3b",7404:"5809c436",7589:"3ae65c80",7681:"bbfc0c56",7884:"34b74eaa",7918:"d5cb46e3",7920:"bd81094a",7991:"152c94b7",8052:"022da9e7",8063:"93bf2927",8787:"ec56e041",8894:"91734414",9106:"04cb8255",9124:"fedac576",9451:"594f8519",9462:"4798c258",9514:"d5cf2d0b",9671:"266370e9",9817:"0f68630a",9822:"94f115f8",9888:"dd84d40b"}[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="/",r.gca=function(e){return e={17896441:"7918",80680481:"7065",f160c361:"12","935f2afb":"53","2efe1410":"90","6ec19757":"220","90b957e4":"434",b2b675dd:"533","05f46752":"685",e044ccdf:"688","6704bb9d":"852","733c4d41":"964","84b73551":"1176","72413e93":"1198",ff8c06e1:"1433",b2f554cd:"1477",e6335e6f:"1634","7bef7309":"1744",dd53d751:"1752","099d81ac":"1977","6d0a6812":"1998","90e3b8d9":"2000","21ad55e6":"2034",f739fd9f:"2182","48d46c19":"2306",a7626ec9:"2365","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","303a7ab0":"3777","0f4b3ece":"3892",e9ab53df:"4103",dd933416:"4130",c4f5d8e4:"4195","9e92f087":"4369",bacda3a9:"4475","39b1bd06":"4569",ebee79fe:"5041","88236a13":"5080",c9aab52f:"5153","032c34c3":"5183","26b576d2":"5367","5dd67a5f":"5649","5148d8fe":"5650","27b4bb7f":"5659","5a96aca1":"5746","1566bc1f":"5936",ccc49370:"6103","1d92ca72":"6290","41bb1898":"6333","4dfc0651":"6468",bdd7c4d4:"6729",f33e1a49:"6848",ba76a366:"7020",fbd8196d:"7040","1b21ecc3":"7087",f4f82255:"7203","742b5987":"7404","0ccd1bc3":"7589",a99908d5:"7681",c71319a4:"7884","1a4e3797":"7920","7faaab83":"7991",b7e34b9a:"8052",f93d3a31:"8063",c55163c5:"8787","3d345fd1":"9106",c4ad3b7e:"9124","355d470d":"9451","9b588bbf":"9462","1be78505":"9514","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,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 分钟

我们在上一节完成了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 cdfd68fc..28a48a7a 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 cd692f48..1b3f79bf 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 f76b9919..2e1f3857 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 364742fb..fd34b795 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 bec7e27e..96edf6ac 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 861adced..b8cdae3a 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 893366e8..ba608649 100644 --- a/docs/basic/aotgeneric.html +++ b/docs/basic/aotgeneric.html @@ -9,7 +9,7 @@ - + @@ -31,7 +31,7 @@ 加载补充元数据不仅导致内存占用明显增加,还增加了启动时间。对于微信小游戏这些对包体和内存要求严苛的场合,这是一个影响较大的问题。 被补充的泛型函数以解释方式执行,还降低了运行性能。

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 05d13850..990d37fe 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 0963c15e..b012fc47 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 478fcf45..9b934744 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. 根据你项目原来的打包流程打包

优化的打包流程

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 63d48e65..294237aa 100644 --- a/docs/basic/buildwebgl.html +++ b/docs/basic/buildwebgl.html @@ -9,13 +9,13 @@ - +

发布WebGL平台

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

使用的版本

不同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平台只支持全局安装。

select_il2cpp_module_webgl

建立 Editor目录的libil2cpp到本地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 de923d45..28c17f9a 100644 --- a/docs/basic/codestriping.html +++ b/docs/basic/codestriping.html @@ -9,7 +9,7 @@ - + @@ -22,7 +22,7 @@ 请确保在主工程代码中显式引用过它的某个类或函数。

AOT类型及函数预留

com.code-philosophy.hybridclrophy.hybridclr的HybridCLR/Generate/LinkXml命令虽然可以智能地扫描出你当前引用的AOT类型,却不能预知你未来将来使用的 类型。因此你仍然需要有规划地提前在 Assets/link.xml(注意!不是自动生成的那个link.xml)预留你将来 可能用到的类型。切记不要疏漏,免得出现上线后某次更新使用的类型被裁剪的尴尬状况!

- + \ 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 e4a47b9f..52500df1 100644 --- a/docs/basic/com.code-philosophy.hybridclr.html +++ b/docs/basic/com.code-philosophy.hybridclr.html @@ -9,14 +9,14 @@ - +

hybridclr Package手册

com.code-philosophy.hybridclr是一个Unity package,它提供了HybridCLR所需的Editor工作流工具脚本及Runtime脚本。借助 com.code-philosophy.hybridclr提供的工作流工具,打包一个支持HybridCLR热更新功能的App变得非常简单。hybridclr_unity包主要包含以下内容:

  • Editor相关脚本
  • Runtime相关脚本
  • iOSBuild脚本
警告

v3.0.0 之前的包名叫 com.focus-creative-games.hybridclr_unity

HybridCLR菜单介绍

以下子菜单均在菜单栏的HybridCLR菜单下,出于简化,我们下面提到子菜单时不再包含HybridCLR。

Installer...

详细文档见安装HybridCLR

Installer是一个方便的安装器,帮助正确设置本地il2cpp目录,其中包含替换HybridCLRData/LocalIl2CppData-{platform}/il2cpp/libil2cpp目录为HybridCLR修改版本。

安装界面中 安装状态:已安装|未安装 指示是否完成HybridCLR初始化。点击安装,如成功,则最后会显示安装成功日志,并且安装状态切换为已安装,否则请检查错误日志。

提示

如果已经安装HybridCLR,点击安装按钮会安装最新的HybridCLR版本的libil2cpp。

com.code-philosophy.hybridclr中 Data~/hybridclr_version.json 文件中已经配置了当前package版本对应的兼容 hybridclr及il2cpp_plus的分支或者tag, -Installer会安装配置中指定的版本,不再支持自定义待安装的版本。

配置类似如下:

{
"versions": [
{
"unity_version":"2019",
"hybridclr" : { "branch":"v2.0.1"},
"il2cpp_plus": { "branch":"v2019-2.0.1"}
},
{
"unity_version":"2020",
"hybridclr" : { "branch":"v2.0.1"},
"il2cpp_plus": { "branch":"v2020-2.0.1"}
},
{
"unity_version":"2021",
"hybridclr" : { "branch":"v2.0.1"},
"il2cpp_plus": { "branch":"v2021-2.0.1"}
},
{
"unity_version":"2022",
"hybridclr" : { "branch":"v2.0.1"},
"il2cpp_plus": { "branch":"v2020-2.0.1"}
}
]
}

如果你一定要安装其他版本的hybridclr或il2cpp_plus,修改该配置文件中的branch为目标分支或者tag。

install_default

从版本2.3.1起新增支持直接从本地自己制作的包含hybridclr的libil2cpp目录复制安装。如果你网络不好,或者没有安装git导致无法从仓库远程下载安装,则可以先将 il2cpp_plushybridclr下载到本地后,再根据下面安装原理小节的文档,由这两个仓库合并出含hybridclr的libil2cpp目录,接着在Installer安装界面中启用从本地复制libil2cpp选项,选择你制作的libil2cpp目录,再点击安装执行安装。如下图所示。

install

Compile Dll

对于每个target,必须使用目标平台编译开关下编译出的热更新dll,否则会出现热更新代码与AOT主包或者热更新资源的代码信息不匹配的情况。

com.code-philosophy.hybridclr的HybridCLR.Editor程序集提供了HybridCLR.Editor.Commands.CompileDllCommand.CompileDll(BuildTarget target)接口, +Installer会安装配置中指定的版本,不再支持自定义待安装的版本。

配置类似如下:

{
"versions": [
{
"unity_version":"2019",
"hybridclr" : { "branch":"v2.0.1"},
"il2cpp_plus": { "branch":"v2019-2.0.1"}
},
{
"unity_version":"2020",
"hybridclr" : { "branch":"v2.0.1"},
"il2cpp_plus": { "branch":"v2020-2.0.1"}
},
{
"unity_version":"2021",
"hybridclr" : { "branch":"v2.0.1"},
"il2cpp_plus": { "branch":"v2021-2.0.1"}
},
{
"unity_version":"2022",
"hybridclr" : { "branch":"v2.0.1"},
"il2cpp_plus": { "branch":"v2020-2.0.1"}
}
]
}

如果你一定要安装其他版本的hybridclr或il2cpp_plus,修改该配置文件中的branch为目标分支或者tag。

install_default

从版本2.3.1起新增支持直接从本地自己制作的包含hybridclr的libil2cpp目录复制安装。如果你网络不好,或者没有安装git导致无法从仓库远程下载安装,则可以先将 il2cpp_plushybridclr下载到本地后,再根据下面安装原理小节的文档,由这两个仓库合并出含hybridclr的libil2cpp目录,接着在Installer安装界面中启用从本地复制libil2cpp选项,选择你制作的libil2cpp目录,再点击安装执行安装。如下图所示。

install

Compile Dll

对于每个target,必须使用目标平台编译开关下编译出的热更新dll,否则会出现热更新代码与AOT主包或者热更新资源的代码信息不匹配的情况。

com.code-philosophy.hybridclr的HybridCLR.Editor程序集提供了HybridCLR.Editor.Commands.CompileDllCommand.CompileDll(BuildTarget target)接口, 方便开发者灵活地自行编译热更新dll。编译完成后的热更新dll放到 {project}/HybridCLRData/HotUpdateDlls/{platform} 目录下。

Generate

Generate下包含打包时需要的生成命令。

Generate/Il2CppDef

hybridclr代码要兼容多个Unity版本,需要当前Unity版本相关宏定义。Generate/Il2CppDef命令生成了相关版本宏及其他必须的代码,生成的代码类似如下。

// hybridclr/generated/UnityVersion.h

#define HYBRIDCLR_UNITY_VERSION 2020333
#define HYBRIDCLR_UNITY_2020 1
#define HYBRIDCLR_UNITY_2019_OR_NEW 1
#define HYBRIDCLR_UNITY_2020_OR_NEW 1

Generate/LinkXml

扫描热更新dll引用的AOT类型,生成link.xml,避免热更新脚本用到的AOT类型或函数被裁剪。输出的文件路径在 HybridCLRSettings.asset中OuputLinkXml字段中指定,默认为LinkGenerator/link.xml

更具体的裁剪相关介绍请看代码裁剪原理及解决办法

Generate/AotDlls

生成裁剪后的AOT dlls。脚本通过在一个临时目录导出工程,实现生成裁剪后的AOT dlls的目标。生成AOT dlls依赖于Generate/LinkXmlGenerate/Il2CppDef。 如果你没有用 HybridCLR/Generate/All 这样的一键生成命令,请依次运行以下命令:

  • HybridCLR/Generate/Il2CppDef
  • HybridCLR/Generate/LinkXml
  • HybridCLR/Generate/AotDlls

Generate/MethodBridge

根据当前的AOT dll集扫描生成桥接函数文件。相关文档请看桥接函数

生成桥接函数依赖AOT dlls和热更新dlls。如果你没有用 HybridCLR/Generate/All 这样的一键生成命令,请依次运行以下命令:

  • HybridCLR/Generate/Il2CppDef
  • HybridCLR/Generate/LinkXml (隐含调用了 HybridCLR/CompileDll/ActiveBuildTarget)
  • HybridCLR/Generate/AotDlls
  • HybridCLR/Generate/MethodBridge

Generate/AOTGenericReference

根据当前热更新dll扫描出所有产生的AOT泛型类型及函数的实例化,并生成一个启发的泛型实例化文件AOTGenericReferences.cs。 由于将扫描出的泛型类型及函数转换为对应的代码引用比较麻烦,生成的所有泛型实例化代码都是注释代码

AOTGenericReferences.cs文件中还包含了应该补充元数据的assembly列表,类似如下,方便开发者不需要运行游戏也能快速知道应该补充哪些元数据。

    // {{ AOT assemblies
// Main.dll
// System.Core.dll
// UnityEngine.CoreModule.dll
// mscorlib.dll
// }}

请在其他文件中添加泛型类型及函数的实例化引用,因为这个输出文件每次重新生成后会被覆盖。 @@ -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 6669b002..33e1fc40 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/hotupdateassemblysetting.html b/docs/basic/hotupdateassemblysetting.html index 45e59dc1..273d7784 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 4f3d22bf..d91297e0 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 b2479958..5e98c143 100644 --- a/docs/basic/install.html +++ b/docs/basic/install.html @@ -9,20 +9,20 @@ - +

安装

安装兼容的Unity版本

支持2019.4.x、2020.3.x、2021.3.x、2022.3.x 中任一版本。推荐安装2019.4.40、2020.3.26+、2021.3.x、2022.3.x版本。

提示

如果你的版本为 2019.4.0-2019.4.39,需要先切换到2019.4.40版本完成HybridCLR安装,再切换回当前版本

如果你的版本为 2020.3.0-2020.3.25, 在Installer中完成安装后,从2020.3.26+任一版本的安装目录复制2020.3.x/Editor/Data/il2cpp/external替换 {project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp/external

根据你打包的目标平台,安装过程中选择必要模块。如果打包Android或iOS,直接选择相应模块即可。如果你想打包Standalone,必须额外选中 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
    • 安装cmake

选择 com.code-philosophy.hybridclr 版本

提示

v3.0.0 之前的包名叫 com.focus-creative-games.hybridclr_unity。从v3.0.0版本起,移除了对Unity 2019的支持,使用2019的开发者请选择v2.x.y系列版本。

当前存在这些版本:1.0分支、v2.x.yv3.x.yv.4.x.y(也是当前main分支)系列。

  • 1.0分支过于久远,虽然工作稳定,但Package相关工作流比较陈旧,不如后续版本便捷,而且已经停止了维护,强烈建议不要再使用
  • v2.x.y系列版本工作流优化合理,经过大量项目验证,推荐使用Unity 2019版本或马上要上线的项目使用
  • v3.x.y系列 移除了对Unity 2019的支持,新增了Unity 2022版本支持。推荐使用Unity 2020+版本或马上要上线的项目使用
  • v4.x.y系列支持增量式GC并且彻底支持全平台。由于刚刚发布,需要几个星期时间才能稳定,推荐处于项目初期或中期的项目使用。

这些版本都很稳定,不必纠结哪个更好,一般来说越新的版本优化越多,使用体验越好。

安装 com.code-philosophy.hybridclr package

仓库地址为 github ,国内快速的镜像仓库为gitee

有几种安装方式:

  • 使用Package Manager从git url安装
  • 本地安装

从git url安装

主菜单中点击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

  • main分支地址为 https://gitee.com/focus-creative-games/hybridclr_unity.git
  • 其他tag版本地址为 https://gitee.com/focus-creative-games/hybridclr_unity.git#{tag}

想安装某个分支或者tag版本,请在地址后加上#{tag},如 https://gitee.com/focus-creative-games/hybridclr_unity.git#v3.0.1

add package

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

从本地文件安装

将仓库clone到本地后,目录改名为com.code-philosophy.hybridclr(v3.0.0之前的版本请用 com.focus-creative-games.hybridclr_unity),再直接移到项目的Packages目录即可。

更新 com.code-philosophy.hybridclr

更新完com.code-philosophy.hybridclr后需要重新运行HybridCLR/Installer

初始化HybridCLR

为了减少package自身大小,有一些文件需要从Unity Editor的安装目录复制。因此安装完插件后,还需要一个额外的初始化过程。

点击菜单 HybridCLR/Installer...,弹出安装界面。在点击安装之前,可能需要一些设置。由于随着版本变化,Installer一直在调整,请根据你当前版本读取下面对应的说明。

如果你的版本 >= v2.0.5

com.code-philosophy.hybridclr中 Data~/hybridclr_version.json 文件中已经配置了当前package版本对应的兼容 hybridclr及il2cpp_plus的分支或者tag, -Installer会安装配置中指定的版本,不再支持自定义待安装的版本。

配置类似如下:

{
"versions": [
{
"unity_version":"2019",
"hybridclr" : { "branch":"v2.0.1"},
"il2cpp_plus": { "branch":"v2019-2.0.1"}
},
{
"unity_version":"2020",
"hybridclr" : { "branch":"v2.0.1"},
"il2cpp_plus": { "branch":"v2020-2.0.1"}
},
{
"unity_version":"2021",
"hybridclr" : { "branch":"v2.0.1"},
"il2cpp_plus": { "branch":"v2021-2.0.1"}
}
]
}

如果你一定要安装其他版本的hybridclr或il2cpp_plus,修改该配置文件中的branch为目标分支或者tag。绝大多数情况下,直接点击安装默认从远程仓库下载安装即可。安装成功后,控制台会打印安装成功日志。如下图所示。

install_default

从版本2.3.1起新增支持直接从本地自己制作的包含hybridclr的libil2cpp目录复制安装。如果你网络不好,或者没有安装git导致无法从仓库远程下载安装,则可以先将 il2cpp_plushybridclr下载到本地后,再根据下面安装原理小节的文档,由这两个仓库合并出含hybridclr的libil2cpp目录,接着在Installer安装界面中启用从本地复制libil2cpp选项,选择你制作的libil2cpp目录,再点击安装执行安装。如下图所示。

install

如果你的版本 >= 1.1.20

com.code-philosophy.hybridclr中 Data~/hybridclr_version.json 文件中已经配置了当前package版本对应的兼容 hybridclr及il2cpp_plus的版本, +Installer会安装配置中指定的版本,不再支持自定义待安装的版本。

配置类似如下:

{
"versions": [
{
"unity_version":"2019",
"hybridclr" : { "branch":"v2.0.1"},
"il2cpp_plus": { "branch":"v2019-2.0.1"}
},
{
"unity_version":"2020",
"hybridclr" : { "branch":"v2.0.1"},
"il2cpp_plus": { "branch":"v2020-2.0.1"}
},
{
"unity_version":"2021",
"hybridclr" : { "branch":"v2.0.1"},
"il2cpp_plus": { "branch":"v2021-2.0.1"}
}
]
}

如果你一定要安装其他版本的hybridclr或il2cpp_plus,修改该配置文件中的branch为目标分支或者tag。绝大多数情况下,直接点击安装默认从远程仓库下载安装即可。安装成功后,控制台会打印安装成功日志。如下图所示。

install_default

从版本2.3.1起新增支持直接从本地自己制作的包含hybridclr的libil2cpp目录复制安装。如果你网络不好,或者没有安装git导致无法从仓库远程下载安装,则可以先将 il2cpp_plushybridclr下载到本地后,再根据下面安装原理小节的文档,由这两个仓库合并出含hybridclr的libil2cpp目录,接着在Installer安装界面中启用从本地复制libil2cpp选项,选择你制作的libil2cpp目录,再点击安装执行安装。如下图所示。

install

如果你的版本 >= 1.1.20

com.code-philosophy.hybridclr中 Data~/hybridclr_version.json 文件中已经配置了当前package版本对应的兼容 hybridclr及il2cpp_plus的版本, Installer会安装配置中指定的版本,不再支持自定义待安装的版本。

配置类似如下:

{
"versions": [
{
"unity_version":"2019",
"hybridclr" : { "branch":"main", "hash":"531f98365eebce5d1390175be2b41c41e217d918"},
"il2cpp_plus": { "branch":"2019-main", "hash":"ebe5190b0404d1857832bd1d52ebec7c3730a01d"}
},
{
"unity_version":"2020",
"hybridclr" : { "branch":"main", "hash":"531f98365eebce5d1390175be2b41c41e217d918"},
"il2cpp_plus": { "branch":"2020-main", "hash":"c6cf54285381d0b03a58126e0d39b6e4d11937b7"}
},
{
"unity_version":"2021",
"hybridclr" : { "branch":"main", "hash":"531f98365eebce5d1390175be2b41c41e217d918"},
"il2cpp_plus": { "branch":"2021-main", "hash":"99cd1cbbfc1f637460379e81c9a7776cd3e662ad"}
}
]
}

如果你想安装其他版本的hybridclr或il2cpp_plus,修改该配置文件中的branch和hash即可。直接点击安装完成安装。安装成功后,控制台会打印安装成功日志。

如果你的package版本 <= 1.1.19

填写你要安装的hybridclr和il2cpp_plus仓库的 commit id或branch或tag。如果hybridclr的版本号留空,则安装hybridclr仓库main分支的最新版本。 如果il2cpp_plus的版本号留空,则安装相应年度版本主分支(如2020-main)的最新版本。

hybridclr_uniyt分支、hybridclr仓库的分支跟il2cpp_plus仓库分支必须匹配。如果你com.code-philosophy.hybridclr使用了main分支,则hybridclr必须使用main分支,il2cpp_plus必须使用{version}-main,如果你hybridclr_unity使用了1.0分支, 则hybridclr必须使用1.0分支,il2cpp_plus必须使用{version}-1.0分支。 如果你使用了某个tag的版本,确保这个tag所属的分支匹配。

hybridclr仓库推荐填写1.0,即每次安装1.0分支的最新版本;il2cpp_plus仓库推荐填{年度版本}-1.0(如2020-1.0),即每次安装{年度版本}-1.0分支的最新版本。如图:

image

目前已经发布了1.0.1稳定正式版本,同样推荐追求稳定的项目使用。com.code-philosophy.hybridclr取 1.0.1-release,hybridclr 版本取 1.0.1-release,il2cpp_plus版本取 {version}-1.0.1-relase

完成以上设置后,点击install按钮完成安装。安装成功后,控制台会打印安装成功日志。

由于安装过程需要拉取hybridclr及il2cpp_plus仓库,有可能会因为网络故障而失败,如果 发现失败时 HybridCLRData/hybridclr_repoHybridCLRData/il2cpp_plus_repo为空,请再次尝试。

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

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

安装后的特殊处理

WebGL平台

由于Unity自身原因,WebGL平台必须全局安装。 请查阅下面章节的全局安装文档。

Unity 2021

警告

如果你的com.code-philosophy.hybridclr版本 >= v2.0.1,由于已经使用MonoHook技术在不修改UnityEditor.CoreModule.dll的情况下也能复制出裁剪后的AOT dll,不需要执行以下操作。

补充元数据及HybridCLR/Generate/*下的部分命令依赖裁减后的AOT dll。但Unity 2021版本(2019、2020不需要)打包iOS平台(其他平台不需要)时,由于Unity Editor未提供公开接口可以复制出target为iOS时的裁剪后的AOT dll,故必须使用修改后的UnityEditor.CoreModule.dll覆盖Unity自带的相应文件。

具体操作为将 {package目录}/Data~/ModifiedUnityAssemblies/2021.3.x/UnityEditor.CoreModule-{Win,Mac}.dll 覆盖 {Editor安装目录}/Editor/Data/Managed/UnityEngine/UnityEditor.CoreModule.dll,具体相关目录有可能因为操作系统或者Unity版本而有不同。

由于权限问题,该操作无法自动完成,需要你手动执行复制操作。

UnityEditor.CoreModule.dll 每个Unity小版本都不相同,我们目前暂时只提供了2021.3.1版本,如需其他版本请自己手动制作,详情请见 修改Unity编辑器相关dll

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 361e48a7..a1343dc2 100644 --- a/docs/basic/memory.html +++ b/docs/basic/memory.html @@ -9,13 +9,13 @@ - +

内存与GC

对象内存大小

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

primitive type

如byte,int。如熟知, byte占1字节,int占4字节,其他不赘述。

struct值类型

在未指定Explicit Layout的情况下,根据字段大小及内存对齐规则计算出总大小,与c++的struct计算规则相似。这里不详细阐述,直接举例吧。

// 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;
}

class 类型

与值类型相似,但多了对象头的16字节,并且强制内存对齐为8字节。示例:

// 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;
}

与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

加载程序集占用的内存

加载程序集时,会复制一份dll文件字节数组,并且内存中动态生成元数据。最终占的内存一般是1-5倍程序集大小(未大量统计过)。

运行过程中GC

HybridCLR严格按照规范实现,除了assembly加载和函数第一次transfrom时会额外消耗CPU和内存外,运行时消耗的内存跟il2cpp完全相同。

因此你不要再问诸如 foreach 循环会不会产生GC 这样的问题。在il2cpp或mono下产生多少GC,在HybridCLR中解释执行时也产生完全相同的GC。

- + \ No newline at end of file diff --git a/docs/basic/methodbridge.html b/docs/basic/methodbridge.html index f978e0e4..d536801b 100644 --- a/docs/basic/methodbridge.html +++ b/docs/basic/methodbridge.html @@ -9,14 +9,14 @@ - +

桥接函数

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
- + \ No newline at end of file diff --git a/docs/basic/migratefromnetstandard.html b/docs/basic/migratefromnetstandard.html index 8797e978..5f741105 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/modifyunitydll.html b/docs/basic/modifyunitydll.html index 2113d3a8..7ffa0216 100644 --- a/docs/basic/modifyunitydll.html +++ b/docs/basic/modifyunitydll.html @@ -9,7 +9,7 @@ - + @@ -25,7 +25,7 @@ 目前只制作了2021.3.1版本 (将来可能会提供更多),其他版本请自行制作。具体操作方式请阅读下面的文档。

信息

同个版本的Win与Mac版本的UnityEditor.CoreModule.dll并不能混用,必须分别制作。

替换原始UnityEditor.CoreModule.dll

  • 提前备份 {Editor安装目录}/Editor/Data/Managed/UnityEngine/UnityEditor.CoreModule.dll。 UnityEditor.CoreModule.dll 具体位置有可能因为操作系统或者Unity版本而有不同。
  • 如果你正好使用2021.3.1等已经提供了制作好的dll的版本,则使用 com.code-philosophy.hybridclr/Datas~/ModifiedUnityAssemblies/{version}/UnityEditor.CoreModule-{Win,MAC}.dll 覆盖 Editor安装目录中的 UnityEditor.CoreModule.dll。

使用dnspy修改

进行以下操作前,你先仔细看完前面的 使用dnspy工具 这节内容。

  • {Editor安装目录}/Editor/Data/Managed/UnityEngine目录拷贝出来,假设是TempUnityEngine目录
  • 删除TempUnityEngine目录下的所有.pdb类型调试文件。因为Unity的pdb文件有问题,会导致dnspy解析出错,导致无法保存。
  • 打开 dnspy,清除左侧的dll列表。
  • 使用dnspy打开 TempUnityEngine/UnityEditor.CoreModule.dll
  • 打开 UnityEditorInternal.AssemblyStripper.RunAssemblyStripper 函数, 右键菜单 -> 编辑方法(c#)...,弹出源码编辑界面。
  • 此时编辑器缺少mscorlib.dll的引用,需要手动添加。点击源码编辑窗口左下角类似文件夹的按钮,添加 {Editor安装目录}/Editor/Data/UnityReferenceAssemblies/unity-4.8-api/mscorlib.dll
  • RunAssemblyStripper函数尾部,找到这个代码块
    foreach (string text3 in Directory.GetFiles(fullPath))
{
File.Move(text3, Path.Combine(managedAssemblyFolderPath, Path.GetFileName(text3)));
}

修改为

    string dstAOTDir = Path.Combine(UnityEngine.Application.dataPath, "../HybridCLRData/AssembliesPostIl2CppStrip",
EditorUserBuildSettings.activeBuildTarget.ToString());
Directory.CreateDirectory(dstAOTDir);
foreach (string text3 in Directory.GetFiles(fullPath))
{
if (text3.EndsWith(".dll"))
{
string copyDstFile = Path.Combine(dstAOTDir, Path.GetFileName(text3));
File.Copy(text3, copyDstFile, true);
UnityEngine.Debug.Log("[RunAssemblyStripper] copy aot dll " + text3 + " -> " + copyDstFile);
}
File.Move(text3, Path.Combine(managedAssemblyFolderPath, Path.GetFileName(text3)));
}
  • 注意!反编译的代码中,变量名未必是text3,请按实际情况处理。如有遇到编译错误,请自行酌情处理。
  • 点击右下角的 编译 按钮,如果成功,则无任何提示,退出编辑界面,返回反编译查看模式。如果失败,请自行处理编译错误。有时候dnspy会有莫名其妙的引用错误,退出源码编辑模式,重新右键编辑方法,再次进入就能解决。
  • 菜单 文件 -> 保存模块 保存修改后的 UnityEditor.CoreModule.dll文件。如果在Win或Mac下,有可能会遇到权限问题,请酌情处理(比如先保存到其他位置,再手动覆盖)
  • 重新打开Unity Editor。此时iOS便能正确获得裁剪AOT dll。

Unity.IL2CPP.dll

原理

2019版本,我们需要轻微修改il2cpp生成的代码,将 Il2CppOutputProject\Source\il2cppOutput\Il2CppTypeDefinitions.c中定义的常量const Il2CppType换成可变的Il2CppType。 我们需要修改Unity.IL2CPP.dll代码达到这个目标。注意!实际操作过程发现dnspy反编译的代码有问题,最终我们在ILSpy反编译的代码基础上调整后,再在dnspy里编辑保存。 直接复制以下我们修改好的代码,在dnspy里编辑保存。修改过程可能会遇到问题,参照上面修改UnityEditor.CoreModule.dll中使用的解决办法。

修改 Unity.IL2CPP.CppDeclarationsWriter::Write(StreamWriter writer, ICppDeclarations declarationsIn, IInteropDataCollector interopDataCollector)

修改后的代码

     string[] includesToSkip = new string[3] { "\"il2cpp-config.h\"", "<alloca.h>", "<malloc.h>" };
CppDeclarationsCollector.PopulateCache(declarationsIn.TypeIncludes, cache, interopDataCollector);
HashSet<TypeReference> hashSet = new HashSet<TypeReference>(declarationsIn.TypeIncludes, new TypeReferenceEqualityComparer());
ReadOnlyHashSet<TypeReference> dependencies = CppDeclarationsCollector.GetDependencies(declarationsIn.TypeIncludes, cache);
hashSet.UnionWith(dependencies);
ReadOnlyCollection<TypeReference> readOnlyCollection = hashSet.ToSortedCollection(new CppIncludeDepthComparer(comparer));
CppDeclarations cppDeclarations = new CppDeclarations();
cppDeclarations.Add(declarationsIn);
foreach (TypeReference item in readOnlyCollection)
{
cppDeclarations.Add(cache.GetDeclarations(item));
}
writer.WriteLine();
foreach (string rawFileLevelPreprocessorStmt in cppDeclarations.RawFileLevelPreprocessorStmts)
{
writer.WriteLine(rawFileLevelPreprocessorStmt);
}
writer.WriteLine();
foreach (string item2 in cppDeclarations.Includes.Where((string i) => !includesToSkip.Contains(i) && i.StartsWith("<")))
{
writer.WriteLine("#include {0}", item2);
}
writer.WriteLine();
foreach (string item3 in cppDeclarations.Includes.Where((string i) => !includesToSkip.Contains(i) && !i.StartsWith("<")))
{
writer.WriteLine("#include {0}", item3);
}
writer.WriteLine();
WriteVirtualMethodDeclaration(writer, cppDeclarations.VirtualMethods);
writer.WriteLine();
foreach (TypeReference item4 in cppDeclarations.ForwardDeclarations.ToSortedCollection())
{
if (!item4.IsSystemObject() && !item4.IsSystemArray())
{
if (CodeGenOptions.EmitComments)
{
writer.WriteLine(Emit.Comment(item4.FullName));
}
writer.WriteLine("struct {0};", Globals.Naming.ForType(item4));
}
}
writer.WriteLine();
foreach (string item5 in cppDeclarations.RawTypeForwardDeclarations.ToSortedCollection())
{
writer.WriteLine(item5 + ";");
}
writer.WriteLine();
foreach (ArrayType item6 in cppDeclarations.ArrayTypes.ToSortedCollection())
{
writer.WriteLine("struct {0};", Globals.Naming.ForType(item6));
}
writer.WriteLine();
writer.WriteLine("IL2CPP_EXTERN_C_BEGIN");
foreach (TypeReference typeExtern in cppDeclarations.TypeExterns)
{
writer.WriteLine("extern Il2CppType " + Globals.Naming.ForIl2CppType(typeExtern) + ";");
}
foreach (IList<TypeReference> genericInstExtern in cppDeclarations.GenericInstExterns)
{
writer.WriteLine("extern Il2CppGenericInst " + Globals.Naming.ForGenericInst(genericInstExtern) + ";");
}
foreach (TypeReference genericClassExtern in cppDeclarations.GenericClassExterns)
{
writer.WriteLine("extern Il2CppGenericClass " + Globals.Naming.ForGenericClass(genericClassExtern) + ";");
}
writer.WriteLine("IL2CPP_EXTERN_C_END");
writer.WriteLine();
if (readOnlyCollection.Count > 0)
{
writer.WriteClangWarningDisables();
foreach (TypeReference item7 in readOnlyCollection)
{
string source = cache.GetSource(item7);
writer.Write(source);
}
writer.WriteClangWarningEnables();
}
foreach (ArrayType arrayType in cppDeclarations.ArrayTypes)
{
TypeDefinitionWriter.WriteArrayTypeDefinition(arrayType, new CodeWriter(writer));
}
writer.WriteLine();
foreach (string rawMethodForwardDeclaration in cppDeclarations.RawMethodForwardDeclarations)
{
writer.WriteLine(rawMethodForwardDeclaration + ";");
}
writer.WriteLine();
foreach (MethodReference sharedMethod in cppDeclarations.SharedMethods)
{
WriteSharedMethodDeclaration(writer, sharedMethod);
}
writer.WriteLine();
foreach (MethodReference method in cppDeclarations.Methods)
{
WriteMethodDeclaration(writer, method);
}
writer.Flush();

修改Unity.IL2CPP.Il2CppTypeWriter::WriteIl2CppTypeDefinitions(IMetadataCollection metadataCollection)

修改后的代码

    base.Writer.AddCodeGenMetadataIncludes();
IDictionary<Il2CppTypeData, int> items = Globals.Il2CppTypeCollectorReader.Items;
foreach (IGrouping<TypeReference, Il2CppTypeData> item in items.Keys.GroupBy((Il2CppTypeData entry) => entry.Type.GetNonPinnedAndNonByReferenceType(), new TypeReferenceEqualityComparer()))
{
base.Writer.WriteLine();
TypeReference key = item.Key;
GenericParameter genericParameter = key as GenericParameter;
GenericInstanceType genericInstanceType = key as GenericInstanceType;
ArrayType arrayType = key as ArrayType;
PointerType pointerType = key as PointerType;
string text = ((genericParameter != null) ? ("(void*)" + metadataCollection.GetGenericParameterIndex(genericParameter)) : ((genericInstanceType != null) ? WriteGenericInstanceTypeDataValue(genericInstanceType, metadataCollection) : ((arrayType != null) ? WriteArrayDataValue(arrayType) : ((pointerType == null) ? ("(void*)" + metadataCollection.GetTypeInfoIndex(key.Resolve()).ToString(CultureInfo.InvariantCulture)) : WritePointerDataValue(pointerType)))));
foreach (Il2CppTypeData item2 in item)
{
base.Writer.WriteLine("extern Il2CppType {0};", Globals.Naming.ForIl2CppType(item2.Type, item2.Attrs));
base.Writer.WriteLine("Il2CppType {0} = {{ {1}, {2}, {3}, {4}, {5}, {6} }};", Globals.Naming.ForIl2CppType(item2.Type, item2.Attrs), text, item2.Attrs.ToString(CultureInfo.InvariantCulture), Il2CppTypeSupport.For(item2.Type), "0", item2.Type.IsByReference ? "1" : "0", item2.Type.IsPinned ? "1" : "0");
}
}
return MetadataWriter.WriteTable(base.Writer, "const Il2CppType* const ", "g_Il2CppTypeTable", items.ItemsSortedByValue(), (KeyValuePair<Il2CppTypeData, int> kvp) => "&" + Globals.Naming.ForIl2CppType(kvp.Key.Type, kvp.Key.Attrs), externTable: true);

修改 Unity.IL2CPP.Metadata.Il2CppGenericInstWriter::WriteIl2CppGenericInstDefinitions(IIl2CppGenericInstCollectorReaderService genericInstCollection)

修改后的代码

base.Writer.AddCodeGenMetadataIncludes();
foreach (TypeReference[] item in genericInstCollection.Items.Select(delegate(KeyValuePair<TypeReference[], uint> item)
{
KeyValuePair<TypeReference[], uint> keyValuePair = item;
return keyValuePair.Key;
}))
{
for (int i = 0; i < item.Length; i++)
{
base.Writer.WriteExternForIl2CppType(item[i]);
}
string format = "static const Il2CppType* {0}[] = {{ {1} }};";
WriteLine(format, Globals.Naming.ForGenericInst(item) + "_Types", item.Select((TypeReference t) => MetadataWriter.TypeRepositoryTypeFor(t)).AggregateWithComma());
WriteLine("extern Il2CppGenericInst {0};", Globals.Naming.ForGenericInst(item));
WriteLine("Il2CppGenericInst {0} = {{ {1}, {2} }};", Globals.Naming.ForGenericInst(item), item.Length, Globals.Naming.ForGenericInst(item) + "_Types");
}
return MetadataWriter.WriteTable(base.Writer, "const Il2CppGenericInst* const", "g_Il2CppGenericInstTable", genericInstCollection.Items.ItemsSortedByValue(), (KeyValuePair<TypeReference[], uint> item) => "&" + Globals.Naming.ForGenericInst(item.Key), externTable: true);
- + \ No newline at end of file diff --git a/docs/basic/monobehaviour.html b/docs/basic/monobehaviour.html index b846695c..02496a2c 100644 --- a/docs/basic/monobehaviour.html +++ b/docs/basic/monobehaviour.html @@ -9,7 +9,7 @@ - + @@ -20,7 +20,7 @@ 由于不满足条件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文件中。

已知问题

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 4ee2c473..4407286f 100644 --- a/docs/basic/notsupportedfeatures.html +++ b/docs/basic/notsupportedfeatures.html @@ -9,13 +9,13 @@ - +

不支持的特性

提示

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

  • 暂时不支持在热更新脚本中定义extern函数,但可以调用AOT中extern函数。
  • 完全支持2022的dots技术,但无法利用burst加速。如果burst部分在AOT,则仍然原生方式执行;如果burst部分在热更部分,则虽然是Jobs并发执行,但以解释方式执行。
  • 不支持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 2c887ef2..40d9af83 100644 --- a/docs/basic/performance.html +++ b/docs/basic/performance.html @@ -9,7 +9,7 @@ - + @@ -23,7 +23,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完全相同。

其他指令优化技术

其他的优化技术

附录:测试用例代码

下面这些测试用例来自第三方提供,用例并不合理,但我们不想有刻意构造之嫌,直接引用它的用例。

private static void Test0()
{
var go = new GameObject("t");
var transform = go.transform;

var cnt = PerformanceSetting.Count * 1000;
for (var i = 0; i < cnt; i++)
{
transform.position = transform.position;
}

Object.Destroy(go);
}

private static void Test1()
{
var go = new GameObject("t");
var transform = go.transform;

var cnt = PerformanceSetting.Count * 100;
for (var i = 0; i < cnt; i++)
{
transform.Rotate(Vector3.up, 1);
}

Object.Destroy(go);
}

private static void Test2()
{
var cnt = PerformanceSetting.Count * 1000;
for (var i = 0; i < cnt; i++)
{
var v = new Vector3(i, i, i);
var x = v.x;
var y = v.y;
var z = v.z;
var r = x + y * z;
}
}

private static void Test3()
{
var cnt = PerformanceSetting.Count * 10;
for (var i = 0; i < cnt; i++)
{
var go = new GameObject("t");
Object.Destroy(go);
}
}

private static void Test4()
{
var cnt = PerformanceSetting.Count * 10;
for (var i = 0; i < cnt; i++)
{
var go = new GameObject();
go.AddComponent<SkinnedMeshRenderer>();
var c = go.GetComponent<SkinnedMeshRenderer>();
c.receiveShadows = false;
Object.Destroy(go);
}
}

private static void Test5()
{
var cnt = PerformanceSetting.Count * 1000;
for (var i = 0; i < cnt; i++)
{
var p = Input.mousePosition;
}
}

private static void Test6()
{
var cnt = PerformanceSetting.Count * 1000;
for (var i = 0; i < cnt; i++)
{
var v = new Vector3(i, i, i);
Vector3.Normalize(v);
}
}

private static void Test7()
{
var cnt = PerformanceSetting.Count * 100;
for (var i = 0; i < cnt; i++)
{
var q1 = Quaternion.Euler(i, i, i);
var q2 = Quaternion.Euler(i * 2, i * 2, i * 2);
Quaternion.Slerp(Quaternion.identity, q1, 0.5f);
}
}

private static void Test8()
{
double total = 0;
var cnt = PerformanceSetting.Count * 10000;
for (var i = 0; i < cnt; i++)
{
total = total + i - (i / 2) * (i + 3) / (i + 5);
}
}

private static void Test9()
{
var cnt = PerformanceSetting.Count * 1000;
for (var i = 0; i < cnt; i++)
{
var a = new Vector3(1, 2, 3);
var b = new Vector3(4, 5, 6);
var c = a + b;
}
}
local function test0()
local cnt = CS.GameMain.Scripts.Performance.PerformanceSetting.Count * 1000

local go = CS.UnityEngine.GameObject("_")
local transform = go.transform

for i = 1, cnt do
transform.position = transform.position
end

CS.UnityEngine.GameObject.Destroy(go)
end

local function test1()
local cnt = CS.GameMain.Scripts.Performance.PerformanceSetting.Count * 100

local go = CS.UnityEngine.GameObject("_")
local transform = go.transform

for i = 1, cnt do
transform:Rotate(CS.UnityEngine.Vector3.up, 1)
end

CS.UnityEngine.GameObject.Destroy(go)
end

local function test2()
local cnt = CS.GameMain.Scripts.Performance.PerformanceSetting.Count * 1000

local go = CS.UnityEngine.GameObject("_")
local transform = go.transform

for i = 1, cnt do
local tmp = CS.UnityEngine.Vector3(i, i, i)
local x = tmp.x
local y = tmp.y
local z = tmp.z
local r = x + y * z
end
end

local function test3()
local cnt = CS.GameMain.Scripts.Performance.PerformanceSetting.Count * 10
for i = 1, cnt do
local tmp = CS.UnityEngine.GameObject("___")
CS.UnityEngine.GameObject.Destroy(tmp)
end
end

local function test4()
local cnt = CS.GameMain.Scripts.Performance.PerformanceSetting.Count * 10
for i = 1, cnt do
local tmp = CS.UnityEngine.GameObject("___")
tmp:AddComponent(typeof(CS.UnityEngine.SkinnedMeshRenderer))
local c = tmp:GetComponent(typeof(CS.UnityEngine.SkinnedMeshRenderer))
c.receiveShadows = false
CS.UnityEngine.GameObject.Destroy(tmp)
end
end

local function test5()
local cnt = CS.GameMain.Scripts.Performance.PerformanceSetting.Count * 1000
for i = 1, cnt do
local tmp = CS.UnityEngine.Input.mousePosition;
end
end

local function test6()
local cnt = CS.GameMain.Scripts.Performance.PerformanceSetting.Count * 1000
for i = 1, cnt do
local tmp = CS.UnityEngine.Vector3(i, i, i)
CS.UnityEngine.Vector3.Normalize(tmp)
end
end

local function test7()
local cnt = CS.GameMain.Scripts.Performance.PerformanceSetting.Count * 100
for i = 1, cnt do
local t1 = CS.UnityEngine.Quaternion.Euler(i, i, i)
local t2 = CS.UnityEngine.Quaternion.Euler(i * 2, i * 2, i * 2)
CS.UnityEngine.Quaternion.Slerp(t1, t2, CS.UnityEngine.Random.Range(0.1, 0.9))
end
end

local function test8()
local cnt = CS.GameMain.Scripts.Performance.PerformanceSetting.Count * 10000
local total = 0
for i = 1, cnt do
total = total + i - (i / 2) * (i + 3) / (i + 5)
end
end

local function test9()
local cnt = CS.GameMain.Scripts.Performance.PerformanceSetting.Count * 1000
for i = 1, cnt do
local tmp0 = CS.UnityEngine.Vector3(1, 2, 3)
local tmp1 = CS.UnityEngine.Vector3(4, 5, 6)
local tmp2 = tmp0 + tmp1
end
end

- + \ No newline at end of file diff --git a/docs/basic/projectsettings.html b/docs/basic/projectsettings.html index fdf7c0d7..7868670a 100644 --- a/docs/basic/projectsettings.html +++ b/docs/basic/projectsettings.html @@ -9,7 +9,7 @@ - + @@ -17,7 +17,7 @@

配置

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

配置PlayerSettings

警告

目前增量式GC处理alpha阶段,建议已经上线或者快上线的项目不要开启这个选项。

  • 如果你的com.code-philosophy.hybridclr版本低于v4.0.0,需要关闭增量式GC(Use Incremental GC) 选项。自v4.0.0起已经支持增量式GC, 但处于beta版本,建议马上要上线的项目不要开启这个选项。
  • 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 56352eec..87c7b757 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 01ec367d..8acf24f1 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 1051d47a..6c9d2bda 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
  • WebGL 标准WebGL、MiniGame、微信小游戏
  • PS4、PS5
  • UWP

以下是理论支持,但实践中有可能残留小bug的平台:

  • tvOS
  • 其他平台

特殊说明

微信小游戏

微信小游戏转换工具,默认会将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 f35bafee..a89e8c29 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 312cd711..93606720 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 d9603923..7ec5f7b2 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泛型了。

- + \ No newline at end of file diff --git a/docs/beginner/monobehaviour.html b/docs/beginner/monobehaviour.html index a3efccc0..f367e5d9 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 b515eb05..9dc031f6 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 f01a0dac..52549ca5 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也支持2019.4.x,但新手请先按照下面要求跑通流程后,再根据安装HybridCLR文档尝试2019.4.x。

  • 安装 2020.3.26+、 2021.3.0+、2022.3.0+ 中任一版本。如果你不是经验丰富的Unity开发者,推荐使用2021.3.1版本。
  • 根据你所用的操作系统,安装过程中选择模块时,必须选中 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热更新项目

从零开始构造热更新项目的过程较冗长,项目结构及资源及代码均可参考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

安装 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左右,安装完成后会在最后打印 安装成功日志。

配置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 ba036277..82dfdb34 100644 --- a/docs/business.html +++ b/docs/business.html @@ -9,13 +9,13 @@ - +

商业化版本

- + \ No newline at end of file diff --git a/docs/business/advancedcodeoptimization.html b/docs/business/advancedcodeoptimization.html index bcc0a812..0983ac4d 100644 --- a/docs/business/advancedcodeoptimization.html +++ b/docs/business/advancedcodeoptimization.html @@ -9,7 +9,7 @@ - + @@ -17,7 +17,7 @@

高级指令优化

危险

高级指令优化尚在开发中,预计于2023.10有预览版本。

高级指令优化技术是独立于标准指令优化技术的实现。高级指令优化技术使用更丰富的编译优化技术,极大提升了解释模块的性能。 优化后的指令执行性能整体提升100%-1000%(没看错,10倍以上)甚至更高,尤其是数值指令整体提升近300%。 而且由于已经提前转换,加载和指令翻译过程更快,卡顿更小。

实现

高级指令优化技术包含了以下优化技术:

  • 彻底的无用栈指令消除。消除掉所有不必要的栈操作
  • 窥孔优化
  • 常量复制优化
  • 局部复制传播优化
  • 全局复制传播优化
  • 函数inline,甚至能inline AOT函数
  • 提供更多instinct指令,大幅提升常见的指令组合性能
  • CheckOnce运行时检查动态消除优化。例如访问静态成员变量的指令,在第2次执行时不再检查类型是否已经初始化过
  • 其他优化

性能

待发布后再测试。

- + \ No newline at end of file diff --git a/docs/business/advancedencryption.html b/docs/business/advancedencryption.html index 9aca774a..0abc1cf0 100644 --- a/docs/business/advancedencryption.html +++ b/docs/business/advancedencryption.html @@ -9,14 +9,14 @@ - +

高级代码加固

高级代码加固是高级指令优化技术的衍生技术。相比标准代码加固更安全可靠。

原理

高级指令优化在优化过程中将IL指令提前转换为寄存器指令,并且做复杂的优化和变换,最后再输出最终的指令。 最终指令与原始IL指令不仅指令集不同,也不存在一一对应关系,无法直接逆向,极大提高了代码安全性。

- + \ No newline at end of file diff --git a/docs/business/basiccodeoptimization.html b/docs/business/basiccodeoptimization.html index 81f1acd7..28570d17 100644 --- a/docs/business/basiccodeoptimization.html +++ b/docs/business/basiccodeoptimization.html @@ -9,13 +9,13 @@ - +

标准指令优化

对常见的代码范式进行谨慎可靠的优化,大幅提升了变量访问(50%-100%)、数值计算(100-300%)、对象访问(50-200%)等常见指令的性能,像一些特殊代码如typeof指令的性能,提升了1000%以上。

实现

由于运行时的时间及内存限制,标准指令优化只做一些无用栈消除、窥孔优化等等简单但较可靠的优化,无法执行一些复杂的优化。但由于IL指令是栈指令,即使只做了一些不复杂的常见优化,性能相比于社区的未优化版本也有巨幅提升。

高级指令优化相对标准指令优化,使用了复杂的离线优化技术,性能大幅优于标准指令优化技术。

性能数据

以下是标准指令优化相比于社区版本的解释性能提升数据(0表示性能持平,n表示提升n倍)。

interpreter_optimization

以下是数值计算方面原生与标准指令优化的性能对比,纵坐标为耗时。标准指令优化的加法大约为原生7-16倍左右,乘法是4倍,除法是2倍。

benchmark_numeric

- + \ No newline at end of file diff --git a/docs/business/basicencryption.html b/docs/business/basicencryption.html index c730d5ab..8fafdc47 100644 --- a/docs/business/basicencryption.html +++ b/docs/business/basicencryption.html @@ -9,7 +9,7 @@ - + @@ -21,7 +21,7 @@ 该字段是一个int类型值,提供了一个默认值,强烈建议开发者修改此值。

每个不同的encryptionSeed会导致HybridCLR/Genrate/EncryptXXX指令生成完全不同的hybridclr代码。

打包流程

  • HybridCLR/Generate/All
  • 使用HybridCLR.Editor.Encryption.DllEncrypter类对 补充元数据dll及热更新dll加密(即使补充元数据dll也需要加密!)
  • 将加密后的dll加入你项目的资源管理系统
  • 其他与社区版本操作完全相同

加密dll

使用HybridCLR.Editor.Encryption.DllEncrypter类对 补充元数据dll及热更新dll加密。 下面代码中EncryptDll演示了 如何加密一个dll,EncryptDllsInDirectory演示了如何加密多个dll。


public static class EncryptDllCommand
{
public static void EncryptDll(string originalDllFile, string encryptedDllFile)
{
int seed = SettingsUtil.EncryptionSeedOrZeroWhileDisable;
if (seed == 0)
{
Debug.LogWarning($"enableEncryption is false or encryptionSeed == 0, encryption is skipped");
return;
}
var encryptor = new DllEncryptor(seed);
byte[] originBytes = File.ReadAllBytes(originalDllFile);
byte[] encryptedBytes = encryptor.EncryptDll(originBytes);
File.WriteAllBytes(encryptedDllFile, encryptedBytes);
}

public static void EncryptDllsInDirectory(string dllDir)
{
int seed = SettingsUtil.EncryptionSeedOrZeroWhileDisable;
if (seed == 0)
{
Debug.LogWarning($"enableEncryption is false or encryptionSeed == 0, encryption is skipped");
return;
}
var encryptor = new DllEncryptor(seed);
foreach (var dllFile in Directory.GetFiles(dllDir, "*.dll.bytes"))
{
byte[] originBytes = File.ReadAllBytes(dllFile);
byte[] encryptedBytes = encryptor.EncryptDll(originBytes);
File.WriteAllBytes(dllFile, encryptedBytes);
Debug.Log($"EncryptDllsInDirectory {dllFile} length:{encryptedBytes.Length}");
}
}
}

运行时加载

与社区版本完全相同,直接调用Assembly.Load或RuntimeApi.LoadMetadataForAOTAssembly加载加密后的dll文件内容。 hybridclr会在内部解密,不需要开发者执行额外的解密操作。

- + \ No newline at end of file diff --git a/docs/business/businesscase.html b/docs/business/businesscase.html index 4ee8089a..327ad21a 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 e0bf3be4..9a5d4cbd 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 ffb5d95f..430d0066 100644 --- a/docs/business/fullgenericsharing.html +++ b/docs/business/fullgenericsharing.html @@ -9,7 +9,7 @@ - + @@ -21,7 +21,7 @@ full generic sharing 机制。启用此选项后,泛型函数的所有泛型实例(无论泛型参数是值类型还是class类型)完全共享一份代码。
  • 2022.3.x LTS版本。强制支持full generic sharing,即使Build Settings中使用faster runtime选项也会开启此机制。与faster(smaller) build区别 在于:faster runtime对于AOT中已经实例化的泛型函数,会使用单独的泛型函数实现,不走完全泛型共享的版本,提升了泛型函数的执行性能; faster(smaller) build选项则迫使同个函数的所有泛型函数都使用一份代码,此时与2021版本的含义相同。
  • - + \ No newline at end of file diff --git a/docs/business/intro.html b/docs/business/intro.html index 12077f24..27d2452c 100644 --- a/docs/business/intro.html +++ b/docs/business/intro.html @@ -9,7 +9,7 @@ - + @@ -17,7 +17,7 @@

    介绍

    我们提供多种高端商业版本及可灵活定制的技术服务,满足游戏项目在各种应用场景下的需求。

    商业化版本

    提示

    所有商业化版本均支持付费获得试用版本。如果最终决定购买,则试用费可以从最终价格中完全抵扣;如果 最终因为非产品因素而放弃购买则不退试用费用。

    目前有三个商业化版本:专业版、旗舰版、热重载版,它们的具体特性对比如下。

    • 专业版。优化了性能、内存,提供更高的代码安全
    • 旗舰版。包含专业版的所有功能,另外包含了我们最核心的DHE技术,极大提升了性能,几乎(未改动时为100%)达到同等的原生AOT水平
    • 热重载版。包含专业版的所有功能,同时支持卸载和重新加载单独的assembly
    特性社区版专业版旗舰版热重载版
    解释执行
    MonoBehaviour
    补充元数据
    增量式GC
    完全泛型共享
    标准指令优化
    标准代码加固
    DHE技术
    热重载
    技术支持

    价格标准

    版本价格(人民币)描述
    社区版0免费
    专业版30k/项目买断一个项目的使用权,同时包含2小时的技术支持,提供1年代码更新
    热重载版邮件咨询商务买断一个项目的使用权,同时包含2年技术支持,提供2年代码更新
    旗舰版邮件咨询商务买断一个项目的使用权,同时包含2年技术支持,提供2年代码更新

    企业技术支持

    可以灵活选择企业所需要的技术服务项目,如果按年订阅则根据服务项计费,否则根据服务时长计费。

    技术支持内容

    • Bug标准响应及解决,包含一对一远程协助指导,大多数可复现bug会在2-7天内修复或者提供规避方案
    • 解决一些特殊的平台兼容性问题
    • 支持一些当前未支持的版本(不含2018及更早的版本)
    • 优化指导
    • 其他服务

    价格标准

    由于hybridclr使用简单、运行稳定,大多数公司并不需要长时间的技术支持,因此只提供计时技术支持服务。 单次服务中未使用完的时间可以保留下次使用。为了节约商务成本,总价2000以内的按时计费不提供合同与发票,敬请谅解。

    服务级别解决问题范围价格
    标准提供基础使用问题的技术答疑,不含bug及未实现特性的解决400人民币/小时
    专家解决技术支持内容里各种复杂问题,包含解决bug及未实现特性2000人民币/小时

    联系我们

    请使用贵公司的公司邮箱向邮箱business@code-philosophy.com发起咨询,以QQ或者126邮箱之类个人发起的邮件会被忽略,敬请谅解。

    - + \ No newline at end of file diff --git a/docs/business/pro/intro.html b/docs/business/pro/intro.html index 4562e264..374a8fce 100644 --- a/docs/business/pro/intro.html +++ b/docs/business/pro/intro.html @@ -9,13 +9,13 @@ - +

    介绍

    专业版本提供了一些社区版本所不支持的高级特性,适合对内存和包体要求较高或者有一定性能压力的场合,如WebGL游戏。

    支持的版本

    支持所有 Unity 2020-2022 LTS版本。

    优势

    • 支持Unity 2021起的il2cpp的full generic sharing技术,值类型也可以泛型共享了,以原生方式执行AOT泛型函数,极大提升了泛型函数的执行性能。不再需要对AOT进行补充元数据,简化了工作流,并且有效降低包体大小,明显降低了内存占用。对WebGL等包体和内存要求严苛的平台尤其有用
    • 优化加载及运行过程中元数据分配,内存占用更小
    • 包含标准指令优化。对常见的代码范式进行谨慎可靠的优化,大幅提升了变量访问(50%-100%)、数值计算(100-300%)、对象访问(50-200%)等常见指令的性能,像一些特殊代码如typeof指令的性能,提升了1000%以上
    • 支持标准代码加固。对IL指令进行预处理,使其无法被ILSpy这样的反编译工具直接破解
    • 更敏捷的维护支持,随时获得最新的代码(社区版本出于维护成本考虑,只会定期发布版本)
    - + \ No newline at end of file diff --git a/docs/business/pro/quickstart.html b/docs/business/pro/quickstart.html index d4209b42..d4e4e9b0 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版本解压对应的libil2cpp-{version}.7z
    • 打开 HybridCLR/Installer,开启从本地复制libil2cpp选项,选中刚才解压的libil2cpp目录,进行安装

    installer

    开启完全泛型共享

    • 2020版本不支持完全泛型共享
    • 2021版本需要设置 IL2CPP Code Generation选项为faster(smaller)
    • 2022版本默认开启完全泛型共享,无法关闭。如果设置 IL2CPP Code Generation选项为faster(smaller)则能进一步减少包体。

    开启和关闭标准指令优化

    默认已经开启标准优化。可以通过 RuntimeApi.EnableTransformOptimization函数主动控制开启或者关闭这个特性。

    - + \ No newline at end of file diff --git a/docs/business/reload/hotreloadassembly.html b/docs/business/reload/hotreloadassembly.html index 72c3e4f3..0b86fbf9 100644 --- a/docs/business/reload/hotreloadassembly.html +++ b/docs/business/reload/hotreloadassembly.html @@ -9,13 +9,13 @@ - +

    热重载技术

    热重载技术用于完全卸载或者重新加载一个assembly,适用于小游戏合集类型的游戏。该方案只提供商业化版本,具体请见热重载版介绍

    支持的特性

    • 支持卸载assembly
    • 支持重新加载assembly,代码基本可以任意变化甚至完全不同(除MonoBehaviour类外)
    • 卸载大部分内存(预计95%以上),但有少量残留(如[ThreadStatic]标记的线程静态成员字段占据的内存)

    待实现,暂未支持的特性

    • 支持限定热更新assembly中能访问的函数的集合,适合UGC游戏中创建沙盒环境,避免恶意玩家代码造成破坏。

    不支持特性及特殊要求

    • 不能在被卸载程序集的代码中卸载自己。
    • 要求业务代码不会再使用被卸载的Assembly中的对象或者函数,并且退出所有在执行的旧逻辑
    • 要求重载的MonoBehaviour中的事件或消息函数如Awake、OnEable之类不发生增删(但函数体可以变化)
    • 要求重载的MonoBehaviour中自定义的Message函数名符合OnXXX形式
    • 要求重载后在旧Assembly中存在同名类的MonoBehaviour类的序列化字段名不发生变化(类型可以变)
    • 不能直接卸载被依赖的Assembly,必须按照逆依赖顺序先卸载依赖者,再卸载被依赖者。例如A依赖B,则需要先卸载B,再卸载A。
    • 由于Unity自身实现的原因,与2022的Jobs不兼容,需要自行小幅修改UnityEngine.CoreModule.dll的代码。 2020-2021仍然可以正常工作。
    • 不支持析构函数,~XXX()。也不允许实例化泛型参数带本程序集类型的带析构函数的泛型类

    一些较佳实践

    • 热重载程序集的MonoBehaviour的类型名上加版本号,一旦特殊函数(如Awake)发生增删或者字段发生增删或改名,则版本号+1
    - + \ No newline at end of file diff --git a/docs/business/reload/intro.html b/docs/business/reload/intro.html index e547d7e1..9e5423fe 100644 --- a/docs/business/reload/intro.html +++ b/docs/business/reload/intro.html @@ -9,13 +9,13 @@ - +

    介绍

    热重载特别版提供独创的热重载技术的支持。可以运行中完全卸载或者重新加载一个assembly,尤其适用于小游戏合集类型的游戏。

    支持的版本

    支持所有 Unity 2021-2022 LTS版本。

    优势

    • 支持Unity 2021起的il2cpp的full generic sharing技术,值类型也可以泛型共享了。不再需要对AOT进行补充元数据,简化了工作流,并且有效降低包体大小,明显降低了内存占用。对WebGL等包体和内存要求严苛的平台尤其有用。
    • 包含标准指令优化。对常见的代码范式进行谨慎可靠的优化,大幅提升了变量访问(50%-100%)、数值计算(100-300%)、对象访问(50-200%)等常见指令的性能,像一些特殊代码如typeof指令的性能,提升了1000%以上
    • 支持标准代码加固。对IL指令进行预处理,使其无法被ILSpy这样的反编译工具直接破解
    • 支持卸载单独的assembly,卸载大部分内存(预计90%以上),但有少量残留(如[ThreadStatic]标记的线程静态成员字段占据的内存)
    • 支持重新加载assembly,代码基本可以任意变化甚至完全不同(除MonoBehaviour类外)。MonoBehaviour及ScriptableObject的热重载。
    • 支持限定热更新assembly中能访问的函数的集合,适合UGC游戏中创建沙盒环境,避免恶意玩家代码造成破坏。
    • 更敏捷的维护支持,随时获得最新的代码(社区版本出于维护成本考虑,只会定期发布版本)。
    • 附含两年的技术支持,快速解决使用过程中遇到的各种问题
    - + \ No newline at end of file diff --git a/docs/business/reload/modifydll.html b/docs/business/reload/modifydll.html index e655d064..50bafa61 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 948300a9..b2b9c21a 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版本解压对应的libil2cpp-{version}.7z
    • 打开 HybridCLR/Installer,开启从本地复制libil2cpp选项,选中刚才解压的libil2cpp目录,进行安装
    • Unity 2022+版本需要使用修改版本的 UnityEngine.CoreModule.dll, 详细见修改dll

    installer

    开启完全泛型共享

    • 2020版本不支持完全泛型共享
    • 2021版本需要设置 IL2CPP Code Generation选项为faster(smaller)
    • 2022版本默认开启完全泛型共享,无法关闭。如果设置 IL2CPP Code Generation选项为faster(smaller)则能进一步减少包体。

    开启和关闭标准指令优化

    默认已经开启标准优化。可以通过 RuntimeApi.EnableTransformOptimization函数主动控制开启或者关闭这个特性。

    代码中使用

    调用 RuntimeApi.UnloadAssembly 卸载程序集,使用Assembly.Load重新加载程序集。当前不支持在未卸载该程序集的情况下再次加载该程序集,示例代码如下:

        // 第一次加载
    Assembly ass = Assembly.Load(yyy);

    // 执行一些代码
    Type mainType = ass.GetType("Entry");
    mainType.GetMethod("Main").Invoke(null, null);

    // 第一次卸载
    RuntimeApi.UnloadAssembly(ass);

    // 第二次加载
    Assembly newAss = Assembly.Load(yyy);

    // 执行一些代码
    Type mainType = ass.GetType("Entry");
    mainType.GetMethod("Main").Invoke(null, null);

    // 第二次卸载
    RuntimeApi.UnloadAssembly(ass);

    注意事项

    • async或者协程很容易隐式地在其他线程保持了对卸载程序集代码的引用,卸载前请务必清理所有异步或者协程函数
    • UI的OnClick或者各种回调事件很容易导致保持了对卸载程序集的引用,一定要清理干净
    • 注册到全局的事件或者其他加高,容易意外保持了对卸载程序集的引用,一定要清理干净
    - + \ No newline at end of file diff --git a/docs/business/ultimate/intro.html b/docs/business/ultimate/intro.html index fc02136c..70b8dfdd 100644 --- a/docs/business/ultimate/intro.html +++ b/docs/business/ultimate/intro.html @@ -9,7 +9,7 @@ - + @@ -18,7 +18,7 @@ 使用无优化或标准指令优化高级指令优化的方式去执行它们。生成dhao文件时,也会将所有变化函数打印出来。DHE的效果 是可提前观测的。

    以接入的某个非常火的SLG游戏为例:在重度战斗的场景下,原生帧率为44;接入社区版本后,帧率降到20多;接入DHE后,未热更新前,帧率甚至略高于原生(测试波动引起); 接入DHE并且做了一定改动热更新后,帧率降到42。这与理论估计是完全相符的。

    与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 06ad8050..560c4f4b 100644 --- a/docs/business/ultimate/manual.html +++ b/docs/business/ultimate/manual.html @@ -9,7 +9,7 @@ - + @@ -28,7 +28,7 @@ 注意此时不能用 HybridCLR/CompileDll/xxx编译的热更新dll替代打包时生成的AOT dll,因为编译是不稳定的,它们未必一样,很可能会导致严重崩溃问题。

    注意事项:

    • 要按照assembly的依赖顺序加载 差分混合执行 assembly。
    • 如果某个程序集未发生改变,dhao字段可以传null,但此时一定要使用打包时生成的AOT dll,而不能使用通过HybridCLR/CompileDll/xxx命令生成的热更新dll。
    • DHE程序集本身已经包含了元数据,即使未开启完全泛型共享时也不要对DHE程序集进行补充元数据,补充了也会失败,其他非DHE的AOT程序集可以照常补充元数据。
    加载DHE程序集
    void InitDifferentialHybridAssembly(string assemblyName)
    {
    // 没有任何热更新时,传递的参数为null。
    byte[] dhaoBytes = needHotUpdate ? GetAssemblyOptionData(assemblyName) : null;
    LoadImageErrCode err = RuntimeApi.LoadDifferentialHybridAssembly(GetAssemblyData(assemblyName), dhaoBytes);
    }

    你可以通过传递 new DifferentialHybridAssemblyOptions() { ForceAllChanged = true } 来强迫hybridclr认为所有函数都发生变化,此时等价于社区版本的全解释模式。由于线上项目并不能很完整地测试各种代码变化的情形,这种方式可以较方便地 检验发生变化后是否各项功能正常。

    打包

    在打包管线中生成AOT dll后运行HybridCLR/CreateAOTDllSnapshot备份AOT文件,并且加入版本管理系统,因为将来热更新生成dhao文件时需要它们。注意!由于裁剪AOT dll生成的不稳定性,千万不要用HybridCLR/Generate/All命令生成的AOT dll。

    由于DHE机制正常工作需要提供dhe程序集,在未发生任何热更新时,DHE程序集等价于打包时生成的AOT 程序集,此时不需要提供dhao文件。尽管这些程序集可以通过热更新下载,强烈推荐随包携带。

    新增DHE相关菜单命令

    菜单项描述
    HybridCLR/CreateAOTDllSnapshot备份AOT dlls到快照目录,将来用于与最新热更新dll对比,生成dhao文件
    HybridCLR/Generate/DHEAssemblyOptionDatas对比最新热更新dll和备份的AOT dll,生成dhao文件
    HybridCLR/Generate/DHEAssemblyOptionDatas_NoChange无论热更新dll是否发生变化,强行生成表示没有任何代码变化的dhao文件。由于LoadDifferentialHybridAssembly的dhaoOption参数可以取null表示这是首包,没有任何变化,所以一般不使用这个菜单项

    随包携带DHE程序集

    如果想随包携带DHE程序集对应的AOT dll,根据你的BuildTarget:

    • iOS。新增IPostprocessBuildWithReport处理类,在OnPostprocessBuild函数中复制 {proj}/HybridCLRData/AssembliesPostIl2CppStrip/{buildTarget}下的DHE dll到StreamingAssets目录(或子目录)。也可以手动在导出工程后复制
    • Android。如果你是先导出gradle工程再打包,则跟iOS相同。如果是直接出APK包,则新增 IPostGenerateGradleAndroidProject处理类,在OnPostGenerateGradleAndroidProject事件中复制生成的DHE AOT程序集到gradle工程
    打包流程中复制DHE程序集

    // iOS或者Android导出工程后,复制文件到工程
    public class CopyDHEAOTDllsToProject : IPostprocessBuildWithReport
    {
    public int callbackOrder => 0;

    public void OnPostprocessBuild(BuildReport report)
    {
    BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
    YourCopyDHEAssembliesToStreamingAssetsOrAssetBundle();
    }
    }

    /// 生成Gradle工程后,复制需要的文件
    public class CopyDHEAOTDllsToAndroidProject : IPostGenerateGradleAndroidProject
    {
    public int callbackOrder => 0;

    public void OnPostGenerateGradleAndroidProject(string path)
    {
    BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
    YourCopyDHEAssembliesToStreamingAssetsOrAssetBundle();
    }
    }

    警告

    如果打包使用 development build 选项,请一定要对应使用HybridCLR/CompileDll/ActivedBuildTarget_Development编译Development模式的热更新dll,否则对比结果为几乎所有函数都被判定为发生变化。

    热更新

    • 使用 HybridCLR/CompileDll/ActivedBuildTarget 生成热更新dll。
    • 确保之前已经运行运行HybridCLR/CreateAOTDllSnapshot备份AOT文件,确保备份目录下的AOT dll为打包时生成的AOT dll。
    • 使用 HybridCLR/generate/DHEAssemblyOptionDatas 生成dhao文件。
    警告

    由于 DHEAssemblyOptionDatas 的工作原理是对比最新热更新DHE dll与原始AOT dll的备份目录的AOT dll,生成变化的函数及类型信息。请一定一定要确保热更新dll和备份 的AOT dll的正确性!

    注意事项

    外部dll引发的计算dhao的结果有巨量差异

    如果有外部dll被标记为DHE程序集,由于外部dll打包时会被裁剪,而计算dhao文件时,取的是原始的外部dll,导致产生巨量的差异,这不是所期望的。解决办法有几个:

    1. 在link.xml里<assembly fullname="YourExternDll" preserve="all"/> 完全保留外部dll
    2. 不用最新的热更新dll去计算差异,而是使用最新代码重新打包时生成的aot dll去计算差异。这个需要你自己修改计算dhao相关的代码,使用AssembliesPostIl2CppStrip目录跟AOTDllSnapshot目录对比。
    - + \ No newline at end of file diff --git a/docs/business/ultimate/quickstart.html b/docs/business/ultimate/quickstart.html index c6f344d7..d7bd3737 100644 --- a/docs/business/ultimate/quickstart.html +++ b/docs/business/ultimate/quickstart.html @@ -9,7 +9,7 @@ - + @@ -17,7 +17,7 @@

    快速上手

    本教程引导从空项目开始体验HybridCLR热更新。出于简化起见,只演示BuildTarget为WindowsMacOS Standalone平台的情况。 请在Standalone平台上正确跑通热更新流程后再自行尝试Android、iOS平台的热更新,它们的流程非常相似。

    旗舰版本使用难度跟社区版本相似,大多数原理相同,建议先熟悉社区版本后再尝试旗舰版本。

    体验目标

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

    准备环境

    安装Unity

    警告

    旗舰版本不支持2019.4.x系列。

    • 安装 2020.3.26+、 2021.3.0+、2022.3.0+ 中任一版本。也支持2020.3.0-2020.3.25版本,但在Installer中完成安装后,需要额外从2020.3.26+任一版本的安装目录复制2020.3.x/Editor/Data/il2cpp/external替换 {project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp/external
    • 根据你所用的操作系统,安装过程中选择模块时,必须选中 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热更新项目

    从零开始构造热更新项目的过程较冗长,项目结构及资源及代码均可参考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的程序集模块

    安装和配置HybridCLR

    安装

    • 将hybridclr_unity.zip解压后,放到项目Packages目录下,改名为com.code-philosophy.hybridclr
    • 根据你的unity版本解压对应的libil2cpp-{version}.7z
    • 打开 HybridCLR/Installer,启用从本地复制libil2cpp选项,选中刚才解压的libil2cpp目录,进行安装
    • 根据你的Unity版本将 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+)。如果没有你的版本,联系我们制作一个

    installer

    配置HybridCLR

    • 打开菜单 HybridCLR/Settings
    • differentialHybridAssemblies列表中添加HotUpdate程序集

    settings

    配置PlayerSettings

    • 关闭增量式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");
    }
    }

    加载热更新程序集

    为了简化演示,我们不通过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 = 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");
    string dhaoPath = $"{Application.streamingAssetsPath}/{assName}.dhao.bytes";
    byte[] dhaoBytes = File.Exists(dhaoPath) ? File.ReadAllBytes(dhaoPath) : null;
    LoadImageErrorCode err = RuntimeApi.LoadDifferentialHybridAssembly(dllBytes, dhaoBytes, true);
    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,选择输出目录Release-Win64,打包工程。
    • 运行菜单 HybridCLR/CreateAOTDllSnapshot这一步不可遗漏!!!
    • {proj}/HybridCLRData/AOTDllOutput/StandaloneWindows64/HotUpdate.dll(MacOS下为StandaloneMacXxx)复制到XXX_Data/StreamingAssets/HotUpdate.dll.bytes
    • 运行Release-Win64/Xxx.exe,屏幕显示 'Hello,HybridCLR',表示热更新代码被顺利执行!

    测试热更新

    • 修改Assets/HotUpdate/Hello.cs的Run函数中Debug.Log("Hello, HybridCLR");代码,改成Debug.Log("Hello, World");
    • 运行菜单命令HybridCLR/CompileDll/ActiveBulidTarget重新编译热更新代码。
    • 运行HybridCLR/Generate/DHEAssmeblyOptionData 生成 dhao数据。
    • {proj}/HybridCLRData/HotUpdateDlls/StandaloneWindows64/HotUpdate.dll复制替换XXX_Data/StreamingAssets/HotUpdate.dll.bytes
    • {proj}/HybridCLRData/DifferentialHybridOptionDatas/HotUpdate.dhao.bytes复制为XXX_Data/StreamingAssets/HotUpdate.dhao.bytes
    • 重新运行程序,会发现屏幕中显示Hello, World,表示热更新代码生效了!
    警告

    没有发生任何热更新时,使用原始AOT dll,来自{proj}/HybridCLRData/AOTDllOutput/{target}目录。 发生热更新时,使用最新的热更新dll,来自{proj}/HybridCLRData/HotUpdateDlls/{target}目录。

    至此完成热更新体验!!!

    - + \ No newline at end of file diff --git a/docs/help.html b/docs/help.html index 596c80f0..c404aad3 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 a47efe53..33bcb7b1 100644 --- a/docs/help/commonerrors.html +++ b/docs/help/commonerrors.html @@ -9,7 +9,7 @@ - + @@ -25,7 +25,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的加载之类的操作。

    使用 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类!居然没报错。 导致出现桥接函数缺失的问题。

    原始代码如下。


    public class NetworkPlayer : NetworkBehaviour
    {

    public static string msgFromHost;
    public static string msgFromClient;


    [ClientRpc]
    public void SendMsgClientRpc(string msgFromHost)
    {
    NetworkPlayer.msgFromHost = msgFromHost;
    }


    [ServerRpc]
    public void SendMsgServerRpc(string msgFromClient)
    {
    NetworkPlayer.msgFromClient = msgFromClient;
    }
    }

    打包时生成的代码添加了几个函数,如下。

    public class NetworkPlayer : NetworkBehaviour
    {
    public static string msgFromHost;

    public static string msgFromClient;

    [ClientRpc]
    public void SendMsgClientRpc(string msgFromHost)
    {
    // ...
    }

    [ServerRpc]
    public void SendMsgServerRpc(string msgFromClient)
    {
    // ...
    }

    static NetworkPlayer()
    {
    // NetworkManager.__rpc_func_table 在自己的代码里是无法访问的!因为它是internal
    NetworkManager.__rpc_func_table.Add(3066788814u, __rpc_handler_3066788814);
    NetworkManager.__rpc_func_table.Add(901396020u, __rpc_handler_901396020);
    }

    private static void __rpc_handler_3066788814(NetworkBehaviour target, FastBufferReader reader, __RpcParams rpcParams)
    {
    // ...
    }

    private static void __rpc_handler_901396020(NetworkBehaviour target, FastBufferReader reader, __RpcParams rpcParams)
    {
    // ...
    }

    internal override string __getTypeName()
    {
    return "NetworkPlayer";
    }
    }

    解决办法为你在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 ce29f463..9bb060ce 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吗?

    支持,最大支持255个。但是不会自动加载依赖dll。需要你手动按依赖顺序加载热更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 badbd402..ff210ec8 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 acdc0980..183d8ddb 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全系列LTS版本。2023.2.0ax版本也已支持,但未对外发布。
    • 支持所有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 1ee02f8d..68d07608 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 e96c5281..9c2e39f0 100644 --- a/docs/other/businesscase.html +++ b/docs/other/businesscase.html @@ -9,13 +9,13 @@ - +

    已上线项目

    目前已经上线的商业项目有数百个,已经接入的则有上千个甚至更多,其中绝大多数头部公司都已经接入HybridCLR。 HybridCLR目前已经被广泛验证是非常高效、稳定的Unity热更新解决方案。

    虽然业内使用HybridCLR并已经上线(AppStore或GooglePlay)的项目有数百之多,但考虑到大多数人比较关心头部公司的项目, 我们在下面罗列了部分已经接入HybridCLR的头部公司。

    由于我们一般不向客户收集项目信息, 再加上头部公司项目周期长,所以已知的上线的较少。

    提示

    以下罗列的都是已经有项目接入HybridCLR的公司。

    公司上线项目1上线项目2上线项目3
    腾讯
    网易瑶台
    百度希壤
    funplusBingo Aloha
    吉比特奇葩战斗家
    散爆少女前线2流浪地球
    朝夕光年(字节)
    叠纸
    莉莉丝
    bilibili
    智明星通
    babybus
    竞技世界
    游族
    巨人
    盛趣游戏
    完美
    畅游
    IGG
    紫龙游戏
    英雄互娱
    - + \ No newline at end of file diff --git a/docs/other/changelog.html b/docs/other/changelog.html index 37fbbefd..993d69a2 100644 --- a/docs/other/changelog.html +++ b/docs/other/changelog.html @@ -9,13 +9,13 @@ - +

    改动日志

    此文档只记录关键性事件,更具体的发布日志请看 RELEASELOG

    • 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 06397d9e..fcec30b9 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 bd014c78..bdddc503 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 479621ea..49e6c729 100644 --- a/docs/other/relativepojects.html +++ b/docs/other/relativepojects.html @@ -9,14 +9,14 @@ - +

    相关的第三方项目

    - + \ No newline at end of file diff --git a/docs/other/roadmap.html b/docs/other/roadmap.html index 84e0bba5..cd4f663d 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 652ed227..94a4ef53 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 5cb5ae24..f84ba352 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 a6d615aa..8dd07c90 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 16301bdc..2b20273f 100644 --- a/en/404.html +++ b/en/404.html @@ -9,13 +9,13 @@ - +

    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/images/install-c1f84913c18dfa0406fc90db65085a56.jpg b/en/assets/images/install-c1f84913c18dfa0406fc90db65085a56.jpg deleted file mode 100644 index 4a04f1856b5f90ac6d91b590cfa5784d383c3121..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25485 zcmeFYcU)7?wl^F^M5HUdMT&sZr1u2mhk%HHNN*~=i?q-Z1OcfL5Kuq@(z|r&B27ek z?+|(k5NaTWC!Sl*J@?+v`#$gc*L|MPnPfhnoxNu!vu3SXd(C&Pbvbpp1fYBLQ0pOp zgoFg}ocIA;E(3VfAx`!HfQ}B}9smHiPJDI~KuWwKA%6Z=E@uI1fGZ>KivGsfdM~f`*owissMtpF#d~|I>^3rMgaj z{ZEVkZ{_kkfc`pQid2-8ga>eio`jU1q%j|`p}8X23In%UUe**iEoIeU5g`1(Qo z1H#^hM?}7hicU=WkerhG@l)EDoZP(pg2JNWs_L5By83SojXyd%ySjUR_Wl|f9UGsR zoSL3lMy{-`t#52@ZKDs5j!#a{Fy|M4=pq4-{tH?EPT7B=i=If=6(SF=lK-KLO5%}05`6PAMA%AKS=O^wAvTI3)1X?>4#Lp`i%0*~|9m?LhaTms&{df|5kx+umD zJzHBGX^lJ>h&kBM!~jZX>fhB4($sfS-~L3We66jD$#0z09fD-}joBXhp=XyLCUQGbsbZdPQ?#qlMe z>5QXIKk}^gXl-%VnsS4nKE8hm5T^+A!4)b1!^e)6$2kfw0q@_on7nWMbd}b^csLFG zW07PLce@!A=ehj(k&xpt#lFo@e8uhX^)QpKmC>I3N}~)3R==7&zcoLFODg{5(770y z$$2ny6NHc6nJx%I51=cxIyx!X@Y2*HKxbjqnI=&Mrt!8b7ID;<4KSLpa@QEFT?i`h~;_zX02tvZ`IYcn!NQ#!Y2HP}$eXaNU!rmD$@`ac{7<>%+ z627ZTxkBXT(iIr-9u$FaKHF!--99iZk3sf}JsGZ5w6y84q8tj+#pj)#dig2OIPtjH zT$2$Nz^b1fC4GI_R6!BMb08Xy4%BEJQ4(`PicywLROYdY*0M)@vel-@loJ!lk6@12 zaeUpS@nN99W>4)ls0F--Z$Dv!Eno)|VPAtJ;pa>jC{jGg%8x^A9m16l-vv^=L0}$Y zHW2lFQ?`VR$)SboctnsmaxmO)(s(AgJf_^4i)CENpdfs#TvRNLlAU*8vBjO4Y{+cq{=zj=#Pji6i)$&+knJc2g)cm$Tt z_dD3pq&x66E!A1{p4uG`qV-RWpL&|JKQIrfuFDy9$eA&?EV;4H*D#)_WEBLvVLo34Z(%(5itMRtK;fV~K(p1V`sM zUjlq!`w0XoeU)qf9Pyv)_>W@tKUjLUo6Qd*cG?kC%>u|MX`L&FlU6et<|8Tz-mGn zTKhB+^9E_jY>9F4&&I@b2E-Q>3<`9LT%iyM4gAC<(t8KlxW;;In$gz{rNZ?B`!EPF z0kdn1n>h=?Y4E3emSX&VnSXNq*zbxodHX%W`({vyM0|!@kq_;3dXH}_|3M~EX=hhfO($z}HP$p6_H&d>hXGQ#1 z0Zqs4oHG z4nVq6LLv|sDH13egA7E_Sk}x0_m9VpsPSFX_B}Me**WyHO%!UtPJHpdF^<7WUIG#> z1_=$lmw+#;6;GWQX@CC$<%h`Dh_T3gtTdSmF5XdPem_6&)aLBywjb;IUHm$Q(Uc~! zbFBwtc4e<&MK^j9DjI)y^l&^cN*J~JwIPK<3i~YFjW^=1+NYq}dR5lGJ;x)Qjs-BS zY1~hAec+nE94oj$igie4ey6Ei@&q_Uct$b>saSD{R`Lt<_bi7bb+XlV=Xsn8^@@q* zrlR!rw1VpIWYkzW=ca3tzaDLtMR#jeeGPg zTgQ)K%#fT%az(#ZP2Kbyd^2)ZxDChO=i?(EW)J1!X_v!+K<@{n$yI zx!#LlnS8drtz4ip>?A2!^b*kGR=Jl9wLhG6EX>73m4Kr>q$^N5)>v~riM)BNMca(~ z+u}ZfkIQG;Eq4;kUvavS59;n$Y-oJU!K7wp43VoW(nL$bMY1~1 znN~k(2MNVsev!)H#rX>9vSPvzNMcgjP4oiW&2_K2b;vSrWY2yqKhaHj+R*yfYc+1c zgz_ckJu$3dXX$3|H-+RA@@S>;g^~t8nOBm%-+S@$Sv0GJp;npTYx~+BERejK7WMqZ z_1;?fpr$a;zT7u*7mqpY*!!xz4Mun{TF7FI=Wuo6q~zV%On>Fxs`taXSatUfuZVfN z*CqHaa{FZq^k-7Hg{H<8us)v1j7*sUPUusTjcjE>(pogQXOR^$t7qPC)72_4A-YnN z(y8NL)TfmBSLOLr)z$|+eV&I^Htq|Ua&uGy(ZG9#J0@HZy#2TEC%DZkZqM*-j~eT( zlgqgirNQ&ZTU;&y zFa%Dh_-GWnb3VLLa5j0AMPvMrMFV&HVs>Ig6bLhiti0b2 zr(}*eo+h!_T~~F_syxY}WgWr^1-q1({T?xw=uzPCfG&pn2QlvXigK+>5k&qD}qaO6PUKy-;ov88AqGaHDrhm z4R_pT?Y3l~u`u4UeiI~5i{d^&bj*Z*hIeY033N`5xIpVChT9vXEG1KZ_e0u z`(}9OJivd#c~tCJu~pU%t;#|Ls~W~@KFI26Y^ahWHtJXisL1KHAN9B+>wV)*1j;=B z9oW9gd*1x%Xb^bKv?qJpIKZ`1i|P!dH?IiF;){Cv?AT%!Qs>k2+UCYFQB<0cfd4fnjm8fNILA9e@6`d&m(JGJ74VvV z=>8+vHO!|9;Y4|N?&xlpxG0{8xT<1^-R+{i>%6ze;ZcaHvre(Ffbwhyq}a8cG#AHj zM7z}f2vv4OL{YTfvdqe_WV)bWVP?kh&5CIA9=}lx7pFky0@K)w5m#Z9&ul;Qk25h3 z8|B|mrPCypXKPzB4;l`m?>~R#_3RSh1m<*JtO0&seAoDv_ZeC@GDx|ghaZllR_v11 z$5-A-Vft2^$rLJBKrgl3<@>PjW@lW>n=IPF5U)!>uq!SY)C~O9r27_Fjfe+eTXF;QT_#i~GoB-PnAtDeX2v)ku=_y-Vi{Zqv6|4NFB>ASW{xRUB}i+Lw1d&bFN*35|+?iNJSe8q;I zf`6&7Jy$@U{pS)J;7B*hx^km)|1B2>VG1o9Dvbx~N(l3;BS}J0XU19Q@E2pF$MC61 zWL@<#k*kr*XED?%(LA!z7dGv@9_I#q;i*AWR@TquI9)b_1@uQx7N_^S&A{OVHq2~? zf>`99u#$Pn0wl&&ZV*Olyw0wv1jpD-udw*;;+;aw*+SjL%fq3R|x+t`$}o2 z<#E(%*bi5d_g#bR{^g3NW0<&QOAn&I;Vn)m3zB3q;nB>0XfjXnHp-P?=6YnyXaArh z+p6{`oeH+dF0Z!(|1B-lAl6bJjK@aJ4W49CUXbI68n`@p`M~&_Bk*vL&}baluEI(F zpY-y_Nk2CCBF?VnIL=N{IL9xQWZjj+$xk>TFxqPR!v8gFvq5M?8hBDeG6~r@{eTHa z3t3i=)$X%FQlGYrE(T`|7Cmlz2|n&;Og12pRK3OLC~IU88WL^ zTt95ixb~bGWX=B3_$JMo!E__g$`jWP3}+x2vkLM==a;yYtNS1?0XOHxqGVvsxfikh z_=0MCrTui5;Py!$MH9?25*4`I4-eaG)++8>c9VZ*wq@_jG|ZEl%<%Bq0^jGs->`R= z0PmIL!NYk>z;M&vC7|Y2E77m<8|qCmF^z+u>(Y^*Wi7(G(hgk|9_1Pte>sUw(R^QX zTTsdEuooU%H|d;&Y(tT+z~f6CTSd`E)kwizzu-*8*?ONC-+7GD(~=CDPuLg>PdVVm zEKXxxut)9!+6*5ExoN$Knk$_(G+4*4U%~%g#V->E`^?*Dp#qrnWl$}D-ZG5N#fo!` z5B|&Lv*?A3(~SUeqX&!0FUeji0Q(LI-XVLGaHOI}!*Lc3wv%{x|N;^`-7Cu}QC z=w1Pi)XwvsA0uhvlEfRF%0|LD@aHfhofEUNGZLa``~S)%LuHMXg18eC=|cA)OJrbS zkZv{BOukm$9II{r+y4?EGl31;cQ4ob#@X|jz)%Cw*YLRnIJ#SVZr^>7!U|YnuZ~}+ z0epD6`z^J+dh~r0v6z;^%lO zem;3`=&Q`uds(@jUk!Gwa;&@)t%+zj$|oO_x-!`16b3O25N8ZJLtoPqnm@K5nRp2MA-}eYV;ZCerz1P+M>{+{ko!OrQ>Tm6&*X_u5cyX`*7a2Q z**{<@r z(M3f;3gc2bLb@ps_nR9=jV_wU>$MpcKlM~ph3akgnduX14#i_9$Bzi3ne-zt#}=0uI~cb7YTb==;YO&B3^hnd5@3{_^C-$KctLh(kGPM=AcQ!N?|JW3H?sl z^`qRa)y=Knf*<+SS}IJiUNz8uZ9yxd^!;5SLS{pM?2VQeR$|9`Rw1}H?li2-sj)7- z$a>n@mzk_|�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 y?As*Up&9rc)#%qr{i.d(t,{Zo:()=>c,kt:()=>m});var n=i(7294);function l(e,t,i){return t in e?Object.defineProperty(e,t,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[t]=i,e}function a(e,t){var i=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),i.push.apply(i,n)}return i}function o(e){for(var t=1;t=0||(l[i]=e[i]);return l}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,i)&&(l[i]=e[i])}return l}var s=n.createContext({}),p=function(e){var t=n.useContext(s),i=t;return e&&(i="function"==typeof e?e(t):o(o({},t),e)),i},c=function(e){var t=p(e.components);return n.createElement(s.Provider,{value:t},e.children)},d="mdxType",h={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},u=n.forwardRef((function(e,t){var i=e.components,l=e.mdxType,a=e.originalType,s=e.parentName,c=r(e,["components","mdxType","originalType","parentName"]),d=p(i),u=l,m=d["".concat(s,".").concat(u)]||d[u]||h[u]||a;return i?n.createElement(m,o(o({ref:t},c),{},{components:i})):n.createElement(m,o({ref:t},c))}));function m(e,t){var i=arguments,l=t&&t.mdxType;if("string"==typeof e||l){var a=i.length,o=new Array(a);o[0]=u;var r={};for(var s in t)hasOwnProperty.call(t,s)&&(r[s]=t[s]);r.originalType=e,r[d]="string"==typeof e?e:l,o[1]=r;for(var p=2;p{i.r(t),i.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>h,frontMatter:()=>a,metadata:()=>r,toc:()=>p});var n=i(7462),l=(i(7294),i(3905));const a={},o="Install",r={unversionedId:"basic/install",id:"basic/install",title:"Install",description:"Install a compatible Unity version",source:"@site/i18n/en/docusaurus-plugin-content-docs/current/basic/install.md",sourceDirName:"basic",slug:"/basic/install",permalink:"/en/docs/basic/install",draft:!1,tags:[],version:"current",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"Supported Unity versions and platforms",permalink:"/en/docs/basic/supportedplatformanduniyversion"},next:{title:"Settings",permalink:"/en/docs/basic/projectsettings"}},s={},p=[{value:"Install a compatible Unity version",id:"install-a-compatible-unity-version",level:2},{value:"Install IDE and related tools",id:"install-ide-and-related-tools",level:2},{value:"Select com.code-philosophy.hybridclr version",id:"select-comcode-philosophyhybridclr-version",level:3},{value:"Install the com.code-philosophy.hybridclr package",id:"install-the-comcode-philosophyhybridclr-package",level:2},{value:"Install from git url",id:"install-from-git-url",level:3},{value:"Install from openupm",id:"install-from-openupm",level:3},{value:"Install from local files",id:"install-from-local-files",level:3},{value:"Update com.code-philosophy.hybridclr",id:"update-comcode-philosophyhybridclr",level:2},{value:"Initialize HybridCLR",id:"initialize-hybridclr",level:2},{value:"If your version >= v2.0.5",id:"if-your-version--v205",level:3},{value:"If your version >= 1.1.20",id:"if-your-version--1120",level:3},{value:"If your package version <= 1.1.19",id:"if-your-package-version--1119",level:3},{value:"Special handling after installation",id:"special-handling-after-installation",level:2},{value:"WebGL Platform",id:"webgl-platform",level:3},{value:"Unity 2019",id:"unity-2019",level:3},{value:"Using HybridCLR in non-compatible versions of Unity",id:"using-hybridclr-in-non-compatible-versions-of-unity",level:2},{value:"How HybridCLR/Installer works",id:"how-hybridclrinstaller-works",level:2},{value:"Replace libil2cpp code",id:"replace-libil2cpp-code",level:3},{value:"Local installation",id:"local-installation",level:3},{value:"Global installation",id:"global-installation",level:3},{value:"Precautions",id:"precautions",level:2}],c={toc:p},d="wrapper";function h(e){let{components:t,...a}=e;return(0,l.kt)(d,(0,n.Z)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,l.kt)("h1",{id:"install"},"Install"),(0,l.kt)("h2",{id:"install-a-compatible-unity-version"},"Install a compatible Unity version"),(0,l.kt)("p",null,"Any version of 2019.4.x, 2020.3.x, 2021.3.x, or 2022.3.x is supported. It is recommended to install versions 2019.4.40, 2020.3.26+, 2021.3.x, and 2022.3.x."),(0,l.kt)("admonition",{type:"tip"},(0,l.kt)("p",{parentName:"admonition"},"If your version is 2019.4.0-2019.4.39, ",(0,l.kt)("strong",{parentName:"p"},"Need to switch to 2019.4.40 to complete HybridCLR installation, and then switch back to the current version"),"."),(0,l.kt)("p",{parentName:"admonition"},"If your version is 2020.3.0-2020.3.25, after completing the installation in Installer, copy ",(0,l.kt)("inlineCode",{parentName:"p"},"2020.3.x/Editor/Data/il2cpp/external")," from the installation directory of any version 2020.3.26+ to replace\n",(0,l.kt)("inlineCode",{parentName:"p"},"{project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp/external"),".")),(0,l.kt)("admonition",{type:"caution"},(0,l.kt)("p",{parentName:"admonition"},"If you are not an experienced Unity developer, it is recommended to use version 2021.3.1 to experience HybridCLR first.")),(0,l.kt)("p",null,"According to the target platform you packaged, select the necessary modules during the installation process. If you package Android or iOS, just select the corresponding module directly. If you want to package Standalone, you must additionally select ",(0,l.kt)("inlineCode",{parentName:"p"},"Windows Build Support(IL2CPP)")," or ",(0,l.kt)("inlineCode",{parentName:"p"},"Mac Build Support(IL2CPP)"),"."),(0,l.kt)("p",null,(0,l.kt)("img",{alt:"select il2cpp modules",src:i(8581).Z,width:"721",height:"507"})),(0,l.kt)("h2",{id:"install-ide-and-related-tools"},"Install IDE and related tools"),(0,l.kt)("p",null,"-Windows"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"Under Win, you need to install ",(0,l.kt)("inlineCode",{parentName:"li"},"visual studio 2019")," or later. The installation must include at least the ",(0,l.kt)("inlineCode",{parentName:"li"},"Game Development with Unity")," and ",(0,l.kt)("inlineCode",{parentName:"li"},"Game Development with C++")," components."),(0,l.kt)("li",{parentName:"ul"},"install git\n-Mac"),(0,l.kt)("li",{parentName:"ul"},"Requires MacOS version >= 12, xcode version >= 13, for example ",(0,l.kt)("inlineCode",{parentName:"li"},"xcode 13.4.1, macos 12.4"),"."),(0,l.kt)("li",{parentName:"ul"},"install git"),(0,l.kt)("li",{parentName:"ul"},"install cmake")),(0,l.kt)("h3",{id:"select-comcode-philosophyhybridclr-version"},"Select ",(0,l.kt)("inlineCode",{parentName:"h3"},"com.code-philosophy.hybridclr")," version"),(0,l.kt)("admonition",{type:"caution"},(0,l.kt)("p",{parentName:"admonition"},"Before v3.0.0 the package name was ",(0,l.kt)("inlineCode",{parentName:"p"},"com.focus-creative-games.hybridclr_unity"),".")),(0,l.kt)("p",null,"These versions currently exist: ",(0,l.kt)("inlineCode",{parentName:"p"},"1.0")," branch, ",(0,l.kt)("inlineCode",{parentName:"p"},"v2.x.y"),", ",(0,l.kt)("inlineCode",{parentName:"p"},"v3.x.y"),", ",(0,l.kt)("inlineCode",{parentName:"p"},"v.4.x.y")," (also current main branch) ."),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"The ",(0,l.kt)("inlineCode",{parentName:"li"},"1.0")," branch is too old, although the work is stable, but the Package-related workflow is relatively old, not as convenient as subsequent versions, and maintenance has been stopped, it is strongly recommended not to use it again"),(0,l.kt)("li",{parentName:"ul"},"The workflow of ",(0,l.kt)("inlineCode",{parentName:"li"},"v2.x.y")," versions is well optimized and verified by a large number of projects. It is recommended to use the Unity 2019 version or the projects that will be launched soon"),(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("inlineCode",{parentName:"li"},"v3.x.y")," versions ",(0,l.kt)("strong",{parentName:"li"},"removed support for Unity 2019"),", added support for Unity 2022 version. It is recommended to use Unity 2020+ version or projects that will be launched soon"),(0,l.kt)("li",{parentName:"ul"},"The ",(0,l.kt)("inlineCode",{parentName:"li"},"v4.x.y")," versions supports incremental GC and fully supports all platforms. Since it has just been released, it will take a few weeks to stabilize. It is recommended for projects in the early or middle stages of the project.")),(0,l.kt)("admonition",{type:"tip"},(0,l.kt)("p",{parentName:"admonition"},"The versions of these three series are very stable, so there is no need to worry about which one is better. Generally speaking, the newer the version, the more optimizations and the better the user experience.")),(0,l.kt)("h2",{id:"install-the-comcode-philosophyhybridclr-package"},"Install the ",(0,l.kt)("inlineCode",{parentName:"h2"},"com.code-philosophy.hybridclr")," package"),(0,l.kt)("p",null,"The warehouse address is ",(0,l.kt)("a",{parentName:"p",href:"https://github.com/focus-creative-games/hybridclr_unity"},"github"),", and the domestic fast mirror warehouse is ",(0,l.kt)("a",{parentName:"p",href:"https://gitee.com/focus-creative-games/hybridclr_unity"},"gitee")," ."),(0,l.kt)("p",null,"There are three installation methods:"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"Install from git url using Package Manager"),(0,l.kt)("li",{parentName:"ul"},"Install from openupm using Package Manager"),(0,l.kt)("li",{parentName:"ul"},"local installation")),(0,l.kt)("h3",{id:"install-from-git-url"},"Install from git url"),(0,l.kt)("p",null,"Click ",(0,l.kt)("inlineCode",{parentName:"p"},"Windows/Package Manager")," in the main menu to open the package manager. Click ",(0,l.kt)("inlineCode",{parentName:"p"},"Add package from git URL...")," as shown below, fill in ",(0,l.kt)("inlineCode",{parentName:"p"},"https://gitee.com/focus-creative-games/hybridclr_unity.git")," or ",(0,l.kt)("inlineCode",{parentName:"p"},"https://github.com/focus-creative -games/hybridclr_unity.git"),"."),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"The main branch address is ",(0,l.kt)("inlineCode",{parentName:"li"},"https://gitee.com/focus-creative-games/hybridclr_unity.git")),(0,l.kt)("li",{parentName:"ul"},"Other tag version addresses are ",(0,l.kt)("inlineCode",{parentName:"li"},"https://gitee.com/focus-creative-games/hybridclr_unity.git#{tag}"))),(0,l.kt)("p",null,"If you want to install a certain branch or tag version, please add ",(0,l.kt)("inlineCode",{parentName:"p"},"#{tag}")," after the address, such as ",(0,l.kt)("inlineCode",{parentName:"p"},"https://gitee.com/focus-creative-games/hybridclr_unity.git#v3.0.1"),"."),(0,l.kt)("p",null,(0,l.kt)("img",{alt:"add package",src:i(5283).Z,width:"808",height:"223"})),(0,l.kt)("p",null,"If you are not familiar with installing packages from url, please see ",(0,l.kt)("a",{parentName:"p",href:"https://docs.unity3d.com/Manual/upm-ui-giturl.html"},"install from giturl"),"."),(0,l.kt)("h3",{id:"install-from-openupm"},"Install from openupm"),(0,l.kt)("p",null,"openump address ",(0,l.kt)("a",{parentName:"p",href:"https://openupm.com/packages/com.focus-creative-games.hybridclr_unity/"},"com.focus-creative-games.hybridclr_unity"),"."),(0,l.kt)("p",null,"For the specific installation method, please open this link and view the detailed installation instructions on the page."),(0,l.kt)("h3",{id:"install-from-local-files"},"Install from local files"),(0,l.kt)("p",null,"After cloning the warehouse locally, rename the directory to ",(0,l.kt)("inlineCode",{parentName:"p"},"com.code-philosophy.hybridclr")," (for versions before v3.0.0, please use ",(0,l.kt)("inlineCode",{parentName:"p"},"com.focus-creative-games.hybridclr_unity"),"), and then directly move to the Packages directory of the project. Can."),(0,l.kt)("h2",{id:"update-comcode-philosophyhybridclr"},"Update com.code-philosophy.hybridclr"),(0,l.kt)("p",null,"After updating com.code-philosophy.hybridclr, you need to re-run ",(0,l.kt)("inlineCode",{parentName:"p"},"HybridCLR/Installer"),"."),(0,l.kt)("h2",{id:"initialize-hybridclr"},"Initialize HybridCLR"),(0,l.kt)("p",null,"In order to reduce the size of the package itself, some files need to be copied from the Unity Editor installation directory. Therefore, after installing the plug-in, an additional initialization process is required."),(0,l.kt)("p",null,"Click the menu ",(0,l.kt)("inlineCode",{parentName:"p"},"HybridCLR/Installer...")," to pop up the installation interface. Some setup may be required before clicking install. Since the Installer has been adjusted as the version changes, please read the corresponding instructions below according to your current version."),(0,l.kt)("h3",{id:"if-your-version--v205"},"If your version >= v2.0.5"),(0,l.kt)("p",null,"The branch or tag compatible with hybridclr and il2cpp_plus corresponding to the current package version has been configured in the ",(0,l.kt)("inlineCode",{parentName:"p"},"Data~/hybridclr_version.json")," file in com.code-philosophy.hybridclr.\nThe Installer will install the version specified in the configuration, and no longer supports customizing the version to be installed."),(0,l.kt)("p",null,"The configuration looks like this:"),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre",className:"language-json"},'{\n "versions": [\n {\n "unity_version": "2019",\n "hybridclr" : { "branch": "v2.0.1"},\n "il2cpp_plus": { "branch": "v2019-2.0.1"}\n },\n {\n "unity_version": "2020",\n "hybridclr" : { "branch": "v2.0.1"},\n "il2cpp_plus": { "branch": "v2020-2.0.1"}\n },\n {\n "unity_version": "2021",\n "hybridclr" : { "branch": "v2.0.1"},\n "il2cpp_plus": { "branch": "v2021-2.0.1"}\n }\n ]\n}\n')),(0,l.kt)("p",null,"If you must install other versions of hybridclr or il2cpp_plus, modify the branch in the configuration file to be the target branch or tag."),(0,l.kt)("p",null,"In most cases, just click ",(0,l.kt)("inlineCode",{parentName:"p"},"Install")," to download and install from the remote repository by default. After the installation is successful, the console will print the ",(0,l.kt)("inlineCode",{parentName:"p"},"installation successful")," log. As shown below."),(0,l.kt)("p",null,(0,l.kt)("img",{alt:"install_default",src:i(8648).Z,width:"802",height:"196"})),(0,l.kt)("p",null,"From version 2.3.1 onwards, it supports copying and installing directly from the libil2cpp directory that contains hybridclr made locally. If your network is not good, or git is not installed and you cannot download and install remotely from the warehouse, you can first ",(0,l.kt)("a",{parentName:"p",href:"https://github.com/focus-creative-games/il2cpp_plus"},"il2cpp_plus")," and ","[hybridclr]","(https:/ /github.com/focus-creative-games/hybridclr) is downloaded to the local, and then according to the document in the ",(0,l.kt)("strong",{parentName:"p"},"Installation Principle")," section below, the libil2cpp directory containing hybridclr is merged from these two warehouses, and then installed in ",(0,l.kt)("inlineCode",{parentName:"p"},"Installer")," Enable ",(0,l.kt)("inlineCode",{parentName:"p"},"Copy libil2cpp from local")," option in the interface, select the libil2cpp directory you made, and click ",(0,l.kt)("inlineCode",{parentName:"p"},"Install")," to execute the installation. As shown below."),(0,l.kt)("p",null,(0,l.kt)("img",{alt:"install",src:i(5568).Z,width:"814",height:"216"})),(0,l.kt)("h3",{id:"if-your-version--1120"},"If your version >= 1.1.20"),(0,l.kt)("p",null,"The ",(0,l.kt)("inlineCode",{parentName:"p"},"Data~/hybridclr_version.json")," file in com.code-philosophy.hybridclr has been configured with the version compatible with hybridclr and il2cpp_plus corresponding to the current package version.\nThe Installer will install the version specified in the configuration, and no longer supports customizing the version to be installed."),(0,l.kt)("p",null,"The configuration looks like this:"),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre",className:"language-json"},'{\n "versions": [\n {\n "unity_version": "2019",\n "hybridclr" : { "branch": "main", "hash": "531f98365eebce5d1390175be2b41c41e217d918"},\n "il2cpp_plus": { "branch": "2019-main", "hash": "ebe5190b0404d1857832bd1d52ebec7c3730a01d"}\n },\n {\n "unity_version": "2020",\n "hybridclr" : { "branch": "main", "hash": "531f98365eebce5d1390175be2b41c41e217d918"},\n "il2cpp_plus": { "branch": "2020-main", "hash": "c6cf54285381d0b03a58126e0d39b6e4d11937b7"}\n },\n {\n "unity_version": "2021",\n "hybridclr" : { "branch": "main", "hash": "531f98365eebce5d1390175be2b41c41e217d918"},\n "il2cpp_plus": { "branch": "2021-main", "hash": "99cd1cbbfc1f637460379e81c9a7776cd3e662ad"}\n }\n ]\n}\n\n')),(0,l.kt)("p",null,"If you want to install other versions of hybridclr or il2cpp_plus, just modify the branch and hash in the configuration file."),(0,l.kt)("p",null,"Just click ",(0,l.kt)("inlineCode",{parentName:"p"},"Install")," to complete the installation. After the installation is successful, the console will print the ",(0,l.kt)("inlineCode",{parentName:"p"},"installation successful")," log."),(0,l.kt)("h3",{id:"if-your-package-version--1119"},"If your package version <= 1.1.19"),(0,l.kt)("p",null,"Fill in the commit id or branch or tag of the hybridclr and il2cpp_plus warehouses you want to install. If the version number of hybridclr is left blank, install the latest version from the main branch of the hybridclr repository.\nIf the version number of il2cpp_plus is left blank, install the latest version of the main branch of the corresponding annual release (such as 2020-main)."),(0,l.kt)("p",null,"**hybridclr_uniyt branch"),(0,l.kt)("p",null,", The branch of the hybridclr warehouse and the branch of the il2cpp_plus warehouse must match**. If you use the main branch of com.code-philosophy.hybridclr, hybridclr must use the main branch, il2cpp_plus must use ",(0,l.kt)("inlineCode",{parentName:"p"},"{version}-main"),", if your hybridclr_unity uses the 1.0 branch, then hybridclr must use the ",(0,l.kt)("inlineCode",{parentName:"p"},"1.0")," branch, il2cpp_plus The ",(0,l.kt)("inlineCode",{parentName:"p"},"{version}-1.0")," branch must be used. If you use a version of a tag, make sure the branch the tag belongs to matches."),(0,l.kt)("p",null,"The hybridclr warehouse recommends filling in ",(0,l.kt)("inlineCode",{parentName:"p"},"1.0"),", that is, the latest version of the 1.0 branch is installed each time; the il2cpp_plus warehouse recommends filling in ",(0,l.kt)("inlineCode",{parentName:"p"},"{annual version}-1.0")," (such as 2020-1.0), that is, each installation of the ",(0,l.kt)("inlineCode",{parentName:"p"},"{annual version}-1.0")," branch latest version of . As shown in the picture:"),(0,l.kt)("p",null,(0,l.kt)("img",{alt:"image",src:i(1093).Z,width:"1047",height:"320"})),(0,l.kt)("p",null,"At present, the stable official version 1.0.1 has been released, and it is also recommended for projects that pursue stability. Com.code-philosophy.hybridclr takes ",(0,l.kt)("inlineCode",{parentName:"p"},"1.0.1-release"),", the hybridclr version takes ",(0,l.kt)("inlineCode",{parentName:"p"},"1.0.1-release"),", and the il2cpp_plus version takes ",(0,l.kt)("inlineCode",{parentName:"p"},"{version}-1.0.1-relase"),"."),(0,l.kt)("p",null,"After completing the above settings, click the ",(0,l.kt)("inlineCode",{parentName:"p"},"install")," button to complete the installation. After the installation is successful, the console will print the ",(0,l.kt)("inlineCode",{parentName:"p"},"installation successful")," log."),(0,l.kt)("p",null,"Since the installation process needs to pull the hybridclr and il2cpp_plus warehouses, it may fail due to network failures. If\n",(0,l.kt)("inlineCode",{parentName:"p"},"HybridCLRData/hybridclr_repo")," or ",(0,l.kt)("inlineCode",{parentName:"p"},"HybridCLRData/il2cpp_plus_repo")," is empty when finding failed, please try again."),(0,l.kt)("p",null,"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."),(0,l.kt)("p",null,"If the automated installation cannot be completed due to various special reasons, please refer to the following ",(0,l.kt)("strong",{parentName:"p"},"Installation Principle")," to manually simulate the entire installation process."),(0,l.kt)("h2",{id:"special-handling-after-installation"},"Special handling after installation"),(0,l.kt)("h3",{id:"webgl-platform"},"WebGL Platform"),(0,l.kt)("p",null,"Due to Unity's own reasons, the WebGL platform must be installed globally. See the ",(0,l.kt)("inlineCode",{parentName:"p"},"Global Installation")," documentation in the following sections."),(0,l.kt)("p",null,"###Unity 2021"),(0,l.kt)("admonition",{type:"caution"},(0,l.kt)("p",{parentName:"admonition"},(0,l.kt)("strong",{parentName:"p"},"If your com.code-philosophy.hybridclr version >= v2.0.1"),", since the MonoHook technology has been used, the cropped AOT dll can be copied without modifying UnityEditor.CoreModule.dll,* *Not required** to do the following.")),(0,l.kt)("p",null,"Supplementary metadata and some commands under ",(0,l.kt)("inlineCode",{parentName:"p"},"HybridCLR/Generate/*")," depend on the reduced AOT dll. However, when Unity 2021 version (not required for 2019 and 2020) packages the ",(0,l.kt)("inlineCode",{parentName:"p"},"iOS platform")," (not required for other platforms), since the Unity Editor does not provide a public interface to copy the tailored AOT dll when the target is iOS, the modified version must be used The UnityEditor.CoreModule.dll overrides the corresponding file that comes with Unity."),(0,l.kt)("p",null,"The specific operation is to cover ",(0,l.kt)("inlineCode",{parentName:"p"},"{package directory}/Data~/ModifiedUnityAssemblies/2021.3.x/UnityEditor.CoreModule-{Win,Mac}.dll")," with ",(0,l.kt)("inlineCode",{parentName:"p"},"{Editor installation directory}/Editor/Data/Managed/UnityEngine/UnityEditor.CoreModule .dll"),", the specific related directory may vary depending on the operating system or Unity version."),(0,l.kt)("p",null,(0,l.kt)("strong",{parentName:"p"},"Due to permission issues, this operation cannot be completed automatically, and you need to perform the copy operation manually. ")),(0,l.kt)("p",null,(0,l.kt)("inlineCode",{parentName:"p"},"UnityEditor.CoreModule.dll")," Each small version of Unity is different. We currently only provide version 2021.3.1. If you need other versions, please make them manually. For details, please refer to ",(0,l.kt)("a",{parentName:"p",href:"/en/docs/basic/modifyunitydll"},"Modify Unity Editor-related dll"),"."),(0,l.kt)("h3",{id:"unity-2019"},"Unity 2019"),(0,l.kt)("p",null,"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 ",(0,l.kt)("inlineCode",{parentName:"p"},"{package}/Data~/ModifiedUnityAssemblies/2019.4.40/Unity.IL2CPP.dll")," to ",(0,l.kt)("inlineCode",{parentName:"p"},"{project}/HybridCLRData/LocalIl2CppData/il2cpp/build/deploy/net471 /Unity.IL2CPP.dll")),(0,l.kt)("p",null,(0,l.kt)("strong",{parentName:"p"},"Note that this operation is automatically completed when the Installer is installed, no manual operation is required. ")),(0,l.kt)("p",null,"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."),(0,l.kt)("h2",{id:"using-hybridclr-in-non-compatible-versions-of-unity"},"Using HybridCLR in non-compatible versions of Unity"),(0,l.kt)("p",null,"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:"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"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."),(0,l.kt)("li",{parentName:"ul"},"First switch your Unity project to this latest supported version, install HybridCLR."),(0,l.kt)("li",{parentName:"ul"},"Switch back to your Unity version."),(0,l.kt)("li",{parentName:"ul"},"Try to package, if it can run smoothly, it means that HybridCLR supports your version, if there is a problem, then upgrade the version.")),(0,l.kt)("p",null,"If you must use this version, you can contact us for ",(0,l.kt)("a",{parentName:"p",href:"/en/docs/business/intro"},"Business Technical Support"),"."),(0,l.kt)("h2",{id:"how-hybridclrinstaller-works"},"How ",(0,l.kt)("inlineCode",{parentName:"h2"},"HybridCLR/Installer")," works"),(0,l.kt)("p",null,"This section is just an introduction to the principle. ",(0,l.kt)("strong",{parentName:"p"},"The operation of installing libil2cpp has been completed by the installer, and you do not need to do it manually"),"."),(0,l.kt)("p",null,"The HybridCLR installation process mainly includes these parts:"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"Make libil2cpp that supports hot update"),(0,l.kt)("li",{parentName:"ul"},"Install locally or globally to make the new version of libil2cpp take effect"),(0,l.kt)("li",{parentName:"ul"},"Minor improvements to the Unity Editor")),(0,l.kt)("h3",{id:"replace-libil2cpp-code"},"Replace libil2cpp code"),(0,l.kt)("p",null,"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"),(0,l.kt)("p",null,"-il2cpp_plus"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"hybridclr")),(0,l.kt)("p",null,"The il2cpp_plus repository is a slightly modified version of the original libil2cpp to support dynamic ",(0,l.kt)("strong",{parentName:"p"},"register")," metadata (changed hundreds of lines of code). This repository is highly comparable to the original libil2cpp code\nresemblance. hybridclr is the core code of the interpreter, including metadata loading, code transform (compilation), and code interpretation and execution."),(0,l.kt)("p",null,"As shown in the figure below, merge the ",(0,l.kt)("inlineCode",{parentName:"p"},"il2cpp_plus/libil2cpp")," directory with the ",(0,l.kt)("inlineCode",{parentName:"p"},"hybridclr/hybridclr")," directory to create the final libil2cpp that supports hot updates."),(0,l.kt)("p",null,(0,l.kt)("img",{alt:"merge_hybridclr_dir",src:i(7441).Z,width:"811",height:"626"})),(0,l.kt)("h3",{id:"local-installation"},"Local installation"),(0,l.kt)("p",null,"Unity allows you to use the environment variable ",(0,l.kt)("inlineCode",{parentName:"p"},"UNITY_IL2CPP_PATH")," to customize the location of ",(0,l.kt)("inlineCode",{parentName:"p"},"il2cpp"),", so you can create an il2cpp directory locally in the project, replace the libil2cpp directory under the il2cpp directory with the modified libil2cpp,\nThen point the ",(0,l.kt)("inlineCode",{parentName:"p"},"UNITY_IL2CPP_PATH")," environment variable to this directory. The general process is as follows:"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"Copy the il2cpp directory from the Editor installation directory to ",(0,l.kt)("inlineCode",{parentName:"li"},"{project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp")),(0,l.kt)("li",{parentName:"ul"},"Create the final libil2cpp directory from the clone il2cpp_plus and hybridclr repositories"),(0,l.kt)("li",{parentName:"ul"},"Replace ",(0,l.kt)("inlineCode",{parentName:"li"},"{project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp/libil2cpp")," with the final libil2cpp directory"),(0,l.kt)("li",{parentName:"ul"},"Copy the ",(0,l.kt)("inlineCode",{parentName:"li"},"MonoBleedingEdge")," directory from the Editor installation directory to ",(0,l.kt)("inlineCode",{parentName:"li"},"{project}/HybridCLRData/LocalIl2CppData-{platform}/MonoBleedingEdge")),(0,l.kt)("li",{parentName:"ul"},"Other processing. For the 2019 version, copy ",(0,l.kt)("inlineCode",{parentName:"li"},"{package}/Data~/ModifiedUnityAssemblies/2019.4.40/Unity.IL2CPP.dll")," to ",(0,l.kt)("inlineCode",{parentName:"li"},"{project}/HybridCLRData/LocalIl2CppData/il2cpp/build/deploy/net471/Unity.IL2CPP.dll"))),(0,l.kt)("p",null,"Create the upper-level ",(0,l.kt)("inlineCode",{parentName:"p"},"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 ",(0,l.kt)("inlineCode",{parentName:"p"},"MonoBleedingEdge")," directory at the same level, so the upper level is created directory, copy both the il2cpp and MonoBleedingEdge directories."),(0,l.kt)("p",null,"Because the il2cpp directory that comes with Editor on different platforms is slightly different, LocalIl2CppData needs to distinguish the platform."),(0,l.kt)("h3",{id:"global-installation"},"Global installation"),(0,l.kt)("p",null,"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:"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"Due to directory permissions, auto-completion may not be possible"),(0,l.kt)("li",{parentName:"ul"},"Will affect other projects that don't use hybridclr"),(0,l.kt)("li",{parentName:"ul"},"The ",(0,l.kt)("inlineCode",{parentName:"li"},"HybridCLR/Generate/xxxx")," operation needs to modify the files in the libil2cpp directory, which may fail due to directory permissions.")),(0,l.kt)("p",null,"After completing the installation using ",(0,l.kt)("inlineCode",{parentName:"p"},"HybridCLR/Installer"),", enable the ",(0,l.kt)("inlineCode",{parentName:"p"},"useGlobalIl2Cpp")," option in ",(0,l.kt)("inlineCode",{parentName:"p"},"HybridCLR/Settings")," to start the global installation, and the environment variable ",(0,l.kt)("inlineCode",{parentName:"p"},"UNITY_IL2CPP_PATH")," will be cleared."),(0,l.kt)("p",null,"If you use the replacement directory for global installation, and your com.code-philosophy.hybridclr version >= 2.1.0, please run ",(0,l.kt)("inlineCode",{parentName:"p"},"HybridCLR/Generate/Il2cppDef")," before overriding libil2cpp ",(0,l.kt)("strong",{parentName:"p"},"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. ",(0,l.kt)("strong",{parentName:"p"},"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"),"."),(0,l.kt)("p",null,"Due to permissions, even if it is installed globally, the ",(0,l.kt)("inlineCode",{parentName:"p"},"Generate/xxx")," command modifies the files under the local ",(0,l.kt)("inlineCode",{parentName:"p"},"{project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp/libil2cpp"),". ",(0,l.kt)("strong",{parentName:"p"},"Please overwrite the local libil2cpp directory with the global installation directory after each generate"),"."),(0,l.kt)("p",null,"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:"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"Windows platform. Open the command line window with administrator privileges, delete or rename the original libil2cpp, and then run ",(0,l.kt)("inlineCode",{parentName:"li"},'mklink /D "" "{project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp/libil2cpp" '),"."),(0,l.kt)("li",{parentName:"ul"},"Linux or Mac platform. Open the command line window with administrator privileges, delete or rename the original libil2cpp, and then run ",(0,l.kt)("inlineCode",{parentName:"li"},'ln -s "{project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp/libil2cpp" "" '),".")),(0,l.kt)("p",null,"For the 2019 version replace Unity.IL2CPP.dll, also use a method similar to the above replacement or soft link."),(0,l.kt)("h2",{id:"precautions"},"Precautions"),(0,l.kt)("p",null,"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."))}h.isMDXComponent=!0},5568:(e,t,i)=>{i.d(t,{Z:()=>n});const n=i.p+"assets/images/install-c1f84913c18dfa0406fc90db65085a56.jpg"},8648:(e,t,i)=>{i.d(t,{Z:()=>n});const n=i.p+"assets/images/install_default-c61d323cdd4133368bc575f35dd7a9ec.jpg"},5283:(e,t,i)=>{i.d(t,{Z:()=>n});const n=i.p+"assets/images/install_hybridclrunity_package-9a53b1ee8f7ffd8a700ed1f977ca74e3.jpg"},1093:(e,t,i)=>{i.d(t,{Z:()=>n});const n=i.p+"assets/images/install_version-bafc326c19c3b969342179d820ead842.jpg"},7441:(e,t,i)=>{i.d(t,{Z:()=>n});const n=i.p+"assets/images/merge_hybridclr_dir-04680fdb60dccd43bfd2593b4affd10e.jpg"},8581:(e,t,i)=>{i.d(t,{Z:()=>n});const n=i.p+"assets/images/select_il2cpp_modules-d895c3fb5390e04b53e40ada2b422239.jpg"}}]); \ No newline at end of file +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[7906],{3905:(e,A,i)=>{i.d(A,{Zo:()=>p,kt:()=>m});var t=i(7294);function l(e,A,i){return A in e?Object.defineProperty(e,A,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[A]=i,e}function n(e,A){var i=Object.keys(e);if(Object.getOwnPropertySymbols){var t=Object.getOwnPropertySymbols(e);A&&(t=t.filter((function(A){return Object.getOwnPropertyDescriptor(e,A).enumerable}))),i.push.apply(i,t)}return i}function o(e){for(var A=1;A=0||(l[i]=e[i]);return l}(e,A);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);for(t=0;t=0||Object.prototype.propertyIsEnumerable.call(e,i)&&(l[i]=e[i])}return l}var r=t.createContext({}),s=function(e){var A=t.useContext(r),i=A;return e&&(i="function"==typeof e?e(A):o(o({},A),e)),i},p=function(e){var A=s(e.components);return t.createElement(r.Provider,{value:A},e.children)},c="mdxType",d={inlineCode:"code",wrapper:function(e){var A=e.children;return t.createElement(t.Fragment,{},A)}},h=t.forwardRef((function(e,A){var i=e.components,l=e.mdxType,n=e.originalType,r=e.parentName,p=a(e,["components","mdxType","originalType","parentName"]),c=s(i),h=l,m=c["".concat(r,".").concat(h)]||c[h]||d[h]||n;return i?t.createElement(m,o(o({ref:A},p),{},{components:i})):t.createElement(m,o({ref:A},p))}));function m(e,A){var i=arguments,l=A&&A.mdxType;if("string"==typeof e||l){var n=i.length,o=new Array(n);o[0]=h;var a={};for(var r in A)hasOwnProperty.call(A,r)&&(a[r]=A[r]);a.originalType=e,a[c]="string"==typeof e?e:l,o[1]=a;for(var s=2;s{i.r(A),i.d(A,{assets:()=>r,contentTitle:()=>o,default:()=>d,frontMatter:()=>n,metadata:()=>a,toc:()=>s});var t=i(7462),l=(i(7294),i(3905));const n={},o="Install",a={unversionedId:"basic/install",id:"basic/install",title:"Install",description:"Install a compatible Unity version",source:"@site/i18n/en/docusaurus-plugin-content-docs/current/basic/install.md",sourceDirName:"basic",slug:"/basic/install",permalink:"/en/docs/basic/install",draft:!1,tags:[],version:"current",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"Supported Unity versions and platforms",permalink:"/en/docs/basic/supportedplatformanduniyversion"},next:{title:"Settings",permalink:"/en/docs/basic/projectsettings"}},r={},s=[{value:"Install a compatible Unity version",id:"install-a-compatible-unity-version",level:2},{value:"Install IDE and related tools",id:"install-ide-and-related-tools",level:2},{value:"Select com.code-philosophy.hybridclr version",id:"select-comcode-philosophyhybridclr-version",level:3},{value:"Install the com.code-philosophy.hybridclr package",id:"install-the-comcode-philosophyhybridclr-package",level:2},{value:"Install from git url",id:"install-from-git-url",level:3},{value:"Install from openupm",id:"install-from-openupm",level:3},{value:"Install from local files",id:"install-from-local-files",level:3},{value:"Update com.code-philosophy.hybridclr",id:"update-comcode-philosophyhybridclr",level:2},{value:"Initialize HybridCLR",id:"initialize-hybridclr",level:2},{value:"If your version >= v2.0.5",id:"if-your-version--v205",level:3},{value:"If your version >= 1.1.20",id:"if-your-version--1120",level:3},{value:"If your package version <= 1.1.19",id:"if-your-package-version--1119",level:3},{value:"Special handling after installation",id:"special-handling-after-installation",level:2},{value:"WebGL Platform",id:"webgl-platform",level:3},{value:"Unity 2019",id:"unity-2019",level:3},{value:"Using HybridCLR in non-compatible versions of Unity",id:"using-hybridclr-in-non-compatible-versions-of-unity",level:2},{value:"How HybridCLR/Installer works",id:"how-hybridclrinstaller-works",level:2},{value:"Replace libil2cpp code",id:"replace-libil2cpp-code",level:3},{value:"Local installation",id:"local-installation",level:3},{value:"Global installation",id:"global-installation",level:3},{value:"Precautions",id:"precautions",level:2}],p={toc:s},c="wrapper";function d(e){let{components:A,...n}=e;return(0,l.kt)(c,(0,t.Z)({},p,n,{components:A,mdxType:"MDXLayout"}),(0,l.kt)("h1",{id:"install"},"Install"),(0,l.kt)("h2",{id:"install-a-compatible-unity-version"},"Install a compatible Unity version"),(0,l.kt)("p",null,"Any version of 2019.4.x, 2020.3.x, 2021.3.x, or 2022.3.x is supported. It is recommended to install versions 2019.4.40, 2020.3.26+, 2021.3.x, and 2022.3.x."),(0,l.kt)("admonition",{type:"tip"},(0,l.kt)("p",{parentName:"admonition"},"If your version is 2019.4.0-2019.4.39, ",(0,l.kt)("strong",{parentName:"p"},"Need to switch to 2019.4.40 to complete HybridCLR installation, and then switch back to the current version"),"."),(0,l.kt)("p",{parentName:"admonition"},"If your version is 2020.3.0-2020.3.25, after completing the installation in Installer, copy ",(0,l.kt)("inlineCode",{parentName:"p"},"2020.3.x/Editor/Data/il2cpp/external")," from the installation directory of any version 2020.3.26+ to replace\n",(0,l.kt)("inlineCode",{parentName:"p"},"{project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp/external"),".")),(0,l.kt)("admonition",{type:"caution"},(0,l.kt)("p",{parentName:"admonition"},"If you are not an experienced Unity developer, it is recommended to use version 2021.3.1 to experience HybridCLR first.")),(0,l.kt)("p",null,"According to the target platform you packaged, select the necessary modules during the installation process. If you package Android or iOS, just select the corresponding module directly. If you want to package Standalone, you must additionally select ",(0,l.kt)("inlineCode",{parentName:"p"},"Windows Build Support(IL2CPP)")," or ",(0,l.kt)("inlineCode",{parentName:"p"},"Mac Build Support(IL2CPP)"),"."),(0,l.kt)("p",null,(0,l.kt)("img",{alt:"select il2cpp modules",src:i(8581).Z,width:"721",height:"507"})),(0,l.kt)("h2",{id:"install-ide-and-related-tools"},"Install IDE and related tools"),(0,l.kt)("p",null,"-Windows"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"Under Win, you need to install ",(0,l.kt)("inlineCode",{parentName:"li"},"visual studio 2019")," or later. The installation must include at least the ",(0,l.kt)("inlineCode",{parentName:"li"},"Game Development with Unity")," and ",(0,l.kt)("inlineCode",{parentName:"li"},"Game Development with C++")," components."),(0,l.kt)("li",{parentName:"ul"},"install git\n-Mac"),(0,l.kt)("li",{parentName:"ul"},"Requires MacOS version >= 12, xcode version >= 13, for example ",(0,l.kt)("inlineCode",{parentName:"li"},"xcode 13.4.1, macos 12.4"),"."),(0,l.kt)("li",{parentName:"ul"},"install git"),(0,l.kt)("li",{parentName:"ul"},"install cmake")),(0,l.kt)("h3",{id:"select-comcode-philosophyhybridclr-version"},"Select ",(0,l.kt)("inlineCode",{parentName:"h3"},"com.code-philosophy.hybridclr")," version"),(0,l.kt)("admonition",{type:"caution"},(0,l.kt)("p",{parentName:"admonition"},"Before v3.0.0 the package name was ",(0,l.kt)("inlineCode",{parentName:"p"},"com.focus-creative-games.hybridclr_unity"),".")),(0,l.kt)("p",null,"These versions currently exist: ",(0,l.kt)("inlineCode",{parentName:"p"},"1.0")," branch, ",(0,l.kt)("inlineCode",{parentName:"p"},"v2.x.y"),", ",(0,l.kt)("inlineCode",{parentName:"p"},"v3.x.y"),", ",(0,l.kt)("inlineCode",{parentName:"p"},"v.4.x.y")," (also current main branch) ."),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"The ",(0,l.kt)("inlineCode",{parentName:"li"},"1.0")," branch is too old, although the work is stable, but the Package-related workflow is relatively old, not as convenient as subsequent versions, and maintenance has been stopped, it is strongly recommended not to use it again"),(0,l.kt)("li",{parentName:"ul"},"The workflow of ",(0,l.kt)("inlineCode",{parentName:"li"},"v2.x.y")," versions is well optimized and verified by a large number of projects. It is recommended to use the Unity 2019 version or the projects that will be launched soon"),(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("inlineCode",{parentName:"li"},"v3.x.y")," versions ",(0,l.kt)("strong",{parentName:"li"},"removed support for Unity 2019"),", added support for Unity 2022 version. It is recommended to use Unity 2020+ version or projects that will be launched soon"),(0,l.kt)("li",{parentName:"ul"},"The ",(0,l.kt)("inlineCode",{parentName:"li"},"v4.x.y")," versions supports incremental GC and fully supports all platforms. Since it has just been released, it will take a few weeks to stabilize. It is recommended for projects in the early or middle stages of the project.")),(0,l.kt)("admonition",{type:"tip"},(0,l.kt)("p",{parentName:"admonition"},"The versions of these three series are very stable, so there is no need to worry about which one is better. Generally speaking, the newer the version, the more optimizations and the better the user experience.")),(0,l.kt)("h2",{id:"install-the-comcode-philosophyhybridclr-package"},"Install the ",(0,l.kt)("inlineCode",{parentName:"h2"},"com.code-philosophy.hybridclr")," package"),(0,l.kt)("p",null,"The warehouse address is ",(0,l.kt)("a",{parentName:"p",href:"https://github.com/focus-creative-games/hybridclr_unity"},"github"),", and the domestic fast mirror warehouse is ",(0,l.kt)("a",{parentName:"p",href:"https://gitee.com/focus-creative-games/hybridclr_unity"},"gitee")," ."),(0,l.kt)("p",null,"There are three installation methods:"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"Install from git url using Package Manager"),(0,l.kt)("li",{parentName:"ul"},"Install from openupm using Package Manager"),(0,l.kt)("li",{parentName:"ul"},"local installation")),(0,l.kt)("h3",{id:"install-from-git-url"},"Install from git url"),(0,l.kt)("p",null,"Click ",(0,l.kt)("inlineCode",{parentName:"p"},"Windows/Package Manager")," in the main menu to open the package manager. Click ",(0,l.kt)("inlineCode",{parentName:"p"},"Add package from git URL...")," as shown below, fill in ",(0,l.kt)("inlineCode",{parentName:"p"},"https://gitee.com/focus-creative-games/hybridclr_unity.git")," or ",(0,l.kt)("inlineCode",{parentName:"p"},"https://github.com/focus-creative -games/hybridclr_unity.git"),"."),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"The main branch address is ",(0,l.kt)("inlineCode",{parentName:"li"},"https://gitee.com/focus-creative-games/hybridclr_unity.git")),(0,l.kt)("li",{parentName:"ul"},"Other tag version addresses are ",(0,l.kt)("inlineCode",{parentName:"li"},"https://gitee.com/focus-creative-games/hybridclr_unity.git#{tag}"))),(0,l.kt)("p",null,"If you want to install a certain branch or tag version, please add ",(0,l.kt)("inlineCode",{parentName:"p"},"#{tag}")," after the address, such as ",(0,l.kt)("inlineCode",{parentName:"p"},"https://gitee.com/focus-creative-games/hybridclr_unity.git#v3.0.1"),"."),(0,l.kt)("p",null,(0,l.kt)("img",{alt:"add package",src:i(5283).Z,width:"808",height:"223"})),(0,l.kt)("p",null,"If you are not familiar with installing packages from url, please see ",(0,l.kt)("a",{parentName:"p",href:"https://docs.unity3d.com/Manual/upm-ui-giturl.html"},"install from giturl"),"."),(0,l.kt)("h3",{id:"install-from-openupm"},"Install from openupm"),(0,l.kt)("p",null,"openump address ",(0,l.kt)("a",{parentName:"p",href:"https://openupm.com/packages/com.focus-creative-games.hybridclr_unity/"},"com.focus-creative-games.hybridclr_unity"),"."),(0,l.kt)("p",null,"For the specific installation method, please open this link and view the detailed installation instructions on the page."),(0,l.kt)("h3",{id:"install-from-local-files"},"Install from local files"),(0,l.kt)("p",null,"After cloning the warehouse locally, rename the directory to ",(0,l.kt)("inlineCode",{parentName:"p"},"com.code-philosophy.hybridclr")," (for versions before v3.0.0, please use ",(0,l.kt)("inlineCode",{parentName:"p"},"com.focus-creative-games.hybridclr_unity"),"), and then directly move to the Packages directory of the project. Can."),(0,l.kt)("h2",{id:"update-comcode-philosophyhybridclr"},"Update com.code-philosophy.hybridclr"),(0,l.kt)("p",null,"After updating com.code-philosophy.hybridclr, you need to re-run ",(0,l.kt)("inlineCode",{parentName:"p"},"HybridCLR/Installer"),"."),(0,l.kt)("h2",{id:"initialize-hybridclr"},"Initialize HybridCLR"),(0,l.kt)("p",null,"In order to reduce the size of the package itself, some files need to be copied from the Unity Editor installation directory. Therefore, after installing the plug-in, an additional initialization process is required."),(0,l.kt)("p",null,"Click the menu ",(0,l.kt)("inlineCode",{parentName:"p"},"HybridCLR/Installer...")," to pop up the installation interface. Some setup may be required before clicking install. Since the Installer has been adjusted as the version changes, please read the corresponding instructions below according to your current version."),(0,l.kt)("h3",{id:"if-your-version--v205"},"If your version >= v2.0.5"),(0,l.kt)("p",null,"The branch or tag compatible with hybridclr and il2cpp_plus corresponding to the current package version has been configured in the ",(0,l.kt)("inlineCode",{parentName:"p"},"Data~/hybridclr_version.json")," file in com.code-philosophy.hybridclr.\nThe Installer will install the version specified in the configuration, and no longer supports customizing the version to be installed."),(0,l.kt)("p",null,"The configuration looks like this:"),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre",className:"language-json"},'{\n "versions": [\n {\n "unity_version": "2019",\n "hybridclr" : { "branch": "v2.0.1"},\n "il2cpp_plus": { "branch": "v2019-2.0.1"}\n },\n {\n "unity_version": "2020",\n "hybridclr" : { "branch": "v2.0.1"},\n "il2cpp_plus": { "branch": "v2020-2.0.1"}\n },\n {\n "unity_version": "2021",\n "hybridclr" : { "branch": "v2.0.1"},\n "il2cpp_plus": { "branch": "v2021-2.0.1"}\n }\n ]\n}\n')),(0,l.kt)("p",null,"If you must install other versions of hybridclr or il2cpp_plus, modify the branch in the configuration file to be the target branch or tag."),(0,l.kt)("p",null,"In most cases, just click ",(0,l.kt)("inlineCode",{parentName:"p"},"Install")," to download and install from the remote repository by default. After the installation is successful, the console will print the ",(0,l.kt)("inlineCode",{parentName:"p"},"installation successful")," log. As shown below."),(0,l.kt)("p",null,(0,l.kt)("img",{alt:"install_default",src:i(8648).Z,width:"803",height:"221"})),(0,l.kt)("p",null,"From version 2.3.1 onwards, it supports copying and installing directly from the libil2cpp directory that contains hybridclr made locally. If your network is not good, or git is not installed and you cannot download and install remotely from the warehouse, you can first ",(0,l.kt)("a",{parentName:"p",href:"https://github.com/focus-creative-games/il2cpp_plus"},"il2cpp_plus")," and ","[hybridclr]","(https:/ /github.com/focus-creative-games/hybridclr) is downloaded to the local, and then according to the document in the ",(0,l.kt)("strong",{parentName:"p"},"Installation Principle")," section below, the libil2cpp directory containing hybridclr is merged from these two warehouses, and then installed in ",(0,l.kt)("inlineCode",{parentName:"p"},"Installer")," Enable ",(0,l.kt)("inlineCode",{parentName:"p"},"Copy libil2cpp from local")," option in the interface, select the libil2cpp directory you made, and click ",(0,l.kt)("inlineCode",{parentName:"p"},"Install")," to execute the installation. As shown below."),(0,l.kt)("p",null,(0,l.kt)("img",{alt:"install",src:i(5568).Z,width:"802",height:"195"})),(0,l.kt)("h3",{id:"if-your-version--1120"},"If your version >= 1.1.20"),(0,l.kt)("p",null,"The ",(0,l.kt)("inlineCode",{parentName:"p"},"Data~/hybridclr_version.json")," file in com.code-philosophy.hybridclr has been configured with the version compatible with hybridclr and il2cpp_plus corresponding to the current package version.\nThe Installer will install the version specified in the configuration, and no longer supports customizing the version to be installed."),(0,l.kt)("p",null,"The configuration looks like this:"),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre",className:"language-json"},'{\n "versions": [\n {\n "unity_version": "2019",\n "hybridclr" : { "branch": "main", "hash": "531f98365eebce5d1390175be2b41c41e217d918"},\n "il2cpp_plus": { "branch": "2019-main", "hash": "ebe5190b0404d1857832bd1d52ebec7c3730a01d"}\n },\n {\n "unity_version": "2020",\n "hybridclr" : { "branch": "main", "hash": "531f98365eebce5d1390175be2b41c41e217d918"},\n "il2cpp_plus": { "branch": "2020-main", "hash": "c6cf54285381d0b03a58126e0d39b6e4d11937b7"}\n },\n {\n "unity_version": "2021",\n "hybridclr" : { "branch": "main", "hash": "531f98365eebce5d1390175be2b41c41e217d918"},\n "il2cpp_plus": { "branch": "2021-main", "hash": "99cd1cbbfc1f637460379e81c9a7776cd3e662ad"}\n }\n ]\n}\n\n')),(0,l.kt)("p",null,"If you want to install other versions of hybridclr or il2cpp_plus, just modify the branch and hash in the configuration file."),(0,l.kt)("p",null,"Just click ",(0,l.kt)("inlineCode",{parentName:"p"},"Install")," to complete the installation. After the installation is successful, the console will print the ",(0,l.kt)("inlineCode",{parentName:"p"},"installation successful")," log."),(0,l.kt)("h3",{id:"if-your-package-version--1119"},"If your package version <= 1.1.19"),(0,l.kt)("p",null,"Fill in the commit id or branch or tag of the hybridclr and il2cpp_plus warehouses you want to install. If the version number of hybridclr is left blank, install the latest version from the main branch of the hybridclr repository.\nIf the version number of il2cpp_plus is left blank, install the latest version of the main branch of the corresponding annual release (such as 2020-main)."),(0,l.kt)("p",null,"**hybridclr_uniyt branch"),(0,l.kt)("p",null,", The branch of the hybridclr warehouse and the branch of the il2cpp_plus warehouse must match**. If you use the main branch of com.code-philosophy.hybridclr, hybridclr must use the main branch, il2cpp_plus must use ",(0,l.kt)("inlineCode",{parentName:"p"},"{version}-main"),", if your hybridclr_unity uses the 1.0 branch, then hybridclr must use the ",(0,l.kt)("inlineCode",{parentName:"p"},"1.0")," branch, il2cpp_plus The ",(0,l.kt)("inlineCode",{parentName:"p"},"{version}-1.0")," branch must be used. If you use a version of a tag, make sure the branch the tag belongs to matches."),(0,l.kt)("p",null,"The hybridclr warehouse recommends filling in ",(0,l.kt)("inlineCode",{parentName:"p"},"1.0"),", that is, the latest version of the 1.0 branch is installed each time; the il2cpp_plus warehouse recommends filling in ",(0,l.kt)("inlineCode",{parentName:"p"},"{annual version}-1.0")," (such as 2020-1.0), that is, each installation of the ",(0,l.kt)("inlineCode",{parentName:"p"},"{annual version}-1.0")," branch latest version of . As shown in the picture:"),(0,l.kt)("p",null,(0,l.kt)("img",{alt:"image",src:i(1093).Z,width:"1047",height:"320"})),(0,l.kt)("p",null,"At present, the stable official version 1.0.1 has been released, and it is also recommended for projects that pursue stability. Com.code-philosophy.hybridclr takes ",(0,l.kt)("inlineCode",{parentName:"p"},"1.0.1-release"),", the hybridclr version takes ",(0,l.kt)("inlineCode",{parentName:"p"},"1.0.1-release"),", and the il2cpp_plus version takes ",(0,l.kt)("inlineCode",{parentName:"p"},"{version}-1.0.1-relase"),"."),(0,l.kt)("p",null,"After completing the above settings, click the ",(0,l.kt)("inlineCode",{parentName:"p"},"install")," button to complete the installation. After the installation is successful, the console will print the ",(0,l.kt)("inlineCode",{parentName:"p"},"installation successful")," log."),(0,l.kt)("p",null,"Since the installation process needs to pull the hybridclr and il2cpp_plus warehouses, it may fail due to network failures. If\n",(0,l.kt)("inlineCode",{parentName:"p"},"HybridCLRData/hybridclr_repo")," or ",(0,l.kt)("inlineCode",{parentName:"p"},"HybridCLRData/il2cpp_plus_repo")," is empty when finding failed, please try again."),(0,l.kt)("p",null,"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."),(0,l.kt)("p",null,"If the automated installation cannot be completed due to various special reasons, please refer to the following ",(0,l.kt)("strong",{parentName:"p"},"Installation Principle")," to manually simulate the entire installation process."),(0,l.kt)("h2",{id:"special-handling-after-installation"},"Special handling after installation"),(0,l.kt)("h3",{id:"webgl-platform"},"WebGL Platform"),(0,l.kt)("p",null,"Due to Unity's own reasons, the WebGL platform must be installed globally. See the ",(0,l.kt)("inlineCode",{parentName:"p"},"Global Installation")," documentation in the following sections."),(0,l.kt)("p",null,"###Unity 2021"),(0,l.kt)("admonition",{type:"caution"},(0,l.kt)("p",{parentName:"admonition"},(0,l.kt)("strong",{parentName:"p"},"If your com.code-philosophy.hybridclr version >= v2.0.1"),", since the MonoHook technology has been used, the cropped AOT dll can be copied without modifying UnityEditor.CoreModule.dll,* *Not required** to do the following.")),(0,l.kt)("p",null,"Supplementary metadata and some commands under ",(0,l.kt)("inlineCode",{parentName:"p"},"HybridCLR/Generate/*")," depend on the reduced AOT dll. However, when Unity 2021 version (not required for 2019 and 2020) packages the ",(0,l.kt)("inlineCode",{parentName:"p"},"iOS platform")," (not required for other platforms), since the Unity Editor does not provide a public interface to copy the tailored AOT dll when the target is iOS, the modified version must be used The UnityEditor.CoreModule.dll overrides the corresponding file that comes with Unity."),(0,l.kt)("p",null,"The specific operation is to cover ",(0,l.kt)("inlineCode",{parentName:"p"},"{package directory}/Data~/ModifiedUnityAssemblies/2021.3.x/UnityEditor.CoreModule-{Win,Mac}.dll")," with ",(0,l.kt)("inlineCode",{parentName:"p"},"{Editor installation directory}/Editor/Data/Managed/UnityEngine/UnityEditor.CoreModule .dll"),", the specific related directory may vary depending on the operating system or Unity version."),(0,l.kt)("p",null,(0,l.kt)("strong",{parentName:"p"},"Due to permission issues, this operation cannot be completed automatically, and you need to perform the copy operation manually. ")),(0,l.kt)("p",null,(0,l.kt)("inlineCode",{parentName:"p"},"UnityEditor.CoreModule.dll")," Each small version of Unity is different. We currently only provide version 2021.3.1. If you need other versions, please make them manually. For details, please refer to ",(0,l.kt)("a",{parentName:"p",href:"/en/docs/basic/modifyunitydll"},"Modify Unity Editor-related dll"),"."),(0,l.kt)("h3",{id:"unity-2019"},"Unity 2019"),(0,l.kt)("p",null,"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 ",(0,l.kt)("inlineCode",{parentName:"p"},"{package}/Data~/ModifiedUnityAssemblies/2019.4.40/Unity.IL2CPP.dll")," to ",(0,l.kt)("inlineCode",{parentName:"p"},"{project}/HybridCLRData/LocalIl2CppData/il2cpp/build/deploy/net471 /Unity.IL2CPP.dll")),(0,l.kt)("p",null,(0,l.kt)("strong",{parentName:"p"},"Note that this operation is automatically completed when the Installer is installed, no manual operation is required. ")),(0,l.kt)("p",null,"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."),(0,l.kt)("h2",{id:"using-hybridclr-in-non-compatible-versions-of-unity"},"Using HybridCLR in non-compatible versions of Unity"),(0,l.kt)("p",null,"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:"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"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."),(0,l.kt)("li",{parentName:"ul"},"First switch your Unity project to this latest supported version, install HybridCLR."),(0,l.kt)("li",{parentName:"ul"},"Switch back to your Unity version."),(0,l.kt)("li",{parentName:"ul"},"Try to package, if it can run smoothly, it means that HybridCLR supports your version, if there is a problem, then upgrade the version.")),(0,l.kt)("p",null,"If you must use this version, you can contact us for ",(0,l.kt)("a",{parentName:"p",href:"/en/docs/business/intro"},"Business Technical Support"),"."),(0,l.kt)("h2",{id:"how-hybridclrinstaller-works"},"How ",(0,l.kt)("inlineCode",{parentName:"h2"},"HybridCLR/Installer")," works"),(0,l.kt)("p",null,"This section is just an introduction to the principle. ",(0,l.kt)("strong",{parentName:"p"},"The operation of installing libil2cpp has been completed by the installer, and you do not need to do it manually"),"."),(0,l.kt)("p",null,"The HybridCLR installation process mainly includes these parts:"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"Make libil2cpp that supports hot update"),(0,l.kt)("li",{parentName:"ul"},"Install locally or globally to make the new version of libil2cpp take effect"),(0,l.kt)("li",{parentName:"ul"},"Minor improvements to the Unity Editor")),(0,l.kt)("h3",{id:"replace-libil2cpp-code"},"Replace libil2cpp code"),(0,l.kt)("p",null,"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"),(0,l.kt)("p",null,"-il2cpp_plus"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"hybridclr")),(0,l.kt)("p",null,"The il2cpp_plus repository is a slightly modified version of the original libil2cpp to support dynamic ",(0,l.kt)("strong",{parentName:"p"},"register")," metadata (changed hundreds of lines of code). This repository is highly comparable to the original libil2cpp code\nresemblance. hybridclr is the core code of the interpreter, including metadata loading, code transform (compilation), and code interpretation and execution."),(0,l.kt)("p",null,"As shown in the figure below, merge the ",(0,l.kt)("inlineCode",{parentName:"p"},"il2cpp_plus/libil2cpp")," directory with the ",(0,l.kt)("inlineCode",{parentName:"p"},"hybridclr/hybridclr")," directory to create the final libil2cpp that supports hot updates."),(0,l.kt)("p",null,(0,l.kt)("img",{alt:"merge_hybridclr_dir",src:i(7441).Z,width:"811",height:"626"})),(0,l.kt)("h3",{id:"local-installation"},"Local installation"),(0,l.kt)("p",null,"Unity allows you to use the environment variable ",(0,l.kt)("inlineCode",{parentName:"p"},"UNITY_IL2CPP_PATH")," to customize the location of ",(0,l.kt)("inlineCode",{parentName:"p"},"il2cpp"),", so you can create an il2cpp directory locally in the project, replace the libil2cpp directory under the il2cpp directory with the modified libil2cpp,\nThen point the ",(0,l.kt)("inlineCode",{parentName:"p"},"UNITY_IL2CPP_PATH")," environment variable to this directory. The general process is as follows:"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"Copy the il2cpp directory from the Editor installation directory to ",(0,l.kt)("inlineCode",{parentName:"li"},"{project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp")),(0,l.kt)("li",{parentName:"ul"},"Create the final libil2cpp directory from the clone il2cpp_plus and hybridclr repositories"),(0,l.kt)("li",{parentName:"ul"},"Replace ",(0,l.kt)("inlineCode",{parentName:"li"},"{project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp/libil2cpp")," with the final libil2cpp directory"),(0,l.kt)("li",{parentName:"ul"},"Copy the ",(0,l.kt)("inlineCode",{parentName:"li"},"MonoBleedingEdge")," directory from the Editor installation directory to ",(0,l.kt)("inlineCode",{parentName:"li"},"{project}/HybridCLRData/LocalIl2CppData-{platform}/MonoBleedingEdge")),(0,l.kt)("li",{parentName:"ul"},"Other processing. For the 2019 version, copy ",(0,l.kt)("inlineCode",{parentName:"li"},"{package}/Data~/ModifiedUnityAssemblies/2019.4.40/Unity.IL2CPP.dll")," to ",(0,l.kt)("inlineCode",{parentName:"li"},"{project}/HybridCLRData/LocalIl2CppData/il2cpp/build/deploy/net471/Unity.IL2CPP.dll"))),(0,l.kt)("p",null,"Create the upper-level ",(0,l.kt)("inlineCode",{parentName:"p"},"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 ",(0,l.kt)("inlineCode",{parentName:"p"},"MonoBleedingEdge")," directory at the same level, so the upper level is created directory, copy both the il2cpp and MonoBleedingEdge directories."),(0,l.kt)("p",null,"Because the il2cpp directory that comes with Editor on different platforms is slightly different, LocalIl2CppData needs to distinguish the platform."),(0,l.kt)("h3",{id:"global-installation"},"Global installation"),(0,l.kt)("p",null,"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:"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"Due to directory permissions, auto-completion may not be possible"),(0,l.kt)("li",{parentName:"ul"},"Will affect other projects that don't use hybridclr"),(0,l.kt)("li",{parentName:"ul"},"The ",(0,l.kt)("inlineCode",{parentName:"li"},"HybridCLR/Generate/xxxx")," operation needs to modify the files in the libil2cpp directory, which may fail due to directory permissions.")),(0,l.kt)("p",null,"After completing the installation using ",(0,l.kt)("inlineCode",{parentName:"p"},"HybridCLR/Installer"),", enable the ",(0,l.kt)("inlineCode",{parentName:"p"},"useGlobalIl2Cpp")," option in ",(0,l.kt)("inlineCode",{parentName:"p"},"HybridCLR/Settings")," to start the global installation, and the environment variable ",(0,l.kt)("inlineCode",{parentName:"p"},"UNITY_IL2CPP_PATH")," will be cleared."),(0,l.kt)("p",null,"If you use the replacement directory for global installation, and your com.code-philosophy.hybridclr version >= 2.1.0, please run ",(0,l.kt)("inlineCode",{parentName:"p"},"HybridCLR/Generate/Il2cppDef")," before overriding libil2cpp ",(0,l.kt)("strong",{parentName:"p"},"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. ",(0,l.kt)("strong",{parentName:"p"},"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"),"."),(0,l.kt)("p",null,"Due to permissions, even if it is installed globally, the ",(0,l.kt)("inlineCode",{parentName:"p"},"Generate/xxx")," command modifies the files under the local ",(0,l.kt)("inlineCode",{parentName:"p"},"{project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp/libil2cpp"),". ",(0,l.kt)("strong",{parentName:"p"},"Please overwrite the local libil2cpp directory with the global installation directory after each generate"),"."),(0,l.kt)("p",null,"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:"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"Windows platform. Open the command line window with administrator privileges, delete or rename the original libil2cpp, and then run ",(0,l.kt)("inlineCode",{parentName:"li"},'mklink /D "" "{project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp/libil2cpp" '),"."),(0,l.kt)("li",{parentName:"ul"},"Linux or Mac platform. Open the command line window with administrator privileges, delete or rename the original libil2cpp, and then run ",(0,l.kt)("inlineCode",{parentName:"li"},'ln -s "{project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp/libil2cpp" "" '),".")),(0,l.kt)("p",null,"For the 2019 version replace Unity.IL2CPP.dll, also use a method similar to the above replacement or soft link."),(0,l.kt)("h2",{id:"precautions"},"Precautions"),(0,l.kt)("p",null,"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."))}d.isMDXComponent=!0},5568:(e,A,i)=>{i.d(A,{Z:()=>t});const t="data:image/jpeg;base64,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88888888888888888888888888888888888888oAQAsIA0Q8gk888888888888888888888888888888888888888oAU888888888888888888888888888888888888888888888888oApU/wA+9HItHYfPPPPPPJUMsEYEYEMAAMIAAAEYIFMMMMBAGDBAFAPLPPLHPPPPDPPPPPPPLL/vvvvvvjb/AM+8/wD+vMMMMMMMc888884A8www8w0888888888888888888888888888888888888888888oAsAQgwAE888888888888888888888888888888888888888888oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8QAJhEAAgEDAwMEAwAAAAAAAAAAAAFhEZHRceHxIUHwEDAxgECBof/aAAgBAwEBPxBudWiFEKIUQohRCiFCSXRfSpqI6RV9hO+wukVZX3RXT49LVVRrqevsLiuiXY1r7GpfYU19jWua1zWua1zUvsal9jWvsa19hN1ab/eDxt5PG3k8beTxt5Ene8nxyrX+U/Jg9ipknBIOQeTkHk5B5OQeTkHk5B5OQeRpVXGSCQSLkgkXJBOJAzqm5+h3/8QAHxEAAwEAAgIDAQAAAAAAAAAAAAFhESExMEFAUYBx/9oACAECAQE/EEpYmVZVlWVZVlWVY23y/wAVLrxi2NE+/sat5Gu4POcmW+GY3s79mzBPh/HgSLyKqqqZVKlSpc2dfJ9W+BJiEiaJomiaJomiaG70iRIkSJEiRI4RPwd//8QAKhABAAIBAwIFBQEBAQEAAAAAAQARITFh8EFREHGRsdFggaHB8SAwUOH/2gAIAQEAAT8QSwBjotXP4itQLijC3uE+0QuwcGEaSW6oNRha3qj7SuqyCMWGB1ymkB5UEBhdPaJkiiCl9gZWzURtWyXnVdtYeISl5drsXtLMFGowFt6ifacISlZethRpqXXBPRgi3qh946S+4MI0k4QnCEAKtp03fo7Ln9aqTg+3pHC/uHYaC6LzsOZM3iLGue06xWn9xs092c96iUeN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"},8648:(e,A,i)=>{i.d(A,{Z:()=>t});const t="data:image/jpeg;base64,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AvMUOgzyUGdvgJCyHaJ5IPt/3QnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmEBZUFEEMBNjCbGENv0iMLSqTJZ5VroJQYAYgKGg1g0QtofNYlXbKOeWqwAfmbGE2MJsYQYiAQBf40O9fqMNR8dXf6jDUfHd3gmKABVU/aH1P2h9T9ofU/aH1P2h9T9ofU/aH1P2h9T9ofU/aH1P2h9QGKQJVX0sNR8afWsfm/6H8D2ZJaiQGMvMK0mMcI3G43nROCACNEkdIFV6o3+6gC/k/ipBZkeQVqCJTPqAKpKT9ySf4fi/6P0sNR8afWsGDAlgiPgWncP8ncP8ncP8gRhigMdDx+QA/qNhIKuEhBIGolmAOS7F0OhGxRBBU0gIUUgJoIAVEGQS64oABQWAncP8ncP8ncP8hgxBiyHg35+lhqPjobwlololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololvjsb/Ursb/Ursb/Urub/AFK6m/1K6m/1K6m/1K//2Q=="},5283:(e,A,i)=>{i.d(A,{Z:()=>t});const t=i.p+"assets/images/install_hybridclrunity_package-9a53b1ee8f7ffd8a700ed1f977ca74e3.jpg"},1093:(e,A,i)=>{i.d(A,{Z:()=>t});const t=i.p+"assets/images/install_version-bafc326c19c3b969342179d820ead842.jpg"},7441:(e,A,i)=>{i.d(A,{Z:()=>t});const t=i.p+"assets/images/merge_hybridclr_dir-04680fdb60dccd43bfd2593b4affd10e.jpg"},8581:(e,A,i)=>{i.d(A,{Z:()=>t});const t=i.p+"assets/images/select_il2cpp_modules-d895c3fb5390e04b53e40ada2b422239.jpg"}}]); \ No newline at end of file diff --git a/en/assets/js/c225ef2b.259b48ea.js b/en/assets/js/c225ef2b.259b48ea.js new file mode 100644 index 00000000..6058208e --- /dev/null +++ b/en/assets/js/c225ef2b.259b48ea.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[9731],{3905:(e,t,i)=>{i.d(t,{Zo:()=>d,kt:()=>m});var n=i(7294);function A(e,t,i){return t in e?Object.defineProperty(e,t,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[t]=i,e}function l(e,t){var i=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),i.push.apply(i,n)}return i}function a(e){for(var t=1;t=0||(A[i]=e[i]);return A}(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,i)&&(A[i]=e[i])}return A}var r=n.createContext({}),s=function(e){var t=n.useContext(r),i=t;return e&&(i="function"==typeof e?e(t):a(a({},t),e)),i},d=function(e){var t=s(e.components);return n.createElement(r.Provider,{value:t},e.children)},p="mdxType",c={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},u=n.forwardRef((function(e,t){var i=e.components,A=e.mdxType,l=e.originalType,r=e.parentName,d=o(e,["components","mdxType","originalType","parentName"]),p=s(i),u=A,m=p["".concat(r,".").concat(u)]||p[u]||c[u]||l;return i?n.createElement(m,a(a({ref:t},d),{},{components:i})):n.createElement(m,a({ref:t},d))}));function m(e,t){var i=arguments,A=t&&t.mdxType;if("string"==typeof e||A){var l=i.length,a=new Array(l);a[0]=u;var o={};for(var r in t)hasOwnProperty.call(t,r)&&(o[r]=t[r]);o.originalType=e,o[p]="string"==typeof e?e:A,a[1]=o;for(var s=2;s{i.r(t),i.d(t,{assets:()=>r,contentTitle:()=>a,default:()=>c,frontMatter:()=>l,metadata:()=>o,toc:()=>s});var n=i(7462),A=(i(7294),i(3905));const l={},a="Hybridclr Package",o={unversionedId:"basic/com.code-philosophy.hybridclr",id:"basic/com.code-philosophy.hybridclr",title:"Hybridclr Package",description:"com.code-philosophy.hybridclr is a Unity package that provides the Editor workflow tool script and Runtime script required by HybridCLR. with the help of",source:"@site/i18n/en/docusaurus-plugin-content-docs/current/basic/com.code-philosophy.hybridclr.md",sourceDirName:"basic",slug:"/basic/com.code-philosophy.hybridclr",permalink:"/en/docs/basic/com.code-philosophy.hybridclr",draft:!1,tags:[],version:"current",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"Unsupported Features",permalink:"/en/docs/basic/notsupportedfeatures"},next:{title:"Best Practices",permalink:"/en/docs/basic/bestpractice"}},r={},s=[{value:"HybridCLR menu introduction",id:"hybridclr-menu-introduction",level:2},{value:"Installer...",id:"installer",level:3},{value:"Compile Dll",id:"compile-dll",level:3},{value:"Generate",id:"generate",level:3},{value:"Generate/Il2CppDef",id:"generateil2cppdef",level:3},{value:"Generate/LinkXml",id:"generatelinkxml",level:3},{value:"Generate/AotDlls",id:"generateaotdlls",level:3},{value:"Generate/MethodBridge",id:"generatemethodbridge",level:3},{value:"Generate/AOTGenericReference",id:"generateaotgenericreference",level:3},{value:"Generate/ReversePInvokeWrapper",id:"generatereversepinvokewrapper",level:3},{value:"Generate/All",id:"generateall",level:3},{value:"HybridCLR configuration",id:"hybridclr-configuration",level:2},{value:"enable",id:"enable",level:3},{value:"useGlobalIl2cpp",id:"useglobalil2cpp",level:3},{value:"hybridclrRepoURL",id:"hybridclrrepourl",level:3},{value:"il2cppPlusRepoURL",id:"il2cppplusrepourl",level:3},{value:"hotUpdateAssemblyDefinitions",id:"hotupdateassemblydefinitions",level:3},{value:"preserveHotUpdateAssemblies",id:"preservehotupdateassemblies",level:3},{value:"hotUpdateDllCompileOutputRootDir",id:"hotupdatedllcompileoutputrootdir",level:3},{value:"externalHotUpdateAssemblyDirs",id:"externalhotupdateassemblydirs",level:3},{value:"strippedAOTDllOutputRootDir",id:"strippedaotdlloutputrootdir",level:3},{value:"patchAOTA Assemblies",id:"patchaota-assemblies",level:3},{value:"outputLinkFile",id:"outputlinkfile",level:3},{value:"outputAOTGenericReferenceFile",id:"outputaotgenericreferencefile",level:3},{value:"maxGenericReferenceIteration",id:"maxgenericreferenceiteration",level:3},{value:"maxMethodBridgeGenericIteration",id:"maxmethodbridgegenericiteration",level:3},{value:"Build Pipeline related scripts",id:"build-pipeline-related-scripts",level:2},{value:"Check and fix settings",id:"check-and-fix-settings",level:3},{value:"Automatically exclude hot update assembly when packaging",id:"automatically-exclude-hot-update-assembly-when-packaging",level:3},{value:"Add the hot update dll name to the assembly list when packaging",id:"add-the-hot-update-dll-name-to-the-assembly-list-when-packaging",level:3},{value:"Backup trimmed AOT dll",id:"backup-trimmed-aot-dll",level:3},{value:"Runtime related scripts",id:"runtime-related-scripts",level:2},{value:"LoadImageErrorCode",id:"loadimageerrorcode",level:3},{value:"Metadata Mode HomologousImageMode",id:"metadata-mode-homologousimagemode",level:3},{value:"HomologousImageMode::Consistent mode",id:"homologousimagemodeconsistent-mode",level:4},{value:"HomologousImageMode::SuperSet mode",id:"homologousimagemodesuperset-mode",level:4},{value:"RuntimeApi",id:"runtimeapi",level:3},{value:"ReversePInvokeWrapperGenerationAttribute",id:"reversepinvokewrappergenerationattribute",level:3}],d={toc:s},p="wrapper";function c(e){let{components:t,...l}=e;return(0,A.kt)(p,(0,n.Z)({},d,l,{components:t,mdxType:"MDXLayout"}),(0,A.kt)("h1",{id:"hybridclr-package"},"Hybridclr Package"),(0,A.kt)("p",null,(0,A.kt)("inlineCode",{parentName:"p"},"com.code-philosophy.hybridclr")," is a Unity package that provides the Editor workflow tool script and Runtime script required by HybridCLR. with the help of\nThe workflow tool provided by com.code-philosophy.hybridclr makes it very easy to package an App that supports HybridCLR hot update function. The hybridclr_unity package mainly includes the following contents:"),(0,A.kt)("ul",null,(0,A.kt)("li",{parentName:"ul"},"Editor related scripts"),(0,A.kt)("li",{parentName:"ul"},"Runtime related scripts\n-iOSBuild script")),(0,A.kt)("admonition",{type:"caution"},(0,A.kt)("p",{parentName:"admonition"},"Before v3.0.0 the package name was ",(0,A.kt)("inlineCode",{parentName:"p"},"com.focus-creative-games.hybridclr_unity"),".")),(0,A.kt)("h2",{id:"hybridclr-menu-introduction"},"HybridCLR menu introduction"),(0,A.kt)("p",null,"The following submenus are all under the ",(0,A.kt)("inlineCode",{parentName:"p"},"HybridCLR")," menu in the menu bar. For the sake of simplification, we no longer include HybridCLR when we mention submenus below."),(0,A.kt)("h3",{id:"installer"},"Installer..."),(0,A.kt)("p",null,"A handy installer is provided to help correctly set up the local il2cpp directory, which contains a modified version replacing the ",(0,A.kt)("inlineCode",{parentName:"p"},"HybridCLRData/LocalIl2CppData-{platform}/il2cpp/libil2cpp")," directory with HybridCLR."),(0,A.kt)("p",null,"The installer needs to copy il2cpp (similar to ",(0,A.kt)("inlineCode",{parentName:"p"},"C:\\Program Files\\Unity\\Hub\\Editor\\2020.3.33f1\\Editor\\Data\\il2cpp"),") related files from the Unity installation directory of the matching version."),(0,A.kt)("ul",null,(0,A.kt)("li",{parentName:"ul"},"For 2019.4.40+, 2021.3.26+, 2021.3.0+, 2022.3.0+ versions, copy the il2cpp file directly from the installation directory of that version."),(0,A.kt)("li",{parentName:"ul"},"For versions 2020.3.16-2020.3.25, an additional version 2020.3.26 or later needs to be installed. After completing the installation in the Installer, switch back to the current version."),(0,A.kt)("li",{parentName:"ul"},"For the 2019.4.0-2019.4.39 version, you need to install the 2019.4.40 version additionally, and switch back to the current version after completing the installation in the Installer.")),(0,A.kt)("p",null,(0,A.kt)("inlineCode",{parentName:"p"},"Installation Status: Installed | Not Installed")," in the installation interface indicates whether HybridCLR initialization is complete. Click Install, if successful, the ",(0,A.kt)("inlineCode",{parentName:"p"},"Installation Successful")," log will be displayed at the end, and the installation status will switch to ",(0,A.kt)("inlineCode",{parentName:"p"},"Installed"),", otherwise please check the error log."),(0,A.kt)("admonition",{type:"tip"},(0,A.kt)("p",{parentName:"admonition"},"If HybridCLR is already installed, clicking the Install button will install the latest HybridCLR version of libil2cpp.")),(0,A.kt)("p",null,"The branch or tag compatible with hybridclr and il2cpp_plus corresponding to the current package version has been configured in the ",(0,A.kt)("inlineCode",{parentName:"p"},"Data~/hybridclr_version.json")," file in com.code-philosophy.hybridclr.\nThe Installer will install the version specified in the configuration, and no longer supports customizing the version to be installed."),(0,A.kt)("p",null,"The configuration looks like this:"),(0,A.kt)("pre",null,(0,A.kt)("code",{parentName:"pre",className:"language-json"},'{\n "versions": [\n {\n "unity_version": "2019",\n "hybridclr" : { "branch": "v2.0.1"},\n "il2cpp_plus": { "branch": "v2019-2.0.1"}\n },\n {\n "unity_version": "2020",\n "hybridclr" : { "branch": "v2.0.1"},\n "il2cpp_plus": { "branch": "v2020-2.0.1"}\n },\n {\n "unity_version": "2021",\n "hybridclr" : { "branch": "v2.0.1"},\n "il2cpp_plus": { "branch": "v2021-2.0.1"}\n }\n ]\n}\n')),(0,A.kt)("p",null,"If you must install other versions of hybridclr or il2cpp_plus, modify the branch in the configuration file to be the target branch or tag."),(0,A.kt)("p",null,(0,A.kt)("img",{alt:"install_default",src:i(8648).Z,width:"803",height:"221"})),(0,A.kt)("p",null,"From version 2.3.1 onwards, it supports copying and installing directly from the libil2cpp directory that contains hybridclr made locally. If your network is not good, or git is not installed and you cannot download and install remotely from the warehouse, you can first ",(0,A.kt)("a",{parentName:"p",href:"https://github.com/focus-creative-games/il2cpp_plus"},"il2cpp_plus")," and ","[hybridclr]","(https:/ /github.com/focus-creative-games/hybridclr) is downloaded to the local, and then according to the document in the ",(0,A.kt)("strong",{parentName:"p"},"Installation Principle")," section below, the libil2cpp directory containing hybridclr is merged from these two warehouses, and then installed in ",(0,A.kt)("inlineCode",{parentName:"p"},"Installer")," Enable ",(0,A.kt)("inlineCode",{parentName:"p"},"Copy libil2cpp from local")," option in the interface, select the libil2cpp directory you made, and click ",(0,A.kt)("inlineCode",{parentName:"p"},"Install")," to execute the installation. As shown below."),(0,A.kt)("p",null,(0,A.kt)("img",{alt:"install",src:i(5568).Z,width:"802",height:"195"})),(0,A.kt)("h3",{id:"compile-dll"},"Compile Dll"),(0,A.kt)("p",null,"For each target, you must use the hot update dll compiled under the compile switch of the target platform, otherwise the hot update code will not match the code information of the AOT main package or hot update resources."),(0,A.kt)("p",null,"The ",(0,A.kt)("inlineCode",{parentName:"p"},"HybridCLR.Editor")," assembly of com.code-philosophy.hybridclr provides the ",(0,A.kt)("inlineCode",{parentName:"p"},"HybridCLR.Editor.Commands.CompileDllCommand.CompileDll(BuildTarget target)")," interface,\nIt is convenient for developers to compile hot update dll by themselves flexibly. After the compilation is completed, the hot update dll is placed in the ",(0,A.kt)("inlineCode",{parentName:"p"},"{project}/HybridCLRData/HotUpdateDlls/{platform}")," directory."),(0,A.kt)("h3",{id:"generate"},"Generate"),(0,A.kt)("p",null,"Generate contains the generation commands needed for packaging."),(0,A.kt)("h3",{id:"generateil2cppdef"},"Generate/Il2CppDef"),(0,A.kt)("p",null,"The hybridclr code needs to be compatible with multiple Unity versions, and macro definitions related to the current Unity version are required. The ",(0,A.kt)("inlineCode",{parentName:"p"},"Generate/Il2CppDef")," command generates relevant version macros and other necessary codes, and the generated codes are similar to the following."),(0,A.kt)("pre",null,(0,A.kt)("code",{parentName:"pre",className:"language-cpp"},"// hybridclr/generated/UnityVersion.h\n\n#define HYBRIDCLR_UNITY_VERSION 2020333\n#define HYBRIDCLR_UNITY_2020 1\n#define HYBRIDCLR_UNITY_2019_OR_NEW 1\n#define HYBRIDCLR_UNITY_2020_OR_NEW 1\n")),(0,A.kt)("h3",{id:"generatelinkxml"},"Generate/LinkXml"),(0,A.kt)("p",null,"Scan the AOT type referenced by the hot update dll, generate link.xml, and prevent the AOT type or function used by the hot update script from being clipped. The output file path is specified in the ",(0,A.kt)("inlineCode",{parentName:"p"},"OuputLinkXml")," field in HybridCLRSettings.asset, and the default is ",(0,A.kt)("inlineCode",{parentName:"p"},"LinkGenerator/link.xml"),"."),(0,A.kt)("p",null,"For a more specific introduction to clipping, please see ",(0,A.kt)("a",{parentName:"p",href:"/en/docs/basic/codestriping"},"Code Clipping Principles and Solutions"),"."),(0,A.kt)("h3",{id:"generateaotdlls"},"Generate/AotDlls"),(0,A.kt)("p",null,"Generate trimmed AOT dlls. The script achieves the goal of generating trimmed AOT dlls by exporting the project in a temporary directory. Generating AOT dlls depends on ",(0,A.kt)("inlineCode",{parentName:"p"},"Generate/LinkXml")," and ",(0,A.kt)("inlineCode",{parentName:"p"},"Generate/Il2CppDef"),".\nIf you did not use ",(0,A.kt)("inlineCode",{parentName:"p"},"HybridCLR/Generate/All")," such a one-click generation command, please run the following commands in sequence:"),(0,A.kt)("ul",null,(0,A.kt)("li",{parentName:"ul"},(0,A.kt)("inlineCode",{parentName:"li"},"HybridCLR/Generate/Il2CppDef")),(0,A.kt)("li",{parentName:"ul"},(0,A.kt)("inlineCode",{parentName:"li"},"HybridCLR/Generate/LinkXml")),(0,A.kt)("li",{parentName:"ul"},(0,A.kt)("inlineCode",{parentName:"li"},"HybridCLR/Generate/AotDlls"))),(0,A.kt)("h3",{id:"generatemethodbridge"},"Generate/MethodBridge"),(0,A.kt)("p",null,"Scan and generate bridge function files according to the current AOT dll set. For related documents, please see ",(0,A.kt)("a",{parentName:"p",href:"/en/docs/basic/methodbridge"},"bridge function"),"."),(0,A.kt)("p",null,"Generate bridge function depends on AOT dlls and hot update dlls. If you did not use ",(0,A.kt)("inlineCode",{parentName:"p"},"HybridCLR/Generate/All")," such a one-click generation command, please run the following commands in sequence:"),(0,A.kt)("ul",null,(0,A.kt)("li",{parentName:"ul"},(0,A.kt)("inlineCode",{parentName:"li"},"HybridCLR/Generate/Il2CppDef")),(0,A.kt)("li",{parentName:"ul"},(0,A.kt)("inlineCode",{parentName:"li"},"HybridCLR/Generate/LinkXml")," (implicitly calls ",(0,A.kt)("inlineCode",{parentName:"li"},"HybridCLR/CompileDll/ActiveBuildTarget"),")"),(0,A.kt)("li",{parentName:"ul"},(0,A.kt)("inlineCode",{parentName:"li"},"HybridCLR/Generate/AotDlls")),(0,A.kt)("li",{parentName:"ul"},(0,A.kt)("inlineCode",{parentName:"li"},"HybridCLR/Generate/MethodBridge"))),(0,A.kt)("h3",{id:"generateaotgenericreference"},"Generate/AOTGenericReference"),(0,A.kt)("p",null,"Scan all generated AOT generic types and function instantiations according to the current hot update dll, and generate a ",(0,A.kt)("strong",{parentName:"p"},"inspired")," generic instantiation file ",(0,A.kt)("inlineCode",{parentName:"p"},"AOTGenericReferences.cs"),".\nSince it is troublesome to convert the scanned generic types and functions into corresponding code references, all generated generic instantiation codes are ",(0,A.kt)("strong",{parentName:"p"},"comment code"),"."),(0,A.kt)("p",null,"The ",(0,A.kt)("inlineCode",{parentName:"p"},"AOTGenericReferences.cs")," file also contains a list of assemblies that should be supplemented with metadata, similar to the following, so that developers can quickly know which metadata should be supplemented without running the game."),(0,A.kt)("pre",null,(0,A.kt)("code",{parentName:"pre",className:"language-csharp"},"// {{ AOT assemblies\n// Main.dll\n// System. Core. dll\n// UnityEngine.CoreModule.dll\n// mscorlib.dll\n// }}\n")),(0,A.kt)("p",null,"Please add instantiation references to generic types and functions in other files, as this output file will be overwritten every time it is regenerated.\nThis generic instantiation documentation is only for inspiration, telling you which classes and functions can be instantiated with aot generics.\nFor more specific AOT generic related documents, please see ",(0,A.kt)("a",{parentName:"p",href:"/en/docs/basic/aotgeneric"},"AOT Generic Introduction"),"."),(0,A.kt)("admonition",{type:"tip"},(0,A.kt)("p",{parentName:"admonition"},"After using the supplementary metadata mechanism, ",(0,A.kt)("strong",{parentName:"p"},"does not process")," and does not affect normal operation. But if you manually instantiate aot generics, you can improve performance. The suggestion is to manually instantiate a small number of performance-sensitive classes or functions, such as ",(0,A.kt)("inlineCode",{parentName:"p"},"Dictionary"),".")),(0,A.kt)("p",null,"It is at the discretion of the developer to convert to the correct instantiation reference (",(0,A.kt)("strong",{parentName:"p"},"this operation is optional, it can be completely ignored or only partially processed"),"), that is, instantiate the generic class or generic in this annotation in the AOT code type function. The method is roughly as follows:"),(0,A.kt)("pre",null,(0,A.kt)("code",{parentName:"pre",className:"language-csharp"},"\n // System.Collections.Generics.List`1.ctor\n new List();\n\n // System.Byte[] Array.Empty`1()\n Array. Empty();\n\n")),(0,A.kt)("h3",{id:"generatereversepinvokewrapper"},"Generate/ReversePInvokeWrapper"),(0,A.kt)("p",null,"Generate a ReversePInvokeWrapper function for hot-updated C# static functions marked with ",(0,A.kt)("inlineCode",{parentName:"p"},"[MonoPInvokeCallback]")," annotation. Please refer to the document ",(0,A.kt)("a",{parentName:"p",href:"/en/docs/basic/workwithscriptlanguage"},"MonoPInvokeCallback Support")," for the specific introduction of MonoPInvokeCallback"),(0,A.kt)("h3",{id:"generateall"},"Generate/All"),(0,A.kt)("p",null,"One-click execution of necessary generation operations before packaging."),(0,A.kt)("h2",{id:"hybridclr-configuration"},"HybridCLR configuration"),(0,A.kt)("p",null,"Click the menu ",(0,A.kt)("inlineCode",{parentName:"p"},"HybridCLR/Settings")," to open the configuration interface. The fields are detailed below."),(0,A.kt)("h3",{id:"enable"},"enable"),(0,A.kt)("p",null,"Whether to enable HybridCLR hot update. The default is true. If false, the packaging no longer includes HybridCLR functionality."),(0,A.kt)("admonition",{type:"caution"},(0,A.kt)("p",{parentName:"admonition"},"If HybridCLR is disabled, please also remove the reference to the HybridCLR.Runtime assembly in the main project, otherwise there will be errors such as missing symbols such as ",(0,A.kt)("inlineCode",{parentName:"p"},"RuntimeApi::LoadMetadataForAOTAssembly")," when packaging.")),(0,A.kt)("h3",{id:"useglobalil2cpp"},"useGlobalIl2cpp"),(0,A.kt)("p",null,"Whether to use the global installation location, that is, the il2cpp directory under the editor installation location. The default is false. Generally, ",(0,A.kt)("inlineCode",{parentName:"p"},"useGlobalIl2cpp=true")," is only required when packaging WebGL."),(0,A.kt)("p",null,"Note that even if ",(0,A.kt)("inlineCode",{parentName:"p"},"useGlobalIl2Cpp=true"),", il2cpp will still be copied to the HybridCLRData directory during installation. Before copying, you need to run ",(0,A.kt)("inlineCode",{parentName:"p"},"HybridCLR/Generate/Il2CppDef")," to generate the version macro,\nThen manually replace the ",(0,A.kt)("inlineCode",{parentName:"p"},"{project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp/libil2cpp")," directory with the corresponding directory under the editor installation directory.\nIn addition, every time you run ",(0,A.kt)("inlineCode",{parentName:"p"},"HybridCLR/Generate/*")," to execute the generation operation, the output directory is still the local directory, and you need to manually copy and replace the libil2cpp directory in the global installation location."),(0,A.kt)("h3",{id:"hybridclrrepourl"},"hybridclrRepoURL"),(0,A.kt)("p",null,"The address of the hybridclr warehouse, the default value is ",(0,A.kt)("inlineCode",{parentName:"p"},"https://gitee.com/focus-creative-games/hybridclr"),". When installing the Installer, clone the hybridclr warehouse code from this address."),(0,A.kt)("h3",{id:"il2cppplusrepourl"},"il2cppPlusRepoURL"),(0,A.kt)("p",null,"The address of the il2cpp_plus warehouse, the default value is ",(0,A.kt)("inlineCode",{parentName:"p"},"https://gitee.com/focus-creative-games/il2cpp_plus"),". When the Installer is installed, clone the il2cpp_plus warehouse code from this address."),(0,A.kt)("h3",{id:"hotupdateassemblydefinitions"},"hotUpdateAssemblyDefinitions"),(0,A.kt)("p",null,"The list of hot update modules defined in the form of assembly definition (asmdef) is equivalent to ",(0,A.kt)("inlineCode",{parentName:"p"},"hotUpdateAssemblies")," below, but it is more convenient to drag and drop asmdef modules in the editor, and it is not easy to make mistakes and write wrong names."),(0,A.kt)("admonition",{type:"caution"},(0,A.kt)("p",{parentName:"admonition"},(0,A.kt)("inlineCode",{parentName:"p"},"hotUpdateAssemblyDefinitions")," and ",(0,A.kt)("inlineCode",{parentName:"p"},"hotUpdateAssemblies")," are combined to form the final hot update dll list. The same assembly should not appear in two lists at the same time, an error will be reported!")),(0,A.kt)("p",null,"###hotUpdateAssemblies"),(0,A.kt)("p",null,"Some assemblies exist in the form of dll, such as the hot update dll you created in an external project, or you directly use Assembly-CSharp as your hot update dll. Since there is no corresponding asmdef file, it can only be manually configured in the form of dll name.\nDo not include the '.dll' suffix when filling in the assembly name, just like ",(0,A.kt)("inlineCode",{parentName:"p"},"Main"),", ",(0,A.kt)("inlineCode",{parentName:"p"},"Assembly-CSharp"),". Assembly in asmdef form, you can also choose not to add it to ",(0,A.kt)("inlineCode",{parentName:"p"},"hotUpdateAssemblyDefinitions"),",\nInstead, add to ",(0,A.kt)("inlineCode",{parentName:"p"},"hotUpdateAssemblies"),". But this is not as convenient as directly dragging into the list, you can choose at your own discretion."),(0,A.kt)("p",null,(0,A.kt)("inlineCode",{parentName:"p"},"hotUpdateAssemblyDefinitions")," and ",(0,A.kt)("inlineCode",{parentName:"p"},"hotUpdateAssemblies")," are combined to form the final hot update dll list. The same assembly should not appear in two lists at the same time, an error will be reported!"),(0,A.kt)("h3",{id:"preservehotupdateassemblies"},"preserveHotUpdateAssemblies"),(0,A.kt)("p",null,"A list of reserved hot update dll names. Sometimes I want to add some hot update dlls in the future, and expect the scripts of these new hot update dlls to be mounted on resources. If you directly add the hot update dll name to hotUpdateAssemblies, an error will be reported that the assembly does not exist.\nThe preserveHotUpdateAssemblies field is used to meet this requirement. These dlls are not checked for validity when packaging and will be added to an assembly list file like scriptingassemblies.json.\nDo not include the ",(0,A.kt)("inlineCode",{parentName:"p"},".dll")," suffix when filling in the assembly name, just like ",(0,A.kt)("inlineCode",{parentName:"p"},"Assembly-CSharp"),"."),(0,A.kt)("h3",{id:"hotupdatedllcompileoutputrootdir"},"hotUpdateDllCompileOutputRootDir"),(0,A.kt)("p",null,"The output root directory of the compiled hot update dll. The final output directory is under the platform subdirectory of this directory, namely ",(0,A.kt)("inlineCode",{parentName:"p"},"${hotUpdateDllCompileOutputRootDir}/{platform}"),"."),(0,A.kt)("h3",{id:"externalhotupdateassemblydirs"},"externalHotUpdateAssemblyDirs"),(0,A.kt)("p",null,"Customize the search path for external hot update dlls. Some hot update projects of frameworks or projects are placed outside Unity, and the compiled dll is also outside. This parameter provides a hot update dll\nThe search path, so that there is no need to copy the external dll to the project or to the hotUpdateAssemblies directory every time."),(0,A.kt)("ul",null,(0,A.kt)("li",{parentName:"ul"},"Search in the order of the search path, the higher the priority, the higher the priority."),(0,A.kt)("li",{parentName:"ul"},"The search path must be a relative location, relative to the project root directory (that is, the ",(0,A.kt)("strong",{parentName:"li"},"parent directory")," of Assets). That is, fill in ",(0,A.kt)("inlineCode",{parentName:"li"},"mydir")," and search for ",(0,A.kt)("inlineCode",{parentName:"li"},"{proj}/mydir"),"."),(0,A.kt)("li",{parentName:"ul"},"For each path ",(0,A.kt)("inlineCode",{parentName:"li"},"dir"),", it will first try to search ",(0,A.kt)("inlineCode",{parentName:"li"},"{dir}/{platform}"),", and then try to search ",(0,A.kt)("inlineCode",{parentName:"li"},"{dir}"),". This is done in order to take into account the specificity and versatility of the platform.")),(0,A.kt)("p",null,"An example of usage is shown below. You have an external dll at ",(0,A.kt)("inlineCode",{parentName:"p"},"{proj}/MyDir1/MyDir2/Foo.dll"),", then you should:"),(0,A.kt)("ul",null,(0,A.kt)("li",{parentName:"ul"},"Add ",(0,A.kt)("inlineCode",{parentName:"li"},"Foo")," to hotUpdateAssemblies"),(0,A.kt)("li",{parentName:"ul"},"Add directory ",(0,A.kt)("inlineCode",{parentName:"li"},"MyDir1/Mydir2")," in externalHotUpdateAssemblyDirs")),(0,A.kt)("h3",{id:"strippedaotdlloutputrootdir"},"strippedAOTDllOutputRootDir"),(0,A.kt)("p",null,"Staging directory for trimmed AOT dlls. The final directory is under the platform subdirectory of this directory, namely ",(0,A.kt)("inlineCode",{parentName:"p"},"${strippedAOTDllOutputRootDir}/{platform}"),"."),(0,A.kt)("h3",{id:"patchaota-assemblies"},"patchAOTA Assemblies"),(0,A.kt)("p",null,"Supplementary metadata AOT dll list. ",(0,A.kt)("strong",{parentName:"p"},"package itself does not use this configuration item"),". It provides a place to configure the AOT dll list, which is convenient for developers to use in their own building pipeline, so that developers do not need to define a supplementary metadata AOT dll configuration script separately.\nDo not include the '.dll' suffix when filling in the assembly name, just like ",(0,A.kt)("inlineCode",{parentName:"p"},"Main"),", ",(0,A.kt)("inlineCode",{parentName:"p"},"Assembly-CSharp"),"."),(0,A.kt)("h3",{id:"outputlinkfile"},"outputLinkFile"),(0,A.kt)("p",null,"When running the menu ",(0,A.kt)("inlineCode",{parentName:"p"},"HybridCLR/Generate/LinkXml")," command, the output link.xml file path."),(0,A.kt)("admonition",{type:"danger"},(0,A.kt)("p",{parentName:"admonition"},"Do not point to ",(0,A.kt)("inlineCode",{parentName:"p"},"Assets/link.xml"),", that link.xml is generally used to manually reserve the AOT type, and this automatically output link.xml will be overwritten every time.")),(0,A.kt)("h3",{id:"outputaotgenericreferencefile"},"outputAOTGenericReferenceFile"),(0,A.kt)("p",null,"The path of the AOT generic instantiation assembly file output when running the menu ",(0,A.kt)("inlineCode",{parentName:"p"},"HybridCLR/Generate/AOTGenericReference"),"."),(0,A.kt)("h3",{id:"maxgenericreferenceiteration"},"maxGenericReferenceIteration"),(0,A.kt)("p",null,"When running the menu ",(0,A.kt)("inlineCode",{parentName:"p"},"HybridCLR/Generate/AOTGenericReference"),", the generation tool recursively analyzes the number of iterations of AOT generic instantiation."),(0,A.kt)("p",null,"Because new generic classes and generic functions may be used indirectly in generic functions, multiple rounds of iterations are required to analyze all generic instantiations. The ",(0,A.kt)("inlineCode",{parentName:"p"},"maxGenericReferenceIteration")," parameter is used to control the number of iterations. This parameter is generally within 10 enough, you can observe the log\nIt can be seen that the calculation terminates after several rounds of iterations. If there are still a large number of uncalculated iterations of generics when the iteration terminates, this value can be increased appropriately."),(0,A.kt)("p",null,"Why not iterate until all generic instantiations are computed? Because there may be situations that can never be calculated. The following code, AOT.Show()\nDue to recursive generic instantiation, it can never be calculated."),(0,A.kt)("pre",null,(0,A.kt)("code",{parentName:"pre",className:"language-csharp"},"\n struct AOT\n {\n\n public void Show()\n {\n var a = new AOT>();\n a.Show();\n }\n }\n\n")),(0,A.kt)("h3",{id:"maxmethodbridgegenericiteration"},"maxMethodBridgeGenericIteration"),(0,A.kt)("p",null,"When running the menu ",(0,A.kt)("inlineCode",{parentName:"p"},"HybridCLR/Generate/MethodBridge"),", the generation tool recursively analyzes the number of iterations of AOT generic instantiation. The meaning is similar to ",(0,A.kt)("inlineCode",{parentName:"p"},"maxGenericReferenceIteration"),"."),(0,A.kt)("h2",{id:"build-pipeline-related-scripts"},"Build Pipeline related scripts"),(0,A.kt)("p",null,"It mainly includes the following functions:"),(0,A.kt)("ul",null,(0,A.kt)("li",{parentName:"ul"},"Check and fix settings"),(0,A.kt)("li",{parentName:"ul"},"Automatically exclude hot update assembly when packaging"),(0,A.kt)("li",{parentName:"ul"},"Add the hot update dll name to the assembly list when packaging"),(0,A.kt)("li",{parentName:"ul"},"backup trimmed AOT dll")),(0,A.kt)("h3",{id:"check-and-fix-settings"},"Check and fix settings"),(0,A.kt)("p",null,"It is part of the packaging workflow, and the relevant code is in ",(0,A.kt)("inlineCode",{parentName:"p"},"Editor/BuildProcessors/CheckSettings.cs"),". Contains the following actions:"),(0,A.kt)("ul",null,(0,A.kt)("li",{parentName:"ul"},"Set or clear the UNITY_IL2CPP_PATH environment variable according to whether HybridCLR is enabled. The UNITY_IL2CPP_PATH environment variable modified in the script is the environment variable of this process, so don't worry about interfering with other projects."),(0,A.kt)("li",{parentName:"ul"},"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. WebGL platforms ignore this option. ",(0,A.kt)("strong",{parentName:"li"},"com.code-philosophy.hybridclr will automatically turn off this option, you don't have to do it manually"),"."),(0,A.kt)("li",{parentName:"ul"},(0,A.kt)("inlineCode",{parentName:"li"},"Scripting Backend")," is switched to ",(0,A.kt)("inlineCode",{parentName:"li"},"il2cpp"),", WebGL platform does not need to set this option. ",(0,A.kt)("strong",{parentName:"li"},"Since ",(0,A.kt)("inlineCode",{parentName:"strong"},"v2.4.0"),", this option is set automatically, you can do it without manually"),"."),(0,A.kt)("li",{parentName:"ul"},(0,A.kt)("inlineCode",{parentName:"li"},"Api Compatability Level")," switched to ",(0,A.kt)("inlineCode",{parentName:"li"},".NetFramework 4")," (Unity 2019, 2020) or ",(0,A.kt)("inlineCode",{parentName:"li"},".Net Framework")," (Unity 2021+). ",(0,A.kt)("strong",{parentName:"li"},"Since ",(0,A.kt)("inlineCode",{parentName:"strong"},"v2.4.0"),", this option is set automatically, you can do it without manually"),"."),(0,A.kt)("li",{parentName:"ul"},"If no hot update assembly is set in HybridCLRSettings, an error will be displayed.")),(0,A.kt)("h3",{id:"automatically-exclude-hot-update-assembly-when-packaging"},"Automatically exclude hot update assembly when packaging"),(0,A.kt)("p",null,"It is part of the packaging workflow, and the relevant code is in ",(0,A.kt)("inlineCode",{parentName:"p"},"Editor/BuildProcessors/FilterHotFixAssemblies.cs"),"."),(0,A.kt)("p",null,"Obviously, the hot update assembly should not be processed by il2cpp and compiled into the final package body. We handle the ",(0,A.kt)("inlineCode",{parentName:"p"},"IFilterBuildAssemblies")," callback,\nRemove the hot update dll from the list of build assemblies. The script will additionally check whether the name of the assembly is wrongly written, and whether there is a duplicate assembly configured by mistake."),(0,A.kt)("h3",{id:"add-the-hot-update-dll-name-to-the-assembly-list-when-packaging"},"Add the hot update dll name to the assembly list when packaging"),(0,A.kt)("p",null,"It is part of the packaging workflow, and the relevant code is in ",(0,A.kt)("inlineCode",{parentName:"p"},"Editor/BuildProcessors/PatchScriptingAssemblyList.cs"),"."),(0,A.kt)("p",null,"When the tool is packaged, it will automatically add the dll name of the hot update assembly to the assembly list configuration file. The dll name of the assembly where the hot update MonoBehaviour script is located must be added to the assembly list configuration file,\nUnity's resource management system can correctly identify and restore hot update scripts. For a more detailed introduction to the principle, please see ",(0,A.kt)("a",{parentName:"p",href:"/en/docs/basic/monobehaviour"},"Using Hot Update MonoBehaviour")," ."),(0,A.kt)("h3",{id:"backup-trimmed-aot-dll"},"Backup trimmed AOT dll"),(0,A.kt)("p",null,"It is part of the packaging workflow, and the relevant code is in ",(0,A.kt)("inlineCode",{parentName:"p"},"Editor/BuildProcessors/CopyStrippedAOTAssemblies.cs"),"."),(0,A.kt)("p",null,"When the supplementary metadata mode is ",(0,A.kt)("inlineCode",{parentName:"p"},"HomologousImageMode::Consistent"),", the cropped AOT dll generated during packaging needs to be used. Therefore, the cropped AOT dll generated during the building pipeline will be automatically\nCopy it to the ",(0,A.kt)("inlineCode",{parentName:"p"},"{project}/HybridCLRData/AssembliesPostIl2CppStrip/{platform}")," directory for future processing. When the data mode is ",(0,A.kt)("inlineCode",{parentName:"p"},"HomologousImageMode::SuperSet"),",\nThe original aot dll can be used directly. The advantage of this is that the workflow is more convenient, and there is no need to update the aot dll after each package. The disadvantage is that it takes up more memory, and at the same time greatly increases the size of the trimmed dll. Please use the original or trimmed aot dll."),(0,A.kt)("p",null,"##iOSBuild-script"),(0,A.kt)("p",null,(0,A.kt)("inlineCode",{parentName:"p"},"Editor/Data~/iOSBuild")," in the package contains the scripts needed to compile the iOS version libil2cpp.a. After running the ",(0,A.kt)("inlineCode",{parentName:"p"},"HybridCLR/Installer...")," menu command to successfully initialize HybridCLR, it will be automatically copied to the ",(0,A.kt)("inlineCode",{parentName:"p"},"{project}/HybridCLRData/iOSBuild")," directory.\n",(0,A.kt)("strong",{parentName:"p"}," Subsequent operations must be performed in the ",(0,A.kt)("inlineCode",{parentName:"strong"},"{project}/HybridCLRData/iOSBuild")," directory"),". For the specific operation of compiling libil2cpp.a, please refer to the document ",(0,A.kt)("a",{parentName:"p",href:"/en/docs/basic/buildpipeline"},"iOS Platform Packaging"),"."),(0,A.kt)("h2",{id:"runtime-related-scripts"},"Runtime related scripts"),(0,A.kt)("p",null,"Contains classes used at runtime."),(0,A.kt)("h3",{id:"loadimageerrorcode"},"LoadImageErrorCode"),(0,A.kt)("p",null,"Error code of loading hot update dll."),(0,A.kt)("h3",{id:"metadata-mode-homologousimagemode"},"Metadata Mode HomologousImageMode"),(0,A.kt)("p",null,"Two metadata schemas are currently supported:"),(0,A.kt)("h4",{id:"homologousimagemodeconsistent-mode"},(0,A.kt)("inlineCode",{parentName:"h4"},"HomologousImageMode::Consistent")," mode"),(0,A.kt)("p",null,"That is, the supplementary dll is exactly the same as the trimmed dll when packaging. Therefore, the clipped dll generated during the build process must be used, and the original dll cannot be copied directly. We added processing code in ",(0,A.kt)("inlineCode",{parentName:"p"},"HybridCLR.BuildProcessors.CopyStrippedAOTAssemblies"),", automatically copy these clipped dlls to the ",(0,A.kt)("inlineCode",{parentName:"p"},"{project}/HybridCLRData/AssembliesPostIl2CppStrip/{target}")," directory when packaging."),(0,A.kt)("h4",{id:"homologousimagemodesuperset-mode"},(0,A.kt)("inlineCode",{parentName:"h4"},"HomologousImageMode::SuperSet")," mode"),(0,A.kt)("p",null,"That is, the supplementary dll is a superset of the trimmed dll when packaging, and contains all the metadata of the trimmed dll. One of the easiest superset dlls to get is the original aot dll, which is also the recommended superset dll."),(0,A.kt)("ul",null,(0,A.kt)("li",{parentName:"ul"},"The original UnityEngine-related AOT dll is in the PlayBackEngines subdirectory of the Unity installation directory"),(0,A.kt)("li",{parentName:"ul"},"The original .net core AOT dll such as mscorlib.dll is in the ",(0,A.kt)("inlineCode",{parentName:"li"},"unityaot{xxx}")," directory of the Unity installation directory. 2019-2020 will be unified into the unityaot directory, and will be split into multiple directories starting from 2021. If you package android, take unityaot-linux, and if you package iOS, take unityaot-macos."),(0,A.kt)("li",{parentName:"ul"},"The AOT dll of the plug-in is the original dll of the corresponding platform in the project directory. If it is in the form of source code, it is a compiled dll, just take the corresponding dll in the ",(0,A.kt)("inlineCode",{parentName:"li"},"HybridCLR/HotUpdateDlls/{platform}")," directory")),(0,A.kt)("p",null,"Take the Win64 target under Win of Unity 2020.3.33 version as an example:"),(0,A.kt)("ul",null,(0,A.kt)("li",{parentName:"ul"},"mscorlib.dll in ",(0,A.kt)("inlineCode",{parentName:"li"},"{editor}/Editor/Data/MonoBleedingEdge/lib/mono/unityaot")),(0,A.kt)("li",{parentName:"ul"},"UnityEngine.CoreModule.dll in ",(0,A.kt)("inlineCode",{parentName:"li"},"{editor}/Editor/Data/Playbackengines/windowsstandalonesupport/Variations/il2cpp/Managed")),(0,A.kt)("li",{parentName:"ul"},"protobuf-net.dll is the original ",(0,A.kt)("inlineCode",{parentName:"li"},"protobuf-net.dll")," in your project"),(0,A.kt)("li",{parentName:"ul"},"The AOT dll corresponding to your AOT module Main is ",(0,A.kt)("inlineCode",{parentName:"li"},"HybridCLR/HotUpdateDlls/{platform}/Main.dll"))),(0,A.kt)("p",null,"The ",(0,A.kt)("inlineCode",{parentName:"p"},"SuerSet")," mode can also use the trimmed dll of the ",(0,A.kt)("inlineCode",{parentName:"p"},"Consistent")," mode, since it obviously contains all the metadata for itself."),(0,A.kt)("h3",{id:"runtimeapi"},"RuntimeApi"),(0,A.kt)("p",null,"The underlying tool class for operating HybridCLR. The more commonly used ones are:"),(0,A.kt)("ul",null,(0,A.kt)("li",{parentName:"ul"},(0,A.kt)("inlineCode",{parentName:"li"},"LoadImageErrorCode LoadMetadataForAOTAssembly(byte[] dllBytes, HomologousImageMode mode)")," is used to load supplementary metadata assembly.")),(0,A.kt)("h3",{id:"reversepinvokewrappergenerationattribute"},"ReversePInvokeWrapperGenerationAttribute"),(0,A.kt)("p",null,"If a scripting language such as xlua is used in the project, the ",(0,A.kt)("inlineCode",{parentName:"p"},"[MonoPInvokeCallback]")," annotation needs to be added to the C# function to be registered in lua. This returns a corresponding C++ for these C# functions\nFunction pointer, used to register in the scripting language. HybridCLR supports registering hot-updated C# code in lua, but the C++ stub function corresponding to ",(0,A.kt)("inlineCode",{parentName:"p"},"[MonoPInvokeCallback]")," must be generated in advance to return a corresponding C++ function pointer for each C# function.\nThe script provides the function of automatically generating stub functions. For details, see ",(0,A.kt)("a",{parentName:"p",href:"/en/docs/basic/workwithscriptlanguage"},"MonoPInvokeCallback support")," and ",(0,A.kt)("a",{parentName:"p",href:"/en/docs/basic/workwithscriptlanguage"},"HybridCLR+lua/js/python")," documents"),(0,A.kt)("p",null,"Each function with the ",(0,A.kt)("inlineCode",{parentName:"p"},"[MonoPInvokeCallback]")," attribute requires a unique corresponding wrapper function. These wrapper functions must be pre-generated during packaging and cannot be changed.\nTherefore, if a function with the ",(0,A.kt)("inlineCode",{parentName:"p"},"[MonoPInvokeCallback]")," feature is added in subsequent hot updates, there will be insufficient wrapper functions. ReversePInvokeWrapperGenerationAttribute\nIt is used to reserve the specified number of wrapper functions for the functions currently added with the ",(0,A.kt)("inlineCode",{parentName:"p"},"[MonoPInvokeCallback]")," feature. In the following example, 10 wrapper functions are reserved for functions signed by LuaFunction."),(0,A.kt)("pre",null,(0,A.kt)("code",{parentName:"pre",className:"language-csharp"}," delegate int LuaFunction(IntPtr luaState);\n\n public class MonoPInvokeWrapperPreserves\n {\n [ReversePInvokeWrapperGeneration(10)]\n [MonoPInvokeCallback(typeof(LuaFunction))]\n public static int LuaCallback(IntPtr luaState)\n {\n return 0;\n }\n\n [MonoPInvokeCallback(typeof(Func))]\n public static int Sum(int a, int b)\n {\n return a + b;\n }\n\n [MonoPInvokeCallback(typeof(Func))]\n public static int Sum2(int a, int b)\n {\n return a + b;\n }\n\n [MonoPInvokeCallback(typeof(Func))]\n public static int Sum3()\n {\n return 0;\n }\n }\n")))}c.isMDXComponent=!0},5568:(e,t,i)=>{i.d(t,{Z:()=>n});const n="data:image/jpeg;base64,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88888888888888888888888888888888888888oAQAsIA0Q8gk888888888888888888888888888888888888888oAU888888888888888888888888888888888888888888888888oApU/wA+9HItHYfPPPPPPJUMsEYEYEMAAMIAAAEYIFMMMMBAGDBAFAPLPPLHPPPPDPPPPPPPLL/vvvvvvjb/AM+8/wD+vMMMMMMMc888884A8www8w0888888888888888888888888888888888888888888oAsAQgwAE888888888888888888888888888888888888888888oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8QAJhEAAgEDAwMEAwAAAAAAAAAAAAFhEZHRceHxIUHwEDAxgECBof/aAAgBAwEBPxBudWiFEKIUQohRCiFCSXRfSpqI6RV9hO+wukVZX3RXT49LVVRrqevsLiuiXY1r7GpfYU19jWua1zWua1zUvsal9jWvsa19hN1ab/eDxt5PG3k8beTxt5Ene8nxyrX+U/Jg9ipknBIOQeTkHk5B5OQeTkHk5B5OQeRpVXGSCQSLkgkXJBOJAzqm5+h3/8QAHxEAAwEAAgIDAQAAAAAAAAAAAAFhESExMEFAUYBx/9oACAECAQE/EEpYmVZVlWVZVlWVY23y/wAVLrxi2NE+/sat5Gu4POcmW+GY3s79mzBPh/HgSLyKqqqZVKlSpc2dfJ9W+BJiEiaJomiaJomiaG70iRIkSJEiRI4RPwd//8QAKhABAAIBAwIFBQEBAQEAAAAAAQARITFh8EFREHGRsdFggaHB8SAwUOH/2gAIAQEAAT8QSwBjotXP4itQLijC3uE+0QuwcGEaSW6oNRha3qj7SuqyCMWGB1ymkB5UEBhdPaJkiiCl9gZWzURtWyXnVdtYeISl5drsXtLMFGowFt6ifacISlZethRpqXXBPRgi3qh946S+4MI0k4QnCEAKtp03fo7Ln9aqTg+3pHC/uHYaC6LzsOZM3iLGue06xWn9xs092c96iUeNpeTkvFuh11g4TAIANCcB3hKpwq5FIAYRydZnAzYQtBfUC/JhhUqwfZVOTSVWi3i7inwtFd8da4W64e0EJIdBTgZAKw5v7x1SgGsTkGwKvAyGL0NpHMuWtBIV1TEyreT0L7Qlow1epAwECqydA9xe9ZiSmhDTSaLNFNhLULFajkrhsaIod4VqXVcFggFA4ND7xTr28hqWLnBIYYXRKY1tbsa9Hfw0vJ7v0do5/HWjDzEcr+5Um6UBFI/c30gbLAqC3ZsarrFaf1WzR2Zx3qBR421ZOCs06PTTwVcLMWgaAO86NKtlpaWlpaWlpaWlpaWlpaijVYnEJxCcQnEJxCcQnEJxCcQnEJxCcQnEJxCcQnEJxCcQnEJxCcQnEJxCcQnEJxCcQnEJxCcQnEJxCcQnEJxCcQnEJxCcQnEJxCcQnEJxCcQnEJxCcQnEJxCcQnEJxCcQnEJxCcQnEJxCcQnEJxCcQnEJxCcQnEJxCcQnEJxCcQnEJxCcQnEJxCcQnEJxCcQnEJxCcQmHDzFd5XeV3ld5XeV3ld5XeV3ld5XeV3ld5XeV3ld5XeV3ld5XeV3ld5XeV3ld5XeV3ld5XeV3ld5XeV3ld5XeV3ld5XeV3ld5XeV3ld5XeV3ld5XeV3ld5XeV3ld5XeV3ld5XeV3ld5XeV3ld5XeV3ld5XeV3ld5XeV3ld5XeV3ld5XeV3ld5XeV3ld5XeV3ld5XeV3ld5XeV3ld5XeV3ld5XeV3ld5XeV3geyF1Gw0Cy/WdQHtFMNDQz2lDBYNQdzw0eeYBcEjU2qjroxMhkK78nePoEgpDZ67QYDtSVR84pVFuRydDsZlcIrGL5EoPmjtL02dI2vGkMC2sPQh8oF26V1dbMSUby3LHd5ZIf3bQDyd/pYFKhBKq+o9ICOiaC2WlK17TriCigzoeGhzzFvMjzZbMHSyaLBmhgyUgt6NmtQu5NLofZMXRS8ssHUqaYAGtjLbfaVZjhWbTWVhTziLLFpjWsl1poMRUfPu1UZLRIqMHEWrIwZ1SE1+i4iah5kyq2iu1o0rOj9S5ekggdb2ZqhTt9MGp4aPPP1GNTw0eefqManhp88xTEeQWjtsf7iXqsRxSI7l09BMwO8hCWuHlZtmJXViaCiHdenz6ECiADA4Chuh3LzMAREK1C3w7YjaXowRVnnk9YJrPpb6h2v88epaJnHUjtk9SUBZQLK0nvl5daghuEFqFmwxkCsq+cCuCCMVVQ9RB5cdJVxo1Ma1vp4ULIZ3myWPuk61OuuBr1BXdgxdUXrWtLQqAZr2oU9zcj2vOFtJDsU10Xj6QNTw0+eYV15YgBrGt+ydfYTNXhoqrzVaQTBgSL24MmDSnX7LZtYnBBjHQnUPKrk6p17XcnVa6zGaVBopooc2jbnaWkauU7a0wBp2uAKu28y0e1IcUQS2NMldUO9a47Bt345SmpQM9ukocg7c0iqappVOPwNhPUWgnGVzvHlDgy7kGQpXTOet6eBknS0GKS+kaVDtKFeo1qxTw5NDZtAF3jOOveBB7mjoZpQUYrPfsy7uOnC2GtA1rrX0ganhp88/UY1PDT55+oxqeGlzzGOmWlAHdXBFTjBp6joj1PGxkBb0FD3SVhaACNFgmup6yxgNwga1D2z0gFKIORUlgho7PiZagAOrMvjppF+ZjxUaiu4Sr9z18VGoruEq/c9fBPojBAWnZ7TUwFPSherC1fT/vaoKW1hqGxQFjkS+i+EGzhs5bKWz/wE0E714MGL/EkkwbOGzlspbOGz8CDbw2/gU3h+1+wP+QggggggggmBvFZQlUelRIsBCIWg2pfVoNc1rKIcovQJr1EYY3tyNLRGqBG9KgsJYC0CwKJY6dpTke8pyPeU5HvKcj3lOR7ynI95Tke8pyPeU5HvCqWCXkfeKhQK2nCvmcK+YWFexoO6rR94hYG10JARvIo07zhXzOFfM4V8zhXzOFfM4V8zhXzC4Ug1d9Xw0OeYJTIQkgEtxeDneVMEAHMqHKrzplrMUKi4JqhjoXWtMy1jaoCqNgIOywrMmC6r8d8C/aYQRxxeLnVVoa11qWBkpL6g88Hylcee1VRRWlttZt2mIERCegVhV6DpmK7ZLYh1XYXm+pB6eXDdWY01TrcAFguCp1Ouh86YZSYIi2imNh8yzUJhClVfkDeHHGbilvt6FmFxZtDAcsGrdPmWb21l1ekSEptobL1Bl7zKUyE083ljHl4PK8VGsOkp2cUzHcIW6yW3V6v/fRw0ZTrXqti/wA3/wCHV4ut5ioTDo2umSjcxLIACmSZjoDL0BL6QsYa4eyu6sAOjpKEI6QYLQgl5TWjpMToQl6BKtjKYaavrmmwgfENV9DuYjSlHQdSzX4r/fvpzXbxAe0iIClqBwcveYhKc5reRotx1vtcFCH1ToOBxpr+Y2xByi0lGT0xnOmksqwBTMgpWuKKUG8VSCstOAjRVi1rVv8Ax+L93w0OefEGBUF0KWD+X1lKAK3pXv8A5WISknZKT0WPWKCdSxr1D0/5bOCQIoJDqL/7gaYISFZVH2n9x8xKC56mj8zin6nFP1OIfqYa4nlOYfqcU/U4p+pxT9Tin6nMP1M98DynFP1OYfqcU/Ur4H4haDftY9Km363zNnl5zb9b5m3y8/Bs+nLzm363zMPL3nH/APU2eXnNv1vmbfrfM/uPmf3HzMenrfM2eXnGyQpalPvKfwlP4Sn8JT+EKEOOtIEH7C9ZYa4y0NBfvT0lP4Sn8JT+Ep/CU/hKfwlP4Qndq7y2ICi1fvNj1PmbHqfM2PU+Zsep8zY9T5mx6nzNj1PmbHqfM2PU+Zsep8zY9T5hlrYMKK8NDnn6jGp4aHPP1GNTw0OeYPsqgUn94+J/ePif3j4iATaowpSrLrc9Z/ePiYr/ADnxGEaAqFOAtdO0uClDTRhLHTtP7x8T+8fE/vHxH7LolOdPpY1PDQ55j837H+OxL61C03CGyzOgPUxZgQ0u6iIg0pYoLoLZrfSGMOOQJWt7s4X/ACf/ABfs/SxqeGhzzAFJW80/U5j4nMfE5j4nMfED0HHlOY+JfqOPKcx8TmPicx8TmPiEWSN4t+vpY1PDm+/1K5vv9SuZ7/Urme/1K5nv9Sv/2Q=="},8648:(e,t,i)=>{i.d(t,{Z:()=>n});const n="data:image/jpeg;base64,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AvMUOgzyUGdvgJCyHaJ5IPt/3QnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmcGZwZnBmEBZUFEEMBNjCbGENv0iMLSqTJZ5VroJQYAYgKGg1g0QtofNYlXbKOeWqwAfmbGE2MJsYQYiAQBf40O9fqMNR8dXf6jDUfHd3gmKABVU/aH1P2h9T9ofU/aH1P2h9T9ofU/aH1P2h9T9ofU/aH1P2h9QGKQJVX0sNR8afWsfm/6H8D2ZJaiQGMvMK0mMcI3G43nROCACNEkdIFV6o3+6gC/k/ipBZkeQVqCJTPqAKpKT9ySf4fi/6P0sNR8afWsGDAlgiPgWncP8ncP8ncP8gRhigMdDx+QA/qNhIKuEhBIGolmAOS7F0OhGxRBBU0gIUUgJoIAVEGQS64oABQWAncP8ncP8ncP8hgxBiyHg35+lhqPjobwlololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololvjsb/Ursb/Ursb/Urub/AFK6m/1K6m/1K6m/1K//2Q=="}}]); \ No newline at end of file diff --git a/en/assets/js/c225ef2b.5cdabdac.js b/en/assets/js/c225ef2b.5cdabdac.js deleted file mode 100644 index a7b15041..00000000 --- a/en/assets/js/c225ef2b.5cdabdac.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[9731],{3905:(e,t,n)=>{n.d(t,{Zo:()=>p,kt:()=>m});var i=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 l(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);t&&(i=i.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,i)}return n}function o(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(i=0;i=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var s=i.createContext({}),d=function(e){var t=i.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},p=function(e){var t=d(e.components);return i.createElement(s.Provider,{value:t},e.children)},c="mdxType",u={inlineCode:"code",wrapper:function(e){var t=e.children;return i.createElement(i.Fragment,{},t)}},h=i.forwardRef((function(e,t){var n=e.components,a=e.mdxType,l=e.originalType,s=e.parentName,p=r(e,["components","mdxType","originalType","parentName"]),c=d(n),h=a,m=c["".concat(s,".").concat(h)]||c[h]||u[h]||l;return n?i.createElement(m,o(o({ref:t},p),{},{components:n})):i.createElement(m,o({ref:t},p))}));function m(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var l=n.length,o=new Array(l);o[0]=h;var r={};for(var s in t)hasOwnProperty.call(t,s)&&(r[s]=t[s]);r.originalType=e,r[c]="string"==typeof e?e:a,o[1]=r;for(var d=2;d{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>u,frontMatter:()=>l,metadata:()=>r,toc:()=>d});var i=n(7462),a=(n(7294),n(3905));const l={},o="Hybridclr Package",r={unversionedId:"basic/com.code-philosophy.hybridclr",id:"basic/com.code-philosophy.hybridclr",title:"Hybridclr Package",description:"com.code-philosophy.hybridclr is a Unity package that provides the Editor workflow tool script and Runtime script required by HybridCLR. with the help of",source:"@site/i18n/en/docusaurus-plugin-content-docs/current/basic/com.code-philosophy.hybridclr.md",sourceDirName:"basic",slug:"/basic/com.code-philosophy.hybridclr",permalink:"/en/docs/basic/com.code-philosophy.hybridclr",draft:!1,tags:[],version:"current",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"Unsupported Features",permalink:"/en/docs/basic/notsupportedfeatures"},next:{title:"Best Practices",permalink:"/en/docs/basic/bestpractice"}},s={},d=[{value:"HybridCLR menu introduction",id:"hybridclr-menu-introduction",level:2},{value:"Installer...",id:"installer",level:3},{value:"Compile Dll",id:"compile-dll",level:3},{value:"Generate",id:"generate",level:3},{value:"Generate/Il2CppDef",id:"generateil2cppdef",level:3},{value:"Generate/LinkXml",id:"generatelinkxml",level:3},{value:"Generate/AotDlls",id:"generateaotdlls",level:3},{value:"Generate/MethodBridge",id:"generatemethodbridge",level:3},{value:"Generate/AOTGenericReference",id:"generateaotgenericreference",level:3},{value:"Generate/ReversePInvokeWrapper",id:"generatereversepinvokewrapper",level:3},{value:"Generate/All",id:"generateall",level:3},{value:"HybridCLR configuration",id:"hybridclr-configuration",level:2},{value:"enable",id:"enable",level:3},{value:"useGlobalIl2cpp",id:"useglobalil2cpp",level:3},{value:"hybridclrRepoURL",id:"hybridclrrepourl",level:3},{value:"il2cppPlusRepoURL",id:"il2cppplusrepourl",level:3},{value:"hotUpdateAssemblyDefinitions",id:"hotupdateassemblydefinitions",level:3},{value:"preserveHotUpdateAssemblies",id:"preservehotupdateassemblies",level:3},{value:"hotUpdateDllCompileOutputRootDir",id:"hotupdatedllcompileoutputrootdir",level:3},{value:"externalHotUpdateAssemblyDirs",id:"externalhotupdateassemblydirs",level:3},{value:"strippedAOTDllOutputRootDir",id:"strippedaotdlloutputrootdir",level:3},{value:"patchAOTA Assemblies",id:"patchaota-assemblies",level:3},{value:"outputLinkFile",id:"outputlinkfile",level:3},{value:"outputAOTGenericReferenceFile",id:"outputaotgenericreferencefile",level:3},{value:"maxGenericReferenceIteration",id:"maxgenericreferenceiteration",level:3},{value:"maxMethodBridgeGenericIteration",id:"maxmethodbridgegenericiteration",level:3},{value:"Build Pipeline related scripts",id:"build-pipeline-related-scripts",level:2},{value:"Check and fix settings",id:"check-and-fix-settings",level:3},{value:"Automatically exclude hot update assembly when packaging",id:"automatically-exclude-hot-update-assembly-when-packaging",level:3},{value:"Add the hot update dll name to the assembly list when packaging",id:"add-the-hot-update-dll-name-to-the-assembly-list-when-packaging",level:3},{value:"Backup trimmed AOT dll",id:"backup-trimmed-aot-dll",level:3},{value:"Runtime related scripts",id:"runtime-related-scripts",level:2},{value:"LoadImageErrorCode",id:"loadimageerrorcode",level:3},{value:"Metadata Mode HomologousImageMode",id:"metadata-mode-homologousimagemode",level:3},{value:"HomologousImageMode::Consistent mode",id:"homologousimagemodeconsistent-mode",level:4},{value:"HomologousImageMode::SuperSet mode",id:"homologousimagemodesuperset-mode",level:4},{value:"RuntimeApi",id:"runtimeapi",level:3},{value:"ReversePInvokeWrapperGenerationAttribute",id:"reversepinvokewrappergenerationattribute",level:3}],p={toc:d},c="wrapper";function u(e){let{components:t,...l}=e;return(0,a.kt)(c,(0,i.Z)({},p,l,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("h1",{id:"hybridclr-package"},"Hybridclr Package"),(0,a.kt)("p",null,(0,a.kt)("inlineCode",{parentName:"p"},"com.code-philosophy.hybridclr")," is a Unity package that provides the Editor workflow tool script and Runtime script required by HybridCLR. with the help of\nThe workflow tool provided by com.code-philosophy.hybridclr makes it very easy to package an App that supports HybridCLR hot update function. The hybridclr_unity package mainly includes the following contents:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Editor related scripts"),(0,a.kt)("li",{parentName:"ul"},"Runtime related scripts\n-iOSBuild script")),(0,a.kt)("admonition",{type:"caution"},(0,a.kt)("p",{parentName:"admonition"},"Before v3.0.0 the package name was ",(0,a.kt)("inlineCode",{parentName:"p"},"com.focus-creative-games.hybridclr_unity"),".")),(0,a.kt)("h2",{id:"hybridclr-menu-introduction"},"HybridCLR menu introduction"),(0,a.kt)("p",null,"The following submenus are all under the ",(0,a.kt)("inlineCode",{parentName:"p"},"HybridCLR")," menu in the menu bar. For the sake of simplification, we no longer include HybridCLR when we mention submenus below."),(0,a.kt)("h3",{id:"installer"},"Installer..."),(0,a.kt)("p",null,"A handy installer is provided to help correctly set up the local il2cpp directory, which contains a modified version replacing the ",(0,a.kt)("inlineCode",{parentName:"p"},"HybridCLRData/LocalIl2CppData-{platform}/il2cpp/libil2cpp")," directory with HybridCLR."),(0,a.kt)("p",null,"The installer needs to copy il2cpp (similar to ",(0,a.kt)("inlineCode",{parentName:"p"},"C:\\Program Files\\Unity\\Hub\\Editor\\2020.3.33f1\\Editor\\Data\\il2cpp"),") related files from the Unity installation directory of the matching version."),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"For 2019.4.40+, 2021.3.26+, 2021.3.0+, 2022.3.0+ versions, copy the il2cpp file directly from the installation directory of that version."),(0,a.kt)("li",{parentName:"ul"},"For versions 2020.3.16-2020.3.25, an additional version 2020.3.26 or later needs to be installed. After completing the installation in the Installer, switch back to the current version."),(0,a.kt)("li",{parentName:"ul"},"For the 2019.4.0-2019.4.39 version, you need to install the 2019.4.40 version additionally, and switch back to the current version after completing the installation in the Installer.")),(0,a.kt)("p",null,(0,a.kt)("inlineCode",{parentName:"p"},"Installation Status: Installed | Not Installed")," in the installation interface indicates whether HybridCLR initialization is complete. Click Install, if successful, the ",(0,a.kt)("inlineCode",{parentName:"p"},"Installation Successful")," log will be displayed at the end, and the installation status will switch to ",(0,a.kt)("inlineCode",{parentName:"p"},"Installed"),", otherwise please check the error log."),(0,a.kt)("admonition",{type:"tip"},(0,a.kt)("p",{parentName:"admonition"},"If HybridCLR is already installed, clicking the Install button will install the latest HybridCLR version of libil2cpp.")),(0,a.kt)("p",null,"The branch or tag compatible with hybridclr and il2cpp_plus corresponding to the current package version has been configured in the ",(0,a.kt)("inlineCode",{parentName:"p"},"Data~/hybridclr_version.json")," file in com.code-philosophy.hybridclr.\nThe Installer will install the version specified in the configuration, and no longer supports customizing the version to be installed."),(0,a.kt)("p",null,"The configuration looks like this:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-json"},'{\n "versions": [\n {\n "unity_version": "2019",\n "hybridclr" : { "branch": "v2.0.1"},\n "il2cpp_plus": { "branch": "v2019-2.0.1"}\n },\n {\n "unity_version": "2020",\n "hybridclr" : { "branch": "v2.0.1"},\n "il2cpp_plus": { "branch": "v2020-2.0.1"}\n },\n {\n "unity_version": "2021",\n "hybridclr" : { "branch": "v2.0.1"},\n "il2cpp_plus": { "branch": "v2021-2.0.1"}\n }\n ]\n}\n')),(0,a.kt)("p",null,"If you must install other versions of hybridclr or il2cpp_plus, modify the branch in the configuration file to be the target branch or tag."),(0,a.kt)("p",null,(0,a.kt)("img",{alt:"install_default",src:n(8648).Z,width:"802",height:"196"})),(0,a.kt)("p",null,"From version 2.3.1 onwards, it supports copying and installing directly from the libil2cpp directory that contains hybridclr made locally. If your network is not good, or git is not installed and you cannot download and install remotely from the warehouse, you can first ",(0,a.kt)("a",{parentName:"p",href:"https://github.com/focus-creative-games/il2cpp_plus"},"il2cpp_plus")," and ","[hybridclr]","(https:/ /github.com/focus-creative-games/hybridclr) is downloaded to the local, and then according to the document in the ",(0,a.kt)("strong",{parentName:"p"},"Installation Principle")," section below, the libil2cpp directory containing hybridclr is merged from these two warehouses, and then installed in ",(0,a.kt)("inlineCode",{parentName:"p"},"Installer")," Enable ",(0,a.kt)("inlineCode",{parentName:"p"},"Copy libil2cpp from local")," option in the interface, select the libil2cpp directory you made, and click ",(0,a.kt)("inlineCode",{parentName:"p"},"Install")," to execute the installation. As shown below."),(0,a.kt)("p",null,(0,a.kt)("img",{alt:"install",src:n(5568).Z,width:"814",height:"216"})),(0,a.kt)("h3",{id:"compile-dll"},"Compile Dll"),(0,a.kt)("p",null,"For each target, you must use the hot update dll compiled under the compile switch of the target platform, otherwise the hot update code will not match the code information of the AOT main package or hot update resources."),(0,a.kt)("p",null,"The ",(0,a.kt)("inlineCode",{parentName:"p"},"HybridCLR.Editor")," assembly of com.code-philosophy.hybridclr provides the ",(0,a.kt)("inlineCode",{parentName:"p"},"HybridCLR.Editor.Commands.CompileDllCommand.CompileDll(BuildTarget target)")," interface,\nIt is convenient for developers to compile hot update dll by themselves flexibly. After the compilation is completed, the hot update dll is placed in the ",(0,a.kt)("inlineCode",{parentName:"p"},"{project}/HybridCLRData/HotUpdateDlls/{platform}")," directory."),(0,a.kt)("h3",{id:"generate"},"Generate"),(0,a.kt)("p",null,"Generate contains the generation commands needed for packaging."),(0,a.kt)("h3",{id:"generateil2cppdef"},"Generate/Il2CppDef"),(0,a.kt)("p",null,"The hybridclr code needs to be compatible with multiple Unity versions, and macro definitions related to the current Unity version are required. The ",(0,a.kt)("inlineCode",{parentName:"p"},"Generate/Il2CppDef")," command generates relevant version macros and other necessary codes, and the generated codes are similar to the following."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cpp"},"// hybridclr/generated/UnityVersion.h\n\n#define HYBRIDCLR_UNITY_VERSION 2020333\n#define HYBRIDCLR_UNITY_2020 1\n#define HYBRIDCLR_UNITY_2019_OR_NEW 1\n#define HYBRIDCLR_UNITY_2020_OR_NEW 1\n")),(0,a.kt)("h3",{id:"generatelinkxml"},"Generate/LinkXml"),(0,a.kt)("p",null,"Scan the AOT type referenced by the hot update dll, generate link.xml, and prevent the AOT type or function used by the hot update script from being clipped. The output file path is specified in the ",(0,a.kt)("inlineCode",{parentName:"p"},"OuputLinkXml")," field in HybridCLRSettings.asset, and the default is ",(0,a.kt)("inlineCode",{parentName:"p"},"LinkGenerator/link.xml"),"."),(0,a.kt)("p",null,"For a more specific introduction to clipping, please see ",(0,a.kt)("a",{parentName:"p",href:"/en/docs/basic/codestriping"},"Code Clipping Principles and Solutions"),"."),(0,a.kt)("h3",{id:"generateaotdlls"},"Generate/AotDlls"),(0,a.kt)("p",null,"Generate trimmed AOT dlls. The script achieves the goal of generating trimmed AOT dlls by exporting the project in a temporary directory. Generating AOT dlls depends on ",(0,a.kt)("inlineCode",{parentName:"p"},"Generate/LinkXml")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"Generate/Il2CppDef"),".\nIf you did not use ",(0,a.kt)("inlineCode",{parentName:"p"},"HybridCLR/Generate/All")," such a one-click generation command, please run the following commands in sequence:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"HybridCLR/Generate/Il2CppDef")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"HybridCLR/Generate/LinkXml")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"HybridCLR/Generate/AotDlls"))),(0,a.kt)("h3",{id:"generatemethodbridge"},"Generate/MethodBridge"),(0,a.kt)("p",null,"Scan and generate bridge function files according to the current AOT dll set. For related documents, please see ",(0,a.kt)("a",{parentName:"p",href:"/en/docs/basic/methodbridge"},"bridge function"),"."),(0,a.kt)("p",null,"Generate bridge function depends on AOT dlls and hot update dlls. If you did not use ",(0,a.kt)("inlineCode",{parentName:"p"},"HybridCLR/Generate/All")," such a one-click generation command, please run the following commands in sequence:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"HybridCLR/Generate/Il2CppDef")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"HybridCLR/Generate/LinkXml")," (implicitly calls ",(0,a.kt)("inlineCode",{parentName:"li"},"HybridCLR/CompileDll/ActiveBuildTarget"),")"),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"HybridCLR/Generate/AotDlls")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"HybridCLR/Generate/MethodBridge"))),(0,a.kt)("h3",{id:"generateaotgenericreference"},"Generate/AOTGenericReference"),(0,a.kt)("p",null,"Scan all generated AOT generic types and function instantiations according to the current hot update dll, and generate a ",(0,a.kt)("strong",{parentName:"p"},"inspired")," generic instantiation file ",(0,a.kt)("inlineCode",{parentName:"p"},"AOTGenericReferences.cs"),".\nSince it is troublesome to convert the scanned generic types and functions into corresponding code references, all generated generic instantiation codes are ",(0,a.kt)("strong",{parentName:"p"},"comment code"),"."),(0,a.kt)("p",null,"The ",(0,a.kt)("inlineCode",{parentName:"p"},"AOTGenericReferences.cs")," file also contains a list of assemblies that should be supplemented with metadata, similar to the following, so that developers can quickly know which metadata should be supplemented without running the game."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-csharp"},"// {{ AOT assemblies\n// Main.dll\n// System. Core. dll\n// UnityEngine.CoreModule.dll\n// mscorlib.dll\n// }}\n")),(0,a.kt)("p",null,"Please add instantiation references to generic types and functions in other files, as this output file will be overwritten every time it is regenerated.\nThis generic instantiation documentation is only for inspiration, telling you which classes and functions can be instantiated with aot generics.\nFor more specific AOT generic related documents, please see ",(0,a.kt)("a",{parentName:"p",href:"/en/docs/basic/aotgeneric"},"AOT Generic Introduction"),"."),(0,a.kt)("admonition",{type:"tip"},(0,a.kt)("p",{parentName:"admonition"},"After using the supplementary metadata mechanism, ",(0,a.kt)("strong",{parentName:"p"},"does not process")," and does not affect normal operation. But if you manually instantiate aot generics, you can improve performance. The suggestion is to manually instantiate a small number of performance-sensitive classes or functions, such as ",(0,a.kt)("inlineCode",{parentName:"p"},"Dictionary"),".")),(0,a.kt)("p",null,"It is at the discretion of the developer to convert to the correct instantiation reference (",(0,a.kt)("strong",{parentName:"p"},"this operation is optional, it can be completely ignored or only partially processed"),"), that is, instantiate the generic class or generic in this annotation in the AOT code type function. The method is roughly as follows:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-csharp"},"\n // System.Collections.Generics.List`1.ctor\n new List();\n\n // System.Byte[] Array.Empty`1()\n Array. Empty();\n\n")),(0,a.kt)("h3",{id:"generatereversepinvokewrapper"},"Generate/ReversePInvokeWrapper"),(0,a.kt)("p",null,"Generate a ReversePInvokeWrapper function for hot-updated C# static functions marked with ",(0,a.kt)("inlineCode",{parentName:"p"},"[MonoPInvokeCallback]")," annotation. Please refer to the document ",(0,a.kt)("a",{parentName:"p",href:"/en/docs/basic/workwithscriptlanguage"},"MonoPInvokeCallback Support")," for the specific introduction of MonoPInvokeCallback"),(0,a.kt)("h3",{id:"generateall"},"Generate/All"),(0,a.kt)("p",null,"One-click execution of necessary generation operations before packaging."),(0,a.kt)("h2",{id:"hybridclr-configuration"},"HybridCLR configuration"),(0,a.kt)("p",null,"Click the menu ",(0,a.kt)("inlineCode",{parentName:"p"},"HybridCLR/Settings")," to open the configuration interface. The fields are detailed below."),(0,a.kt)("h3",{id:"enable"},"enable"),(0,a.kt)("p",null,"Whether to enable HybridCLR hot update. The default is true. If false, the packaging no longer includes HybridCLR functionality."),(0,a.kt)("admonition",{type:"caution"},(0,a.kt)("p",{parentName:"admonition"},"If HybridCLR is disabled, please also remove the reference to the HybridCLR.Runtime assembly in the main project, otherwise there will be errors such as missing symbols such as ",(0,a.kt)("inlineCode",{parentName:"p"},"RuntimeApi::LoadMetadataForAOTAssembly")," when packaging.")),(0,a.kt)("h3",{id:"useglobalil2cpp"},"useGlobalIl2cpp"),(0,a.kt)("p",null,"Whether to use the global installation location, that is, the il2cpp directory under the editor installation location. The default is false. Generally, ",(0,a.kt)("inlineCode",{parentName:"p"},"useGlobalIl2cpp=true")," is only required when packaging WebGL."),(0,a.kt)("p",null,"Note that even if ",(0,a.kt)("inlineCode",{parentName:"p"},"useGlobalIl2Cpp=true"),", il2cpp will still be copied to the HybridCLRData directory during installation. Before copying, you need to run ",(0,a.kt)("inlineCode",{parentName:"p"},"HybridCLR/Generate/Il2CppDef")," to generate the version macro,\nThen manually replace the ",(0,a.kt)("inlineCode",{parentName:"p"},"{project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp/libil2cpp")," directory with the corresponding directory under the editor installation directory.\nIn addition, every time you run ",(0,a.kt)("inlineCode",{parentName:"p"},"HybridCLR/Generate/*")," to execute the generation operation, the output directory is still the local directory, and you need to manually copy and replace the libil2cpp directory in the global installation location."),(0,a.kt)("h3",{id:"hybridclrrepourl"},"hybridclrRepoURL"),(0,a.kt)("p",null,"The address of the hybridclr warehouse, the default value is ",(0,a.kt)("inlineCode",{parentName:"p"},"https://gitee.com/focus-creative-games/hybridclr"),". When installing the Installer, clone the hybridclr warehouse code from this address."),(0,a.kt)("h3",{id:"il2cppplusrepourl"},"il2cppPlusRepoURL"),(0,a.kt)("p",null,"The address of the il2cpp_plus warehouse, the default value is ",(0,a.kt)("inlineCode",{parentName:"p"},"https://gitee.com/focus-creative-games/il2cpp_plus"),". When the Installer is installed, clone the il2cpp_plus warehouse code from this address."),(0,a.kt)("h3",{id:"hotupdateassemblydefinitions"},"hotUpdateAssemblyDefinitions"),(0,a.kt)("p",null,"The list of hot update modules defined in the form of assembly definition (asmdef) is equivalent to ",(0,a.kt)("inlineCode",{parentName:"p"},"hotUpdateAssemblies")," below, but it is more convenient to drag and drop asmdef modules in the editor, and it is not easy to make mistakes and write wrong names."),(0,a.kt)("admonition",{type:"caution"},(0,a.kt)("p",{parentName:"admonition"},(0,a.kt)("inlineCode",{parentName:"p"},"hotUpdateAssemblyDefinitions")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"hotUpdateAssemblies")," are combined to form the final hot update dll list. The same assembly should not appear in two lists at the same time, an error will be reported!")),(0,a.kt)("p",null,"###hotUpdateAssemblies"),(0,a.kt)("p",null,"Some assemblies exist in the form of dll, such as the hot update dll you created in an external project, or you directly use Assembly-CSharp as your hot update dll. Since there is no corresponding asmdef file, it can only be manually configured in the form of dll name.\nDo not include the '.dll' suffix when filling in the assembly name, just like ",(0,a.kt)("inlineCode",{parentName:"p"},"Main"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"Assembly-CSharp"),". Assembly in asmdef form, you can also choose not to add it to ",(0,a.kt)("inlineCode",{parentName:"p"},"hotUpdateAssemblyDefinitions"),",\nInstead, add to ",(0,a.kt)("inlineCode",{parentName:"p"},"hotUpdateAssemblies"),". But this is not as convenient as directly dragging into the list, you can choose at your own discretion."),(0,a.kt)("p",null,(0,a.kt)("inlineCode",{parentName:"p"},"hotUpdateAssemblyDefinitions")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"hotUpdateAssemblies")," are combined to form the final hot update dll list. The same assembly should not appear in two lists at the same time, an error will be reported!"),(0,a.kt)("h3",{id:"preservehotupdateassemblies"},"preserveHotUpdateAssemblies"),(0,a.kt)("p",null,"A list of reserved hot update dll names. Sometimes I want to add some hot update dlls in the future, and expect the scripts of these new hot update dlls to be mounted on resources. If you directly add the hot update dll name to hotUpdateAssemblies, an error will be reported that the assembly does not exist.\nThe preserveHotUpdateAssemblies field is used to meet this requirement. These dlls are not checked for validity when packaging and will be added to an assembly list file like scriptingassemblies.json.\nDo not include the ",(0,a.kt)("inlineCode",{parentName:"p"},".dll")," suffix when filling in the assembly name, just like ",(0,a.kt)("inlineCode",{parentName:"p"},"Assembly-CSharp"),"."),(0,a.kt)("h3",{id:"hotupdatedllcompileoutputrootdir"},"hotUpdateDllCompileOutputRootDir"),(0,a.kt)("p",null,"The output root directory of the compiled hot update dll. The final output directory is under the platform subdirectory of this directory, namely ",(0,a.kt)("inlineCode",{parentName:"p"},"${hotUpdateDllCompileOutputRootDir}/{platform}"),"."),(0,a.kt)("h3",{id:"externalhotupdateassemblydirs"},"externalHotUpdateAssemblyDirs"),(0,a.kt)("p",null,"Customize the search path for external hot update dlls. Some hot update projects of frameworks or projects are placed outside Unity, and the compiled dll is also outside. This parameter provides a hot update dll\nThe search path, so that there is no need to copy the external dll to the project or to the hotUpdateAssemblies directory every time."),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Search in the order of the search path, the higher the priority, the higher the priority."),(0,a.kt)("li",{parentName:"ul"},"The search path must be a relative location, relative to the project root directory (that is, the ",(0,a.kt)("strong",{parentName:"li"},"parent directory")," of Assets). That is, fill in ",(0,a.kt)("inlineCode",{parentName:"li"},"mydir")," and search for ",(0,a.kt)("inlineCode",{parentName:"li"},"{proj}/mydir"),"."),(0,a.kt)("li",{parentName:"ul"},"For each path ",(0,a.kt)("inlineCode",{parentName:"li"},"dir"),", it will first try to search ",(0,a.kt)("inlineCode",{parentName:"li"},"{dir}/{platform}"),", and then try to search ",(0,a.kt)("inlineCode",{parentName:"li"},"{dir}"),". This is done in order to take into account the specificity and versatility of the platform.")),(0,a.kt)("p",null,"An example of usage is shown below. You have an external dll at ",(0,a.kt)("inlineCode",{parentName:"p"},"{proj}/MyDir1/MyDir2/Foo.dll"),", then you should:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Add ",(0,a.kt)("inlineCode",{parentName:"li"},"Foo")," to hotUpdateAssemblies"),(0,a.kt)("li",{parentName:"ul"},"Add directory ",(0,a.kt)("inlineCode",{parentName:"li"},"MyDir1/Mydir2")," in externalHotUpdateAssemblyDirs")),(0,a.kt)("h3",{id:"strippedaotdlloutputrootdir"},"strippedAOTDllOutputRootDir"),(0,a.kt)("p",null,"Staging directory for trimmed AOT dlls. The final directory is under the platform subdirectory of this directory, namely ",(0,a.kt)("inlineCode",{parentName:"p"},"${strippedAOTDllOutputRootDir}/{platform}"),"."),(0,a.kt)("h3",{id:"patchaota-assemblies"},"patchAOTA Assemblies"),(0,a.kt)("p",null,"Supplementary metadata AOT dll list. ",(0,a.kt)("strong",{parentName:"p"},"package itself does not use this configuration item"),". It provides a place to configure the AOT dll list, which is convenient for developers to use in their own building pipeline, so that developers do not need to define a supplementary metadata AOT dll configuration script separately.\nDo not include the '.dll' suffix when filling in the assembly name, just like ",(0,a.kt)("inlineCode",{parentName:"p"},"Main"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"Assembly-CSharp"),"."),(0,a.kt)("h3",{id:"outputlinkfile"},"outputLinkFile"),(0,a.kt)("p",null,"When running the menu ",(0,a.kt)("inlineCode",{parentName:"p"},"HybridCLR/Generate/LinkXml")," command, the output link.xml file path."),(0,a.kt)("admonition",{type:"danger"},(0,a.kt)("p",{parentName:"admonition"},"Do not point to ",(0,a.kt)("inlineCode",{parentName:"p"},"Assets/link.xml"),", that link.xml is generally used to manually reserve the AOT type, and this automatically output link.xml will be overwritten every time.")),(0,a.kt)("h3",{id:"outputaotgenericreferencefile"},"outputAOTGenericReferenceFile"),(0,a.kt)("p",null,"The path of the AOT generic instantiation assembly file output when running the menu ",(0,a.kt)("inlineCode",{parentName:"p"},"HybridCLR/Generate/AOTGenericReference"),"."),(0,a.kt)("h3",{id:"maxgenericreferenceiteration"},"maxGenericReferenceIteration"),(0,a.kt)("p",null,"When running the menu ",(0,a.kt)("inlineCode",{parentName:"p"},"HybridCLR/Generate/AOTGenericReference"),", the generation tool recursively analyzes the number of iterations of AOT generic instantiation."),(0,a.kt)("p",null,"Because new generic classes and generic functions may be used indirectly in generic functions, multiple rounds of iterations are required to analyze all generic instantiations. The ",(0,a.kt)("inlineCode",{parentName:"p"},"maxGenericReferenceIteration")," parameter is used to control the number of iterations. This parameter is generally within 10 enough, you can observe the log\nIt can be seen that the calculation terminates after several rounds of iterations. If there are still a large number of uncalculated iterations of generics when the iteration terminates, this value can be increased appropriately."),(0,a.kt)("p",null,"Why not iterate until all generic instantiations are computed? Because there may be situations that can never be calculated. The following code, AOT.Show()\nDue to recursive generic instantiation, it can never be calculated."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-csharp"},"\n struct AOT\n {\n\n public void Show()\n {\n var a = new AOT>();\n a.Show();\n }\n }\n\n")),(0,a.kt)("h3",{id:"maxmethodbridgegenericiteration"},"maxMethodBridgeGenericIteration"),(0,a.kt)("p",null,"When running the menu ",(0,a.kt)("inlineCode",{parentName:"p"},"HybridCLR/Generate/MethodBridge"),", the generation tool recursively analyzes the number of iterations of AOT generic instantiation. The meaning is similar to ",(0,a.kt)("inlineCode",{parentName:"p"},"maxGenericReferenceIteration"),"."),(0,a.kt)("h2",{id:"build-pipeline-related-scripts"},"Build Pipeline related scripts"),(0,a.kt)("p",null,"It mainly includes the following functions:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Check and fix settings"),(0,a.kt)("li",{parentName:"ul"},"Automatically exclude hot update assembly when packaging"),(0,a.kt)("li",{parentName:"ul"},"Add the hot update dll name to the assembly list when packaging"),(0,a.kt)("li",{parentName:"ul"},"backup trimmed AOT dll")),(0,a.kt)("h3",{id:"check-and-fix-settings"},"Check and fix settings"),(0,a.kt)("p",null,"It is part of the packaging workflow, and the relevant code is in ",(0,a.kt)("inlineCode",{parentName:"p"},"Editor/BuildProcessors/CheckSettings.cs"),". Contains the following actions:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Set or clear the UNITY_IL2CPP_PATH environment variable according to whether HybridCLR is enabled. The UNITY_IL2CPP_PATH environment variable modified in the script is the environment variable of this process, so don't worry about interfering with other projects."),(0,a.kt)("li",{parentName:"ul"},"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. WebGL platforms ignore this option. ",(0,a.kt)("strong",{parentName:"li"},"com.code-philosophy.hybridclr will automatically turn off this option, you don't have to do it manually"),"."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"Scripting Backend")," is switched to ",(0,a.kt)("inlineCode",{parentName:"li"},"il2cpp"),", WebGL platform does not need to set this option. ",(0,a.kt)("strong",{parentName:"li"},"Since ",(0,a.kt)("inlineCode",{parentName:"strong"},"v2.4.0"),", this option is set automatically, you can do it without manually"),"."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"Api Compatability Level")," switched to ",(0,a.kt)("inlineCode",{parentName:"li"},".NetFramework 4")," (Unity 2019, 2020) or ",(0,a.kt)("inlineCode",{parentName:"li"},".Net Framework")," (Unity 2021+). ",(0,a.kt)("strong",{parentName:"li"},"Since ",(0,a.kt)("inlineCode",{parentName:"strong"},"v2.4.0"),", this option is set automatically, you can do it without manually"),"."),(0,a.kt)("li",{parentName:"ul"},"If no hot update assembly is set in HybridCLRSettings, an error will be displayed.")),(0,a.kt)("h3",{id:"automatically-exclude-hot-update-assembly-when-packaging"},"Automatically exclude hot update assembly when packaging"),(0,a.kt)("p",null,"It is part of the packaging workflow, and the relevant code is in ",(0,a.kt)("inlineCode",{parentName:"p"},"Editor/BuildProcessors/FilterHotFixAssemblies.cs"),"."),(0,a.kt)("p",null,"Obviously, the hot update assembly should not be processed by il2cpp and compiled into the final package body. We handle the ",(0,a.kt)("inlineCode",{parentName:"p"},"IFilterBuildAssemblies")," callback,\nRemove the hot update dll from the list of build assemblies. The script will additionally check whether the name of the assembly is wrongly written, and whether there is a duplicate assembly configured by mistake."),(0,a.kt)("h3",{id:"add-the-hot-update-dll-name-to-the-assembly-list-when-packaging"},"Add the hot update dll name to the assembly list when packaging"),(0,a.kt)("p",null,"It is part of the packaging workflow, and the relevant code is in ",(0,a.kt)("inlineCode",{parentName:"p"},"Editor/BuildProcessors/PatchScriptingAssemblyList.cs"),"."),(0,a.kt)("p",null,"When the tool is packaged, it will automatically add the dll name of the hot update assembly to the assembly list configuration file. The dll name of the assembly where the hot update MonoBehaviour script is located must be added to the assembly list configuration file,\nUnity's resource management system can correctly identify and restore hot update scripts. For a more detailed introduction to the principle, please see ",(0,a.kt)("a",{parentName:"p",href:"/en/docs/basic/monobehaviour"},"Using Hot Update MonoBehaviour")," ."),(0,a.kt)("h3",{id:"backup-trimmed-aot-dll"},"Backup trimmed AOT dll"),(0,a.kt)("p",null,"It is part of the packaging workflow, and the relevant code is in ",(0,a.kt)("inlineCode",{parentName:"p"},"Editor/BuildProcessors/CopyStrippedAOTAssemblies.cs"),"."),(0,a.kt)("p",null,"When the supplementary metadata mode is ",(0,a.kt)("inlineCode",{parentName:"p"},"HomologousImageMode::Consistent"),", the cropped AOT dll generated during packaging needs to be used. Therefore, the cropped AOT dll generated during the building pipeline will be automatically\nCopy it to the ",(0,a.kt)("inlineCode",{parentName:"p"},"{project}/HybridCLRData/AssembliesPostIl2CppStrip/{platform}")," directory for future processing. When the data mode is ",(0,a.kt)("inlineCode",{parentName:"p"},"HomologousImageMode::SuperSet"),",\nThe original aot dll can be used directly. The advantage of this is that the workflow is more convenient, and there is no need to update the aot dll after each package. The disadvantage is that it takes up more memory, and at the same time greatly increases the size of the trimmed dll. Please use the original or trimmed aot dll."),(0,a.kt)("p",null,"##iOSBuild-script"),(0,a.kt)("p",null,(0,a.kt)("inlineCode",{parentName:"p"},"Editor/Data~/iOSBuild")," in the package contains the scripts needed to compile the iOS version libil2cpp.a. After running the ",(0,a.kt)("inlineCode",{parentName:"p"},"HybridCLR/Installer...")," menu command to successfully initialize HybridCLR, it will be automatically copied to the ",(0,a.kt)("inlineCode",{parentName:"p"},"{project}/HybridCLRData/iOSBuild")," directory.\n",(0,a.kt)("strong",{parentName:"p"}," Subsequent operations must be performed in the ",(0,a.kt)("inlineCode",{parentName:"strong"},"{project}/HybridCLRData/iOSBuild")," directory"),". For the specific operation of compiling libil2cpp.a, please refer to the document ",(0,a.kt)("a",{parentName:"p",href:"/en/docs/basic/buildpipeline"},"iOS Platform Packaging"),"."),(0,a.kt)("h2",{id:"runtime-related-scripts"},"Runtime related scripts"),(0,a.kt)("p",null,"Contains classes used at runtime."),(0,a.kt)("h3",{id:"loadimageerrorcode"},"LoadImageErrorCode"),(0,a.kt)("p",null,"Error code of loading hot update dll."),(0,a.kt)("h3",{id:"metadata-mode-homologousimagemode"},"Metadata Mode HomologousImageMode"),(0,a.kt)("p",null,"Two metadata schemas are currently supported:"),(0,a.kt)("h4",{id:"homologousimagemodeconsistent-mode"},(0,a.kt)("inlineCode",{parentName:"h4"},"HomologousImageMode::Consistent")," mode"),(0,a.kt)("p",null,"That is, the supplementary dll is exactly the same as the trimmed dll when packaging. Therefore, the clipped dll generated during the build process must be used, and the original dll cannot be copied directly. We added processing code in ",(0,a.kt)("inlineCode",{parentName:"p"},"HybridCLR.BuildProcessors.CopyStrippedAOTAssemblies"),", automatically copy these clipped dlls to the ",(0,a.kt)("inlineCode",{parentName:"p"},"{project}/HybridCLRData/AssembliesPostIl2CppStrip/{target}")," directory when packaging."),(0,a.kt)("h4",{id:"homologousimagemodesuperset-mode"},(0,a.kt)("inlineCode",{parentName:"h4"},"HomologousImageMode::SuperSet")," mode"),(0,a.kt)("p",null,"That is, the supplementary dll is a superset of the trimmed dll when packaging, and contains all the metadata of the trimmed dll. One of the easiest superset dlls to get is the original aot dll, which is also the recommended superset dll."),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"The original UnityEngine-related AOT dll is in the PlayBackEngines subdirectory of the Unity installation directory"),(0,a.kt)("li",{parentName:"ul"},"The original .net core AOT dll such as mscorlib.dll is in the ",(0,a.kt)("inlineCode",{parentName:"li"},"unityaot{xxx}")," directory of the Unity installation directory. 2019-2020 will be unified into the unityaot directory, and will be split into multiple directories starting from 2021. If you package android, take unityaot-linux, and if you package iOS, take unityaot-macos."),(0,a.kt)("li",{parentName:"ul"},"The AOT dll of the plug-in is the original dll of the corresponding platform in the project directory. If it is in the form of source code, it is a compiled dll, just take the corresponding dll in the ",(0,a.kt)("inlineCode",{parentName:"li"},"HybridCLR/HotUpdateDlls/{platform}")," directory")),(0,a.kt)("p",null,"Take the Win64 target under Win of Unity 2020.3.33 version as an example:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"mscorlib.dll in ",(0,a.kt)("inlineCode",{parentName:"li"},"{editor}/Editor/Data/MonoBleedingEdge/lib/mono/unityaot")),(0,a.kt)("li",{parentName:"ul"},"UnityEngine.CoreModule.dll in ",(0,a.kt)("inlineCode",{parentName:"li"},"{editor}/Editor/Data/Playbackengines/windowsstandalonesupport/Variations/il2cpp/Managed")),(0,a.kt)("li",{parentName:"ul"},"protobuf-net.dll is the original ",(0,a.kt)("inlineCode",{parentName:"li"},"protobuf-net.dll")," in your project"),(0,a.kt)("li",{parentName:"ul"},"The AOT dll corresponding to your AOT module Main is ",(0,a.kt)("inlineCode",{parentName:"li"},"HybridCLR/HotUpdateDlls/{platform}/Main.dll"))),(0,a.kt)("p",null,"The ",(0,a.kt)("inlineCode",{parentName:"p"},"SuerSet")," mode can also use the trimmed dll of the ",(0,a.kt)("inlineCode",{parentName:"p"},"Consistent")," mode, since it obviously contains all the metadata for itself."),(0,a.kt)("h3",{id:"runtimeapi"},"RuntimeApi"),(0,a.kt)("p",null,"The underlying tool class for operating HybridCLR. The more commonly used ones are:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"LoadImageErrorCode LoadMetadataForAOTAssembly(byte[] dllBytes, HomologousImageMode mode)")," is used to load supplementary metadata assembly.")),(0,a.kt)("h3",{id:"reversepinvokewrappergenerationattribute"},"ReversePInvokeWrapperGenerationAttribute"),(0,a.kt)("p",null,"If a scripting language such as xlua is used in the project, the ",(0,a.kt)("inlineCode",{parentName:"p"},"[MonoPInvokeCallback]")," annotation needs to be added to the C# function to be registered in lua. This returns a corresponding C++ for these C# functions\nFunction pointer, used to register in the scripting language. HybridCLR supports registering hot-updated C# code in lua, but the C++ stub function corresponding to ",(0,a.kt)("inlineCode",{parentName:"p"},"[MonoPInvokeCallback]")," must be generated in advance to return a corresponding C++ function pointer for each C# function.\nThe script provides the function of automatically generating stub functions. For details, see ",(0,a.kt)("a",{parentName:"p",href:"/en/docs/basic/workwithscriptlanguage"},"MonoPInvokeCallback support")," and ",(0,a.kt)("a",{parentName:"p",href:"/en/docs/basic/workwithscriptlanguage"},"HybridCLR+lua/js/python")," documents"),(0,a.kt)("p",null,"Each function with the ",(0,a.kt)("inlineCode",{parentName:"p"},"[MonoPInvokeCallback]")," attribute requires a unique corresponding wrapper function. These wrapper functions must be pre-generated during packaging and cannot be changed.\nTherefore, if a function with the ",(0,a.kt)("inlineCode",{parentName:"p"},"[MonoPInvokeCallback]")," feature is added in subsequent hot updates, there will be insufficient wrapper functions. ReversePInvokeWrapperGenerationAttribute\nIt is used to reserve the specified number of wrapper functions for the functions currently added with the ",(0,a.kt)("inlineCode",{parentName:"p"},"[MonoPInvokeCallback]")," feature. In the following example, 10 wrapper functions are reserved for functions signed by LuaFunction."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-csharp"}," delegate int LuaFunction(IntPtr luaState);\n\n public class MonoPInvokeWrapperPreserves\n {\n [ReversePInvokeWrapperGeneration(10)]\n [MonoPInvokeCallback(typeof(LuaFunction))]\n public static int LuaCallback(IntPtr luaState)\n {\n return 0;\n }\n\n [MonoPInvokeCallback(typeof(Func))]\n public static int Sum(int a, int b)\n {\n return a + b;\n }\n\n [MonoPInvokeCallback(typeof(Func))]\n public static int Sum2(int a, int b)\n {\n return a + b;\n }\n\n [MonoPInvokeCallback(typeof(Func))]\n public static int Sum3()\n {\n return 0;\n }\n }\n")))}u.isMDXComponent=!0},5568:(e,t,n)=>{n.d(t,{Z:()=>i});const i=n.p+"assets/images/install-c1f84913c18dfa0406fc90db65085a56.jpg"},8648:(e,t,n)=>{n.d(t,{Z:()=>i});const i=n.p+"assets/images/install_default-c61d323cdd4133368bc575f35dd7a9ec.jpg"}}]); \ No newline at end of file diff --git a/en/assets/js/runtime~main.725762af.js b/en/assets/js/runtime~main.18df0249.js similarity index 97% rename from en/assets/js/runtime~main.725762af.js rename to en/assets/js/runtime~main.18df0249.js index a1f5c5ba..a42d1d1b 100644 --- a/en/assets/js/runtime~main.725762af.js +++ b/en/assets/js/runtime~main.18df0249.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/"+({53:"935f2afb",60:"7008f309",145:"1de28ba9",321:"c637a75c",439:"18ac6946",447:"f347166a",499:"01e35b3c",687:"ad4ac6bf",1171:"d896988d",1175:"5eaad662",1693:"12aa5f76",1811:"7c709933",1970:"53decd28",1990:"9f25e85a",2034:"21ad55e6",2163:"1f4f5b8d",2241:"fa5dbaff",2261:"528693c6",2365:"a7626ec9",2535:"814f3328",2616:"e9748e8f",2757:"9e04c579",2828:"b7eeea20",2854:"08a2e2e3",2857:"cab0a0b1",2896:"e8c48351",3089:"a6aa9e1f",3092:"5d75b024",3134:"31bbbef9",3250:"815038dd",3378:"e4a581d5",3608:"9e4087bc",3665:"cd40287e",3777:"303a7ab0",3836:"f6cbeee1",4106:"3bd86665",4191:"42d29336",4195:"c4f5d8e4",4255:"e272dbc8",4340:"e1c419d2",4364:"fba6c282",4449:"359abbd3",4569:"39b1bd06",4916:"90ae8e83",5306:"29217152",5428:"45bec6ca",5657:"3dd68940",5746:"5a96aca1",6103:"ccc49370",6686:"a73ec7fd",6690:"aa55467b",6727:"a2720703",7020:"ba76a366",7198:"82b0332e",7566:"aac64df7",7589:"0ccd1bc3",7738:"530cdc5e",7842:"a6590bfd",7906:"50ff9c4d",7918:"17896441",7920:"1a4e3797",7995:"375669b6",8052:"b7e34b9a",8091:"05ba35d7",8373:"f9d8bc1b",8656:"757481ff",8699:"445802cd",8983:"09708493",9024:"02b1d4e6",9107:"82373234",9167:"369df949",9178:"dd55ff2d",9225:"5b44acae",9235:"a85eafba",9388:"64772a77",9451:"355d470d",9479:"0874f5ae",9514:"1be78505",9570:"40a20ee1",9643:"a2309c10",9673:"140989a5",9695:"27f4d295",9731:"c225ef2b",9792:"8fc0d838",9817:"14eb3368",9892:"1a8c3b80"}[e]||e)+"."+{53:"6c88f9c2",60:"e8d010c7",145:"7e5501a3",321:"7d9b2f97",439:"4df6b2fe",447:"f6676a23",499:"8ef598ac",687:"7642c90e",1171:"04e01dfa",1175:"01e69b27",1426:"f6ed8f65",1693:"a501a605",1811:"34e645a4",1970:"ad7da850",1990:"b8403871",2034:"e6a67a2a",2163:"4709e471",2241:"35351c62",2261:"c51cf6ef",2365:"1a7ed67a",2535:"184cc1e2",2616:"8ff4f04a",2757:"6a1656d9",2828:"cbb0c6af",2854:"4f2e1b0f",2857:"11a341e7",2896:"44e61745",3089:"d1467cbe",3092:"a97fbbb4",3134:"6b9fdf99",3250:"ab8419a5",3378:"93309c9e",3608:"e989768d",3665:"3202e1ba",3777:"365866cc",3836:"ce2b79a4",4106:"3f5e491c",4191:"fb48ab16",4195:"e054211d",4255:"ade5fa21",4340:"c2cf7c1d",4364:"ada73ec4",4449:"88ed136b",4569:"443223c6",4916:"eb8f2528",4972:"3d0f496c",5306:"1a64ac3f",5428:"d1845150",5657:"a40656ab",5746:"f49f1414",6048:"779f8c90",6103:"1d3911bc",6186:"170d1bc9",6686:"e582f07c",6690:"636072b2",6727:"189def4b",6945:"94f4a660",7020:"d6d0e9af",7198:"1f2eedd3",7566:"a562889a",7589:"199747a5",7738:"8066713a",7842:"02034742",7906:"6b54584a",7918:"d5cb46e3",7920:"bd81094a",7995:"fd998a55",8052:"139bf164",8091:"7b73d20c",8373:"48cd2fb7",8656:"985c2d89",8699:"6a721cf3",8894:"91734414",8983:"e9155f3e",9024:"2e97ef25",9107:"17637bc8",9167:"0d4507aa",9178:"079d05ce",9225:"ecd41481",9235:"090bff20",9388:"0a148ea7",9451:"a68c3273",9479:"569786b1",9514:"d5cf2d0b",9570:"3c736330",9643:"4f7dab42",9673:"87071a9b",9695:"ab4a94f6",9731:"5cdabdac",9792:"afc9e4af",9817:"0f68630a",9892:"ac3d26d7"}[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","935f2afb":"53","7008f309":"60","1de28ba9":"145",c637a75c:"321","18ac6946":"439",f347166a:"447","01e35b3c":"499",ad4ac6bf:"687",d896988d:"1171","5eaad662":"1175","12aa5f76":"1693","7c709933":"1811","53decd28":"1970","9f25e85a":"1990","21ad55e6":"2034","1f4f5b8d":"2163",fa5dbaff:"2241","528693c6":"2261",a7626ec9:"2365","814f3328":"2535",e9748e8f:"2616","9e04c579":"2757",b7eeea20:"2828","08a2e2e3":"2854",cab0a0b1:"2857",e8c48351:"2896",a6aa9e1f:"3089","5d75b024":"3092","31bbbef9":"3134","815038dd":"3250",e4a581d5:"3378","9e4087bc":"3608",cd40287e:"3665","303a7ab0":"3777",f6cbeee1:"3836","3bd86665":"4106","42d29336":"4191",c4f5d8e4:"4195",e272dbc8:"4255",e1c419d2:"4340",fba6c282:"4364","359abbd3":"4449","39b1bd06":"4569","90ae8e83":"4916","45bec6ca":"5428","3dd68940":"5657","5a96aca1":"5746",ccc49370:"6103",a73ec7fd:"6686",aa55467b:"6690",a2720703:"6727",ba76a366:"7020","82b0332e":"7198",aac64df7:"7566","0ccd1bc3":"7589","530cdc5e":"7738",a6590bfd:"7842","50ff9c4d":"7906","1a4e3797":"7920","375669b6":"7995",b7e34b9a:"8052","05ba35d7":"8091",f9d8bc1b:"8373","757481ff":"8656","445802cd":"8699","09708493":"8983","02b1d4e6":"9024","369df949":"9167",dd55ff2d:"9178","5b44acae":"9225",a85eafba:"9235","64772a77":"9388","355d470d":"9451","0874f5ae":"9479","1be78505":"9514","40a20ee1":"9570",a2309c10:"9643","140989a5":"9673","27f4d295":"9695",c225ef2b:"9731","8fc0d838":"9792","14eb3368":"9817","1a8c3b80":"9892"}[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/"+({53:"935f2afb",60:"7008f309",145:"1de28ba9",321:"c637a75c",439:"18ac6946",447:"f347166a",499:"01e35b3c",687:"ad4ac6bf",1171:"d896988d",1175:"5eaad662",1693:"12aa5f76",1811:"7c709933",1970:"53decd28",1990:"9f25e85a",2034:"21ad55e6",2163:"1f4f5b8d",2241:"fa5dbaff",2261:"528693c6",2365:"a7626ec9",2535:"814f3328",2616:"e9748e8f",2757:"9e04c579",2828:"b7eeea20",2854:"08a2e2e3",2857:"cab0a0b1",2896:"e8c48351",3089:"a6aa9e1f",3092:"5d75b024",3134:"31bbbef9",3250:"815038dd",3378:"e4a581d5",3608:"9e4087bc",3665:"cd40287e",3777:"303a7ab0",3836:"f6cbeee1",4106:"3bd86665",4191:"42d29336",4195:"c4f5d8e4",4255:"e272dbc8",4340:"e1c419d2",4364:"fba6c282",4449:"359abbd3",4569:"39b1bd06",4916:"90ae8e83",5306:"29217152",5428:"45bec6ca",5657:"3dd68940",5746:"5a96aca1",6103:"ccc49370",6686:"a73ec7fd",6690:"aa55467b",6727:"a2720703",7020:"ba76a366",7198:"82b0332e",7566:"aac64df7",7589:"0ccd1bc3",7738:"530cdc5e",7842:"a6590bfd",7906:"50ff9c4d",7918:"17896441",7920:"1a4e3797",7995:"375669b6",8052:"b7e34b9a",8091:"05ba35d7",8373:"f9d8bc1b",8656:"757481ff",8699:"445802cd",8983:"09708493",9024:"02b1d4e6",9107:"82373234",9167:"369df949",9178:"dd55ff2d",9225:"5b44acae",9235:"a85eafba",9388:"64772a77",9451:"355d470d",9479:"0874f5ae",9514:"1be78505",9570:"40a20ee1",9643:"a2309c10",9673:"140989a5",9695:"27f4d295",9731:"c225ef2b",9792:"8fc0d838",9817:"14eb3368",9892:"1a8c3b80"}[e]||e)+"."+{53:"6c88f9c2",60:"e8d010c7",145:"7e5501a3",321:"7d9b2f97",439:"4df6b2fe",447:"f6676a23",499:"8ef598ac",687:"7642c90e",1171:"04e01dfa",1175:"01e69b27",1426:"f6ed8f65",1693:"a501a605",1811:"34e645a4",1970:"ad7da850",1990:"b8403871",2034:"e6a67a2a",2163:"4709e471",2241:"35351c62",2261:"c51cf6ef",2365:"1a7ed67a",2535:"184cc1e2",2616:"8ff4f04a",2757:"6a1656d9",2828:"cbb0c6af",2854:"4f2e1b0f",2857:"11a341e7",2896:"44e61745",3089:"d1467cbe",3092:"a97fbbb4",3134:"6b9fdf99",3250:"ab8419a5",3378:"93309c9e",3608:"e989768d",3665:"3202e1ba",3777:"365866cc",3836:"ce2b79a4",4106:"3f5e491c",4191:"fb48ab16",4195:"e054211d",4255:"ade5fa21",4340:"c2cf7c1d",4364:"ada73ec4",4449:"88ed136b",4569:"443223c6",4916:"eb8f2528",4972:"3d0f496c",5306:"1a64ac3f",5428:"d1845150",5657:"a40656ab",5746:"f49f1414",6048:"779f8c90",6103:"1d3911bc",6186:"170d1bc9",6686:"e582f07c",6690:"636072b2",6727:"189def4b",6945:"94f4a660",7020:"d6d0e9af",7198:"1f2eedd3",7566:"a562889a",7589:"199747a5",7738:"8066713a",7842:"02034742",7906:"d672bb77",7918:"d5cb46e3",7920:"bd81094a",7995:"fd998a55",8052:"139bf164",8091:"7b73d20c",8373:"48cd2fb7",8656:"985c2d89",8699:"6a721cf3",8894:"91734414",8983:"e9155f3e",9024:"2e97ef25",9107:"17637bc8",9167:"0d4507aa",9178:"079d05ce",9225:"ecd41481",9235:"090bff20",9388:"0a148ea7",9451:"a68c3273",9479:"569786b1",9514:"d5cf2d0b",9570:"3c736330",9643:"4f7dab42",9673:"87071a9b",9695:"ab4a94f6",9731:"259b48ea",9792:"afc9e4af",9817:"0f68630a",9892:"ac3d26d7"}[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","935f2afb":"53","7008f309":"60","1de28ba9":"145",c637a75c:"321","18ac6946":"439",f347166a:"447","01e35b3c":"499",ad4ac6bf:"687",d896988d:"1171","5eaad662":"1175","12aa5f76":"1693","7c709933":"1811","53decd28":"1970","9f25e85a":"1990","21ad55e6":"2034","1f4f5b8d":"2163",fa5dbaff:"2241","528693c6":"2261",a7626ec9:"2365","814f3328":"2535",e9748e8f:"2616","9e04c579":"2757",b7eeea20:"2828","08a2e2e3":"2854",cab0a0b1:"2857",e8c48351:"2896",a6aa9e1f:"3089","5d75b024":"3092","31bbbef9":"3134","815038dd":"3250",e4a581d5:"3378","9e4087bc":"3608",cd40287e:"3665","303a7ab0":"3777",f6cbeee1:"3836","3bd86665":"4106","42d29336":"4191",c4f5d8e4:"4195",e272dbc8:"4255",e1c419d2:"4340",fba6c282:"4364","359abbd3":"4449","39b1bd06":"4569","90ae8e83":"4916","45bec6ca":"5428","3dd68940":"5657","5a96aca1":"5746",ccc49370:"6103",a73ec7fd:"6686",aa55467b:"6690",a2720703:"6727",ba76a366:"7020","82b0332e":"7198",aac64df7:"7566","0ccd1bc3":"7589","530cdc5e":"7738",a6590bfd:"7842","50ff9c4d":"7906","1a4e3797":"7920","375669b6":"7995",b7e34b9a:"8052","05ba35d7":"8091",f9d8bc1b:"8373","757481ff":"8656","445802cd":"8699","09708493":"8983","02b1d4e6":"9024","369df949":"9167",dd55ff2d:"9178","5b44acae":"9225",a85eafba:"9235","64772a77":"9388","355d470d":"9451","0874f5ae":"9479","1be78505":"9514","40a20ee1":"9570",a2309c10:"9643","140989a5":"9673","27f4d295":"9695",c225ef2b:"9731","8fc0d838":"9792","14eb3368":"9817","1a8c3b80":"9892"}[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 8038999e..653a97ba 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 fb9700fa..02dd928b 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 c17b7e99..e108e560 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 e8ff762b..97005796 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 d88c43e3..69c6451d 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 e30e52b4..7fe86c4d 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 6d5a8d0c..16070901 100644 --- a/en/docs/basic/aotgeneric.html +++ b/en/docs/basic/aotgeneric.html @@ -9,7 +9,7 @@ - + @@ -26,7 +26,7 @@ It is the List<T>.Add function that is missing raw IL function body metadata, not YourValueType that is missing metadata, so The metadata of the aot dll where the generic class resides should be supplemented. For example, in order to use List<Vector3>, you should supplement the metadata of the dll where List<T> resides (namely mscorlib), instead of supplementing the metadata of the dll where YourValueType resides.

    If the AOT generic supplements the corresponding generic metadata, and il2cpp generic sharing instantiation also exists, in order to maximize performance, HybridCLR will give priority to il2cpp generic sharing.

    Although the generic function instantiation technology based on supplementary metadata is quite perfect, after all, the instantiated function is executed in an interpreted manner. If the generic instantiation in AOT can be performed in advance, the performance can be greatly improved. Therefore, generic classes and functions that are commonly used, especially performance-sensitive, can be instantiated in AOT in advance. We provide tools to help automatically scan and collect corresponding generic instances, you can run the menu command HybridCLR/Generate/AOTGenericReference.

    tip

    This command only collects the AOT generic instances used in the hot update, and all generated are in the form of annotations. You need to refer to this file yourself and explicitly instantiate some generics in other places according to actual needs.

    Get Supplementary Metadata dll

    The clipped AOT dll generated by building pipeline can be used to supplement metadata. The com.code-philosophy.hybridclr plugin will automatically copy them to {project}/HybridCLRData/AssembliesPostIl2CppStrip/{target}. Note that tailoring 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.

    should make up

    A list of assemblies filled with metadata

    The AOTGenericReferences.cs file generated by the HybridCLR/generate/AOTGenericReference command contains a list of assemblies that should be supplemented with metadata, like this. You don't need to run the game to quickly know which metadata should be added.

    // {{ AOT assemblies
    // Main.dll
    // System. Core. dll
    // UnityEngine.CoreModule.dll
    // mscorlib.dll
    // }}

    Metadata Mode HomologousImageMode

    Two metadata schemas are currently supported:

    • HomologousImageMode::Consistent mode, that is, the supplementary dll is exactly the same as the cropped dll when packaging. Therefore, the clipped dll generated during the build process must be used, and the original dll cannot be copied directly.
    • HomologousImageMode::SuperSet mode, that is, the supplementary dll is a superset of the cropped dll when packaging. This mode relaxes the requirements for AOT dll, you can use either the cut AOT dll or the original AOT dll.

    Load supplementary metadata sample code

    See the sample code below for how to load the supplementary metadata dll in the code, and you can also refer to hybridclr_trial.

         public static unsafe void LoadMetadataForAOTAssembly()
    {
    List<string> aotDllList = new List<string>
    {
    "mscorlib.dll",
    "System.dll",
    "System.Core.dll", // required if using Linq
    // "Newtonsoft.Json.dll",
    // "protobuf-net.dll",
    };

    AssetBundle dllAB = LoadDll. AssemblyAssetBundle;
    foreach (var aotDllName in aotDllList)
    {
    byte[] dllBytes = dllAB.LoadAsset<TextAsset>(aotDllName).bytes;
    int err = HybridCLR.RuntimeApi.LoadMetadataForAOTAssembly(dllBytes, HomologousImageMode.SuperSet);
    Debug.Log($"LoadMetadataForAOTAssembly:{aotDllName}.ret:{err}");
    }
    }

    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 e7c5c39a..e99128a9 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 eca76626..fca00d9f 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 2d5581b1..17483bcd 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 bb53f791..4cfba6ac 100644 --- a/en/docs/basic/buildwebgl.html +++ b/en/docs/basic/buildwebgl.html @@ -9,13 +9,13 @@ - +

    Publish the 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.

    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 Use Global Il2cpp option. Because the webgl platform only supports global installation.

    select_il2cpp_module_webgl

    Create a soft (hard) reference from libil2cpp in the Editor directory to the local libil2cpp directory

    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 46403285..cf6b105d 100644 --- a/en/docs/basic/codestriping.html +++ b/en/docs/basic/codestriping.html @@ -9,7 +9,7 @@ - + @@ -21,7 +21,7 @@ Make sure to explicitly reference one of its classes or functions in the main project code.

    AOT type and function reserved

    Although the HybridCLR/Generate/LinkXml command of com.code-philosophy.hybridclrophy.hybridclr can intelligently scan out the AOT type you are currently referencing, it cannot predict the AOT type you will use in the future 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!

    - + \ 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 8f4cf5c7..e71a1c4f 100644 --- a/en/docs/basic/com.code-philosophy.hybridclr.html +++ b/en/docs/basic/com.code-philosophy.hybridclr.html @@ -9,7 +9,7 @@ - + @@ -17,7 +17,7 @@

    Hybridclr Package

    com.code-philosophy.hybridclr is a Unity package that provides the Editor workflow tool script and Runtime script required by HybridCLR. with the help of The workflow tool provided by com.code-philosophy.hybridclr makes it very easy to package an App that supports HybridCLR hot update function. The hybridclr_unity package mainly includes the following contents:

    • Editor related scripts
    • Runtime related scripts -iOSBuild script
    caution

    Before v3.0.0 the package name was com.focus-creative-games.hybridclr_unity.

    HybridCLR menu introduction

    The following submenus are all under the HybridCLR menu in the menu bar. For the sake of simplification, we no longer include HybridCLR when we mention submenus below.

    Installer...

    A handy installer is provided to help correctly set up the local il2cpp directory, which contains a modified version replacing the HybridCLRData/LocalIl2CppData-{platform}/il2cpp/libil2cpp directory with HybridCLR.

    The installer needs to copy il2cpp (similar to C:\Program Files\Unity\Hub\Editor\2020.3.33f1\Editor\Data\il2cpp) related files from the Unity installation directory of the matching version.

    • For 2019.4.40+, 2021.3.26+, 2021.3.0+, 2022.3.0+ versions, copy the il2cpp file directly from the installation directory of that version.
    • For versions 2020.3.16-2020.3.25, an additional version 2020.3.26 or later needs to be installed. After completing the installation in the Installer, switch back to the current version.
    • For the 2019.4.0-2019.4.39 version, you need to install the 2019.4.40 version additionally, and switch back to the current version after completing the installation in the Installer.

    Installation Status: Installed | Not Installed in the installation interface indicates whether HybridCLR initialization is complete. Click Install, if successful, the Installation Successful log will be displayed at the end, and the installation status will switch to Installed, otherwise please check the error log.

    tip

    If HybridCLR is already installed, clicking the Install button will install the latest HybridCLR version of libil2cpp.

    The branch or tag compatible with hybridclr and il2cpp_plus corresponding to the current package version has been configured in the Data~/hybridclr_version.json file in com.code-philosophy.hybridclr. -The Installer will install the version specified in the configuration, and no longer supports customizing the version to be installed.

    The configuration looks like this:

    {
    "versions": [
    {
    "unity_version": "2019",
    "hybridclr" : { "branch": "v2.0.1"},
    "il2cpp_plus": { "branch": "v2019-2.0.1"}
    },
    {
    "unity_version": "2020",
    "hybridclr" : { "branch": "v2.0.1"},
    "il2cpp_plus": { "branch": "v2020-2.0.1"}
    },
    {
    "unity_version": "2021",
    "hybridclr" : { "branch": "v2.0.1"},
    "il2cpp_plus": { "branch": "v2021-2.0.1"}
    }
    ]
    }

    If you must install other versions of hybridclr or il2cpp_plus, modify the branch in the configuration file to be the target branch or tag.

    install_default

    From version 2.3.1 onwards, it supports copying and installing directly from the libil2cpp directory that contains hybridclr made locally. If your network is not good, or git is not installed and you cannot download and install remotely from the warehouse, you can first il2cpp_plus and [hybridclr](https:/ /github.com/focus-creative-games/hybridclr) is downloaded to the local, and then according to the document in the Installation Principle section below, the libil2cpp directory containing hybridclr is merged from these two warehouses, and then installed in Installer Enable Copy libil2cpp from local option in the interface, select the libil2cpp directory you made, and click Install to execute the installation. As shown below.

    install

    Compile Dll

    For each target, you must use the hot update dll compiled under the compile switch of the target platform, otherwise the hot update code will not match the code information of the AOT main package or hot update resources.

    The HybridCLR.Editor assembly of com.code-philosophy.hybridclr provides the HybridCLR.Editor.Commands.CompileDllCommand.CompileDll(BuildTarget target) interface, +The Installer will install the version specified in the configuration, and no longer supports customizing the version to be installed.

    The configuration looks like this:

    {
    "versions": [
    {
    "unity_version": "2019",
    "hybridclr" : { "branch": "v2.0.1"},
    "il2cpp_plus": { "branch": "v2019-2.0.1"}
    },
    {
    "unity_version": "2020",
    "hybridclr" : { "branch": "v2.0.1"},
    "il2cpp_plus": { "branch": "v2020-2.0.1"}
    },
    {
    "unity_version": "2021",
    "hybridclr" : { "branch": "v2.0.1"},
    "il2cpp_plus": { "branch": "v2021-2.0.1"}
    }
    ]
    }

    If you must install other versions of hybridclr or il2cpp_plus, modify the branch in the configuration file to be the target branch or tag.

    install_default

    From version 2.3.1 onwards, it supports copying and installing directly from the libil2cpp directory that contains hybridclr made locally. If your network is not good, or git is not installed and you cannot download and install remotely from the warehouse, you can first il2cpp_plus and [hybridclr](https:/ /github.com/focus-creative-games/hybridclr) is downloaded to the local, and then according to the document in the Installation Principle section below, the libil2cpp directory containing hybridclr is merged from these two warehouses, and then installed in Installer Enable Copy libil2cpp from local option in the interface, select the libil2cpp directory you made, and click Install to execute the installation. As shown below.

    install

    Compile Dll

    For each target, you must use the hot update dll compiled under the compile switch of the target platform, otherwise the hot update code will not match the code information of the AOT main package or hot update resources.

    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 the compilation is completed, the hot update dll is placed in the {project}/HybridCLRData/HotUpdateDlls/{platform} directory.

    Generate

    Generate contains the generation commands needed for packaging.

    Generate/Il2CppDef

    The hybridclr code needs to be compatible with multiple Unity versions, and macro definitions related to the current Unity version are required. The Generate/Il2CppDef command generates relevant version macros and other necessary codes, and the generated codes are similar to the following.

    // hybridclr/generated/UnityVersion.h

    #define HYBRIDCLR_UNITY_VERSION 2020333
    #define HYBRIDCLR_UNITY_2020 1
    #define HYBRIDCLR_UNITY_2019_OR_NEW 1
    #define HYBRIDCLR_UNITY_2020_OR_NEW 1

    Generate/LinkXml

    Scan the AOT type referenced by the hot update dll, generate link.xml, and prevent the AOT type or function used by the hot update script from being clipped. The output file path is specified in the OuputLinkXml field in HybridCLRSettings.asset, and the default is LinkGenerator/link.xml.

    For a more specific introduction to clipping, please see Code Clipping Principles and Solutions.

    Generate/AotDlls

    Generate trimmed AOT dlls. The script achieves the goal of generating trimmed AOT dlls by exporting the project in a temporary directory. Generating AOT dlls depends on Generate/LinkXml and Generate/Il2CppDef. If you did not use HybridCLR/Generate/All such a one-click generation command, please run the following commands in sequence:

    • HybridCLR/Generate/Il2CppDef
    • HybridCLR/Generate/LinkXml
    • HybridCLR/Generate/AotDlls

    Generate/MethodBridge

    Scan and generate bridge function files according to the current AOT dll set. For related documents, please see bridge function.

    Generate bridge function depends on AOT dlls and hot update dlls. If you did not use HybridCLR/Generate/All such a one-click generation command, please run the following commands in sequence:

    • HybridCLR/Generate/Il2CppDef
    • HybridCLR/Generate/LinkXml (implicitly calls HybridCLR/CompileDll/ActiveBuildTarget)
    • HybridCLR/Generate/AotDlls
    • HybridCLR/Generate/MethodBridge

    Generate/AOTGenericReference

    Scan all generated AOT generic types and function instantiations according to the current hot update dll, and generate a inspired generic instantiation file AOTGenericReferences.cs. Since it is troublesome to convert the scanned generic types and functions into corresponding code references, all generated generic instantiation codes are comment code.

    The AOTGenericReferences.cs file also contains a list of assemblies that should be supplemented with metadata, similar to the following, so that developers can quickly know which metadata should be supplemented without running the game.

    // {{ AOT assemblies
    // Main.dll
    // System. Core. dll
    // UnityEngine.CoreModule.dll
    // mscorlib.dll
    // }}

    Please add instantiation references to generic types and functions in other files, as this output file will be overwritten every time it is regenerated. @@ -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 eaac5df8..c1fac61c 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/hotupdateassemblysetting.html b/en/docs/basic/hotupdateassemblysetting.html index 5a309758..7b0b6905 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 22e9bfc6..43e568af 100644 --- a/en/docs/basic/il2cppbugs.html +++ b/en/docs/basic/il2cppbugs.html @@ -9,13 +9,13 @@ - +

    il2cpp bug log

    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 1d774142..39dcafeb 100644 --- a/en/docs/basic/install.html +++ b/en/docs/basic/install.html @@ -9,7 +9,7 @@ - + @@ -17,13 +17,13 @@

    Install

    Install a compatible Unity version

    Any version of 2019.4.x, 2020.3.x, 2021.3.x, or 2022.3.x is supported. It is recommended to install versions 2019.4.40, 2020.3.26+, 2021.3.x, and 2022.3.x.

    tip

    If your version is 2019.4.0-2019.4.39, Need to switch to 2019.4.40 to complete HybridCLR installation, and then switch back to the current version.

    If your version is 2020.3.0-2020.3.25, after completing the installation in Installer, copy 2020.3.x/Editor/Data/il2cpp/external from the installation directory of any version 2020.3.26+ to replace {project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp/external.

    caution

    If you are not an experienced Unity developer, it is recommended to use version 2021.3.1 to experience HybridCLR first.

    According to the target platform you packaged, select the necessary modules during the installation process. If you package Android or iOS, just select the corresponding module directly. If you want to package Standalone, you must additionally 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

    Select com.code-philosophy.hybridclr version

    caution

    Before v3.0.0 the package name was com.focus-creative-games.hybridclr_unity.

    These versions currently exist: 1.0 branch, v2.x.y, v3.x.y, v.4.x.y (also current main branch) .

    • The 1.0 branch is too old, although the work is stable, but the Package-related workflow is relatively old, not as convenient as subsequent versions, and maintenance has been stopped, it is strongly recommended not to use it again
    • The workflow of v2.x.y versions is well optimized and verified by a large number of projects. It is recommended to use the Unity 2019 version or the projects that will be launched soon
    • v3.x.y versions removed support for Unity 2019, added support for Unity 2022 version. It is recommended to use Unity 2020+ version or projects that will be launched soon
    • The v4.x.y versions supports incremental GC and fully supports all platforms. Since it has just been released, it will take a few weeks to stabilize. It is recommended for projects in the early or middle stages of the project.
    tip

    The versions of these three series are very stable, so there is no need to worry about which one is better. Generally speaking, the newer the version, the more optimizations and the better the user experience.

    Install the com.code-philosophy.hybridclr package

    The warehouse address is github, and the domestic fast mirror warehouse is gitee .

    There are three installation methods:

    • Install from git url using Package Manager
    • Install from openupm using Package Manager
    • local installation

    Install from git url

    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.

    • The main branch address is https://gitee.com/focus-creative-games/hybridclr_unity.git
    • Other tag version addresses are https://gitee.com/focus-creative-games/hybridclr_unity.git#{tag}

    If you want to install a certain branch or tag version, please add #{tag} after the address, such as https://gitee.com/focus-creative-games/hybridclr_unity.git#v3.0.1.

    add package

    If you are not familiar with installing packages from url, please see install from giturl.

    Install from openupm

    openump address com.focus-creative-games.hybridclr_unity.

    For the specific installation method, please open this link and view the detailed installation instructions on the page.

    Install from local files

    After cloning the warehouse locally, rename the directory to com.code-philosophy.hybridclr (for versions before v3.0.0, please use com.focus-creative-games.hybridclr_unity), and then directly move to the Packages directory of the project. Can.

    Update com.code-philosophy.hybridclr

    After updating com.code-philosophy.hybridclr, you need to re-run HybridCLR/Installer.

    Initialize HybridCLR

    In order to reduce the size of the package itself, some files need to be copied from the Unity Editor installation directory. Therefore, after installing the plug-in, an additional initialization process is required.

    Click the menu HybridCLR/Installer... to pop up the installation interface. Some setup may be required before clicking install. Since the Installer has been adjusted as the version changes, please read the corresponding instructions below according to your current version.

    If your version >= v2.0.5

    The branch or tag compatible with hybridclr and il2cpp_plus corresponding to the current package version has been configured in the Data~/hybridclr_version.json file in com.code-philosophy.hybridclr. -The Installer will install the version specified in the configuration, and no longer supports customizing the version to be installed.

    The configuration looks like this:

    {
    "versions": [
    {
    "unity_version": "2019",
    "hybridclr" : { "branch": "v2.0.1"},
    "il2cpp_plus": { "branch": "v2019-2.0.1"}
    },
    {
    "unity_version": "2020",
    "hybridclr" : { "branch": "v2.0.1"},
    "il2cpp_plus": { "branch": "v2020-2.0.1"}
    },
    {
    "unity_version": "2021",
    "hybridclr" : { "branch": "v2.0.1"},
    "il2cpp_plus": { "branch": "v2021-2.0.1"}
    }
    ]
    }

    If you must install other versions of hybridclr or il2cpp_plus, modify the branch in the configuration file to be the target branch or tag.

    In most cases, just click Install to download and install from the remote repository by default. After the installation is successful, the console will print the installation successful log. As shown below.

    install_default

    From version 2.3.1 onwards, it supports copying and installing directly from the libil2cpp directory that contains hybridclr made locally. If your network is not good, or git is not installed and you cannot download and install remotely from the warehouse, you can first il2cpp_plus and [hybridclr](https:/ /github.com/focus-creative-games/hybridclr) is downloaded to the local, and then according to the document in the Installation Principle section below, the libil2cpp directory containing hybridclr is merged from these two warehouses, and then installed in Installer Enable Copy libil2cpp from local option in the interface, select the libil2cpp directory you made, and click Install to execute the installation. As shown below.

    install

    If your version >= 1.1.20

    The Data~/hybridclr_version.json file in com.code-philosophy.hybridclr has been configured with the version compatible with hybridclr and il2cpp_plus corresponding to the current package version. +The Installer will install the version specified in the configuration, and no longer supports customizing the version to be installed.

    The configuration looks like this:

    {
    "versions": [
    {
    "unity_version": "2019",
    "hybridclr" : { "branch": "v2.0.1"},
    "il2cpp_plus": { "branch": "v2019-2.0.1"}
    },
    {
    "unity_version": "2020",
    "hybridclr" : { "branch": "v2.0.1"},
    "il2cpp_plus": { "branch": "v2020-2.0.1"}
    },
    {
    "unity_version": "2021",
    "hybridclr" : { "branch": "v2.0.1"},
    "il2cpp_plus": { "branch": "v2021-2.0.1"}
    }
    ]
    }

    If you must install other versions of hybridclr or il2cpp_plus, modify the branch in the configuration file to be the target branch or tag.

    In most cases, just click Install to download and install from the remote repository by default. After the installation is successful, the console will print the installation successful log. As shown below.

    install_default

    From version 2.3.1 onwards, it supports copying and installing directly from the libil2cpp directory that contains hybridclr made locally. If your network is not good, or git is not installed and you cannot download and install remotely from the warehouse, you can first il2cpp_plus and [hybridclr](https:/ /github.com/focus-creative-games/hybridclr) is downloaded to the local, and then according to the document in the Installation Principle section below, the libil2cpp directory containing hybridclr is merged from these two warehouses, and then installed in Installer Enable Copy libil2cpp from local option in the interface, select the libil2cpp directory you made, and click Install to execute the installation. As shown below.

    install

    If your version >= 1.1.20

    The Data~/hybridclr_version.json file in com.code-philosophy.hybridclr has been configured with the version compatible with hybridclr and il2cpp_plus corresponding to the current package version. The Installer will install the version specified in the configuration, and no longer supports customizing the version to be installed.

    The configuration looks like this:

    {
    "versions": [
    {
    "unity_version": "2019",
    "hybridclr" : { "branch": "main", "hash": "531f98365eebce5d1390175be2b41c41e217d918"},
    "il2cpp_plus": { "branch": "2019-main", "hash": "ebe5190b0404d1857832bd1d52ebec7c3730a01d"}
    },
    {
    "unity_version": "2020",
    "hybridclr" : { "branch": "main", "hash": "531f98365eebce5d1390175be2b41c41e217d918"},
    "il2cpp_plus": { "branch": "2020-main", "hash": "c6cf54285381d0b03a58126e0d39b6e4d11937b7"}
    },
    {
    "unity_version": "2021",
    "hybridclr" : { "branch": "main", "hash": "531f98365eebce5d1390175be2b41c41e217d918"},
    "il2cpp_plus": { "branch": "2021-main", "hash": "99cd1cbbfc1f637460379e81c9a7776cd3e662ad"}
    }
    ]
    }

    If you want to install other versions of hybridclr or il2cpp_plus, just modify the branch and hash in the configuration file.

    Just click Install to complete the installation. After the installation is successful, the console will print the installation successful log.

    If your package version <= 1.1.19

    Fill in the commit id or branch or tag of the hybridclr and il2cpp_plus warehouses you want to install. If the version number of hybridclr is left blank, install the latest version from the main branch of the hybridclr repository. If the version number of il2cpp_plus is left blank, install the latest version of the main branch of the corresponding annual release (such as 2020-main).

    **hybridclr_uniyt branch

    , The branch of the hybridclr warehouse and the branch of the il2cpp_plus warehouse must match**. If you use the main branch of com.code-philosophy.hybridclr, hybridclr must use the main branch, il2cpp_plus must use {version}-main, if your hybridclr_unity uses the 1.0 branch, then hybridclr must use the 1.0 branch, il2cpp_plus The {version}-1.0 branch must be used. If you use a version of a tag, make sure the branch the tag belongs to matches.

    The hybridclr warehouse recommends filling in 1.0, that is, the latest version of the 1.0 branch is installed each time; the il2cpp_plus warehouse recommends filling in {annual version}-1.0 (such as 2020-1.0), that is, each installation of the {annual version}-1.0 branch latest version of . As shown in the picture:

    image

    At present, the stable official version 1.0.1 has been released, and it is also recommended for projects that pursue stability. Com.code-philosophy.hybridclr takes 1.0.1-release, the hybridclr version takes 1.0.1-release, and the il2cpp_plus version takes {version}-1.0.1-relase.

    After completing the above settings, click the install button to complete the installation. After the installation is successful, the console will print the installation successful log.

    Since the installation process needs to pull the hybridclr and il2cpp_plus warehouses, it may fail due to network failures. If 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

    Due to Unity's own reasons, the WebGL platform must be installed globally. See the Global Installation documentation in the following sections.

    ###Unity 2021

    caution

    If your com.code-philosophy.hybridclr version >= v2.0.1, since the MonoHook technology has been used, the cropped AOT dll can be copied without modifying UnityEditor.CoreModule.dll,* *Not required** to do the following.

    Supplementary metadata and some commands under HybridCLR/Generate/* depend on the reduced AOT dll. However, when Unity 2021 version (not required for 2019 and 2020) packages the iOS platform (not required for other platforms), since the Unity Editor does not provide a public interface to copy the tailored AOT dll when the target is iOS, the modified version must be used The UnityEditor.CoreModule.dll overrides the corresponding file that comes with Unity.

    The specific operation is to cover {package directory}/Data~/ModifiedUnityAssemblies/2021.3.x/UnityEditor.CoreModule-{Win,Mac}.dll with {Editor installation directory}/Editor/Data/Managed/UnityEngine/UnityEditor.CoreModule .dll, the specific related directory may vary depending on the operating system or Unity version.

    Due to permission issues, this operation cannot be completed automatically, and you need to perform the copy operation manually.

    UnityEditor.CoreModule.dll Each small version of Unity is different. We currently only provide version 2021.3.1. If you need other versions, please make them manually. For details, please refer to Modify Unity Editor-related dll.

    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 abec8cfe..1b782356 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 e9b71ac0..698aa008 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 061846c0..3f0de2d2 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/modifyunitydll.html b/en/docs/basic/modifyunitydll.html index 28cd7c5b..fb1eeb96 100644 --- a/en/docs/basic/modifyunitydll.html +++ b/en/docs/basic/modifyunitydll.html @@ -9,7 +9,7 @@ - + @@ -25,7 +25,7 @@ 目前只制作了2021.3.1版本 (将来可能会提供更多),其他版本请自行制作。具体操作方式请阅读下面的文档。

    danger

    同个版本的Win与Mac版本的UnityEditor.CoreModule.dll并不能混用,必须分别制作。

    替换原始UnityEditor.CoreModule.dll

    • 提前备份 {Editor安装目录}/Editor/Data/Managed/UnityEngine/UnityEditor.CoreModule.dll。 UnityEditor.CoreModule.dll 具体位置有可能因为操作系统或者Unity版本而有不同。
    • 如果你正好使用2021.3.1等已经提供了制作好的dll的版本,则使用 com.code-philosophy.hybridclr/Datas~/ModifiedUnityAssemblies/{version}/UnityEditor.CoreModule-{Win,MAC}.dll 覆盖 Editor安装目录中的 UnityEditor.CoreModule.dll。

    使用dnspy修改

    进行以下操作前,你先仔细看完前面的 使用dnspy工具 这节内容。

    • {Editor安装目录}/Editor/Data/Managed/UnityEngine目录拷贝出来,假设是TempUnityEngine目录
    • 删除TempUnityEngine目录下的所有.pdb类型调试文件。因为Unity的pdb文件有问题,会导致dnspy解析出错,导致无法保存。
    • 打开 dnspy,清除左侧的dll列表。
    • 使用dnspy打开 TempUnityEngine/UnityEditor.CoreModule.dll
    • 打开 UnityEditorInternal.AssemblyStripper.RunAssemblyStripper 函数, 右键菜单 -> 编辑方法(c#)...,弹出源码编辑界面。
    • 此时编辑器缺少mscorlib.dll的引用,需要手动添加。点击源码编辑窗口左下角类似文件夹的按钮,添加 {Editor安装目录}/Editor/Data/UnityReferenceAssemblies/unity-4.8-api/mscorlib.dll
    • RunAssemblyStripper函数尾部,找到这个代码块
        foreach (string text3 in Directory.GetFiles(fullPath))
    {
    File.Move(text3, Path.Combine(managedAssemblyFolderPath, Path.GetFileName(text3)));
    }

    修改为

        string dstAOTDir = Path.Combine(UnityEngine.Application.dataPath, "../HybridCLRData/AssembliesPostIl2CppStrip",
    EditorUserBuildSettings.activeBuildTarget.ToString());
    Directory.CreateDirectory(dstAOTDir);
    foreach (string text3 in Directory.GetFiles(fullPath))
    {
    if (text3.EndsWith(".dll"))
    {
    string copyDstFile = Path.Combine(dstAOTDir, Path.GetFileName(text3));
    File.Copy(text3, copyDstFile, true);
    UnityEngine.Debug.Log("[RunAssemblyStripper] copy aot dll " + text3 + " -> " + copyDstFile);
    }
    File.Move(text3, Path.Combine(managedAssemblyFolderPath, Path.GetFileName(text3)));
    }
    • 注意!反编译的代码中,变量名未必是text3,请按实际情况处理。如有遇到编译错误,请自行酌情处理。
    • 点击右下角的 编译 按钮,如果成功,则无任何提示,退出编辑界面,返回反编译查看模式。如果失败,请自行处理编译错误。有时候dnspy会有莫名其妙的引用错误,退出源码编辑模式,重新右键编辑方法,再次进入就能解决。
    • 菜单 文件 -> 保存模块 保存修改后的 UnityEditor.CoreModule.dll文件。如果在Win或Mac下,有可能会遇到权限问题,请酌情处理(比如先保存到其他位置,再手动覆盖)
    • 重新打开Unity Editor。此时iOS便能正确获得裁剪AOT dll。

    Unity.IL2CPP.dll

    原理

    2019版本,我们需要轻微修改il2cpp生成的代码,将 Il2CppOutputProject\Source\il2cppOutput\Il2CppTypeDefinitions.c中定义的常量const Il2CppType换成可变的Il2CppType。 我们需要修改Unity.IL2CPP.dll代码达到这个目标。注意!实际操作过程发现dnspy反编译的代码有问题,最终我们在ILSpy反编译的代码基础上调整后,再在dnspy里编辑保存。 直接复制以下我们修改好的代码,在dnspy里编辑保存。修改过程可能会遇到问题,参照上面修改UnityEditor.CoreModule.dll中使用的解决办法。

    修改 Unity.IL2CPP.CppDeclarationsWriter::Write(StreamWriter writer, ICppDeclarations declarationsIn, IInteropDataCollector interopDataCollector)

    修改后的代码

         string[] includesToSkip = new string[3] { "\"il2cpp-config.h\"", "<alloca.h>", "<malloc.h>" };
    CppDeclarationsCollector.PopulateCache(declarationsIn.TypeIncludes, cache, interopDataCollector);
    HashSet<TypeReference> hashSet = new HashSet<TypeReference>(declarationsIn.TypeIncludes, new TypeReferenceEqualityComparer());
    ReadOnlyHashSet<TypeReference> dependencies = CppDeclarationsCollector.GetDependencies(declarationsIn.TypeIncludes, cache);
    hashSet.UnionWith(dependencies);
    ReadOnlyCollection<TypeReference> readOnlyCollection = hashSet.ToSortedCollection(new CppIncludeDepthComparer(comparer));
    CppDeclarations cppDeclarations = new CppDeclarations();
    cppDeclarations.Add(declarationsIn);
    foreach (TypeReference item in readOnlyCollection)
    {
    cppDeclarations.Add(cache.GetDeclarations(item));
    }
    writer.WriteLine();
    foreach (string rawFileLevelPreprocessorStmt in cppDeclarations.RawFileLevelPreprocessorStmts)
    {
    writer.WriteLine(rawFileLevelPreprocessorStmt);
    }
    writer.WriteLine();
    foreach (string item2 in cppDeclarations.Includes.Where((string i) => !includesToSkip.Contains(i) && i.StartsWith("<")))
    {
    writer.WriteLine("#include {0}", item2);
    }
    writer.WriteLine();
    foreach (string item3 in cppDeclarations.Includes.Where((string i) => !includesToSkip.Contains(i) && !i.StartsWith("<")))
    {
    writer.WriteLine("#include {0}", item3);
    }
    writer.WriteLine();
    WriteVirtualMethodDeclaration(writer, cppDeclarations.VirtualMethods);
    writer.WriteLine();
    foreach (TypeReference item4 in cppDeclarations.ForwardDeclarations.ToSortedCollection())
    {
    if (!item4.IsSystemObject() && !item4.IsSystemArray())
    {
    if (CodeGenOptions.EmitComments)
    {
    writer.WriteLine(Emit.Comment(item4.FullName));
    }
    writer.WriteLine("struct {0};", Globals.Naming.ForType(item4));
    }
    }
    writer.WriteLine();
    foreach (string item5 in cppDeclarations.RawTypeForwardDeclarations.ToSortedCollection())
    {
    writer.WriteLine(item5 + ";");
    }
    writer.WriteLine();
    foreach (ArrayType item6 in cppDeclarations.ArrayTypes.ToSortedCollection())
    {
    writer.WriteLine("struct {0};", Globals.Naming.ForType(item6));
    }
    writer.WriteLine();
    writer.WriteLine("IL2CPP_EXTERN_C_BEGIN");
    foreach (TypeReference typeExtern in cppDeclarations.TypeExterns)
    {
    writer.WriteLine("extern Il2CppType " + Globals.Naming.ForIl2CppType(typeExtern) + ";");
    }
    foreach (IList<TypeReference> genericInstExtern in cppDeclarations.GenericInstExterns)
    {
    writer.WriteLine("extern Il2CppGenericInst " + Globals.Naming.ForGenericInst(genericInstExtern) + ";");
    }
    foreach (TypeReference genericClassExtern in cppDeclarations.GenericClassExterns)
    {
    writer.WriteLine("extern Il2CppGenericClass " + Globals.Naming.ForGenericClass(genericClassExtern) + ";");
    }
    writer.WriteLine("IL2CPP_EXTERN_C_END");
    writer.WriteLine();
    if (readOnlyCollection.Count > 0)
    {
    writer.WriteClangWarningDisables();
    foreach (TypeReference item7 in readOnlyCollection)
    {
    string source = cache.GetSource(item7);
    writer.Write(source);
    }
    writer.WriteClangWarningEnables();
    }
    foreach (ArrayType arrayType in cppDeclarations.ArrayTypes)
    {
    TypeDefinitionWriter.WriteArrayTypeDefinition(arrayType, new CodeWriter(writer));
    }
    writer.WriteLine();
    foreach (string rawMethodForwardDeclaration in cppDeclarations.RawMethodForwardDeclarations)
    {
    writer.WriteLine(rawMethodForwardDeclaration + ";");
    }
    writer.WriteLine();
    foreach (MethodReference sharedMethod in cppDeclarations.SharedMethods)
    {
    WriteSharedMethodDeclaration(writer, sharedMethod);
    }
    writer.WriteLine();
    foreach (MethodReference method in cppDeclarations.Methods)
    {
    WriteMethodDeclaration(writer, method);
    }
    writer.Flush();

    修改Unity.IL2CPP.Il2CppTypeWriter::WriteIl2CppTypeDefinitions(IMetadataCollection metadataCollection)

    修改后的代码

        base.Writer.AddCodeGenMetadataIncludes();
    IDictionary<Il2CppTypeData, int> items = Globals.Il2CppTypeCollectorReader.Items;
    foreach (IGrouping<TypeReference, Il2CppTypeData> item in items.Keys.GroupBy((Il2CppTypeData entry) => entry.Type.GetNonPinnedAndNonByReferenceType(), new TypeReferenceEqualityComparer()))
    {
    base.Writer.WriteLine();
    TypeReference key = item.Key;
    GenericParameter genericParameter = key as GenericParameter;
    GenericInstanceType genericInstanceType = key as GenericInstanceType;
    ArrayType arrayType = key as ArrayType;
    PointerType pointerType = key as PointerType;
    string text = ((genericParameter != null) ? ("(void*)" + metadataCollection.GetGenericParameterIndex(genericParameter)) : ((genericInstanceType != null) ? WriteGenericInstanceTypeDataValue(genericInstanceType, metadataCollection) : ((arrayType != null) ? WriteArrayDataValue(arrayType) : ((pointerType == null) ? ("(void*)" + metadataCollection.GetTypeInfoIndex(key.Resolve()).ToString(CultureInfo.InvariantCulture)) : WritePointerDataValue(pointerType)))));
    foreach (Il2CppTypeData item2 in item)
    {
    base.Writer.WriteLine("extern Il2CppType {0};", Globals.Naming.ForIl2CppType(item2.Type, item2.Attrs));
    base.Writer.WriteLine("Il2CppType {0} = {{ {1}, {2}, {3}, {4}, {5}, {6} }};", Globals.Naming.ForIl2CppType(item2.Type, item2.Attrs), text, item2.Attrs.ToString(CultureInfo.InvariantCulture), Il2CppTypeSupport.For(item2.Type), "0", item2.Type.IsByReference ? "1" : "0", item2.Type.IsPinned ? "1" : "0");
    }
    }
    return MetadataWriter.WriteTable(base.Writer, "const Il2CppType* const ", "g_Il2CppTypeTable", items.ItemsSortedByValue(), (KeyValuePair<Il2CppTypeData, int> kvp) => "&" + Globals.Naming.ForIl2CppType(kvp.Key.Type, kvp.Key.Attrs), externTable: true);

    修改 Unity.IL2CPP.Metadata.Il2CppGenericInstWriter::WriteIl2CppGenericInstDefinitions(IIl2CppGenericInstCollectorReaderService genericInstCollection)

    修改后的代码

    base.Writer.AddCodeGenMetadataIncludes();
    foreach (TypeReference[] item in genericInstCollection.Items.Select(delegate(KeyValuePair<TypeReference[], uint> item)
    {
    KeyValuePair<TypeReference[], uint> keyValuePair = item;
    return keyValuePair.Key;
    }))
    {
    for (int i = 0; i < item.Length; i++)
    {
    base.Writer.WriteExternForIl2CppType(item[i]);
    }
    string format = "static const Il2CppType* {0}[] = {{ {1} }};";
    WriteLine(format, Globals.Naming.ForGenericInst(item) + "_Types", item.Select((TypeReference t) => MetadataWriter.TypeRepositoryTypeFor(t)).AggregateWithComma());
    WriteLine("extern Il2CppGenericInst {0};", Globals.Naming.ForGenericInst(item));
    WriteLine("Il2CppGenericInst {0} = {{ {1}, {2} }};", Globals.Naming.ForGenericInst(item), item.Length, Globals.Naming.ForGenericInst(item) + "_Types");
    }
    return MetadataWriter.WriteTable(base.Writer, "const Il2CppGenericInst* const", "g_Il2CppGenericInstTable", genericInstCollection.Items.ItemsSortedByValue(), (KeyValuePair<TypeReference[], uint> item) => "&" + Globals.Naming.ForGenericInst(item.Key), externTable: true);
    - + \ No newline at end of file diff --git a/en/docs/basic/monobehaviour.html b/en/docs/basic/monobehaviour.html index faefc1fd..7b41be64 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 2098be3e..e96cb6a6 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 feb51d27..21070d69 100644 --- a/en/docs/basic/performance.html +++ b/en/docs/basic/performance.html @@ -9,7 +9,7 @@ - + @@ -23,7 +23,7 @@ For the memory data of the object, by calculating the offset of the field in the object in advance, directly *(int32_t*)(obj + offset) = b; can complete this access operation.

    Compared with other hot update schemes, the efficiency is improved by dozens of times.

    Directly support reference and pointer operations without indirect methods

    Due to the specification restrictions of the CLI, references in C# can only be placed on the managed stack, but not on the interpreter stack (because it is heap memory). To handle something like ref int a = ref b; a = 5;, you have to use very complicated The trick maintains this reference indirectly. And HybridCLR uses c++ to realize, can save and operate these data directly.

    Compared with other hot update solutions, the efficiency is greatly improved.

    Unified metadata, more efficient object creation, and smaller memory footprint

    Due to the unified metadata, you can directly call il2cpp::vm::Object::New to create objects, the efficiency is very close to the original, and the memory is exactly the same. In contrast, other hot update schemes use fake types, The object is bloated, and the process of creating the object is more complicated.

    Compared with other hot update schemes, the efficiency is greatly improved.

    The metadata is unified, the function calling method is unified, and there is no additional overhead of PInvoke and ReservePInvoke

    HybridCLR can directly call the C++ function translated by the IL function without any intermediate links, while ILRuntime and xlua require various complex judgments and parameter conversions, as well as PInvoke and ReservePInvoke between C# and bring a lot of extra overhead.

    The interaction between HybridCLR and il2cpp AOT is extremely lightweight and efficient. No more performance issues.

    additionally provide a large number of instinct functions

    For common operations such as new Vector{2,3,4}, new string(), Nullable<T>.Value, etc., we directly provide the corresponding instructions, and the running overhead is even lower than the implementation of AOT .

    Compared with other hot update schemes, the efficiency is improved by dozens of times.

    Strictly follow the specification and do not introduce additional unnecessary costs

    Due to careful design and optimization, HybridCLR tries to avoid all kinds of unnecessary overhead. For example, the GC of the execution process is exactly the same as that of native il2cpp and mono.

    Other instruction optimization techniques

    Other Optimization Techniques

    Appendix: Test case code

    The following test cases are provided by a third party. The use cases are unreasonable, but we don’t want to be deliberately constructed and directly quote their use cases.

    private static void Test0()
    {
    var go = new GameObject("t");
    var transform = go. transform;

    var cnt = PerformanceSetting. Count * 1000;
    for (var i = 0; i < cnt; i++)
    {
    transform.position = transform.position;
    }

    Object. Destroy(go);
    }

    private static void Test1()
    {
    var go = new GameObject("t");
    var transform = go. transform;

    var cnt = PerformanceSetting. Count * 100;
    for (var i = 0; i < cnt; i++)
    {
    transform. Rotate(Vector3. up, 1);
    }

    Object. Destroy(go);
    }

    private static void Test2()
    {
    var cnt = PerformanceSetting. Count * 1000;
    for (var i = 0; i < cnt; i++)
    {
    var v = new Vector3(i, i, i);
    var x = v.x;
    var y = v.y;
    var z = v.z;
    var r = x + y * z;
    }
    }

    private static void Test3()
    {
    var cnt = PerformanceSetting. Count * 10;
    for (var i = 0; i < cnt; i++)
    {
    var go = new GameObject("t");
    Object. Destroy(go);
    }
    }

    private static void Test4()
    {
    var cnt = PerformanceSetting. Count * 10;
    for (var i = 0; i < cnt; i++)
    {
    var go = new GameObject();
    go.AddComponent<SkinnedMeshRenderer>();
    var c = go. GetComponent<SkinnedMeshRenderer>();
    c. receiveShadows = false;
    Object. Destroy(go);
    }
    }

    private static void Test5()
    {
    var cnt = PerformanceSetting. Count * 1000;
    for (var i = 0; i < cnt; i++)
    {
    var p = Input. mousePosition;
    }
    }

    private static void Test6()
    {
    var cnt = PerformanceSetting. Count * 1000;
    for (var i = 0; i < cnt; i++)
    {
    var v = new Vector3(i, i, i);
    Vector3. Normalize(v);
    }
    }

    private static void Test7()
    {
    var cnt = PerformanceSetting. Count * 100;
    for (var i = 0; i < cnt; i++)
    {
    var q1 = Quaternion. Euler(i, i, i);
    var q2 = Quaternion. Euler(i * 2, i * 2, i * 2);
    Quaternion. Slerp(Quaternion. identity, q1, 0.5f);
    }
    }

    private static void Test8()
    {
    double total = 0;
    var cnt = PerformanceSetting. Count * 10000;
    for (var i = 0; i < cnt; i++)
    {
    total = total + i - (i / 2) * (i + 3) / (i + 5);
    }
    }

    private static void Test9()
    {
    var cnt = PerformanceSetting. Count * 1000;
    for (var i = 0; i < cnt; i++)
    {
    var a = new Vector3(1, 2, 3);
    var b = new Vector3(4, 5, 6);
    var c = a + b;
    }
    }
    local function test0()
    local cnt = CS.GameMain.Scripts.Performance.PerformanceSetting.Count * 1000

    local go = CS.UnityEngine.GameObject("_")
    local transform = go.transform

    for i = 1, cnt do
    transform.position = transform.position
    end

    CS.UnityEngine.GameObject.Destroy(go)
    end

    local function test1()
    local cnt = CS.GameMain.Scripts.Performance.PerformanceSetting.Count * 100

    local go = CS.UnityEngine.GameObject("_")
    local transform = go.transform

    for i = 1, cnt do
    transform:Rotate(CS.UnityEngine.Vector3.up, 1)
    end

    CS.UnityEngine.GameObject.Destroy(go)
    end

    local function test2()
    local cnt = CS.GameMain.Scripts.Performance.PerformanceSetting.Count * 1000

    local go = CS.UnityEngine.GameObject("_")
    local transform = go.transform

    for i = 1, cnt do
    local tmp = CS.UnityEngine.Vector3(i, i, i)
    local x = tmp.x
    local y = tmp.y
    local z = tmp.z
    local r = x + y * z
    end
    end

    local function test3()
    local cnt = CS.GameMain.Scripts.Performance.PerformanceSetting.Count * 10
    for i = 1, cnt do
    local tmp = CS.UnityEngine.GameObject("___")
    CS.UnityEngine.GameObject.Destroy(tmp)
    end
    end

    local function test4()
    local cnt = CS.GameMain.Scripts.Performance.PerformanceSetting.Count * 10
    for i = 1, cnt do
    local tmp = CS.UnityEngine.GameObject("___")
    tmp:AddComponent(typeof(CS.UnityEngine.SkinnedMeshRenderer))
    local c = tmp:GetComponent(typeof(CS.UnityEngine.SkinnedMeshRenderer))
    c.receiveShadows = false
    CS.UnityEngine.GameObject.Destroy(tmp)
    end
    end

    local function test5()
    local cnt = CS.GameMain.Scripts.Performance.PerformanceSetting.Count * 1000
    for i = 1, cnt do
    local tmp = CS.UnityEngine.Input.mousePosition;
    end
    end

    local function test6()
    local cnt = CS.GameMain.Scripts.Performance.PerformanceSetting.Count * 1000
    for i = 1, cnt do
    local tmp = CS.UnityEngine.Vector3(i, i, i)
    CS.UnityEngine.Vector3.Normalize(tmp)
    end
    end

    local function test7()
    local cnt = CS.GameMain.Scripts.Performance.PerformanceSetting.Count * 100
    for i = 1, cnt do
    local t1 = CS.UnityEngine.Quaternion.Euler(i, i, i)
    local t2 = CS.UnityEngine.Quaternion.Euler(i * 2, i * 2, i * 2)
    CS.UnityEngine.Quaternion.Slerp(t1, t2, CS.UnityEngine.Random.Range(0.1, 0.9))
    end
    end

    local function test8()
    local cnt = CS.GameMain.Scripts.Performance.PerformanceSetting.Count * 10000
    local total = 0
    for i = 1, cnt do
    total = total + i - (i / 2) * (i + 3) / (i + 5)
    end
    end

    local function test9()
    local cnt = CS.GameMain.Scripts.Performance.PerformanceSetting.Count * 1000
    for i = 1, cnt do
    local tmp0 = CS.UnityEngine.Vector3(1, 2, 3)
    local tmp1 = CS.UnityEngine.Vector3(4, 5, 6)
    local tmp2 = tmp0 + tmp1
    end
    end

    - + \ No newline at end of file diff --git a/en/docs/basic/projectsettings.html b/en/docs/basic/projectsettings.html index 5df4ef54..200288f2 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

    caution

    At present, the incremental GC process is in the alpha stage. It is recommended not to enable this option for projects that have already been launched or are about to be launched.

    • 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. WebGL platforms ignore this option. com.code-philosophy.hybridclr will automatically turn off this option, you don't have to do it manually.
    • 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 845d8bb0..823690c4 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 6504fbe9..056fb372 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 39449af3..258621f6 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

    The following is a platform with theoretical support, but small bugs may remain in practice:

    • tvOS
    • 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 5e4c9d70..6fa07230 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 5c9b051d..f945a1f9 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 3a3495e3..e96c54a0 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.

    - + \ No newline at end of file diff --git a/en/docs/beginner/monobehaviour.html b/en/docs/beginner/monobehaviour.html index 8a3be5ea..6aa4a67d 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 4953ef16..cb380519 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 11f290ad..32bf9757 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 also supports 2019.4.x, but beginners must install the appropriate version according to the following requirements, and do not install the 2019 version on their own. After running through the process, carefully read the Install HybridCLR document and try other versions.

    • Install any version of 2020.3.26+, 2021.3.0+, 2022.3.0+. If you are not an experienced Unity developer, version 2021.3.1 is recommended.
    • 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 2bac8425..3600c47e 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/advancedcodeoptimization.html b/en/docs/business/advancedcodeoptimization.html index 9864dc4d..c8853b55 100644 --- a/en/docs/business/advancedcodeoptimization.html +++ b/en/docs/business/advancedcodeoptimization.html @@ -9,7 +9,7 @@ - + @@ -17,7 +17,7 @@

    Advanced instruction optimization

    danger

    Advanced instruction optimization is still under development and is expected to have a preview version in October 2023.

    Advanced instruction optimization techniques are implemented independently of standard instruction optimization techniques. Advanced instruction optimization technology uses richer compilation optimization technology to greatly improve the performance of the interpretation module. The overall execution performance of optimized instructions is improved by 100%-1000% (you read that right, 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.

    Implementation

    Advanced instruction optimization techniques include the following optimization techniques:

    • 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
    • Function inline, even inline AOT function
    • Provide more intrinsic instructions, greatly improving the performance of common instruction combinations
    • CheckOnce runtime checks dynamically eliminate optimizations. For example, the instruction to access the static member variable no longer checks whether the type has been initialized when it is executed for the second time
    • Other optimizations

    Performance

    Will test again after release.

    - + \ No newline at end of file diff --git a/en/docs/business/advancedencryption.html b/en/docs/business/advancedencryption.html index a142d63d..66623c71 100644 --- a/en/docs/business/advancedencryption.html +++ b/en/docs/business/advancedencryption.html @@ -9,14 +9,14 @@ - +

    Advanced Code Encryption

    Advanced code encryption is a derivative of advanced instruction optimization technology. It is safer and more reliable than standard code encryption.

    Principle

    Advanced instruction optimization converts IL instructions into register instructions in advance during the optimization process, and performs complex optimization and transformation, and finally outputs the final instructions. The final instruction and the original IL instruction not only have different instruction sets, but also do not have a one-to-one correspondence. They cannot be directly reversed, which greatly improves code security.

    - + \ No newline at end of file diff --git a/en/docs/business/basiccodeoptimization.html b/en/docs/business/basiccodeoptimization.html index fc556add..96a27581 100644 --- a/en/docs/business/basiccodeoptimization.html +++ b/en/docs/business/basiccodeoptimization.html @@ -9,13 +9,13 @@ - +

    Standard Code Optimization

    Careful and reliable optimization of common code paradigms has greatly improved the performance of common instructions such as variable access (50%-100%), numerical calculations (100-300%), object access (50-200%), etc., like some special The performance of codes such as typeof instructions has been improved by more than 1000%.

    Implementation

    Due to runtime time and memory constraints, standard instruction optimization only does some simple but reliable optimizations such as useless stack elimination and peephole optimization, and cannot perform some complex optimizations. However, since the IL instruction is a stack instruction, even if only some common and uncomplicated optimizations are made, the performance is greatly improved compared to the community's unoptimized version.

    [Advanced instruction optimization] (./advancedcodeoptimization) compared with standard instruction optimization, using complex offline optimization technology, the performance is much better than standard instruction optimization technology.

    Performance data

    The following is the performance improvement data of standard instruction optimization compared to the community version (0 means the performance is the same, n means n times improvement).

    interpreter_optimization

    The following is a performance comparison between native and standard instruction optimization in terms of numerical calculations. The ordinate is time consumption. The standard instruction optimized addition is about 7-16 times that of the native one, the multiplication is 4 times, and the division is 2 times.

    benchmark_numeric

    - + \ No newline at end of file diff --git a/en/docs/business/basicencryption.html b/en/docs/business/basicencryption.html index 940ea7e1..409bd41a 100644 --- a/en/docs/business/basicencryption.html +++ b/en/docs/business/basicencryption.html @@ -9,7 +9,7 @@ - + @@ -21,7 +21,7 @@ This field is an int type value, which provides a default value, and developers are strongly recommended to modify this value.

    Each different encryptionSeed will cause the HybridCLR/Genrate/EncryptXXX instructions to generate completely different hybridclr code.

    Building Games

    • HybridCLR/Generate/All
    • Use HybridCLR.Editor.Encryption.DllEncrypter class to encrypt supplementary metadata dll and hot update dll (even supplementary metadata dll needs to be encrypted!)
    • Add the encrypted dll to the resource management system of your project
    • Other operations are exactly the same as the community version

    Encryption dll

    Use the HybridCLR.Editor.Encryption.DllEncrypter class to encrypt supplementary metadata dlls and hot update dlls. EncryptDll demonstrates in the following code How to encrypt a dll, EncryptDllsInDirectory demonstrates how to encrypt multiple dlls.


    public static class EncryptDllCommand
    {
    public static void EncryptDll(string originalDllFile, string encryptedDllFile)
    {
    int seed = SettingsUtil.EncryptionSeedOrZeroWhileDisable;
    if (seed == 0)
    {
    Debug.LogWarning($"enableEncryption is false or encryptionSeed == 0, encryption is skipped");
    return;
    }
    var encryptor = new DllEncryptor(seed);
    byte[] originBytes = File.ReadAllBytes(originalDllFile);
    byte[] encryptedBytes = encryptor.EncryptDll(originBytes);
    File.WriteAllBytes(encryptedDllFile, encryptedBytes);
    }

    public static void EncryptDllsInDirectory(string dllDir)
    {
    int seed = SettingsUtil.EncryptionSeedOrZeroWhileDisable;
    if (seed == 0)
    {
    Debug.LogWarning($"enableEncryption is false or encryptionSeed == 0, encryption is skipped");
    return;
    }
    var encryptor = new DllEncryptor(seed);
    foreach (var dllFile in Directory.GetFiles(dllDir, "*.dll.bytes"))
    {
    byte[] originBytes = File. ReadAllBytes(dllFile);
    byte[] encryptedBytes = encryptor.EncryptDll(originBytes);
    File.WriteAllBytes(dllFile, encryptedBytes);
    Debug.Log($"EncryptDllsInDirectory {dllFile} length:{encryptedBytes.Length}");
    }
    }
    }

    Load at runtime

    Exactly the same as the community version, directly call Assembly.Load or RuntimeApi.LoadMetadataForAOTAssembly to load the encrypted dll file content. hybridclr will decrypt it internally and does not require the developer to perform additional decryption operations.

    - + \ No newline at end of file diff --git a/en/docs/business/businesscase.html b/en/docs/business/businesscase.html index 048585ad..199fad06 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 17ecb4ee..761c4b06 100644 --- a/en/docs/business/differentialhybridexecution.html +++ b/en/docs/business/differentialhybridexecution.html @@ -9,7 +9,7 @@ - + @@ -18,7 +18,7 @@ Since the two versions often do not modify too much code in practice, DHE can basically approach the native performance level.

    Features and Benefits

    • The performance of the unchanged part of the code is exactly the same as that of the native version, which is an astonishing 3-30 times or even higher than the purely interpreted version, and the overall performance almost reaches the native performance level.
    • The code can be changed arbitrarily, there is basically no intrusion to the code, there are almost no special precautions, and the usage method is similar to the community version.
    • The workflow is simple, you don’t need to mark which functions have changed like xxxfix and other solutions, and the tools will automatically handle them
    • Retrofits to items cost less than pure hot update versions. For example, extern functions can be defined directly in DHE without moving to the AOT module.
    • includes interpretation instruction optimization, and the performance of most numerical calculation instructions in the changed part is improved by 100-300% or more, further greatly improving the performance level.
    • The native code is all in the package body, the risk of being rejected by iOS is greatly reduced

    Unsupported feature

    • Any code in the AOT assembly corresponding to DHE cannot be executed before the DHE hot update code is loaded. It means that DHE does not support differential mixing of basic libraries like mscorlib, but supports differential hot update of traditional hot update assembly.
    • Due to the first restriction, [InitializeOnLoadMethod] and Script Execution Order settings are not supported in DHE assemblies.
    • DHE scripts are not supported to be mounted in package resources, including Resources. (This restriction will be relaxed or removed in the future)
    • Cannot add extern function in DHE assembly through hot update.

    dhao file

    The dhao file is the core concept of DHE technology. The dhao file contains information about the types and functions changed in the latest hot update dll that has been calculated offline. When running a hot update function directly based on the information in the dhao file, whether to use the latest interpreted version or directly call the original The AOT function. The dhao file calculated offline is extremely critical for DHE technology. If there is no dhao file, the original AOT dll needs to be carried additionally, and the cost of calculating function changes is extremely high.

    By comparing the latest hot update dll with the AOT dll generated during packaging, the changed type and function are calculated offline and saved as a dhao file. Therefore, in order for the DHE mechanism to work normally, it must depend on the correctness of the dhao file, and the correctness of the dhao file It relies on providing the latest hot update dll and 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 efda0e1f..0f253446 100644 --- a/en/docs/business/fullgenericsharing.html +++ b/en/docs/business/fullgenericsharing.html @@ -9,7 +9,7 @@ - + @@ -20,7 +20,7 @@ That is, generic parameters can be shared regardless of their type (including value types). HybridCLR uses this mechanism to realize that it does not need to supplement metadata, and it can perfectly support AOT generics.

    set up

    • 2021.3.x LTS version. Enabled when Il2Cpp Code Generation option in Build Settings is faster(smaller) buildfull generic sharing mechanism. When this option is enabled, all generic instances of a generic function (regardless of whether the generic parameter is a value type or a class type) completely share a single code.
    • 2022.3.x LTS version. Mandatory support for full generic sharing, even if the faster runtime option is used in Build Settings, this mechanism will be enabled. Difference from faster(smaller) build The reason is: faster runtime will use a separate generic function to implement the generic function that has been instantiated in AOT, instead of using a fully generic shared version, which improves the execution performance of the generic function; The faster(smaller) build option forces all generic functions of the same function to use one code, which has the same meaning as the 2021 version.
    - + \ No newline at end of file diff --git a/en/docs/business/intro.html b/en/docs/business/intro.html index 359a2b5b..de628322 100644 --- a/en/docs/business/intro.html +++ b/en/docs/business/intro.html @@ -9,7 +9,7 @@ - + @@ -17,7 +17,7 @@

    introduce

    We provide a variety of high-end commercial versions and flexibly customizable technical services to meet the needs of game projects in various application scenarios.

    Commercial version

    tip

    All commercial versions support paid trial versions. If you ultimately decide to purchase, the trial fee can be fully deducted from the final price; if If you ultimately abandon the purchase due to non-product factors, the trial fee will not be refunded.

    There are currently three commercial versions: Professional Edition, Ultimate Edition, and Hot Reload Edition. Their specific features are compared as follows.

    • Pro. Optimized performance and memory to provide higher code security
    • Ultimate. Contains all the functions of the professional version, and also includes our core [DHE technology] (./differentialhybridexecution), which greatly improves performance and almost (100% without modification) reaches the same level as native AOT
    • Hot reload version. Contains all the features of the Pro version, while supporting unloading and reloading individual assemblies
    FeaturesCommunity EditionProfessional EditionUltimate EditionHot Reload Edition
    Explanation and Execution
    MonoBehaviour
    Supplemental Metadata
    Incremental GC
    Full Generic Sharing
    Standard command optimization
    Standard code reinforcement
    DHE Technology
    Hot reload
    Technical Support

    Price Standard

    VersionPrice (RMB)Description
    Community Edition0Free
    Professional Edition30k/projectBuy out the right to use a project, while providing 2 hours of technical support and 1 year of code updates
    Hot reload versionE-mail business consultationBuy out the right to use a project, including 2 years of technical support and 2 years of code updates
    Ultimate versionE-mail consultation businessBuy out the right to use a project, including 2 years of technical support and 2 years of code updates

    Enterprise technical support

    You can flexibly choose the technical service items needed by the enterprise. If you subscribe on an annual basis, you will be billed according to the service item, otherwise you will be billed according to the service length.

    Technical support content

    • Standard bug response and resolution, including one-on-one remote assistance guidance. Most reproducible bugs will be fixed within 2-7 days or provide avoidance solutions.
    • Solve some special platform compatibility issues
    • Supports some currently unsupported versions (excluding 2018 and earlier versions)
    • Optimization guidance
    • other service

    Price Standard

    Since hybridclr is easy to use and stable in operation, most companies do not require long-term technical support, so they only provide timing technical support services. The unused time in a single service can be reserved for next use. In order to save business costs, we do not provide contracts and invoices for timely billing with a total price of less than 2,000. Please understand.

    Service levelProblem resolution scopePrice
    StandardProvide technical Q&A on basic usage issues, excluding solutions to bugs and unimplemented features400 RMB/hour
    ExpertSolve various complex problems in technical support content, including solving bugs and unimplemented features2000 RMB/hour

    contact us

    Please use your company's company email to initiate consultations to business@code-philosophy.com. Emails initiated by individuals such as QQ or 126 email addresses will be ignored, please understand.

    - + \ No newline at end of file diff --git a/en/docs/business/pro/intro.html b/en/docs/business/pro/intro.html index e177951c..2d49b0b1 100644 --- a/en/docs/business/pro/intro.html +++ b/en/docs/business/pro/intro.html @@ -9,13 +9,13 @@ - +

    Introduction

    The professional version provides some advanced features that are not supported by the community version. It is highly cost-effective and is suitable for occasions that require high memory and package size, such as WebGL games.

    Supported versions

    All Unity 2020-2022 LTS versions are supported.

    Advantage

    • Supports the full generic sharing technology of il2cpp starting from Unity 2021. Value types can also be shared generically. AOT generic functions are executed natively, which greatly improves the execution performance of generic functions. It is no longer necessary to add metadata to AOT, which simplifies the workflow, effectively reduces the package size, and significantly reduces memory usage. Especially useful for packages and memory-critical platforms such as WebGL.
    • Optimize metadata allocation during loading and running, with smaller memory footprint
    • More agile maintenance support, get the latest code at any time (the community version will only be released regularly due to maintenance cost considerations).
    - + \ No newline at end of file diff --git a/en/docs/business/pro/quickstart.html b/en/docs/business/pro/quickstart.html index 4d4535f5..de617c55 100644 --- a/en/docs/business/pro/quickstart.html +++ b/en/docs/business/pro/quickstart.html @@ -9,13 +9,13 @@ - +

    Getting Started

    Almost the same as the community version Quickstart, this document only introduces the differences.

    Install

    • After decompressing hybridclr_unity, put it in the project Packages directory and rename it to com.code-philosophy.hybridclr
    • Decompress the corresponding libil2cpp-{version}.7z according to your unity version,
    • Open HybridCLR/Installer, turn on the Copy libil2cpp from Local option, select the libil2cpp directory you just decompressed, and install it

    installer

    Enable full generic sharing

    • The 2020 version does not support fully generic sharing
    • The 2021 version needs to set the IL2CPP Code Generation option to faster(smaller)
    • Full generic sharing is enabled by default in the 2022 version and cannot be turned off. If you set the IL2CPP Code Generation option to faster(smaller), you can further reduce the package body.
    - + \ No newline at end of file diff --git a/en/docs/business/reload/hotreloadassembly.html b/en/docs/business/reload/hotreloadassembly.html index 9f24925c..debf458e 100644 --- a/en/docs/business/reload/hotreloadassembly.html +++ b/en/docs/business/reload/hotreloadassembly.html @@ -9,13 +9,13 @@ - +

    Hotreload Technology

    The hot reloading technology is used to completely unload or reload an assembly, which is suitable for small game collection type games. This solution only provides commercial version, please refer to Introduction to Hot Reload Version for details.

    Supported features

    • Support unloading assembly
    • Support reloading assembly, the code can be changed arbitrarily or even completely different (except MonoBehaviour class)
    • Unload most of the memory (expected to be more than 95%), but a small amount remains (such as the memory occupied by the thread static member field marked by [ThreadStatic])

    To be implemented, currently unsupported features

    • Most of the metadata can be released, but Il2CppClass and MethodInfo objects have not been released yet. The follow-up will be perfected soon.
    • Support Limit the collection of functions that can be accessed in the hot update assembly, suitable for creating a sandbox environment in UGC games to avoid damage caused by malicious player code.

    does not support features and special requirements

    • Cannot unload itself in the code of the unloaded assembly.
    • It is required that the business code will no longer use the objects or functions in the uninstalled Assembly, and exit all the old logic that is being executed
    • It is required that after overloading, in the MonoBehaviour of the same name class in the old Assembly, the functions specially processed by the Unity engine, such as Awake, will not be added or deleted (but the function body can be changed)
    • The dependent Assembly cannot be uninstalled directly, the dependent must be uninstalled first and then the dependent must be uninstalled according to the reverse dependency order. For example, if A depends on B, you need to uninstall B first, and then uninstall A.
    • Due to Unity's own implementation, it is not compatible with 2022 Jobs, and you need to slightly modify the code of UnityEngine.CoreModule.dll by yourself. 2020-2021 will still work normally.
    • Does not support unloading assemblies containing MonoBehaviour or Scriptable scripts. It is temporarily recommended to put it in another aot or hot update assembly that will not be uninstalled, and reference it in the uninstallable assembly.
    • Destructors, ~XXX() are not supported. It is also not allowed to instantiate a generic class with a destructor with a generic parameter of this assembly type
    - + \ No newline at end of file diff --git a/en/docs/business/reload/intro.html b/en/docs/business/reload/intro.html index 0bf68d90..d52b34da 100644 --- a/en/docs/business/reload/intro.html +++ b/en/docs/business/reload/intro.html @@ -9,13 +9,13 @@ - +

    Introduction

    Hot Reload Special Edition provides support for the original Hot Reload Technology. It can completely unload or reload an assembly during operation, especially suitable for small game collection type games.

    Supported versions

    All Unity 2021-2022 LTS versions are supported.

    Advantage

    • Support the full generic sharing technology of il2cpp starting from Unity 2021, value types can also be shared generically. It is no longer necessary to add metadata to AOT, which simplifies the workflow, effectively reduces the package size, and significantly reduces memory usage. Especially useful for packages and memory-critical platforms such as WebGL.
    • Support unloading a single assembly, unloading most of the memory (expected to be more than 90%), but there is a small amount of residue (such as the memory occupied by the thread static member field marked by [ThreadStatic])
    • Support reloading assembly, the code can be changed arbitrarily or even completely different (except MonoBehaviour class). Hot overloading of MonoBehaviour and ScriptableObject.
    • Support Limit the collection of functions that can be accessed in the hot update assembly, suitable for creating a sandbox environment in UGC games to avoid damage caused by malicious player code.
    • More agile maintenance support, get the latest code at any time (the community version will only be released regularly due to maintenance cost considerations).
    - + \ No newline at end of file diff --git a/en/docs/business/reload/modifydll.html b/en/docs/business/reload/modifydll.html index 6d351535..94e71648 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 595e983a..8b175bfc 100644 --- a/en/docs/business/reload/quickstart.html +++ b/en/docs/business/reload/quickstart.html @@ -9,13 +9,13 @@ - +

    Getting Started

    Almost identical to the community version of Quickstart, this document only introduces the differences.

    Install

    • After decompressing hybridclr_unity, put it in the project Packages directory and rename it to com.code-philosophy.hybridclr
    • Decompress the corresponding libil2cpp-{version}.7z according to your unity version
    • Open HybridCLR/Installer, turn on the Copy libil2cpp from Local option, select the libil2cpp directory you just decompressed, and install it
    • Unity 2022+ version needs to use the modified version of UnityEngine.CoreModule.dll, see Modify dll for details

    installer

    Enable full generic sharing

    • The 2020 version does not support fully generic sharing
    • The 2021 version needs to set the IL2CPP Code Generation option to faster(smaller)
    • Full generic sharing is enabled by default in the 2022 version and cannot be turned off. If you set the IL2CPP Code Generation option to faster(smaller), you can further reduce the package body.

    used in the code

    Call RuntimeApi.UnloadAssembly to unload the assembly, call Assembly.Load to reload the assembly. It is currently not supported to load the assembly again without unloading the assembly, 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 uninstall
    RuntimeApi. UnloadAssembly(ass);

    // second load
    Assembly newAss = Assembly. Load(yyy);

    // execute some code
    Type mainType = ass. GetType("Entry");
    mainType. GetMethod("Main"). Invoke(null, null);

    // second uninstall
    RuntimeApi. UnloadAssembly(ass);

    Precautions

    • It is easy for async or coroutines to implicitly keep references to the unloaded assembly code in other threads. Be sure to clean up all async or coroutine functions before unloading
    • OnClick or various callback events of the UI can easily cause references to the uninstall assembly to be kept, so it must be cleaned up
    • Registered to the global event or other elevation, it is easy to accidentally keep a reference to the uninstall assembly, be sure to clean it up
    - + \ No newline at end of file diff --git a/en/docs/business/ultimate/intro.html b/en/docs/business/ultimate/intro.html index 8a7fe6ee..8b9e01c1 100644 --- a/en/docs/business/ultimate/intro.html +++ b/en/docs/business/ultimate/intro.html @@ -9,13 +9,13 @@ - +

    introduce

    The Ultimate Edition is mainly for projects with strict performance requirements. Compared with the community version, the performance of the flagship version has been greatly improved, basically reaching the native performance level, and at the same time, it has better optimization in terms of security and memory.

    Supported versions

    All Unity 2020-2022 LTS versions are supported.

    Advantage

    • Contains the original Differential Hybrid Execution(DHE) technology, the performance of the unchanged part of the code is exactly the same as that of the original, compared with the pure interpretation method of the community version, the improvement is amazing3-30 times or even higher, the overall almost reaches the level of native performance
    • Supports the full generic sharing technology of il2cpp starting from Unity 2021. Value types can also be shared generically. AOT generic functions are executed natively, which greatly improves the execution performance of generic functions. It is no longer necessary to add metadata to AOT, which simplifies the workflow, effectively reduces the package size, and significantly reduces memory usage. Especially useful for packages and memory-critical platforms such as WebGL
    • Primary instruction optimization. Careful and reliable optimization of common code paradigms, greatly improving the performance of common instructions such as variable access (50%-100%), numerical calculation (100-300%), and object access (50-200%), like some special The performance of the code, such as the typeof instruction, has been improved by more than 1000%.
    • Deep instruction optimization technology. In-depth optimization of instructions, the overall interpretation performance is improved by 100%-1000% (more than 10 times) or even higher, and the overall improvement of numerical instructions is nearly 300%. Due to the conversion of instructions into register instructions in advance, the runtime instruction translation time is greatly shortened, and the game runs more smoothly
    • Optimized metadata allocation, taking up less memory
    • Convert the original IL instructions into register instructions in advance, which is naturally anti-decompilation and cracking, and is safer
    • Optimize metadata allocation during loading and running to save memory
    • More agile maintenance support, get the latest code at any time (the community version will only be released regularly due to maintenance cost considerations)
    • The native code is all in the package body, and the risk of being rejected by major AppStore is greatly reduced
    • One-year technical support is attached to quickly solve various problems encountered during use
    - + \ No newline at end of file diff --git a/en/docs/business/ultimate/manual.html b/en/docs/business/ultimate/manual.html index fa74e1e9..44c05f09 100644 --- a/en/docs/business/ultimate/manual.html +++ b/en/docs/business/ultimate/manual.html @@ -9,7 +9,7 @@ - + @@ -27,7 +27,7 @@ Note that at this time you cannot use the hot update dll compiled by HybridCLR/CompileDll/xxx to replace the AOT dll generated during packaging, because the compilation is unstable, they may not be the same, and it may cause serious crashes.

    Precautions:

    • To load the differential hybrid execution assembly in the order of its dependencies.
    • If a certain assembly has not changed, the dhao field can pass null, but at this time, the AOT dll generated during packaging must be used instead of the hot update dll generated by the HybridCLR/CompileDll/xxx command.
    • The DHE assembly itself already contains metadata. Even if the full generic sharing is not enabled, Do not add metadata to the DHE assembly. If it is supplemented, it will fail. Other non-DHE AOT assemblies can be supplemented as usual metadata.
    Load DHE assembly
    void InitDifferentialHybridAssembly(string assemblyName)
    {
    // When there is no hot update, the passed parameter is null.
    byte[] dhaoBytes = needHotUpdate ? GetAssemblyOptionData(assemblyName) : null;
    LoadImageErrCode err = RuntimeApi.LoadDifferentialHybridAssembly(GetAssemblyData(assemblyName), dhaoBytes);
    }

    Pack

    After generating the AOT dll in the packaging pipeline, run HybridCLR/CreateAOTDllSnapshot to back up the AOT files and add them to the version management system, because they will be needed for future hot updates to generate dhao files. Notice! Do not use AOT dll generated by HybridCLR/Generate/All command due to the instability generated by clipping AOT dll.

    Since the DHE mechanism needs to provide the dhe assembly to work normally, when no hot update occurs, the DHE assembly is equivalent to the AOT assembly generated during packaging, at this time, there is no need to provide the dhao file. Although these assemblies can be downloaded via HotUpdate, it is highly recommended to carry them with the package.

    Carry the DHE assembly with the package

    If you want to carry the AOT dll corresponding to the DHE assembly with the package, according to your BuildTarget:

    -iOS. Add IPostprocessBuildWithReport processing class, copy the DHE dll under {proj}/HybridCLRData/AssembliesPostIl2CppStrip/{buildTarget} to the StreamingAssets directory (or subdirectory) in the OnPostprocessBuild function. You can also manually copy after exporting the project -Android. If you export the gradle project first and then package it, it is the same as iOS. If the APK package is exported directly, add the IPostGenerateGradleAndroidProject processing class, and copy the generated DHE AOT assembly to the gradle project in the OnPostGenerateGradleAndroidProject event

    Copy DHE assembly during building pipeline

    // After iOS or Android exports the project, copy the file to the project
    public class CopyDHEAOTDllsToProject : IPostprocessBuildWithReport
    {
    public int callbackOrder => 0;

    public void OnPostprocessBuild(BuildReport report)
    {
    BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
    YourCopyDHEAssembliesToStreamingAssetsOrAssetBundle();
    }
    }

    /// After generating the Gradle project, copy the required files
    public class CopyDHEAOTDllsToAndroidProject : IPostGenerateGradleAndroidProject
    {
    public int callbackOrder => 0;

    public void OnPostGenerateGradleAndroidProject(string path)
    {
    BuildTarget target = EditorUserBuildSettings. activeBuildTarget;
    YourCopyDHEAssembliesToStreamingAssetsOrAssetBundle();
    }
    }

    caution

    If the development build option is used for packaging, please 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.

    Hot update

    • Use HybridCLR/CompileDll/ActivedBuildTarget to generate hot update dll.
    • Make sure you have run HybridCLR/CreateAOTDllSnapshot to back up the AOT file, and make sure that the AOT dll in the backup directory is the AOT dll generated during packaging.
    • Use HybridCLR/generate/DHEAssemblyOptionDatas to generate dhao files.
    caution

    Because the working principle of DHEAssemblyOptionDatas is to compare the latest hot update DHE dll with the AOT dll in the backup directory of the original AOT dll, and generate changed function and type information. Please be sure to ensure hot update dll and backup The correctness of the AOT dll!

    Precautions

    There are huge differences in the results of calculating dhao caused by external dlls

    If an external dll is marked as a DHE assembly, since the external dll will be trimmed when it is packaged, and the original external dll is taken when calculating the dhao file, resulting in a huge difference, which is not expected. There are several solutions:

    1. <assembly fullname="YourExternDll" preserve="all"/> in link.xml to completely preserve the external dll
    2. Do not use the latest hot update dll to calculate the difference, but use the aot dll generated when the latest code is repackaged to calculate the difference. This requires you to modify the code related to calculating dhao yourself, and use the AssembliesPostIl2CppStrip directory to compare with the AOTDllSnapshot directory.
    - + \ No newline at end of file diff --git a/en/docs/business/ultimate/quickstart.html b/en/docs/business/ultimate/quickstart.html index 30606eae..52b5f58c 100644 --- a/en/docs/business/ultimate/quickstart.html +++ b/en/docs/business/ultimate/quickstart.html @@ -9,7 +9,7 @@ - + @@ -18,7 +18,7 @@ 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.

    The difficulty of using the ultimate edition is similar to that of the community version, and most of the principles are the same. It is recommended to familiarize yourself with the community version before trying the ultimate edition.

    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

    The ultimate edition does not support the 2019.4.x series.

    • Install any version of 2020.3.26+, 2021.3.0+, 2022.3.0+. Versions 2020.3.0-2020.3.25 are also supported, but after the installation is completed in the Installer, you need to copy 2020.3.x/Editor/Data/il2cpp/external from the installation directory of any version 2020.3.26+ to replace {project}/HybridCLRData/LocalIl2CppData-{platform}/il2cpp/external
    • 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, e.g. xcode 13.4.1, macos 12.4
    • install git

    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

    • After decompressing hybridclr_unity.zip, put it in the project Packages directory and rename it to com.code-philosophy.hybridclr
    • Decompress the corresponding libil2cpp-{version}.7z according to your unity version
    • Open HybridCLR/Installer, enable the copy libil2cpp from local option, select the libil2cpp directory you just decompressed, and install
    • Replace {proj}\HybridCLRData\LocalIl2CppData-WindowsEditor\il2cpp\build\deploy\netcoreapp3.1\Unity.IL2CPP.dll with ModifiedDlls\{verions}\Unity.IL2CPP.dll according to your Unity version ( Unity 2020) or {proj}\HybridCLRData\LocalIl2CppData-WindowsEditor\il2cpp\build\deploy\Unity.IL2CPP.dll (Unity 2021+). If your version is not available, contact us to make one

    installer

    Configure HybridCLR

    • Open the menu HybridCLR/Settings
    • Add HotUpdate assembly in differentialHybridAssemblies list

    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 it is not yet stable, it will not be demonstrated in this tutorial
    • 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");
    }
    }

    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, add 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 = 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");
    string dhaoPath = $"{Application.streamingAssetsPath}/{assName}.dhao.bytes";
    byte[] dhaoBytes = File.Exists(dhaoPath) ? File.ReadAllBytes(dhaoPath) : null;
    LoadImageErrorCode err = RuntimeApi.LoadDifferentialHybridAssembly(dllBytes, dhaoBytes, true);
    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;
    }
    }
    }

    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 properly.

    Build and Run

    • Run the menu HybridCLR/Generate/All to perform the necessary generation operations. This step cannot be missed!!!
    • Open the Build Settings dialog box, click Build, select the output directory Release-Win64, and package the project.
    • Run menu HybridCLR/CreateAOTDllSnapshot. This step cannot be missed!!!
    • Copy {proj}/HybridCLRData/AOTDllOutput/StandaloneWindows64/HotUpdate.dll (StandaloneMacXxx under MacOS) to XXX_Data/StreamingAssets/HotUpdate.dll.bytes
    • Run Release-Win64/Xxx.exe, 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 the menu command HybridCLR/CompileDll/ActiveBulidTarget to recompile the hot update code.
    • Run HybridCLR/Generate/DHEAssmeblyOptionData to generate dhao data.
    • Copy {proj}/HybridCLRData/HotUpdateDlls/StandaloneWindows64/HotUpdate.dll to replace XXX_Data/StreamingAssets/HotUpdate.dll.bytes
    • Copy {proj}/HybridCLRData/DifferentialHybridOptionDatas/HotUpdate.dhao.bytes to XXX_Data/StreamingAssets/HotUpdate.dhao.bytes
    • Re-run the program, and you will find Hello, World displayed on the screen, indicating that the hot update code has taken effect!
    caution

    When no hot update happens, use original AOT dll, from {proj}/HybridCLRData/AOTDllOutput/{target} directory. When a hot update occurs, use the latest hot update dll from {proj}/HybridCLRData/HotUpdateDlls/{target} directory.

    This completes the hot update experience! ! !

    - + \ No newline at end of file diff --git a/en/docs/help.html b/en/docs/help.html index a137b497..443942d5 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 d0361c77..7969d247 100644 --- a/en/docs/help/commonerrors.html +++ b/en/docs/help/commonerrors.html @@ -9,7 +9,7 @@ - + @@ -23,7 +23,7 @@ Fix the crash bug when using Momery Profiler to create a snapshot The changes can be merged 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 the Developemnt mode, the code is as follows. If you use v3.0.2 and above, the HybridCLR/CompileDll/ActivedBuildTarget_Development menu command has been shipped with it.

         var group = BuildPipeline. GetBuildTargetGroup(target);

    ScriptCompilationSettings scriptCompilationSettings = new ScriptCompilationSettings();
    scriptCompilationSettings.group = group;
    scriptCompilationSettings. target = target;
    if (developmentBuild)
    {
    // The core is this line, so that the dll is compiled in Debug mode, and function calls such as Profiler.BeginSample are reserved.
    scriptCompilationSettings.options |= ScriptCompilationOptions.DevelopmentBuild;
    }
    Directory.CreateDirectory(buildDir);
    ScriptCompilationResult scriptCompilationResult = PlayerBuildInterface. CompilePlayerScripts(scriptCompilationSettings, buildDir);

    The NotSupportNative2Managed bridge function is missing exception after using Unity.netcode.runtime

    The reason is that NetworkManager.RpcReceiveHandler is internal in Unity.netcode.runtime.dll, defined as follows

    internal delegate void RpcReceiveHandler(NetworkBehaviour behavior, FastBufferReader reader, __RpcParams parameters);

    Causes the build tool to not generate a bridge function for it. But Unity is very tricky to generate RpcReceiveHandler for functions marked [ClientRpc] and [ServerRpc] when packaging Handling function, and referenced the internal RpcReceiveHandler class! No error was reported. This leads to the problem that the bridge function is missing.

    The original code is as follows.


    public class NetworkPlayer : NetworkBehaviour
    {

    public static string msgFromHost;
    public static string msgFromClient;


    [ClientRpc]
    public void SendMsgClientRpc(string msgFromHost)
    {
    NetworkPlayer.msgFromHost = msgFromHost;
    }


    [ServerRpc]
    public void SendMsgServerRpc(string msgFromClient)
    {
    NetworkPlayer.msgFromClient = msgFromClient;
    }
    }

    Several functions are added to the code generated during packaging, as follows.

    public class NetworkPlayer : NetworkBehaviour
    {
    public static string msgFromHost;

    public static string msgFromClient;

    [ClientRpc]
    public void SendMsgClientRpc(string msgFromHost)
    {
    //...
    }

    [ServerRpc]
    public void SendMsgServerRpc(string msgFromClient)
    {
    //...
    }

    static NetworkPlayer()
    {
    // NetworkManager.__rpc_func_table is not accessible in your own code! because it is internal
    NetworkManager.__rpc_func_table.Add(3066788814u, __rpc_handler_3066788814);
    NetworkManager.__rpc_func_table.Add(901396020u, __rpc_handler_901396020);
    }

    private static void __rpc_handler_3066788814(NetworkBehaviour target, FastBufferReader reader, __RpcParams rpcParams)
    {
    //...
    }

    private static void __rpc_handler_901396020(NetworkBehaviour target, FastBufferReader reader, __RpcParams rpcParams)
    {
    //...
    }

    internal override string __getTypeName()
    {
    return "NetworkPlayer";
    }
    }

    The solution is that you also define a delegate with the same signature in the AOT project.

         // Since __RpcParams is also internal, we redefine 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 behavior, 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 3bcffd9d..d0042a1b 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 d20bc8f4..0f041107 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 ed53e3b0..e92266b7 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

    • Support 2019.4.x, 2020.3.x, 2021.3.x, 2022.3.x full series of LTS 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 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 ba4b834b..03632be3 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 b3269243..c3b73919 100644 --- a/en/docs/other/businesscase.html +++ b/en/docs/other/businesscase.html @@ -9,7 +9,7 @@ - + @@ -18,7 +18,7 @@ HybridCLR has been widely verified as a very efficient and stable hot update solution for Unity.

    Although there are hundreds of projects that use HybridCLR and have been launched (AppStore or GooglePlay) in the industry, considering that most people are more concerned about the projects of leading companies, Below we list some of the leading companies that have already connected to HybridCLR.

    Since we generally do not collect project information from our clients, Coupled with the long project cycle of leading companies, there are fewer known online ones.

    CompanyOnline Project 1Online Project 2Online Project 3
    Tencent
    NetEaseYaotai
    BaiduXirang
    funplusBingo Aloha
    GigabitWonderful Fighter
    SunbornGirls Frontline 2Wandering Earth
    Nuverse (byte dance)
    Paper Games
    Lilith
    bilibili
    elex
    babybus
    JJ World
    Yoo Zoo
    ztgame
    Sheng Qu games
    Perfect World
    Chang You
    IGG
    ZiLong Games
    Hero Games
    - + \ No newline at end of file diff --git a/en/docs/other/changelog.html b/en/docs/other/changelog.html index 7c1e62f6..869642a9 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 6215e421..6b6c9b2d 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 869a71c1..d3aab4f0 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 a65f6d97..b0f46284 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 b1228c5f..bf47dc9e 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 82124362..c0c4d507 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 85950c45..e26d912e 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 5b9c8543..c94ad475 100644 --- a/en/docs/ultimate.html +++ b/en/docs/ultimate.html @@ -9,13 +9,13 @@ - + - + \ No newline at end of file diff --git a/en/img/hybridclr/install.jpg b/en/img/hybridclr/install.jpg index 4a04f1856b5f90ac6d91b590cfa5784d383c3121..6c785349cbd3158637606bec5515ce4e6dae87f2 100644 GIT binary patch literal 8635 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 ATL1t6 literal 25485 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�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 literal 24395 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 y?As*Up&9rc)#%qr