Closed
Description
While experimenting with the Calli
opcode, it appears JIT compilation is more restrictive using a physical assembly vs. a DynamicMethod
compilation, and there is not a good diagnostic message to explain why.
The sample below for DynamicMethod works, while the same IL using PersistedAssemblyBuilder fails:
GenerateDynamicMethod Result:2ca1c70b-eefc-4287-96a1-653bd93dff77
GenerateMethodInPersistedAssembly Result:Unhandled exception. System.InvalidProgramException: Common Language Runtime detected an invalid program.
at MyType.GetGuid(MyClass, IntPtr)
The sample attempts to pass an object instance and a function pointer to a generated method, which then invokes the object's method via the function pointer via calli
.
The IL of the generated method from the persisted assembly:
.method public static valuetype [System.Private.CoreLib]System.Guid
GetGuid(class [ConsoleApp351]ConsoleApp351.MyClass A_0,
native int A_1) cil managed
{
// Code size 8 (0x8)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: calli valuetype [System.Private.CoreLib]System.Guid()
IL_0007: ret
} // end of method MyType::GetGuid
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.Loader;
namespace ConsoleApp
{
class Program
{
static unsafe void Main()
{
IntPtr fn = typeof(MyClass).GetProperty("MYGUID")!.GetGetMethod()!.MethodHandle.GetFunctionPointer();
MyClass obj = new MyClass();
// using "object" instead of "MyClass" should also work for the param type, but just use "MyClass" for now.
// With dynamic method
{
Console.Write($"GenerateDynamicMethod Result:");
Func<MyClass, IntPtr, Guid> callMe = GenerateDynamicMethod();
Guid result = callMe(obj, fn);
Console.WriteLine(result);
}
// With persisted assembly
{
Console.Write($"GenerateMethodInPersistedAssembly Result:");
Func<MyClass, IntPtr, Guid> callMe = GenerateMethodInPersistedAssembly();
Guid result = callMe(obj, fn);
Console.WriteLine(result);
}
}
static unsafe Func<MyClass, IntPtr, Guid> GenerateDynamicMethod()
{
DynamicMethod dynamicMethod = new DynamicMethod(
"GetGuid",
returnType: typeof(Guid),
parameterTypes: new Type[] { typeof(MyClass), typeof(IntPtr) },
typeof(Program).Module,
skipVisibility: false);
ILGenerator il = dynamicMethod.GetILGenerator();
EmitCall(il);
return (Func<MyClass, IntPtr, Guid>)dynamicMethod.CreateDelegate(typeof(Func<MyClass, IntPtr, Guid>));
}
static unsafe Func<MyClass, IntPtr, Guid> GenerateMethodInPersistedAssembly()
{
AssemblyName assemblyName = new ("MyAssembly");
PersistedAssemblyBuilder persistedAssemblyBuilder = new (assemblyName, typeof(object).Assembly);
ModuleBuilder moduleBuilder = persistedAssemblyBuilder.DefineDynamicModule("MyModule");
TypeBuilder typeBuilder = moduleBuilder.DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class);
MethodBuilder methodBuilder = typeBuilder.DefineMethod(
"GetGuid",
MethodAttributes.Public | MethodAttributes.Static,
returnType: typeof(Guid),
parameterTypes: new Type[] { typeof(MyClass), typeof(IntPtr)});
ILGenerator il = methodBuilder.GetILGenerator();
EmitCall(il);
// Create the type
typeBuilder.CreateType();
// Save the assembly to a file
string filePath = "MyAssembly.dll";
using var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write);
persistedAssemblyBuilder.Save(fileStream);
fileStream.Close();
// Load the assembly from the file
Assembly assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.GetFullPath(filePath));
// Get the method
return (Func<MyClass, IntPtr, Guid>)
assembly.GetType("MyType")!.
GetMethod("GetGuid")!.
CreateDelegate(typeof(Func<MyClass, IntPtr, Guid>));
}
static void EmitCall(ILGenerator il)
{
il.Emit(OpCodes.Ldarg_0); // this
il.Emit(OpCodes.Ldarg_1); // fn
il.EmitCalli(OpCodes.Calli, CallingConventions.HasThis, typeof(Guid), parameterTypes: null, null);
il.Emit(OpCodes.Ret);
}
}
public class MyClass
{
private Guid _guid = new Guid("{2CA1C70B-EEFC-4287-96A1-653BD93DFF77}");
public Guid MYGUID
{
get => _guid;
set => _guid = value;
}
}
}