Static Fields
Now that we’ve seen how instance fields look (in the Vector3 type), let’s see how static fields are converted and accessed. Find the definition of
the HelloWorld_Start_m3 method, which is in the Bulk_Assembly-CSharp_0.cpp file in my build. From there, jump to the Important_t1 type
(in theAssemblyU2DCSharp_HelloWorld_Important.h file):
struct Important_t1 : public Object_t
{
// System.Int32 HelloWorld/Important::InstanceIdentifier
int32_t ___InstanceIdentifier_1;
};
struct Important_t1_StaticFields
{
// System.Int32 HelloWorld/Important::ClassIdentifier
int32_t ___ClassIdentifier_0;
};
Notice that il2cpp.exe has generated a separate C++ struct to hold the static field for this type, since the static field is shared between all
instances of this type. So at runtime, there will be one instance of the Important_t1_StaticFields type created, and all of the instances of the
Important_t1 type will share that instance of the static fields type. In generated code, the static field is accessed like this:
int32_t L_1 = (((Important_t1_StaticFields*)InitializedTypeInfo(&Important_t1_il2cpp_TypeInfo)->static_fields)->___ClassIdentifier_0);
The type metadata for Important_t1 holds a pointer to the single instance of the Important_t1_StaticFields type,
and that instance is used to obtain the value of the static field.
Exceptions
Managed exceptions are converted by il2cpp.exe to C++ exceptions. We have chosen this path to again avoid platform-specific
solutions. When il2cpp.exe needs to emit code to raise a managed exception, it calls the il2cpp_codegen_raise_exception
function.
The code in our HelloWorld_Start_m3 method to throw and catch a managed exception looks like this:
try
{ // begin try (depth: 1)
InvalidOperationException_t7 * L_17 = (InvalidOperationException_t7 *)il2cpp_codegen_object_new (InitializedTypeInfo(&InvalidOperationException_t7_il2cpp_TypeInfo));
InvalidOperationException__ctor_m8(L_17, (String_t*) &_stringLiteral5, /*hidden argument*/&InvalidOperationException__ctor_m8_MethodInfo);
il2cpp_codegen_raise_exception(L_17);
// IL_0092: leave IL_00a8
goto IL_00a8;
} // end try (depth: 1)
catch(Il2CppExceptionWrapper& e)
{
__exception_local = (Exception_t8 *)e.ex;
if(il2cpp_codegen_class_is_assignable_from (&InvalidOperationException_t7_il2cpp_TypeInfo, e.ex->object.klass))
goto IL_0097;
throw e;
}
IL_0097:
{ // begin catch(System.InvalidOperationException)
V_1 = ((InvalidOperationException_t7 *)__exception_local);
NullCheck(V_1);
String_t* L_18 = (String_t*)VirtFuncInvoker0< String_t* >::Invoke(&Exception_get_Message_m9_MethodInfo, V_1);
Debug_Log_m6(NULL /*static, unused*/, L_18, /*hidden argument*/&Debug_Log_m6_MethodInfo);
// IL_00a3: leave IL_00a8
goto IL_00a8;
} // end catch (depth: 1)
All managed exceptions are wrapped in the C++ Il2CppExceptionWrapper type. When the generated code catches an exception of
that type, it unpacks the C++ representation of the managed exception (which has type Exception_t8). In this case, we’re
looking only for a InvalidOperationException, so if we don’t find an exception of that type, a copy of the C++ exception
is thrown again. If we do find the correct type, the code jumps to the implementation of the catch handler, and writes out
the exception message.