reflection calling 2 methods - c#

I'm trying to do something like this but with reflection:
var ss1= method1(param).method2();
I know how to invoke the first method but I don't know how to invoke the second one.
method 1 returns an object
method 2 is the GetAwaiter method(in my particular case)
MethodInfo mi = typeof(type).GetMethod("method1");
Type tt = typeof(type);
ParameterInfo[] param = mi.GetParameters();
object[] param = new object[] { //some code };
object mm= mi.Invoke(this, param);
MethodInfo mi2 = typeof(type).GetMethod("GetAwaiter");
var ss1= mi2.Invoke(mm,null);
on the last line i get an error: "object does not match target type"

Your code is more or less correct, but that exception is telling you that mm is not what you think it is. The type returned by your method1 is not the same as the type you have specified in the .GetMethod("GetAwaiter") line.
A safer bet would be to replace typeof(type) with mm.GetType().GetMethod("GetAwaiter"). I suspect that will actually return null, since mm.GetType() doesn't seem to be what you expect, but without seeing more code (in particular, what 'type' is and where the "method1" and "GetAwaiter" methods are actually declared) it's hard to give you much more guidance.

There is an error on this line
MethodInfo mi2 = typeof(type).GetMethod("GetAwaiter");
mm object has a different type, so you get an exception. Change the line to
MethodInfo mi2 = mm.GetType().GetMethod("GetAwaiter");
as Tim suggests.
It's better to use the Type.InvokeMember method since it's shorter and helps avoid errors like this. For example:
object mResult = this.GetType ().InvokeMember ("method1",
BindingFlags.InvokeMethod | BindingFlags.Instance, null, this,
new object[] { /* parameters here */ });
object awaiter = mResult.GetType ().InvokeMember ("GetAwaiter",
BindingFlags.InvokeMethod | BindingFlags.Instance, null, mResult, null);

Related

Method created during runtime changes the parameter order of another method call depending on how it is run

I implement interfaces during runtime using Reflection.Emit and create their defined methods.
Example definition of a method in the interface:
IFoo DoSomething(IBar bar, string name);
To create the methods I do the following:
var args = methodInfo.GetParameters();
MethodBuilder methodBuilder = typeBuilder.DefineMethod(methodInfo.Name, MethodAttributes.Public | MethodAttributes.Virtual,
methodInfo.ReturnType, (from arg in args select arg.ParameterType).ToArray());
typeBuilder.DefineMethodOverride(methodBuilder, methodInfo);
var generator = methodBuilder.GetILGenerator();
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldfld, fieldBuilder);
generator.Emit(OpCodes.Ldtoken, methodInfo.ReturnType);
if (args.Any())
{
generator.Emit(OpCodes.Ldc_I4_S, args.Length);
generator.Emit(OpCodes.Newarr, typeof(object));
for (int i = 0; i < args.Length; i++)
{
generator.Emit(OpCodes.Dup);
generator.Emit(OpCodes.Ldc_I4_S, i);
generator.Emit(OpCodes.Ldarg_S, i + 1);
generator.Emit(OpCodes.Stelem_Ref);
}
}
else
{
generator.Emit(OpCodes.Ldc_I4_0);
}
generator.EmitCall(OpCodes.Callvirt, typeof(ISomeType).GetMethod(nameof(ISomeType.Test), new[] {typeof(Type), typeof(object[])}), null);
generator.Emit(OpCodes.Ret);
This generates me for example my DoSomething(IBar bar, string name) method of the interface.
All generated methods call the method Test() of ISomeType.
This is the method Test():
public object Test(Type type, object[] arguments)
{
//do something
}
When I run my application and call the DoSomething() method, the parameters are passed the wrong way (object[] first and Type second) to Test(), which obviously leads to an exception.
But when I run a unit test and call the DoSomething() method, the parameters are passed correctly (Type first and object[] second) to Test().
Why is there a difference in which order the parameters are passed to the Test() method depending on how it is run?
So with a lot of help of Marc Gravell in the comments I found out what to do to get this problem fixed.
First of all I had an error in the way I created my methods.
The case when a method has no arguments has to be changed from
generator.Emit(OpCodes.Ldc_I4_0);
that pushes just a 0 as Int32 onto the stack, to
MethodInfo emptyArray = typeof(Array).GetMethod(nameof(Array.Empty))?.MakeGenericMethod(typeof(object));
generator.EmitCall(OpCodes.Call, emptyArray, null);
which really pushes an empty array to the stack that will get passed as an argument to the Test() function.
To fix the problem with the wrong parameter order there are two possible solutions in my specific case:
(1) More changes to the generated IL code:
Really push the return type of the method as a Type and not a RuntimeTypeHandle:
generator.Emit(OpCodes.Ldtoken, methodInfo.ReturnType);
MethodInfo getTypeFromHandle = typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle));
generator.EmitCall(OpCodes.Call, getTypeFromHandle, null);
Cast the return type of Test() to the wanted return type of the method before returning:
generator.EmitCall(OpCodes.Callvirt, typeof(ISomeType).GetMethod(nameof(ISomeType.Test), new[] {typeof(Type), typeof(object[])}), null);
generator.Emit(OpCodes.Castclass, methodInfo.ReturnType);
generator.Emit(OpCodes.Ret);
(2) Convert Test() method to a generic method:
In case you have parameters as a Type you can convert your method to a generic method:
public T Test<T>(object[] arguments)
{
//do something
}
If you decide to do so, you will have to change some of the IL code generation as well:
Don't push return type of the method to the stack:
Remove this line:
generator.Emit(OpCodes.Ldtoken, methodInfo.ReturnType);
Change the call of Test():
MethodInfo test = typeof(ISomeType).GetMethod(nameof(ISomeType.Test), new[] { typeof(object[]) }).MakeGenericMethod(methodInfo.ReturnType);
generator.EmitCall(OpCodes.Callvirt, test, null);
generator.Emit(OpCodes.Ret);
You also don't have to cast the return value in this case because your method Test() already returns the correct type with T.
With any of those two solutions the Test() method gets call with the correct parameter order regardless of how it is called (application or unit test).
Another tip of Marc Gravell was to use Sigil to get clear error messages when your generated IL code is invalid. And no matter which of the two solutions above you are choosing I can only recommend to do so.

