Call method via DynamicMethod - Reflection.Emit - c#

I've got a slightly modified class of this answer in order to dynamically call TryParse of various types (char, int, long).
public delegate TRet DynamicMethodDelegate<TRet>(object target, params object[] args);
public delegate void DynamicMethodDelegate(object target, params object[] args);
public class DynamicMethodDelegateFactory
{
public static TDelegate CreateMethodCaller<TDelegate>(MethodInfo method)
where TDelegate : class
{
ParameterInfo[] parameters = method.GetParameters();
Type[] args = { typeof(object), typeof(object[]) };
DynamicMethod dynam =
new DynamicMethod
(
method.Name
, method.ReturnType
, args
, typeof(DynamicMethodDelegateFactory)
, true
);
//Add parmeter attributes to the new method from the existing method
for (int i = 0; i < parameters.Length; i++)
{
dynam.DefineParameter
(
i,
parameters[i].Attributes,
parameters[i].Name
);
}
ILGenerator il = dynam.GetILGenerator();
// If method isn't static push target instance on top of stack.
if (!method.IsStatic)
{
// Argument 0 of dynamic method is target instance.
il.Emit(OpCodes.Ldarg_0);
}
// Lay out args array onto stack.
LocalBuilder[] locals = new LocalBuilder[parameters.Length];
List<LocalBuilder> outOrRefLocals = new List<LocalBuilder>();
for (int i = 0; i < parameters.Length; i++)
{
//Push args array reference onto the stack, followed
//by the current argument index (i). The Ldelem_Ref opcode
//will resolve them to args[i].
if (!parameters[i].IsOut)
{
// Argument 1 of dynamic method is argument array.
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldc_I4, i);
il.Emit(OpCodes.Ldelem_Ref);
}
// If parameter [i] is a value type perform an unboxing.
Type parameterType = parameters[i].ParameterType;
if (parameterType.IsValueType)
{
il.Emit(OpCodes.Unbox_Any, parameterType);
}
}
//Create locals for out parameters
for (int i = 0; i < parameters.Length; i++)
{
if (parameters[i].IsOut)
{
locals[i] = il.DeclareLocal(parameters[i].ParameterType.GetElementType());
il.Emit(OpCodes.Ldloca, locals[locals.Length - 1]);
}
}
if (method.IsFinal || !method.IsVirtual)
{
il.Emit(OpCodes.Call, method);
}
else
{
il.Emit(OpCodes.Callvirt, method);
}
for (int idx = 0; idx < parameters.Length; ++idx)
{
if (parameters[idx].IsOut || parameters[idx].ParameterType.IsByRef)
{
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldc_I4, idx);
il.Emit(OpCodes.Ldloc, locals[idx].LocalIndex);
if (parameters[idx].ParameterType.GetElementType().IsValueType)
il.Emit(OpCodes.Box, parameters[idx].ParameterType.GetElementType());
il.Emit(OpCodes.Stelem_Ref);
}
}
if (method.ReturnType != typeof(void))
{
// If result is of value type it needs to be boxed
if (method.ReturnType.IsValueType)
{
il.Emit(OpCodes.Box, method.ReturnType);
}
}
else
{
il.Emit(OpCodes.Ldnull);
}
il.Emit(OpCodes.Ret);
return dynam.CreateDelegate(typeof(TDelegate)) as TDelegate;
}
}
Unfortunately this throws an AccessViolationException and after checking the code a bit more in detail I'm still unsure why.
This code also "runs" in another project where it seems like the return value is not consistent.
Sometimes it just returns false when the actual parsing via TryParse is successful.
It sounds like undefined behaviour, but I can't seem to find the issue(s).
Here's a sample code of the AccessViolationException AND the undefined behaviour (remove float value from array for UB).
static void Main(string[] args)
{
var arr = new object[] { 'A', (byte)1, (short)2, 3, 4L, 5M, 6.0, 7.0F, "8" };
for (int i = 0; i < 100000; i++)
{
foreach (var item in arr)
ParseTest(item);
}
int a = 1;
int b = a;
}
static Type StringType = typeof(string);
static bool ParseTest(object data)
{
var type = data.GetType();
if (type == StringType)
return true;
else
{
var mi = type.GetMethod(nameof(int.TryParse), new[] { StringType, type.MakeByRefType() });
var dmd = DynamicMethodDelegateFactory.CreateMethodCaller<DynamicMethodDelegate<bool> >(mi);
dynamic dummy = null;
var args = new object[] { data, dummy };
var ok = dmd(null, args);
return ok;
}
}
What is the problem of the UB ?
Why the AccessViolationException ? Another problem or related to UB ?

