locked
Using reflection for binding code taking object[] as a list of arguments to a .NET method with distinct parameters RRS feed

  • Question

  • I am working on an interpreter in .NET that has its own notion of data types that I have to interoperate with pure .NET code. For this current situation, I do plan to comb the DLR for inspiration, but I couldn't directly adopt it because I'm also doing a lot of green threading and all the async-await everywhere was incompatible with it.

    I have a wrapper that takes an array of objects that it iterates and casts so that an Invoke of the wrapped .NET call will resolve with the arguments given to it from the interpreter. Say, the .NET method might take an integer but the data type I feed in is some interpreter-specific number type. There is also the issue of dealing with params. I have to deal with that today and it works.

    I can't do it in reverse. Say that I want to bind to a .NET class one of the interpreter's functions/methods where I just know it takes some number of objects as arguments. In this particular situation, I'm trying to implement subscribing interpreted code to .NET events. So I have a certain signature I have to achieve in order to be able to attach to the event; I can't take my call accepting object[] and have it bind. How might I go about doing this?

    At the moment, I have blundered into creating a DynamicMethod and have been able to create it with a compatible signature. Unfortunately, I'm completely helpless afterwards! I was kinda hoping I could feed in a lambda to accept the arguments as an array and do the conversions kind of like I already do. I guess I could just emit all that code in intermediate language, but that's a crazy way to get into that kind of hobby! I am hoping there's some other way I can nail it down.

    I have some code up on GitHub showing the previous attempt. I tried to do something more "normal" but I wound up with an ArgumentError because I'm still trying to making an object[] work instead of the actual signature of the event. After this, I switched to an experiment with a DynamicMethod but I haven't put that online. So I'm dealing with language stuff so I can't be too afraid of emitting all the instructions to do this using the IL generator, but it also doesn't smell like the first, second, or third things I should try.

    [can't post the link to GitHub because my account hasn't been verified yet]

    Friday, February 14, 2020 8:10 PM

