簡體   English   中英

如何將方法從現有程序集復制到 .NET Core 中的動態程序集?

[英]How to copy a method from an existing assembly to a dynamic assembly in .NET Core?

我想以某種方式將磁盤程序集的方法添加到我正在生成的程序集中,我通過System.Reflection.Emit創建程序集並使用Lokad.ILPack nuget package 將其保存到文件中並使用AssemblyLoadContext加載它,因為這是 .NET 7 Core,磁盤上的程序集也生成了。

我想避免使用外部庫,但我知道使用標准庫可能不太合理,而且我不能使用像 Pinvoke 這樣的東西,因為當需要方法調用時程序集甚至可能不存在,如果答案需要復制包含該方法的類型就可以了。

我如何創建程序集的示例:

public static void CreateMethod(string destDllPath)
{
    AssemblyName asmName = new AssemblyName("CAssembly");

    AssemblyBuilder asmBuilder = AssemblyBuilder.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.RunAndCollect);
    //Its RunAndCollect because if the AssemblyLoadContext fails to unload it will do so automatically

    ModuleBuilder modBuilder = asmBuilder.DefineDynamicModule("CModule");

    TypeBuilder typeBuilder = modBuilder.DefineType("CType", TypeAttributes.Public | TypeAttributes.Abstract | TypeAttributes.Sealed);
    //Abstract | Sealed means that the class is static

    MethodBuilder methodBuilder = typeBuilder.DefineMethod("CMethod",
        MethodAttributes.Static | MethodAttributes.Public,
        CallingConventions.Standard,
        typeof(bool),
        new[] { typeof(bool) });


    ILGenerator ILG = methodBuilder.GetILGenerator();

    ILG.Emit(OpCodes.Ldarg_0);
    //Push the first argument onto the evaluation stack
    ILG.Emit(OpCodes.Ret);
    //Return the first element from the evaluation stack

    _ = typeBuilder.CreateType();

    AssemblyGenerator asmGenerator = new AssemblyGenerator();
    asmGenerator.GenerateAssembly(asmBuilder, destDllPath);
}

然后使用上面生成的方法

public static void CopyMethod(AssemblyBuilder toAssembly, string fromDllPath)
{
    string typeName = "CType";
    string methodName = "CMethod";

    Assembly fromAssembly = Assembly.LoadFile(fromDllPath);
    //note that the assembly at fromDllPath is created via MethodBuilder ILGenerator and Lokad.ILPack

    Type type = fromAssembly.GetType(typeName)!;

    MethodInfo method = type.GetMethod(methodName)!;

    //to test that the generated assembly is valid
    //bool testTesult = (bool)method.Invoke(null, new object[] { true });

    //somehow add method to toAssembly?
}

這是我遇到不知道如何將方法添加到程序集的問題的地方

我花了整整幾天的時間嘗試找到解決這個問題的方法,但是在 .NET Core 5 到 7 中似乎沒有很多關於動態程序集創建的信息。

查看不同的反射庫( Mono.ReflectionMono.CecilLokad.ILPack和大量代碼示例)后,我發現對於我的項目,我將簡單地手動轉換每個操作碼和操作數。 這只有效,因為我的項目保證生成不使用任何模塊定義的標記(或開關)的簡單程序集。

對於所有關注或查看此答案的 0 人,這可能對您不起作用,因此您將不得不自己定義這些操作,如果您需要的方法和類型在與目標方法相同的模塊中定義,那么您可以獲得它通過使用 module.resolve 方法來獲取適當的標記。

我在System.Reflection.Metadata.Ecma335 (支持所有版本的 .Net)中找到了MetadataBuilder class,它似乎是在 CIL 級別生成程序集的最佳方式,但我沒有時間在 Emit API 上使用它所以這是我的解決方案。

解決方案:

public static void CopyMethod(this TypeBuilder destType, MethodInfo fromMethod)
{
    MethodBuilder destMethod = destType.DefineMethod(
        fromMethod.Name, fromMethod.Attributes, 
        fromMethod.CallingConvention, 
        fromMethod.ReturnType, 
        fromMethod.GetParameters()
            .Select((p) => p.ParameterType)
            .ToArray());

    ILGenerator generator = destMethod.GetILGenerator();

    MethodBody fromBody = fromMethod.GetMethodBody()!;
    byte[] il = fromBody.GetILAsByteArray()!;

    foreach(LocalVariableInfo localVar in fromBody.LocalVariables)
    {
        generator.DeclareLocal(localVar.LocalType);
    }

    OpCode code;
    for (int i = 0; i < il.Length;)
    {
        if (il[i] == 254)
        {
            //the opcode is 2 bytes if the first byte is 0xfe
            byte[] rev = BitConverter.IsLittleEndian ? il.Take(i..(i + 2)).Reverse().ToArray() : il.Take(i..(i + 2)).ToArray();
            //if your machine uses littleEndian reverse the order of the 2 bytes
            short numc = BitConverter.ToInt16(rev, 0);
            code = (OpCode)typeof(OpCodes).GetFields().First(t => ((OpCode)t.GetValue(null)!).Value == numc).GetValue(null)!;
        }
        else
        {
            //the opcode is 1 byte
            code = (OpCode)typeof(OpCodes).GetFields().First(t => ((OpCode)t.GetValue(null)!).Value == il[i]).GetValue(null)!;
            //lookes at all fields defined in the class OpCodes and findes the corrosponding OpCode (i know this is a bad way to do it)
        }
        
        i += code.Size;
        //advance 1 or 2 bytes depending on the size of the code itself (not including the operand yet)
        
        switch (code.OperandType)
        {
            case OperandType.InlineBrTarget:
            case OperandType.InlineI:
                generator.Emit(code, BitConverter.ToInt32(il, i));
                i += 4;
                break;

            case OperandType.InlineI8:
                generator.Emit(code, BitConverter.ToInt64(il, i));
                i += 8;
                break;
                
            case OperandType.InlineField:
                //Todo: copy field to this type and permute metadatatoken from fromMethods module to this destTypes module
                FieldInfo fieldInfo = fromMethod.Module.ResolveField(BitConverter.ToInt32(il, i))!;
                generator.Emit(code, fieldInfo);
                break;
            
            case OperandType.InlineMethod:
                //Todo: copy method to this type if it dosnt exist in this module and permute metadatatoken from fromMethods module to this destTypes module
                MethodInfo method = (MethodInfo)fromMethod.Module.ResolveMethod(BitConverter.ToInt32(il, i))!;
                generator.Emit(code, method);
                i += 4;
                break;

            case OperandType.InlineNone:
                generator.Emit(code);
                break;

            //Note: OperandType.InlinePhi is deprecated

            case OperandType.InlineR:
                generator.Emit(code, BitConverter.ToDouble(il, i));
                i += 8;
                break;

            case OperandType.InlineType:
                //Todo: copy type to this module if it doesnt exist in it and permute metadatatoken from fromMethods module to this destTypes module
                Type type = fromMethod.Module.ResolveType(BitConverter.ToInt32(il, i), fromMethod.GetType().GenericTypeArguments, fromMethod.GetGenericArguments());
                generator.Emit(code, type);
                i += 4;
                break;

            case OperandType.ShortInlineBrTarget:
                generator.Emit(code, il[i]);
                i += 1;
                break;

            case OperandType.ShortInlineI:
                generator.Emit(code, il[i]);
                i += 1;
                break;

            case OperandType.ShortInlineR:
                generator.Emit(code, BitConverter.ToDouble(il, i));
                i += 8;
                break;

            default:
                throw new NotImplementedException($"Havnt added { code.OperandType } because it uses tokens or emit api donst support it ¯\\_(ツ)_/¯");
        }
    }
}

該解決方案似乎生成與 ildasm 中顯示的相同的 IL,但它並不理想。 也為我的文筆不好和漫無邊際感到抱歉

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM