Passing string array parameters to Invoke method - c#

I have this class:
using System.Linq;
namespace TestNamespace {
public class Program {
public static void Main(string[] args) {
//does stuff
}
}
}
I am loading the above assembly and want to invoke the method with a string array parameter.
This gives me a null exception:
private static object[] parameters = new object[1];
string[] pa = { "1", "2" };
parameters[0] = pa;
//Creating target and other code
bool retVal = (bool)target.Invoke(null, parameters);
Any thoughts? Thanks

Where's the NullReferenceException. Are you sure that you're reflecting the MethodInfo correctly and that target is not null? That's my suspicion as to what's really going on here. If there were a NullReferenceException being thrown in the method, it would be wrapped in a TargetInvocationException and thus I suspect the NullReferenceException is because target is null.
To be clear, here's how you load and invoke the method:
var target = typeof(Program)
.GetMethod("Main", BindingFlags.Public | BindingFlags.Static);
bool retVal = (bool)target.Invoke(null, new object[] { pa });
The parameters parameter to MethodInfo.Invoke is an object[] with the same number, order and types of the parameters for the method being invoked. In your case, you have a single parameter of type string[]. Thus, the object[] parameter to MethodInfo.Invoke should be an array with one element, and that element is an instance of string[]. That is what I have accomplished with the syntax above.

Related

CreateDelegate Error: System.ArgumentException Cannot bind to the target method

I have an app that takes the dll of an external app, look into it for a specified class and method. It then gets the methodinfo from this external method and tries to then Create a delegate via Delegate.CreateDelegate
I constantly get
System.ArgumentException: 'Cannot bind to the target method because its signature or security transparency is not compatible with that of the delegate type.'
I have gone and extracted the code a bit to make it easier to share and debug as well as write a small simple external app to read from. See the code below:
External App Example as Library (.Net Framework 4.8)
using System;
namespace MethodLib
{
public class PrintText
{
public string Print(string textToPrint, int number)
{
return $"{ PrintPrivate(textToPrint) }: {number}";
}
public static string PrintStatic(string textToPrint)
{
return textToPrint;
}
public void PrintVoid(string textToPrint)
{
Console.WriteLine(textToPrint);
}
private string PrintPrivate(string textToPrint)
{
return $"This is { textToPrint }";
}
}
}
App to CreateDelegate
MethodInfo Creation
using System;
using System.Reflection;
namespace DelegateApp
{
public class PluginSupport
{
public MethodInfo GetMethodInfo(string methodName, string externalLocation)
{
var instance = Activator.CreateInstance(Assembly.LoadFrom(externalLocation)
.GetType("MethodLib.PrintText"));
var methodInfo = instance.GetType()
.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance);
return methodInfo;
}
}
}
Create Delegate Part
namespace DelegateApp
{
public class MethodGenerator
{
private PluginSupport _pluginSupport;
public MethodGenerator()
{
_pluginSupport = new PluginSupport();
}
public MethodDetails Create(string methodName, string path)
{
var method = _pluginSupport.GetMethodInfo(methodName, path);
if (Equals(method, null))
{
throw new KeyNotFoundException($"Method '{ methodName }' doesn't exist in class");
}
return new MethodDetails
{
MethodName = method.Name,
ComponentName = method.DeclaringType.Name,
FriendlyName = method.DeclaringType.Name,
Parameters = method.GetParameters(),
LogicalPath = method.DeclaringType.Assembly.Location,
Method = (Func<string>)Delegate.CreateDelegate(typeof(Func<string>), method)
};
}
}
}
What have I tried
So reading a lot of different post I gather that the call I am using
(Func<string>)Delegate.CreateDelegate(typeof(Func<string>), method) is actually meant for static methods only, and as I am interested in all the public methods I am missing a target/instance.
So from other examples, you need to create the instance and pass that in as well, so I used the var myInstance = Actovator.CreateInstance and then passed this variable in as well, ending up with the following
(Func<string>)Delegate.CreateDelegate(typeof(Func<string>), myInstance, method)
I have also tried to use this one
public static Delegate CreateDelegate(Type type, Type target, string method, bool ignoreCase);
All of this keeps throwing
System.ArgumentException: 'Cannot bind to the target method because its signature or security transparency is not compatible with that of the delegate type.'
The only time I get it to work, is when I do the following:
methodName = PrintStatic from external app
var methodInfo = instance.GetType()
.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static);
var deleg = (Func<string>)Delegate.CreateDelegate(typeof(Func<string>),null, method)
of course this is not what I want as this only does the static for me and I want the non-static as well. But even with this if I add BindingFlags.Instance to the mix the static will also throw the same error.
If I also remove BindingFlags.Instance and my methodName = Print, then methodInfo is null.
My Questions
What am I not understanding/missing with regards to the Delegate.CreateDelegate?
What code am I missing that this is not working as I am expecting?
Is there a different way to do the same thing?
From creating the Delegate I want to invoke it later in the code, but is there a penalty for just using the invoke directly on methodinfo instead of creating a delegate then invoking it?
Why does methodinfo not give me my public non-static member if BindingFlags.Instance is omitted?
Thanks to #Charlieface, I realised my signature types were not corresponding to me creating the delegate.
So what I finally ended up with in this example code was to do the following in MethodGenerator class
Get the parameters from methodinfo
Go through the params and add them to a list of Types and get the type of each param
Build a func where I do not know the number of types it will need and replace the number with the amount of params I have from methodinfo + output type
Check if method isstatic and based on this set it to
methHead = method.IsStatic
? Delegate.CreateDelegate(delegateFunc.MakeGenericType(types.ToArray()), null, method)
: Delegate.CreateDelegate(delegateFunc.MakeGenericType(types.ToArray()), instance, method);
This is a bit of elaborate code I guess, but it works and will need to refine it or drop it in the actual code base where we want to use it. But as #Charlieface mentioned if you don't know the type, there isn't much point to the delegate.
Final piece of code
public MethodDetails Create(string methodName, string path)
{
var method = _pluginSupport.GetMethodInfo(methodName, path);
if (Equals(method, null))
{
throw new KeyNotFoundException($"Method '{ methodName }' doesn't exist in class");
}
var instance = Activator.CreateInstance(method.DeclaringType);
List<Type> types = new List<Type>();
var methodPrams = method.GetParameters();
foreach (var item in methodPrams)
{
types.Add(Type.GetType(item.ParameterType.UnderlyingSystemType.FullName));
}
var funcType = typeof(Func<>);
var delegateFunc = Type.GetType(funcType.FullName.Replace("1", (methodPrams.Length + 1).ToString()));
Delegate methHead;
types.Add(typeof(string));
methHead = method.IsStatic
? Delegate.CreateDelegate(delegateFunc.MakeGenericType(types.ToArray()), null, method)
: Delegate.CreateDelegate(delegateFunc.MakeGenericType(types.ToArray()), instance, method);
return new MethodDetails
{
MethodName = method.Name,
ComponentName = method.DeclaringType.Name,
FriendlyName = method.DeclaringType.Name,
Parameters = method.GetParameters(),
LogicalPath = method.DeclaringType.Assembly.Location,
Method = methHead
};
}

