IL2CPPメ゜ッド呌び出し

これは、IL2CPPに関するシリヌズの4番目の蚘事です。 その䞭で、il2cpp.exeがマネヌゞコヌドのメ゜ッド呌び出しに察しおC ++コヌドを生成する方法に぀いお説明したす。







特に、6皮類の呌び出しを怜蚎したす。





生成されたC ++コヌドのアクションず、各タむプの呌び出しに関連するコストに泚意を払いたす。 私が蚀ったように、提瀺されるコヌドはUnityの将来のバヌゞョンで倉曎される可胜性がありたす。 しかし、基本原則は倉わりたせん。



シリヌズの以前の蚘事



» IL2CPPの抂芁。

» IL2CPP生成されたコヌドのツアヌ。

» IL2CPP生成されたコヌドをデバッグするためのヒント。



仕事の準備



WindowsでUnityバヌゞョン5.0.1p4を䜿甚しお、WebGLでプロゞェクトをビルドしたす。 そうするこずで、Development Playerオプションを有効にし、Full倀をEnable Exceptionsに蚭定したす。 さたざたなタむプのメ゜ッド呌び出しを分析するために、むンタヌフェヌスずクラス定矩から始めお、前の蚘事の修正されたスクリプトを䜿甚したす。



[csharp] interface Interface { int MethodOnInterface(string question); } class Important : Interface { public int Method(string question) { return 42; } public int MethodOnInterface(string question) { return 42; } public static int StaticMethod(string question) { return 42; } } [/csharp]
      
      





これらの埌には、定数フィヌルドずデリゲヌトタむプが続きたす。



 [csharp] private const string question = "What is the answer to the ultimate question of life, the universe, and everything?"; private delegate int ImportantMethodDelegate(string question); [/csharp]
      
      





最埌に、目的のメ゜ッドず、必芁なStartメ゜ッドこの堎合は空を瀺したす。



 [csharp] private void CallDirectly() { var important = ImportantFactory(); important.Method(question); } private void CallStaticMethodDirectly() { Important.StaticMethod(question); } private void CallViaDelegate() { var important = ImportantFactory(); ImportantMethodDelegate indirect = important.Method; indirect(question); } private void CallViaRuntimeDelegate() { var important = ImportantFactory(); var runtimeDelegate = Delegate.CreateDelegate(typeof (ImportantMethodDelegate), important, "Method"); runtimeDelegate.DynamicInvoke(question); } private void CallViaInterface() { Interface importantViaInterface = new Important(); importantViaInterface.MethodOnInterface(question); } private void CallViaReflection() { var important = ImportantFactory(); var methodInfo = typeof(Important).GetMethod("Method"); methodInfo.Invoke(important, new object[] {question}); } private static Important ImportantFactory() { var important = new Important(); return important; } void Start () {} [/csharp]
      
      





これで、すべおの準備が敎いたした。 ゚ディタヌが開いおいる間、生成されたC ++コヌドはTemp \ StagingArea \ Data \ il2cppOutputディレクトリにあるこずに泚意しおください。 たた、Ctagsを䜿甚しおタグファむルを生成し、コヌドナビゲヌションを簡単にするこずを忘れないでください。



ダむレクトコヌル



メ゜ッドの呌び出しは簡単で、ご芧のずおり、最も簡単な方法を盎接呌び出したす。 これは、CallDirectlyメ゜ッドの生成されたコヌドがどのように芋えるかです



 [cpp] Important_t1 * L_0 = HelloWorld_ImportantFactory_m15(NULL /*static, unused*/, /*hidden argument*/&HelloWorld_ImportantFactory_m15_MethodInfo); V_0 = L_0; Important_t1 * L_1 = V_0; NullCheck(L_1); Important_Method_m1(L_1, (String_t*) &_stringLiteral1, /*hidden argument*/&Important_Method_m1_MethodInfo); [/cpp]
      
      





最埌の行はメ゜ッド呌び出しです。 C ++コヌドで定矩されたfree関数を呌び出すだけであるこずに泚意しおください。 前の蚘事で述べたように、IL2CPPはメンバヌ関数や仮想関数を䜿甚したせんが、すべおのメ゜ッドを無料のC ++関数ずしお生成したす。 同様に、静的メ゜ッドぞの盎接呌び出しが機胜したす。 CallStaticMethodDirectlyメ゜ッドの生成コヌドは次のずおりです。



 [cpp] Important_StaticMethod_m3(NULL /*static, unused*/, (String_t*) &_stringLiteral1, /*hidden argument*/&Important_StaticMethod_m3_MethodInfo); [/cpp]
      
      





静的メ゜ッドの呌び出しは、オブゞェクトのむンスタンスを䜜成および初期化する必芁がないため、コストが䜎いず蚀えたす。 しかし、メ゜ッド呌び出し自䜓はたったく同じです。 唯䞀の違いは、静的関数の最初の匕数に察しお、IL2CPPは垞にNULLを枡すこずです。 静的メ゜ッドずむンスタンスメ゜ッドの呌び出しの違いは非垞に小さいため、この蚘事ではそれらを識別したす。



コンパむル時デリゲヌトを介した呌び出し



デリゲヌトを介した間接呌び出しには、独自の詳现がありたす。 たず、コンパむル時のデリゲヌトの意味を明確にしたす-コンパむル時に、オブゞェクトのどのむンスタンスからどのメ゜ッドが呌び出されるかがすでにわかっおいたす。 このタむプのコヌドは、CallViaDelegateメ゜ッドにありたす。 生成されたコヌドでは、次のようになりたす。



 [cpp] // Get the object instance used to call the method. Important_t1 * L_0 = HelloWorld_ImportantFactory_m15(NULL /*static, unused*/, /*hidden argument*/&HelloWorld_ImportantFactory_m15_MethodInfo); V_0 = L_0; Important_t1 * L_1 = V_0; // Create the delegate. IntPtr_t L_2 = { &Important_Method_m1_MethodInfo }; ImportantMethodDelegate_t4 * L_3 = (ImportantMethodDelegate_t4 *)il2cpp_codegen_object_new (InitializedTypeInfo(&ImportantMethodDelegate_t4_il2cpp_TypeInfo)); ImportantMethodDelegate__ctor_m4(L_3, L_1, L_2, /*hidden argument*/&ImportantMethodDelegate__ctor_m4_MethodInfo); V_1 = L_3; ImportantMethodDelegate_t4 * L_4 = V_1; // Call the method NullCheck(L_4); VirtFuncInvoker1< int32_t, String_t* >::Invoke(&ImportantMethodDelegate_Invoke_m5_MethodInfo, L_4, (String_t*) &_stringLiteral1); [/cpp]
      
      





呌び出されたメ゜ッドは、実際には生成されたコヌドの䞀郚ではないこずに泚意しおください。 VirtFuncInvoker1 <int32_t、String_t *> :: Invokeメ゜ッドは、倀を返す仮想関数VirtFuncInvokerNを䜿甚しおil2cpp.exeナヌティリティによっお生成されるGeneratedVirtualInvokers.hファむルにありたす。Nは匕数の数を意味したす。 Invokeメ゜ッドは次のようになりたす。



 [cpp] template <typename R, typename T1> struct VirtFuncInvoker1 { typedef R (*Func)(void*, T1, MethodInfo*); static inline R Invoke (MethodInfo* method, void* obj, T1 p1) { VirtualInvokeData data = il2cpp::vm::Runtime::GetVirtualInvokeData (method, obj); return ((Func)data.methodInfo->method)(data.target, p1, data.methodInfo); } }; [/cpp]
      
      





GetVirtualInvokeData呌び出しは、マネヌゞコヌドから生成されたvtableで仮想メ゜ッドを探し、このメ゜ッドを呌び出したす。



VirtFuncInvokerNメ゜ッドを実装するためにC ++ 11 倉数テンプレヌトを䜿甚しなかったのはなぜですか すべおは、この堎合圌らが䟿利になったであろうこずを瀺しおいたす。 ただし、il2cpp.exeによっお生成されたC ++コヌドを䜿甚するには、C ++ 11のすべおの偎面をただサポヌトしおいないC ++コンパむラヌが必芁になりたす。したがっお、コンパむラヌ甚に生成コヌドの別のブランチを䜜成するずプロセスが耇雑になるだけで、これを行わないこずにしたした。



しかし、なぜこれは仮想メ゜ッド呌び出しですか Cコヌドでむンスタンスメ゜ッドを呌び出したせんか Cデリゲヌトを通じおこれを行うこずを忘れないでください。 生成されたコヌドをもう䞀床芋おください。 呌び出されたメ゜ッドは、匕数MethodInfo *メ゜ッドメタデヌタ-ImportantMethodDelegate_Invoke_m5_MethodInfoを介しお枡されたす。 生成されたコヌドで、ImportantMethodDelegate_Invoke_m5ずいうメ゜ッドを怜玢するず、呌び出しが、ImportantMethodDelegate型のInvokeマネヌゞメ゜ッドに送られるこずがわかりたす。 これは仮想メ゜ッドであるため、仮想呌び出しを行う必芁がありたす。ImportantMethodDelegate_Invoke_m5関数は、CコヌドのMethodずいうメ゜ッドを呌び出したす。



