- 问题缘由以及探索
- mono 源码相关实现
- runtime 源码相关实现
在一个检测IFIX 热更新之后的patch文件与原包之间dll差异的工具中,发现了
System.Int32[,].ctor, System.Int32[,].Get, System.Int32[,].Set
函数无法找到的问题,为了定位这个问题的缘由,展开了以下的探索。
- 首先,和unity游戏开发部门确认之后这个问题本不该出现,因此就排除代码逻辑的问题,转而认为是工具没有判断到这种状况。在demo中重现到了这种情况,并且看到了相关的函数签名。
Demo(unity2.4.4, .Net 4.0, Il2cpp, Android)
using System.IO;
using System;
using UnityEngine;
using IFix.Core;
using IFix;
using System.Collections.Generic;
class Guides<T>
{
private T[,] _guideNames = new T[10,10];
public T this[int index1, int index2]
{
// c# 对于用户自定义的[] 重载, 下面两个方法的签名为 get_Item, set_Item, 签名机制看下链接:
// https://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.indexernameattribute?view=net-6.0
get { return _guideNames[index1, index2]; }
set { _guideNames[index1, index2] = value; }
}
}
public class NewBehaviourScript : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
void Update()
{
FuncA();
}
void FuncA()
{
int[] arr_1_demision = {1, 2, 3};
int[,] arr_2_demision = {{1,2,3}, {4,5,6}};
int[,,] arr_3_demision = {{{1,2,3}}, {{4,5,6}}};
Guides<int> writtenGuides = new Guides<int>();
int k = 10;
writtenGuides[0, 0] = 1;
k = writtenGuides[0, 0];
arr_1_demision.GetType().GetMethod("Set").Invoke(arr_1_demision, new object[] { 0, 10 });
writtenGuides.GetType().GetMethod("set_Item").Invoke(writtenGuides, new object[] { 0, 0, 10 });
Debug.Log("writtenGuides[0, 0]: " + writtenGuides[0,0]);
FunB<int>(arr_1_demision);
FunB<int>(arr_2_demision);
FunB<int>(arr_3_demision);
}
void FunB<T> (T[] arr)
{
// arr.SetValue(10, 0, 0);
var a = arr[0];
arr[0] = a;
Debug.Log("arr_1_demision[0]: " + arr[0]);
}
void FunB<T> (T[,] arr)
{
// arr.SetValue(10, 0, 0);
var a = arr[0,0];
arr[0,0] = a;
Debug.Log("arr_2_demision[0, 0]: " + arr[0,0]);
}
void FunB<T> (T[,,] arr)
{
var a = arr[0,0,0];
arr[0,0,0] = a;
Debug.Log("arr_3_demision[0, 0, 0]: " + arr[0,0,0]);
}
}
- 通过Dnspy 看到:
通过下图可以看到确实生成了int[,], int[,,]
这种类型。
再通过下图,查看c# 的IL指令可以确定,int[,], int[,,]
这种多维数组确实会生成'.Get, .Set
方法。
- 通过微软c# 官方对于 Indexers 的实现,可以看到对于类可以通过指定get 和 set 这种property,来达到类似于c++ []运算符重载的机制。
不过,需要注意的是,对于
- 自然,可以使用[] 运算符,以及
System.Array
的相关方法,说明int[,] 和 int[,,]
这种类型也是System.Array
。此时,就看c# 源码对于System.Array
是如何实现即可。微软官方对于System.Array
的定义如下:
在查看了其继承的相关父类后,最终发现了System.Array
会继承IList,而IList 中存在IList.Item[Int32] Property,
由此可以看到,System.Array
类中的 [] 方法的实现,其实就是继承IList,此时确定了Int[,], System.Array, .get, .set
方法后便可以开始找相关实现的源码了。
GetMatchedPropertyInfo
internal static PropertyInfo GetMatchedPropertyInfo(Type memberType, string[] aryArgName, object[] args)
{
if (memberType == null)
throw new ArgumentNullException("memberType");
if (aryArgName == null)
throw new ArgumentNullException("aryArgName");
if (args == null)
throw new ArgumentNullException("args");
MemberInfo[][] aryMembers = new MemberInfo[][] { memberType.GetDefaultMembers(), null };
if (memberType.IsArray)
{
MemberInfo[] getMember = memberType.GetMember("Get"); //arrays will always implement that
MemberInfo[] setMember = memberType.GetMember("Set"); //arrays will always implement that
PropertyInfo getProperty = new ActivityBindPropertyInfo(memberType, getMember[0] as MethodInfo, setMember[0] as MethodInfo, string.Empty, null);
aryMembers[1] = new MemberInfo[] { getProperty };
}
for (int index = 0; index < aryMembers.Length; ++index)
{
if (aryMembers[index] == null)
continue;
MemberInfo[] defaultMembers = aryMembers[index];
foreach (MemberInfo memberInfo in defaultMembers)
{
PropertyInfo propertyInfo = memberInfo as PropertyInfo;
if (propertyInfo != null)
{
if (MatchIndexerParameters(propertyInfo, aryArgName, args))
return propertyInfo;
}
}
}
return null;
}
由这段代码可以看到,对于array 类型而言,其会实现Get, Set
接口,用作property 的get_item, set_item
方法,但若只是看到这一层,仍不能理解为什么会有Set, Get
方法,因此便继续往更深层次挖掘。遗憾的是,在mono库中没有找到相关实现。因此只能转而取看c# runtime库对于相关的实现。
在辗转反侧多次之后,看到了mono_class_setup_methods 函数:
/*
* mono_class_setup_methods:
* @class: a class
*
* Initializes the 'methods' array in CLASS.
* Calling this method should be avoided if possible since it allocates a lot
* of long-living MonoMethod structures.
* Methods belonging to an interface are assigned a sequential slot starting
* from 0.
*
* On failure this function sets klass->has_failure and stores a MonoErrorBoxed with details
*/
void
mono_class_setup_methods (MonoClass *klass)
{
int i, count;
MonoMethod **methods;
if (klass->methods)
return;
if (mono_class_is_ginst (klass)) {
ERROR_DECL (error);
MonoClass *gklass = mono_class_get_generic_class (klass)->container_class;
mono_class_init_internal (gklass);
if (!mono_class_has_failure (gklass))
mono_class_setup_methods (gklass);
if (mono_class_set_type_load_failure_causedby_class (klass, gklass, "Generic type definition failed to load"))
return;
/* The + 1 makes this always non-NULL to pass the check in mono_class_setup_methods () */
count = mono_class_get_method_count (gklass);
methods = (MonoMethod **)mono_class_alloc0 (klass, sizeof (MonoMethod*) * (count + 1));
for (i = 0; i < count; i++) {
methods [i] = mono_class_inflate_generic_method_full_checked (
gklass->methods [i], klass, mono_class_get_context (klass), error);
if (!is_ok (error)) {
char *method = mono_method_full_name (gklass->methods [i], TRUE);
mono_class_set_type_load_failure (klass, "Could not inflate method %s due to %s", method, mono_error_get_message (error));
g_free (method);
mono_error_cleanup (error);
return;
}
}
} else if (klass->rank) {
ERROR_DECL (error);
MonoMethod *amethod;
MonoMethodSignature *sig;
int count_generic = 0, first_generic = 0;
int method_num = 0;
count = array_get_method_count (klass);
mono_class_setup_interfaces (klass, error);
g_assert (is_ok (error)); /*FIXME can this fail for array types?*/
if (klass->interface_count) {
count_generic = generic_array_methods (klass);
first_generic = count;
count += klass->interface_count * count_generic;
}
methods = (MonoMethod **)mono_class_alloc0 (klass, sizeof (MonoMethod*) * count);
sig = mono_metadata_signature_alloc (klass->image, klass->rank);
sig->ret = mono_get_void_type ();
sig->pinvoke = TRUE;
sig->hasthis = TRUE;
for (i = 0; i < klass->rank; ++i)
sig->params [i] = mono_get_int32_type ();
amethod = create_array_method (klass, ".ctor", sig);
methods [method_num++] = amethod;
if (array_supports_additional_ctor_method (klass)) {
sig = mono_metadata_signature_alloc (klass->image, klass->rank * 2);
sig->ret = mono_get_void_type ();
sig->pinvoke = TRUE;
sig->hasthis = TRUE;
for (i = 0; i < klass->rank * 2; ++i)
sig->params [i] = mono_get_int32_type ();
amethod = create_array_method (klass, ".ctor", sig);
methods [method_num++] = amethod;
}
/* element Get (idx11, [idx2, ...]) */
sig = mono_metadata_signature_alloc (klass->image, klass->rank);
sig->ret = m_class_get_byval_arg (m_class_get_element_class (klass));
sig->pinvoke = TRUE;
sig->hasthis = TRUE;
for (i = 0; i < klass->rank; ++i)
sig->params [i] = mono_get_int32_type ();
amethod = create_array_method (klass, "Get", sig);
methods [method_num++] = amethod;
/* element& Address (idx11, [idx2, ...]) */
sig = mono_metadata_signature_alloc (klass->image, klass->rank);
sig->ret = &klass->element_class->this_arg;
sig->pinvoke = TRUE;
sig->hasthis = TRUE;
for (i = 0; i < klass->rank; ++i)
sig->params [i] = mono_get_int32_type ();
amethod = create_array_method (klass, "Address", sig);
methods [method_num++] = amethod;
/* void Set (idx11, [idx2, ...], element) */
sig = mono_metadata_signature_alloc (klass->image, klass->rank + 1);
sig->ret = mono_get_void_type ();
sig->pinvoke = TRUE;
sig->hasthis = TRUE;
for (i = 0; i < klass->rank; ++i)
sig->params [i] = mono_get_int32_type ();
sig->params [i] = m_class_get_byval_arg (m_class_get_element_class (klass));
amethod = create_array_method (klass, "Set", sig);
methods [method_num++] = amethod;
GHashTable *cache = g_hash_table_new (NULL, NULL);
for (i = 0; i < klass->interface_count; i++)
setup_generic_array_ifaces (klass, klass->interfaces [i], methods, first_generic + i * count_generic, cache);
g_hash_table_destroy (cache);
} else if (mono_class_has_static_metadata (klass)) {
ERROR_DECL (error);
int first_idx = mono_class_get_first_method_idx (klass);
count = mono_class_get_method_count (klass);
methods = (MonoMethod **)mono_class_alloc (klass, sizeof (MonoMethod*) * count);
for (i = 0; i < count; ++i) {
int idx = mono_metadata_translate_token_index (klass->image, MONO_TABLE_METHOD, first_idx + i + 1);
methods [i] = mono_get_method_checked (klass->image, MONO_TOKEN_METHOD_DEF | idx, klass, NULL, error);
if (!methods [i]) {
mono_class_set_type_load_failure (klass, "Could not load method %d due to %s", i, mono_error_get_message (error));
mono_error_cleanup (error);
}
}
} else {
methods = (MonoMethod **)mono_class_alloc (klass, sizeof (MonoMethod*) * 1);
count = 0;
}
if (MONO_CLASS_IS_INTERFACE_INTERNAL (klass)) {
int slot = 0;
/*Only assign slots to virtual methods as interfaces are allowed to have static methods.*/
for (i = 0; i < count; ++i) {
if (methods [i]->flags & METHOD_ATTRIBUTE_VIRTUAL)
{
if (method_is_reabstracted (methods[i]->flags)) {
if (!methods [i]->is_inflated)
mono_method_set_is_reabstracted (methods [i]);
continue;
}
methods [i]->slot = slot++;
}
}
}
mono_image_lock (klass->image);
if (!klass->methods) {
mono_class_set_method_count (klass, count);
/* Needed because of the double-checking locking pattern */
mono_memory_barrier ();
klass->methods = methods;
}
mono_image_unlock (klass->image);
}
在上面的代码可以看到几个关键逻辑:
// 构造.ctor函数
amethod = create_array_method (klass, ".ctor", sig);
// 构造 Address 函数
amethod = create_array_method (klass, "Address", sig);
// 构造 Get 函数
amethod = create_array_method (klass, "Get", sig);
// 构造Set 函数
amethod = create_array_method (klass, "Set", sig);
由此,便找到了get, set, ctor方法找不到最深层次的原因,最后在工具中添加了相关的解析逻辑,解决了函数找不到的问题。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)