Why does Method Invoke fail with argument exception?

Consider this code sample from a WinForms app:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
object[] parms = new object[1];
parms[0] = "foo";
DoSomething(parms);
}
public static string DoSomething(object[] parms)
{
Console.WriteLine("Something good happened");
return null;
}
}
It works as expected, when you click button1 it prints "Something good happened" to the console.
Now consider this code sample, which is the same except that it invokes DoSomething using reflection:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
object[] parms = new object[1];
parms[0] = "foo";
System.Reflection.MethodInfo mi = typeof(Form1).GetMethod("DoSomething");
mi.Invoke(null, parms);
}
public static string DoSomething(object[] parms)
{
Console.WriteLine("Something good happened");
return null;
}
}
It throws an System.ArgumentException on the line mi.Invoke(null, parms) (Object of type 'System.String' cannot be converted to type 'System.Object[]'.)
parms is clearly an object array, and DoSomething's method signature is clearly expecting an object array. So why is invoke pulling the first object out of the array and trying to pass that instead?
Or is something else going on that I'm not understanding?
MethodInfo.Invoke is expecting an object array, where each object in the object array corresponds to an argument to the method. The first argument in the object array is the first argument, the second object in the array the second method, etc.
Since you want the first argument to your method to be an object[], you need to ensure that the first object in the object array you pass to MethodInfo.Invoke is an object array that represents the array that DoSomething should use.
object[] parms = new object[1];
parms[0] = "foo";
with:
public static string DoSomething(object[] parms)
that's the problem; the first parameter is not a string - it is an object[]. The object[] that you pass to Invoke represents each parameter in turn, so an object[] of length 1 with a string would match static string DoSomething(string s), but does not match your method. Either change the signature, or wrap the value. Frankly, I suggest changing the signature is the better idea here, but:
parms[0] = new object[] { "foo" };
would also work
MethodInfo.Invoke expects an array of objects as parameter to pass multiple arguments to the functions, each object in the array will be a different parameter.
As your function also expects an object array you are passing as argument not an object array but an string.
You must wrap that array into another array, in this way the Invoke will unwrap the first array and use the inner array as the first parameter for the call.
mi.Invoke(null, new object[]{ parms });
parms is clearly an object array, and DoSomething's method signature is clearly expecting an object array.
Yes it is expecting an object array. But when you pass this:
object[] parms = new object[1];
You are saying these are the arguments for DoSomething so parms[0] is the first argument to DoSomething and if there were more items in parms then parms[1] will be the 2nd argument and so on.
Clearly the first argument for DoSomething is not a string (parms[0]) so you get this error:
It throws an System.ArgumentException on the line mi.Invoke(null, parms) (Object of type 'System.String' cannot be converted to type 'System.Object[]'.)

How to pass object[] to Activator.CreateInstance(type, params object[])