Related

InvalidProgramException: Invalid IL code happens when emitting IL code with non-void return type

I am trying to lower down reflection method call time by creating a DynamicMethod, emitting IL code with its IL generator, and then creating a delegate using its CreateDelegate method. So far the call times are reduced significantly even though the new method call still uses (object callObject, object[] params) as parameters.
The problem occurs in the generated IL code when the return type is anything other than void. I have no knowledge of MSIL and did quite a bit of research but found nothing. Here is the code that works:
private void Start()
{
var a = new A();
var method = typeof(A).GetMethod("Add");
Type[] paramsTypes = { typeof(object[]) };
Type[] allTypes = { typeof(object), typeof(object[]) };
Type returnType = typeof(void);
var dm = new DynamicMethod("Hi", returnType, allTypes);
var il = dm.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.EmitCall(OpCodes.Call, method, paramsTypes);
il.Emit(OpCodes.Ret);
var del4 = (Action<object, object[]>)dm.CreateDelegate(
typeof(Action<object, object[]>));
var time = DateTime.Now;
for (int i = 0; i < 20000000; i++)
{
//a.Add(); 132 ms
//method.Invoke(a, null);// 25 sec
del4(a, null); // 200 ms
}
var ts = DateTime.Now - time;
Debug.Log($"{ts.Seconds}:{ts.Milliseconds}");
}
public class A
{
public int a = 0;
public void Add() => a++;
}
The resulting time (only the call time is measured) is so much faster compared to normal MethodInfo.Invoke. However, with the following tweaks, the IL generator throws error:
private void Start()
{
var a = new A();
var method = typeof(A).GetMethod("Add");
Type[] paramsTypes = { typeof(object[]) };
Type[] allTypes = { typeof(object), typeof(object[]) };
Type returnType = typeof(object);
var dm = new DynamicMethod("Hi", returnType, allTypes);
var il = dm.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.EmitCall(OpCodes.Call, method, paramsTypes);
il.Emit(OpCodes.Ret);
var del4 = (Func<object, object[], object>)dm.CreateDelegate(
typeof(Func<object, object[], object>));
var time = DateTime.Now;
for (int i = 0; i < 20000000; i++)
{
//a.Add(); 132 ms
//method.Invoke(a, null);// 25 sec
del4(a, null); // 200 ms
}
var ts = DateTime.Now - time;
Debug.Log($"{ts.Seconds}:{ts.Milliseconds}");
}
public class A
{
public int a = 0;
public int Add() => a++;
}
When the return type expected is something other than void, the following exception is thrown:
InvalidProgramException: Invalid IL code in (wrapper dynamic-method) object:Hi (object,object[]): IL_0006: ret
Does anyone know how to solve this issue?
Edit: Added boxing before return opcode:
il.Emit(OpCodes.Ldarg_0);
il.EmitCall(OpCodes.Call, method, paramsTypes);
il.Emit(OpCodes.Box);
il.Emit(OpCodes.Ret);
Then the following is thrown:
VerificationException: Error in System.Object:(wrapper dynamic-method) object:Hi (object,object[]) Invalid instruction 8c

DynamicMethod call Instance method

