[英]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.Reflection 、 Mono.Cecil 、 Lokad.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.