I have a class which contains an empty constructor and one that accepts an array of objects as its only parameter. Something like...
public myClass(){ return; }
public myClass(object[] aObj){ return; }
This is the CreateInstance() method call that I use
object[] objectArray = new object[5];
// Populate objectArray variable
Activator.CreateInstance(typeof(myClass), objectArray);
it throws System.MissingMethodException with an added message that reads
"Constructor on type 'myClass' not found"
The bit of research that I have done has always shown the method as being called
Activator.CreateInstance(typeof(myClass), arg1, arg2);
Where arg1 and arg2 are types (string, int, bool) and not generic objects.
How would I call this method with only the array of objects as its parameter list?
Note: I have tried adding another variable to the method signature. Something like...
public myClass(object[] aObj, bool notUsed){ return; }
and with this the code executed fine.
I have also seen methods using reflection which were appropriate but I am particularly interested in this specific case. Why is this exception raised if the method signature does in fact match the passed parameters?
Cast it to object:
Activator.CreateInstance(yourType, (object) yourArray);
Let's say you have constructor:
class YourType {
public YourType(int[] numbers) {
...
}
}
I believe you would activate like so by nesting your array, the intended parameter, as a item of the params array:
int[] yourArray = new int[] { 1, 2, 4 };
Activator.CreateInstance(typeof(YourType ), new object[] { yourArray });

How could I create a reference parameter object to an object of unknown type?

Say I am working with the following code:
Type type = info.ParameterType;
object activatedTypeToReference = Activator.CreateInstance(type.GetElementType());
How do I create a reference parameter object to the above activatedTypeToReference object in C#?
When you invoke the method, you pass in an array of arguments. For an out parameter, you don't need to specify anything for the array element - the value can just be null. When the method returns, the array will contain the value set by the method. Here's an example:
using System;
public class Test
{
static void Main()
{
var method = typeof(Test).GetMethod("DummyMethod");
object[] args = new object[1];
method.Invoke(null, args);
Console.WriteLine(args[0]); // Prints 10
}
public static void DummyMethod(out int x)
{
x = 10;
}
}

Running into System.MissingMethodException: Method Not Found with PrivateObject

Basically, some of my tests are succeeding, some are failing. Per Mr. Skeet's excellent suggestion, I created a full code sample to confirm I'm not crazy. This is the code:
namespace ClassLibrary
{
using System;
public class Manager
{
private int SampleMethod(int id)
{
return id;
}
}
}
My test is:
namespace UnitTestProject
{
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class UnitTest
{
[TestMethod]
public void TestPasses()
{
var privateInfo = new PrivateObject(new ClassLibrary.Manager());
var actual = privateInfo.Invoke("SampleMethod", 1);
}
[TestMethod]
public void TestErrorsOut()
{
var privateInfo = new PrivateObject(new ClassLibrary.Manager());
var actual = privateInfo.Invoke("SampleMethod", 0);
}
[TestMethod]
public void TestWorksAsWell()
{
var privateInfo = new PrivateObject(new ClassLibrary.Manager());
privateInfo.Invoke("SampleMethod", new object[] { 0 });
}
[TestMethod]
public void TestAlsoErrorsOut()
{
var privateInfo = new PrivateObject(new ClassLibrary.Manager());
var types = new Type[] { typeof(int) };
var actual = privateInfo.Invoke("SampleMethod", types, 0);
}
}
}
The first test (TestPasses()) works.
The second test (TestErrorsOut()) fails with the following error:
{"Method 'ClassLibrary.Manager.SampleMethod' not found."}
The baffling thing is the error is consistent, but the actual test is almost identical. It makes no sense. I tried this on VS2012 RC and VS2010, with the same results.
The only thing I can think of is "0" is getting cast as something besides int, which means it can't find the method signature of SampleMethod? I tried a third test to explicitly pass in the type I'm looking for (TestAlsoErrorsOut()), but that also errors out with the same error.
Ideas? Thanks.
Edit to add
By using Ian's suggestion of using the obj[] instead of params obj[] overload, it works (test TestWorksAsWell()). And that explains why TestAlsoErrorsOut() fails, because I'm using params method, which wouldn't work with Type[]. So, this is fixed. But, why? Why will params obj[] work when passing a 1, but not a 0?
According to the docs (http://msdn.microsoft.com/en-us/library/ms243710.aspx) the arguments are supposed to be passed as an array of objects. Explicitly passing an object array appears to work correctly:
var actual = (int)privateInfo.Invoke("SampleMethod", new object[] {0});
Passing 0 on its own appears to result in the compiler selecting this overload
Invoke(string name = "SampleMethod", System.Reflection.BindingFlags bindingFlags = Default, object[] args = {object[0]})
I was getting the System.MissingMethodException when calling the following test:
PrivateObject shadow = new PrivateObject(target);
shadow.Invoke("PrivateMethod", new string[]{"arg1","arg2"});
On the target's private method signature
private void PrivateMethod(string[] args)
I had to add params to the private method signature to resolve the exception as follows:
private void PrivateMethod(params string[] args)
Ian Gilroy is right, but the reason why; it is because PrivateObject.Invoke has many signatures, and one of them is: Invoke(string name, Type[] types, object[] objects).
And in my case, I was passing values like: Invoke("MethodName", null, null), in which case C# compiler prefers the mentioned signature, than Invoke(string name, params object[] objects), which results in error.

Categories