c# System.Array Indexer 相关源码剖析

问题缘由以及探索

在一个检测IFIX 热更新之后的patch文件与原包之间dll差异的工具中,发现了System.Int32[,].ctor, System.Int32[,].Get, System.Int32[,].Set 函数无法找到的问题,为了定位这个问题的缘由,展开了以下的探索。

  1. 首先,和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]);
    }
}
  1. 通过Dnspy 看到:
    通过下图可以看到确实生成了int[,], int[,,] 这种类型。
    在这里插入图片描述

再通过下图,查看c# 的IL指令可以确定,int[,], int[,,] 这种多维数组确实会生成'.Get, .Set方法。
在这里插入图片描述

  1. 通过微软c# 官方对于 Indexers 的实现,可以看到对于类可以通过指定getset 这种property,来达到类似于c++ []运算符重载的机制。
    在这里插入图片描述

不过,需要注意的是,对于

  1. 自然,可以使用[] 运算符,以及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 方法后便可以开始找相关实现的源码了。

mono 源码相关实现

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库对于相关的实现。

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
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值