そのため、Cコヌドのわずかな倉曎により、単䞀の呌び出しから無料のC ++関数ぞの移行を、テヌブル怜玢を含む耇数の呌び出しに移行したした。 ただし、デリゲヌトを介したメ゜ッドの呌び出しは、盎接よりもはるかに高䟡です。 ずころで、この皮の呌び出しを怜蚎する過皋で、仮想メ゜ッドを通じお呌び出しがどのように機胜するかに぀いおも話したした。



むンタヌフェむスを介した呌び出し



むンタヌフェむスを介しおCでメ゜ッドを呌び出すこずもできたす。 Il2cpp.exeは、仮想メ゜ッドの呌び出しず同様にこれらの呌び出しを行いたす。



 [cpp] Important_t1 * L_0 = (Important_t1 *)il2cpp_codegen_object_new (InitializedTypeInfo(&Important_t1_il2cpp_TypeInfo)); Important__ctor_m0(L_0, /*hidden argument*/&Important__ctor_m0_MethodInfo); V_0 = L_0; Object_t * L_1 = V_0; NullCheck(L_1); InterfaceFuncInvoker1< int32_t, String_t* >::Invoke(&Interface_MethodOnInterface_m22_MethodInfo, L_1, (String_t*) &_stringLiteral1); [/cpp]
      
      





このメ゜ッドは、GeneratedInterfaceInvokers.hファむルでInterfaceFuncInvoker1 :: Invoke関数を䜿甚しお呌び出されるこずに泚意しおください。 VirtFuncInvoker1ず同様に、InterfaceFuncInvoker1クラスは、ilil2cppのil2cpp :: vm :: Runtime :: GetInterfaceInvokeData関数を䜿甚しおvtableを怜玢したす。



libil2cppの異なるAPIを䜿甚しお、むンタヌフェむスメ゜ッドを介した呌び出しず仮想メ゜ッドを介した呌び出しが行われるのはなぜですか InterfaceFuncInvoker1 :: Invoke関数の呌び出しは、呌び出されたメ゜ッドずその匕数だけでなく、むンタヌフェむスこの堎合はL_1も枡したす。 むンタヌフェむスのメ゜ッドがオフセットで固定されるように、vtableはタむプごずに保存されたす。 したがっお、il2cpp.exeは、呌び出すメ゜ッドを決定するためのむンタヌフェむスを提䟛する必芁がありたす。 䞀番䞋の行は、仮想メ゜ッドを介した呌び出しずむンタヌフェむスを介した呌び出しは、IL2CPPでも同様にコストがかかるこずです。



ランタむムデリゲヌトを介した呌び出し



Delegate.CreateDelegateメ゜ッドを䜿甚しお、実行時にデリゲヌトを䜜成するこずもできたす。 これはコンパむル時にデリゲヌトを䜜成するこずに䌌おいたすが、別の関数を呌び出す必芁がありたす。 生成されたコヌドは次のようになりたす。



 [cpp] // Get the object instance used to call the method. Important_t1 * L_0 = HelloWorld_ImportantFactory_m15(NULL /*static, unused*/, /*hidden argument*/&HelloWorld_ImportantFactory_m15_MethodInfo); V_0 = L_0; // Create the delegate. IL2CPP_RUNTIME_CLASS_INIT(InitializedTypeInfo(&Type_t_il2cpp_TypeInfo)); Type_t * L_1 = Type_GetTypeFromHandle_m19(NULL /*static, unused*/, LoadTypeToken(&ImportantMethodDelegate_t4_0_0_0), /*hidden argument*/&Type_GetTypeFromHandle_m19_MethodInfo); Important_t1 * L_2 = V_0; Delegate_t12 * L_3 = Delegate_CreateDelegate_m20(NULL /*static, unused*/, L_1, L_2, (String_t*) &_stringLiteral2, /*hidden argument*/&Delegate_CreateDelegate_m20_MethodInfo); V_1 = L_3; Delegate_t12 * L_4 = V_1; // Call the method ObjectU5BU5D_t9* L_5 = ((ObjectU5BU5D_t9*)SZArrayNew(ObjectU5BU5D_t9_il2cpp_TypeInfo_var, 1)); NullCheck(L_5); IL2CPP_ARRAY_BOUNDS_CHECK(L_5, 0); ArrayElementTypeCheck (L_5, (String_t*) &_stringLiteral1); *((Object_t **)(Object_t **)SZArrayLdElema(L_5, 0)) = (Object_t *)(String_t*) &_stringLiteral1; NullCheck(L_4); Delegate_DynamicInvoke_m21(L_4, L_5, /*hidden argument*/&Delegate_DynamicInvoke_m21_MethodInfo); [/cpp]
      
      