If I create a DynamicMethod from inside a class method how can I call another method of my class from the DynamicMethod Delegate? I need somehow to capture the this reference in the DynamicMethod code.
But I can't find an overladed version of ILGenerator.Emit which takes an object as parameter.
The code is so far:
void CallOpc(string name, object[] inp, object[] outp)
{
//...
}
public D CreateDelegate<D>(string opcName) where D : class
{
var dType = typeof(D);
var invoke = dType.GetMethod("Invoke");
var parameters = invoke.GetParameters();
var paramTypes = parameters.Select(p => p.ParameterType).ToArray();
DynamicMethod dm = new DynamicMethod(
opcName,
invoke.ReturnType,
paramTypes,
true);
var inp = parameters.Where(p => !p.IsOut).Select(p => p.ParameterType).ToList();
var outp = parameters.Where(p => p.IsOut).Select(p => p.ParameterType).ToList();
if (invoke.ReturnType != typeof(void))
{
outp.Insert(0, invoke.ReturnType);
}
ILGenerator il = dm.GetILGenerator();
LocalBuilder invar = il.DeclareLocal(typeof(object[]));
LocalBuilder outvar = il.DeclareLocal(typeof(object[]));
il.Emit(OpCodes.Ldc_I4, inp.Count);
il.Emit(OpCodes.Newarr, typeof(object));
il.Emit(OpCodes.Stloc, invar);
for (int i = 0; i < inp.Count; i++)
{
il.Emit(OpCodes.Ldloc, invar);
il.Emit(OpCodes.Ldc_I4, i);
int j = Array.IndexOf(paramTypes, inp[i]);
il.Emit(OpCodes.Ldarg, j);
if (!inp[i].IsClass)
{
il.Emit(OpCodes.Box, inp[i]);
}
il.Emit(OpCodes.Stelem_Ref);
}
il.Emit(OpCodes.Ldc_I4, outp.Count);
il.Emit(OpCodes.Newarr, typeof(object));
il.Emit(OpCodes.Stloc, outvar);
il.Emit(OpCodes.Ldarg_0); // <- push this on the evaluation stack ???
il.Emit(OpCodes.Ldstr, opcName);
il.Emit(OpCodes.Ldloc, invar);
il.Emit(OpCodes.Ldloc, outvar);
MethodInfo callOpcMeth = GetType().GetMethod("CallOpc", BindingFlags.Instance | BindingFlags.NonPublic);
il.Emit(OpCodes.Callvirt, callOpcMeth);
for (int o = 0; o < outp.Count; o++)
{
// TODO: handle out params and return value
}
il.Emit(OpCodes.Ret);
return (D)(object)dm.CreateDelegate(dType);
}
My problem is the line marked with ???
How to reference the this pointer from a DynamicMethod?
At low level, methods do not have a specific this pointer, instead the first argument of the method is used as this.
To reference the this pointer, here is what you have to do:
Prepend a new argument to your argument list, matching the type you want to use as this
Use that argument as this wherever you want
Create the delegate using DynamicMethod.CreateDelegate(Type,Object) to bind the first parameter to your object: return (D)(object)dm.CreateDelegate(dType, this);
Note that creating dynamic methods is expensive. If you generate the same DynamicMethod for multiple instances, you should cache the method itself, and create delegates using the cached method, but with different target parameter.

Assigning to an array field through reflection in c#

I use reflection to get FieldInfos in a class and assign to them with FieldInfo.SetValue. It works fine when i assign primitive types (i use Convert.ChangeType to convert from object to type) but doesn't work if the field is an array. It works if i use Array.Cast but the type is only known at runtime so i cant cast.
I saw lots of topics on this but none of them worked so far.
I get this exception:
ArgumentException: Object type System.Object[] cannot be converted to target type: System.Single[]
I know why it happens i just can't find a way to convert the data. Any ideas?
EDIT: relevant code:
public static object Deserialize (string path){
string[] lines = File.ReadAllLines(path);
ConstructorInfo ci = type.GetConstructor(Type.EmptyTypes);
object newObj = ci.Invoke(new object[] {});
Type type = Type.GetType(lines[0], true);
FieldInfo[] fields = type.GetFields(BindingFlags.Public);
for (int i = 1; i < lines.Length; i++){
FieldInfo thisField = currentType.GetField(lines[i]);
if (thisField != null) {
if (line != "") {
if (fieldType == typeof(string)) {
thisField.SetValue(currentObject, line);
thisField = null;
}
else if (fieldType.IsPrimitive) {
val = Convert.ChangeType(line, fieldType);
thisField.SetValue(currentObject, val);
thisField = null;
}
else if (fieldType.IsArray){
string[] values = ReadUntil(']');
//ReadUntil just returns the values as string[] as read from text file
object[] newValues = Convert.ChangeType(values, fieldType);
thisField.SetValue(newObj, newValues);
}
}
}
}
}
return newObj;
}
You can use something like this
else if (fieldType.IsArray)
{
string[] values = ReadUntil(']');
var elementType = fieldType.GetElementType();
if (elementType == typeof(string))
thisField.SetValue(newObj, values);
else
{
var actualValues = Array.CreateInstance(elementType, values.Length);
for (int i = 0; i < values.Length; i++)
actualValues.SetValue(Convert.ChangeType(values[i], elementType), i);
thisField.SetValue(newObj, actualValues);
}
}
Type.GetElementType method is used to retrieve the type of the array elements, then Array.CreateInstance method to create an array of the desired type, and finally Array.SetValue method to populate the new array elements with the converted values.