Why can't I retrieve the value for parameters of type out or ref using Type.InvokeMember?

A long title, but I wanted it to be specific. The title is really the question. Even though the method that InvokeMember is calling has an out parameter and is assigning a value to to that parameter I can't grab that value. Here is the code I was initially using:
string parameter = "";
int result = Convert.ToInt32(typeof(Ability).InvokeMember(selectedMove, BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Static, null, null, new object[] { parameter }));
I changed it this, which now makes it work as intended but I don't know why:
object[] args = new object[1]; //necessary to retrieve ref/out parameter
int result = Convert.ToInt32(typeof(Ability).InvokeMember(selectedMove, BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Static, null, null, args));
I just wanted to help someone who is struggling(I did) with unmanaged(COM) and getting ref-parameter back. So, when using InvokeMember against COM-method, you have to tell which arguments are ref-type. This is achieved by using ParameterModifier-class, For example:
object[] args = new object[3] { param1, param2, errorStr };
ParameterModifier pMod = new ParameterModifier(3);
pMod[2] = true;
ParameterModifier[] mods = { pMod };
object tempObj = myCOMObject.GetType().InvokeMember("MyCOMMethod", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Public, null, myCOMObject, args, mods, null, null);
In the code above, the 3rd argument is set to be a reference (pMod[2] = true;)
Your second snippet is missing a rather essential line of code. It should look like this, assuming the out argument is of type string:
object[] args = new object[1]; //necessary to retrieve ref/out parameter
int result = Convert.ToInt32(typeof(Ability).InvokeMember(selectedMove,
BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Static,
null, null, args));
string outValue = (string)args[0]; // <=== here!
It should now also be obvious why your 1st snippet cannot work, you don't have a reference to the object[] array that you pass so you can never retrieve the modified argument.
In your first code example, the call to InvokeMember doesn't modify the value of the parameter variable, it just replaces the first item in the parameter array (which now points to a different string instance). Since you didn't keep a reference to this array, you can't retrieve the value of the output parameter.
In other words: the array initially contains a copy of the parameter variable (i.e. a copy of the reference to an empty string). After the call, parameter and the value in the array refer to 2 different string instances.

How to call a method with arguments of different types using reflection in C#

I'm trying to call a function that takes two parameters (a boolean and a string) using .NET reflection in C#. However, with the following code i get an exception:
object[] paramList = new object[] { true, "Foo" };
Type wsType = typeof(MyWS);
MyWS inst = (MyWS)Activator.CreateInstance(wsType);
MethodInfo method = wsType.GetMethod(function); // function = the name of the function to be called
method.Invoke(inst, paramList);
This throws an ArrayTypeMismatchException ("Attempted to access an element as a type incompatible with the array.").
It seems that paramList is causing the exception, but I have no idea why?
The function I'm trying to call would be something like:
public bool EnableSchedule(bool enable, string password)
{
...
}
It doesn't seem like there is anything wrong with what you are doing - unless the problem lies in the "MyWS". I assume the class is public.
Meanwhile, try adding some binding flags to GetMethod(), like
wsType.GetMethod(function, BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance);

