1. 从虚方法的调用看起
在 il2Cpp代码转换规则测试和整理 文章中介绍了 il2cpp 如何将 C# 代码转换成对应的 C++ 代码,其中介绍了C#中的虚函数会转成什么样的代码,以及运行时如何查找到对应的方法来进行调用,这里从虚方法的调用源码来进行分析。
当代码中有定义 virtual
函数时,C++代码会生成一个VirtActionInvoker
结构体:
struct VirtActionInvoker0
{
typedef void (*Action)(void*, const RuntimeMethod*);
static inline void Invoke (Il2CppMethodSlot slot, RuntimeObject* obj)
{
const VirtualInvokeData& invokeData = il2cpp_codegen_get_virtual_invoke_data(slot, obj);
((Action)invokeData.methodPtr)(obj, invokeData.method);
}
};
DisplayEntity
是一个虚函数是定义在 C# 类的一个虚函数,C# 的调用代码如下:
// 虚方法
entity.DisplayEntity();
生成的 C++ 调用代码如下:
BaseEntity_t10F88AF9625E7158E20269E2A8CEA034B0C8EEFC * L_8 = ___entity0;
NullCheck(L_8);
VirtActionInvoker0::Invoke(5 /* System.Void BaseEntity::DisplayEntity() */, L_8);
跟踪代码,最后可以看到虚函数的调用逻辑在 il2cpp 源码中的如下方法中实现:
FORCE_INLINE const VirtualInvokeData& il2cpp_codegen_get_virtual_invoke_data(Il2CppMethodSlot slot, const RuntimeObject* obj)
{
Assert(slot != kInvalidIl2CppMethodSlot && "il2cpp_codegen_get_virtual_invoke_data got called on a non-virtual method");
return obj->klass->vtable[slot];
}
obj
是 RuntimeObject*
, 它的成员 klass
是 Il2CppClass*
, Il2CppObject
和 Il2CppClass
定义在文件 il2cpp-class-internals.h
中,Il2CppClass
中有一个 VirtualInvokeData
类型的数组,而虚方法就是从这个数组中拿到的 VirtualInvokeData
。
typedef struct Il2CppClass
{
FieldInfo* fields; // Initialized in SetupFields
const EventInfo* events; // Initialized in SetupEvents
const PropertyInfo* properties; // Initialized in SetupProperties
const MethodInfo** methods; // Initialized in SetupMethods
Il2CppClass** nestedTypes; // Initialized in SetupNestedTypes
Il2CppClass** implementedInterfaces; // Initialized in SetupInterfaces
Il2CppRuntimeInterfaceOffsetPair* interfaceOffsets; // Initialized in Init
void* static_fields; // Initialized in Init
const Il2CppRGCTXData* rgctx_data; // Initialized in Init
// used for fast parent checks
Il2CppClass** typeHierarchy; // Initialized in SetupTypeHierachy
// ....
// 省略一大堆成员
VirtualInvokeData vtable[IL2CPP_ZERO_LEN_ARRAY];
} Il2CppClass;
我们需要看看 VirtualInvokeData
的定义:
typedef struct VirtualInvokeData
{
Il2CppMethodPointer methodPtr;
#if RUNTIME_MONO
const MonoMethod* method;
#else
const MethodInfo* method;
#endif
} VirtualInvokeData;
其中 Il2CppMethodPointer
就是一个函数指针,它的定义是
typedef void (*Il2CppMethodPointer)();
MethodInfo
的定义:
typedef struct MethodInfo
{
Il2CppMethodPointer methodPointer;
InvokerMethod invoker_method;
const char* name;
Il2CppClass *klass;
const Il2CppType *return_type;
const ParameterInfo* parameters;
union
{
const Il2CppRGCTXData* rgctx_data; /* is_inflated is true and is_generic is false, i.e. a generic instance method */
const Il2CppMethodDefinition* methodDefinition;
};
/* note, when is_generic == true and is_inflated == true the method represents an uninflated generic method on an inflated type. */
union
{
const Il2CppGenericMethod* genericMethod; /* is_inflated is true */
const Il2CppGenericContainer* genericContainer; /* is_inflated is false and is_generic is true */
};
uint32_t token;
uint16_t flags;
uint16_t iflags;
uint16_t slot;
uint8_t parameters_count;
uint8_t is_generic : 1; /* true if method is a generic method definition */
uint8_t is_inflated : 1; /* true if declaring_type is a generic instance or if method is a generic instance*/
uint8_t wrapper_type : 1; /* always zero (MONO_WRAPPER_NONE) needed for the debugger */
uint8_t is_marshaled_from_native : 1; /* a fake MethodInfo wrapping a native function pointer */
} MethodInfo;
定义中包含了函数入口地址、函数名、返回值类型、参数信息等数据。
脚本后端为 il2cpp 时,VirtualInvokeData
包括一个 Il2CppMethodPointer
和一个 MethodInfo
指针,我们在源码中搜索一下 methodPtr
在哪里被赋值,可以查找到代码 Class.cpp
中的 SetupVTable
方法,进一步查找 SetupVTable
方法的引用,可以查到 Class.cpp
中有一个 InitLocked
方法和一堆的 SetupXXXX
方法,其中 InitLocked
方法在 Class::Init
函数中调用了,这个应该是类的初始化方法。在 InitLocked
的源码基本上都是对 klass 的数据进行设置,也就是初始化 Class 的信息了,带着两个问题去分析:
1.1 这些 class 信息包括哪些内容
查看 Class::Init
的实现
static bool InitLocked(Il2CppClass *klass, const FastAutoLock& lock)
{
if (klass->initialized)
return true;
if (klass->generic_class && (klass->flags & TYPE_ATTRIBUTE_EXPLICIT_LAYOUT))
{
std::string message;
message += "Could not load type '";
message += klass->namespaze;
message += ":";
message += klass->name;
message += "' because generic types cannot have explicit layout.";
klass->has_initialization_error = true;
Class::UpdateInitializedAndNoError(klass);
klass->initializationExceptionGCHandle = gc::GCHandle::New(il2cpp::vm::Exception::GetTypeLoadException(message.c_str()), false);
return false;
}
IL2CPP_NOT_IMPLEMENTED_NO_ASSERT(Class::Init, "Audit and compare to mono version");
klass->init_pending = true;
klass->genericRecursionDepth++;
if (klass->generic_class)
// 初始化泛型类信息
InitLocked(GenericClass::GetTypeDefinition(klass->generic_class), lock);
if (klass->byval_arg.type == IL2CPP_TYPE_ARRAY || klass->byval_arg.type == IL2CPP_TYPE_SZARRAY)
{
Il2CppClass *element_class = klass->element_class;
if (!element_class->initialized)
// 初始化元素类型信息
InitLocked(element_class, lock);
}
// 设置接口数据
SetupInterfacesLocked(klass, lock);
if (klass->parent && !klass->parent->initialized)
// 基类信息
InitLocked(klass->parent, lock);
// 设置方法数据
SetupMethodsLocked(klass, lock);
// 继承链数据
SetupTypeHierarchyLocked(klass, lock);
// 设置虚表数据
SetupVTable(klass, lock);
if (!klass->size_inited)
// 设置字段数据
SetupFieldsLocked(klass, lock);
if (klass->has_initialization_error)
return false;
// 设置委托数据
SetupEventsLocked(klass, lock);
// 设置属性数据
SetupPropertiesLocked(klass, lock);
// 设置嵌套类型数据
SetupNestedTypesLocked(klass, lock);
if (klass == il2cpp_defaults.object_class)
{
for (uint16_t slot = 0; slot < klass->vtable_count; slot++)
{
const MethodInfo* vmethod = klass->vtable[slot].method;
if (!strcmp(vmethod->name, "GetHashCode"))
s_GetHashCodeSlot = slot;
else if (!strcmp(vmethod->name, "Finalize"))
s_FinalizerSlot = slot;
}
IL2CPP_ASSERT(s_FinalizerSlot > 0);
IL2CPP_ASSERT(s_GetHashCodeSlot > 0);
}
if (!Class::IsGeneric(klass))
SetupGCDescriptor(klass);
if (klass->generic_class)
{
// 设置泛型实例上下文数据
const Il2CppTypeDefinition* typeDefinition = GenericClass::GetTypeDefinition(klass->generic_class)->typeDefinition;
if (klass->genericRecursionDepth < GenericMetadata::MaximumRuntimeGenericDepth)
klass->rgctx_data = GenericMetadata::InflateRGCTX(typeDefinition->rgctxStartIndex, typeDefinition->rgctxCount, &klass->generic_class->context);
}
klass->initialized = true;
Class::UpdateInitializedAndNoError(klass);
klass->init_pending = false;
++il2cpp_runtime_stats.initialized_class_count;
return true;
}
大体可以看出来,Class 信息中包含如下的数据
- Methods 数据
typedef struct MethodInfo
{
Il2CppMethodPointer methodPointer;
InvokerMethod invoker_method;
const char* name;
Il2CppClass *klass;
const Il2CppType *return_type;
const ParameterInfo* parameters;
union
{
const Il2CppRGCTXData* rgctx_data; /* is_inflated is true and is_generic is false, i.e. a generic instance method */
const Il2CppMethodDefinition* methodDefinition;
};
/* note, when is_generic == true and is_inflated == true the method represents an uninflated generic method on an inflated type. */
union
{
const Il2CppGenericMethod* genericMethod; /* is_inflated is true */
const Il2CppGenericContainer* genericContainer; /* is_inflated is false and is_generic is true */
};
uint32_t token;
uint16_t flags;
uint16_t iflags;
uint16_t slot;
uint8_t parameters_count;
uint8_t is_generic : 1; /* true if method is a generic method definition */
uint8_t is_inflated : 1; /* true if declaring_type is a generic instance or if method is a generic instance*/
uint8_t wrapper_type : 1; /* always zero (MONO_WRAPPER_NONE) needed for the debugger */
uint8_t is_marshaled_from_native : 1; /* a fake MethodInfo wrapping a native function pointer */
} MethodInfo;
包括 函数指针、函数名、函数所属类指针、返回值类型、参数信息等,如果是泛型方法,还包括泛型上下文数据
- Fields 数据
typedef struct FieldInfo
{
const char* name; // 字段名
const Il2CppType* type; // 类型
Il2CppClass *parent; // 所属类
// 相对偏移量
int32_t offset; // If offset is -1, then it's thread static
uint32_t token;
} FieldInfo;
包括字段名、字段类型、字段偏移值等数据
- Properties 数据
typedef struct PropertyInfo
{
Il2CppClass *parent;
const char *name;
const MethodInfo *get;
const MethodInfo *set;
uint32_t attrs;
uint32_t token;
} PropertyInfo;
- Events 数据
typedef struct EventInfo
{
const char* name;
const Il2CppType* eventType;
Il2CppClass* parent;
const MethodInfo* add;
const MethodInfo* remove;
const MethodInfo* raise;
uint32_t token;
} EventInfo;
- Interface 数据
修改的是Il2CppClass
中的implementedInterfaces
字段的数据
Il2CppClass** implementedInterfaces; // Initialized in SetupInterfaces
- VTable 虚表数据
修改Il2CppClass
中的vtable
字段
VirtualInvokeData vtable[IL2CPP_ZERO_LEN_ARRAY];
- RGCTX Data 泛型上下文数据
修改的是Il2CppClass
中的rgctx_data
字段数据
const Il2CppRGCTXData* rgctx_data; // Initialized in Init
1.2 数据怎么来的,有什么用
从代码的实现来看,这些数据是 Il2Cpp 在进行代码转换时放到了 global-metadata.dat 文件中,然后在 il2cpp 运行时初始化的时候,加载到 MetadataCache 中,然后在类调用 Class::Init
方法时调用各种 Setup 方法设置到 Class 中