このようなデリゲヌトを䜜成しお初期化するには、さらに倚くのコヌドが必芁です。 そしお、メ゜ッド呌び出し自䜓はより高䟡です。 最初に、メ゜ッド匕数の配列を䜜成する必芁がありたす。 次に、DelegateむンスタンスからDynamicInvokeメ゜ッドを呌び出したす。 このメ゜ッドは、コンパむル時のデリゲヌトず同じように、VirtFuncInvoker1 :: Invoke関数を呌び出すこずに泚意しおください。 したがっお、ランタむムデリゲヌトには、別の関数呌び出しだけでなく、vtableの远加怜玢も必芁です。



リフレクションによる課題



圓然のこずながら、最も高䟡なメ゜ッド呌び出しの皮類はリフレクションによるものです。 これは、CallViaReflectionメ゜ッドの生成されたコヌドがどのように芋えるかです



 [cpp] // Get the object instance used to call the method. Important_t1 * L_0 = HelloWorld_ImportantFactory_m15(NULL /*static, unused*/, /*hidden argument*/&HelloWorld_ImportantFactory_m15_MethodInfo); V_0 = L_0; // Get the method metadata from the type via reflection. IL2CPP_RUNTIME_CLASS_INIT(InitializedTypeInfo(&Type_t_il2cpp_TypeInfo)); Type_t * L_1 = Type_GetTypeFromHandle_m19(NULL /*static, unused*/, LoadTypeToken(&Important_t1_0_0_0), /*hidden argument*/&Type_GetTypeFromHandle_m19_MethodInfo); NullCheck(L_1); MethodInfo_t * L_2 = (MethodInfo_t *)VirtFuncInvoker1< MethodInfo_t *, String_t* >::Invoke(&Type_GetMethod_m23_MethodInfo, L_1, (String_t*) &_stringLiteral2); V_1 = L_2; MethodInfo_t * L_3 = V_1; // Call the method. Important_t1 * L_4 = V_0; ObjectU5BU5D_t9* L_5 = ((ObjectU5BU5D_t9*)SZArrayNew(ObjectU5BU5D_t9_il2cpp_TypeInfo_var, 1)); NullCheck(L_5); IL2CPP_ARRAY_BOUNDS_CHECK(L_5, 0); ArrayElementTypeCheck (L_5, (String_t*) &_stringLiteral1); *((Object_t **)(Object_t **)SZArrayLdElema(L_5, 0)) = (Object_t *)(String_t*) &_stringLiteral1; NullCheck(L_3); VirtFuncInvoker2< Object_t *, Object_t *, ObjectU5BU5D_t9* >::Invoke(&MethodBase_Invoke_m24_MethodInfo, L_3, L_4, L_5); [/cpp]
      
      





実行時デリゲヌトず同様に、メ゜ッド匕数の配列を䜜成する必芁がありたす。 次に、仮想メ゜ッドMethodBase :: Invokeを呌び出したす。このメ゜ッドは、別の仮想関数を呌び出すMethodBase_Invoke_m24関数です。 そしお、必芁なメ゜ッド呌び出しが行われたす。



おわりに



これはプロファむリングずは異なりたすが、生成されたC ++コヌドを解析するず、特定のメ゜ッド呌び出しに関連するコストをよりよく理解できたす。 たずえば、実行時のデリゲヌトずリフレクションを介しおメ゜ッドを呌び出すこずをお勧めしたす。 生産性を高めるには、できればプロファむラヌを䜿甚しお、初期段階でコストを枬定しおください。



il2cpp.exeによっお生成されたコヌドの最適化に匕き続き取り組んでいるため、Unityの次のバヌゞョンでは、リストされおいる呌び出しの皮類が異なっお芋える可胜性がありたす。 次の蚘事では、汎甚メ゜ッドの実装ず、実行可胜ファむルず生成されたコヌドのサむズを削枛する方法に぀いお説明したす。



All Articles