InvokeMember in c# with "reflected" parameters

I want to call a method of a DLL via reflection (can't be sure that the other dll is loaded) but have problems with the parameters.
The method I want to call is
public void Add(DBTable table, String sField, DBValue value, SQLConditionType type)
in the MP-TVSeries project
What I tried is this:
// WindowPlugins.GUITVSeries
Assembly MPTVSeries = Assembly.Load("MP-TVSeries");
Type sqlConditionType = MPTVSeries.GetType("WindowPlugins.GUITVSeries.SQLCondition");
Type sqlConditionTypeEnumType = MPTVSeries.GetType("WindowPlugins.GUITVSeries.SQLConditionType");
Type dbEpisode = MPTVSeries.GetType("WindowPlugins.GUITVSeries.DBEpisode");
// SQLCondition sql = new SQLCondition();
object sql = Activator.CreateInstance(sqlConditionType);
// sql.Add(new DBEpisode(), DBEpisode.cFilename, filename, SQLConditionType.Equal);
sqlConditionType.InvokeMember("Add",
BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public,
null,
sql,
new object[] {
Activator.CreateInstance(dbEpisode),
dbEpisode.GetField("cFilename"),
filename,
sqlConditionTypeEnumType.GetField("Equal")
});
but this throws an exception with the message
The method "WindowPlugins.GUITVSeries.SQLCondition.Add"
could not be found.
My guess is that I am doing something wrong with the parameters, but as I am totally new to reflection I can't get my head around it.
Someone please help ;-)
You are going too fast. Get sqlConditionType.GetMethod() working first to get a MethodInfo so you can be sure this is not a method overload resolution problem. The arguments you pass are smelly, particularly filename and sqlConditionTypeEnumType.GetField("Equal").

Reflection.Emit: How to convert MethodBuilder to RuntimeMethodInfo reliably?

After generating a type dynamically and calling TypeBuilder.CreateType, I want to create a delegate that points to a method in the new type. But if I use code like
loadedType = typeBuilder.CreateType();
myDelegate = (MyDelegate)Delegate.CreateDelegate(
typeof(MyDelegate), methodBuilder);
Reusing the methodBuilder as a methodInfo, I get the exception "MethodInfo must be a RuntimeMethodInfo". Now normally I can re-acquire the MethodInfo with
MethodInfo mi = loadedType.GetMethod(methodBuilder.Name);
myDelegate = (MyDelegate)Delegate.CreateDelegate(typeof(MyDelegate), mi);
But my class may contain several overloaded methods with the same name. How do I make sure I get the right one? Do methods have some persistent identifier I could look up in loadedType?
Update: okay, here's what I'm using to re-acquire the MethodInfo. I just wish I could be sure it works in all cases.
private static MethodInfo ReacquireMethod(Type type, MethodInfo method)
{
BindingFlags flags = BindingFlags.DeclaredOnly;
flags |= (method.IsPublic ? BindingFlags.Public : BindingFlags.NonPublic);
flags |= (method.IsStatic ? BindingFlags.Static : BindingFlags.Instance);
MethodInfo m = type.GetMethod(method.Name, flags, null,
ParameterTypes(method), null);
Debug.Assert(m != null);
return m;
}
As far as I'm aware there's no persistent shared identifier.
Overloads of a method are distinguished by their parameter lists, so my guess is that you'll need to call GetParameters on methodBuilder and then translate the returned ParameterInfo[] array into a Type[] array to pass to the the appropriate GetMethod overload:
MethodInfo mi = loadedType.GetMethod(
methodBuilder.Name,
methodBuilder.GetParameters().Select(p => p.ParameterType).ToArray());
You need to use the GetMethod overload that takes an array of types. Method overloads are selected for binding by the parameters passed to them. Since you are not actually calling a method when getting their metadata via reflection, you have to provide an ordered array of Type objects to get the right MethodInfo object:
Type.GetMethod Method (String, Type[])
Used like:
MethodInfo mi = loadedType.GetMethod(
methodBuilder.Name,
new[]
{
typeof(string),
typeof(int)
}
);
Assiming methodBuilder.Name is "MyMethod", that would bind the following:
public <returnType> MyMethod(string param1, int param2);
Remember that the return type is not a part of a method signature, only the method name and its parameter types are.

Categories

Resources