All replies

  • Hi Adam Preble,

    Thank you for posting here.

    According to your description, I need more information to make a test.

    Could you describe your question more clearly or provide some related code here? It will help us to analyze your problem.

    We are waiting for your update.

    Best Regards,

    Xingyu Zhao 


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Monday, February 17, 2020 8:10 AM
  • Let's say I have an event based on this delegate:

    public void integer_func(int some_number);

    I want to subscribe a call to a completely different system that will run a bunch of code if I am able to get the arguments in the form of an object array:

    .... public object PassToRuntime(object[] args) { ... }

    PassToRuntime is an all-in-one here. 

    If I had:

    public event integer_func AnIntegerEvent;

    I could not attach to it with:

    instance.AnIntegerEvent += PassToRuntime

    The signatures won't match.

    I'd like to figure out how to create a binder that could take any number of different type of delegates, repack the arguments as an object[], be able to bind that to any event of the same delegate signature.

    I have done some poking around and it looks like I have to create a DynamicMethod and do some manual MSIL generation to be able to do this kind of thing. I still wonder if there's another way because that's turning out to be a real handful. PassToRuntime is actually part of a different object, so the code I generate needs to be more like a class taking in the instance separately so I don't have to include it in the signature of the actual call.

    Wednesday, February 19, 2020 6:12 AM
  • Hi Adam Preble,

    Thanks for your feedback.

    You can use following delegate to match 'PassToRuntime' method.

    public delegate object integer_func(params object[] arguments);

    My simple test:

        public delegate object integer_func(params object[] arguments);
        class Program
        {
            public static event integer_func AnIntegerEvent;
            public object PassToRuntime(object[] args)
            {
                return args.First();
            }
            static void Main(string[] args)
            {
                object[] o = {"1",2,'3' };
                Program p = new Program();
                AnIntegerEvent = p.PassToRuntime;
                var result = AnIntegerEvent(o);
                Console.WriteLine(result);
                Console.ReadLine();
            }
        }

    Result:

    Best Regards,

    Xingyu Zhao


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Thursday, February 20, 2020 9:23 AM
  • That changes the signature of integer_func. That's not something I can change in the real situation.

    Thursday, February 20, 2020 10:07 PM
  • Hey, Adam Preble.  I'm interested in helping with this because it's challenging.  Please don't take this the wrong way:

    Is this a homework assignment or a real-world problem?  If it's homework you should slap your professor, but I think I can figure out a good working solution for you anyway.  If it's real-world it's pretty simple stuff.  I just don't want to spend half an hour writing a code demo to be told it's not going to work for you.

    Thanks!

    Friday, February 21, 2020 1:40 AM
  • I am writing a Python interpreter in C# that has to bind with .NET objects. The common code I have written to invoke Python calls takes an array of objects since everything is dynamic and I can't count on parameter types. I'm able to invoke .NET code just fine from the interpreter using a little reflection. I can't flip that around and have my Python interpreter code attach to a .NET event. That's because the events expect a certain signature to what binds to it, but all I have is a giant hole expecting the arguments as object[]. So I have to use something that generates a matching signature, then takes the arguments from the call and arranges them into an object array for the internal interpreter invocation. This is despite not knowing the length of the arguments or even if I have them.

    So far, I have started experimenting with generating MSIL for this because I don't know a way to look bag this up otherwise. MSIL is really tedious and the errors are really cryptic so I'm fine not having to use it.

    I stumbled around with this in a development branch here:

    https://github.com/rockobonaparte/cloaca/blob/d1667ef4e678f17ac2b237a2ae3d9bb1becd8e0d/CloacaInterpreter/Interpreter.cs#L411

    What I concluded is that I needed to dynamically construct my own class and a method off of that class that will match the signature of the event. The MSIL will pop all the arguments off of the stack passed to the method, pack them into an object array, and call the internal calling method with that argument. I can generate the objects array to the right size and package it based on the MethodInfo's parameters. It has to be a class because I have to pass a handle to the interpreter code that would get run, and I can't pass it at the actual call site since that would change the signature.

    I just got a basic version of that working in this giant code trash heap:

    https://github.com/rockobonaparte/cloaca/blob/d1667ef4e678f17ac2b237a2ae3d9bb1becd8e0d/MsilEmissionExperiments/Program.cs#L46

    If I knew I'd be posting it now then I would have cleaned it up more, but it should at least give an impression. I'm saving the DLL so I can use ILSpy to compare what I generate to what C# does for the same if I mimick it in one circumstance. I'm only spitting it all out right now because it's clear what I'm doing is being seriously underestimated. And yes, this is a personal project.

    (Final note, I know about IronPython but I need something with good coroutine management so I'm having to use a lot of async-await.)

    Friday, February 21, 2020 5:22 PM
  • Second note and also Re: IronPython: one reason I felt I had to go this way is because I think I found how IronPython was doing similar and they admitted to having to generate MSIL in their code to do it. I figured this all out after the initial responses.
    Friday, February 21, 2020 5:23 PM
  • It was suggested to me elsewhere to try to do the matching using generic methods. That so far seems to work and I'm trying to carry it forward for full experiments. I basically just create generic wrappers up to X amount of args and probably just scream at the user if they try to bind beyond that.
    Saturday, February 22, 2020 4:32 PM
  • It was suggested to me elsewhere to try to do the matching using generic methods. That so far seems to work and I'm trying to carry it forward for full experiments. I basically just create generic wrappers up to X amount of args and probably just scream at the user if they try to bind beyond that.

    Hey, Adam.  I got slammed with work stuff yesterday and never had a chance to get back to this thread.  Sorry about that.  So this isn't the demo I wanted to write out but since you're in a BFH this is a quick and dirty version and I think it probably performs better than using Generics.

    using System;
    using System.Collections.Generic;
    using System.Text;
    
    namespace csConsoleApp
    {
    
        class Program
        {
            // Functions and Instances here are static because this is done in a console app
    
            public delegate void integer_func_delegate(int some_integer);
            public static event integer_func_delegate integer_func_event;
    
            public static void integer_func_event_handler_bridge(int some_integer)
            {
                if (integer_func_object_event != null)
                {
                    integer_func_object_event(new object[] { some_integer });
                }
            }
    
            public delegate void integer_func_object_delegate( object[] args );
            // Your consumers subscribe to this
            public static event integer_func_object_delegate integer_func_object_event;
            
            
            private static void regularHandler(int arg)
            {
                Console.WriteLine(@"regularHandler Arg was " + arg.ToString());
            }
    
            private static void objectHandler(object[] args)
            {
                int arg = 0;

                if ((args.Length < 1))
                {
                    throw new ArgumentException(@"objectHandler expected an array with at least 1 integer item at element 0");
                }
                // I think Python may pass strings instead of ints depending on Py version.  I don't know where you're raising the event from.
                else if (args[0].GetType() == typeof(string))
                {
                    // If this if doesn't throw an exception then arg should contain the parsed int value later on.
                    if (!int.TryParse((string)args[0], out arg))
                    {
                        throw new ArgumentException(@"objectHandler expected an array with at least 1 integer item at element 0");
                    }
                }
                else if (args[0].GetType() == typeof(int))
                {
                    arg = (int)args[0];
                }
                else
                {
                    throw new ArgumentException(@"objectHandler expected an array with at least 1 integer item at element 0");
                }

                Console.WriteLine(@"objectHandler Arg was " + (arg.ToString()));
            } static void Main(string[] args) { integer_func_event += integer_func_event_handler_bridge; integer_func_event += regularHandler; integer_func_object_event += objectHandler; integer_func_event(42); } } }



    • Edited by Andrew B. Painter Saturday, February 22, 2020 6:15 PM rewrote the objectHandler
    Saturday, February 22, 2020 6:04 PM