Type casting object[] to generic type K which is also an array

I try to write a generic type casting method which will work for both complex objects and arrays of objects. Below is my code:
public void Test()
{
MyClass2[] t2 = m.MapItem<MyClass1[], MyClass2[]>(t1);
return;
}
public K MapItem<T, K>(T source)
{
if (typeof(T).IsArray && typeof(K).IsArray)
{
Type ek = typeof(K).GetElementType();
IList sourceList = (IList)source;
List<object> tmp = new List<object>();
for (int i = 0; i < sourceList.Count; i++)
{
var k = Activator.CreateInstance(ek);
tmp.Add(k);
}
var resultObj = tmp.ToArray();
MapItem(source, resultObj);
//Here i have resultObj is an object[] of the results,
//which is to be casted result type K
//BUT DOES NOT WORK!!!
return (K)Convert.ChangeType(resultObj, typeof(K));
}
else
{
MethodInfo myMapperMethod = GetMyMapperMethodForThisType(typeof(T), typeof(K));
return (K)myMapperMethod.Invoke(null, new object[] { source });
}
}
public K MapItem<T, K>(T source, K dest)
{
if (typeof(T).IsArray && typeof(K).IsArray)
{
IList sourceList = (IList)source;
IList destList = (IList)dest;
for (int i = 0; i < sourceList.Count; i++)
{
MapItem(sourceList[i], destList[i]);
}
return dest;
}
else
{
MethodInfo myMapperMethod = GetMyMapperMethodForThisType(typeof(T),typeof(K));
return (K) myMapperMethod.Invoke(null, new object[] { source, dest });
}
}
private MethodInfo GetMyMapperMethodForThisType(Type type1, Type type2)
{
//some code to find appropriate function...
}
But, return (K) Convert.ChangeType(y, typeof(K)); cannot cast from object[] to K. How can I do this casting to return K from object[]?
Note: json serialization works but I don't want to use reflection or serialization.
string jsonStr = JsonConvert.SerializeObject(resultObj);
return JsonConvert.DeserializeObject<K>(jsonStr);
Fundamentally I think you want to avoid using List<object> at all. You should just create the array of the right size:
IList dest = Array.CreateInstance(ek, sourceList.Count);
for (int i = 0; i < sourceList.Count; i++)
{
dest[i] = Activator.CreateInstance(ek);
}
K result = (K) dest;
// Note that this is calling MapItem<T, K>, not MapItem<T, object[]>
MapItem(source, result);
return result;

Reflection.Emit create object with parameters

I'm creating a dynamic function to create an object at runtime given an object[] of constructor params. I keep getting the generic exception 'Operation could destablise the runtime' and I can't see what I've done wrong.
The method works fine if the created object needs no constructor arguments - so the problem must be in the code in the for loop.
The code indexes into the given object[] putting the object onto the stack after which the ctor is called and the object returned.
Any ideas???
internal static Func<object[], object> CreateObjectFactoryMethodWithCtorParams(ConstructorInfo ctor, int ctorArgsLength)
{
Func<object[], object> factoryMethod = null;
if (ctor != null)
{
var dm = new DynamicMethod(string.Format("_CreationFacotry_{0}", Guid.NewGuid()), typeof(object), new Type[] { typeof(object[])}, true);
var il = dm.GetILGenerator();
il.DeclareLocal(typeof(int));
il.DeclareLocal(typeof(object));
il.BeginExceptionBlock();
il.Emit(OpCodes.Ldc_I4_0); // [0]
il.Emit(OpCodes.Stloc_0); //[nothing]
for (int i = 0; i < ctorArgsLength; i++)
{
EmitInt32(il, i); // [args][index]
il.Emit(OpCodes.Stloc_0); // [args][index]
il.Emit(OpCodes.Ldarg_0); //[args]
EmitInt32(il, i); // [args][index]
il.Emit(OpCodes.Ldelem_Ref); // [item-in-args-at-index]
}
il.Emit(OpCodes.Newobj, ctor); //[new-object]
il.Emit(OpCodes.Stloc_1); // nothing
il.BeginCatchBlock(ExceptionType); // stack is Exception
il.Emit(OpCodes.Ldloc_0); // stack is Exception, index
il.EmitCall(OpCodes.Call, EmitGeneratorType.GetMethod("ThrowFactoryException"), null);
il.EndExceptionBlock();
il.Emit(OpCodes.Ldloc_1); //[new-object]
il.Emit(OpCodes.Ret);
factoryMethod = (Func<object[], object>)dm.CreateDelegate(typeof(Func<object[], object>));
}
else
{
throw new EmitGeneratorException("Cannot create instance factory for a null ctor instance");
}
return factoryMethod;
}
private static void EmitInt32(ILGenerator il, int value)
{
switch (value)
{
case -1: il.Emit(OpCodes.Ldc_I4_M1); break;
case 0: il.Emit(OpCodes.Ldc_I4_0); break;
case 1: il.Emit(OpCodes.Ldc_I4_1); break;
case 2: il.Emit(OpCodes.Ldc_I4_2); break;
case 3: il.Emit(OpCodes.Ldc_I4_3); break;
case 4: il.Emit(OpCodes.Ldc_I4_4); break;
case 5: il.Emit(OpCodes.Ldc_I4_5); break;
case 6: il.Emit(OpCodes.Ldc_I4_6); break;
case 7: il.Emit(OpCodes.Ldc_I4_7); break;
case 8: il.Emit(OpCodes.Ldc_I4_8); break;
default:
if (value >= -128 && value <= 127)
{
il.Emit(OpCodes.Ldc_I4_S, (sbyte)value);
}
else
{
il.Emit(OpCodes.Ldc_I4, value);
}
break;
}
}
Calling code
Func<object[], object> factoryFunction = GetFunction(someCtor, new object[] { arg1, arg2});
var obj = factoryFunction(new object[] {new SomeClass, "A String" }); //input ctor args
It works fine for me, as long as I make all the constructor parameters object:
class SomeClass {
public SomeClass(object s, object t) { }
}
static void Main()
{
var someCtor = typeof(SomeClass).GetConstructors()[0];
Func<object[], object> factoryFunction = CreateObjectFactoryMethodWithCtorParams(someCtor, someCtor.GetParameters().Length);
var obj = factoryFunction(new object[] {"A String", 123 });
}
I think the problem is that you haven't done any conversions from the objects from the array to the actual constructor types, noting that you need to consider both reference types and value-types (unbox). Like so:
var parameters = ctor.GetParameters();
for (int i = 0; i < parameters.Length ; i++)
{
EmitInt32(il, i); // [index]
il.Emit(OpCodes.Stloc_0); // [nothing]
il.Emit(OpCodes.Ldarg_0); //[args]
EmitInt32(il, i); // [args][index]
il.Emit(OpCodes.Ldelem_Ref); // [item-in-args-at-index]
var paramType = parameters[i].ParameterType;
if (paramType != typeof(object))
{
il.Emit(OpCodes.Unbox_Any, paramType); // same as a cast if ref-type
}
}
il.Emit(OpCodes.Newobj, ctor); //[new-object]
il.Emit(OpCodes.Stloc_1); // nothing
as a minor note: since you need to call .GetParameters(), you should not pass in the parameter length as a parameter to the method; that is redundant, and could cause errors when wrong.
This then works with my exmaple:
class SomeClass {
public SomeClass(string s, int t) { }
}
static void Main()
{
var someCtor = typeof(SomeClass).GetConstructors()[0];
Func<object[], object> factoryFunction = CreateObjectFactoryMethodWithCtorParams(someCtor);
var obj = factoryFunction(new object[] {"A String", 123 });
}

Categories

Resources