1

I have a .net assembly providing a huge number of API's and I need to write a COM wrapper for it . I can not write interfaces to each and every method and property provided my the .net assembly due to time constraints. Instead What i plan to do is to write a generic commandParser function which takes the string argument giving the full path (?) of the property or method being referred and in turn the method has to call the correct property or method . e.g suppose i need to set the property Visible , i would pass the string as below (refer image also for the class structure) or if need to call a method

"APx.AcousticResponse.AcquiredWaveform.DeltaCursor.Visible"

or

"APx.BandpassLevel.FixedTuningFrequency.GetValue(Unit)"**

Obviously i am clueless , I know using reflections it is possible but i am not at there yet.Here is the dummy code i have been working so far with out any success :P

public bool methodFromString(string methodName,object [] parameteres)
            {
                string [] split = methodName.Split(new char []{'.'});
                apx = new APx500();
                Type wrapType = apx .GetType();
                FieldInfo[] fields = wrapType.GetFields();
                MemberInfo[] members = wrapType.GetMembers();
                Console.WriteLine(members.Length);
                MethodInfo [] methods = wrapType.GetMethods();
                foreach (MemberInfo mem in members)
                {
                    //Console.WriteLine(mem.Name);
                    if(mem.Name == split[0])
                    {
                        Console.WriteLine(mem.Name);
                        Type memType = mem.GetType();
                        MethodInfo[] temp = memType.GetMethods();
                        foreach (MethodInfo t in temp)
                        {

                            Console.WriteLine(memType.Name);
                        }
                    }
                    //if (met.Name == methodName)
                    //{
                    //    try
                    //    {
                    //        met.Invoke(APx, parameteres);
                    //        break;
                    //    }
                    //    catch (TargetInvocationException ex)
                    //    {
                    //        Console.WriteLine(ex.Message);
                    //        Console.WriteLine(ex.InnerException);
                    //        break;
                    //    }
                    //}
                }


               // MethodInfo theMethod = wrapType.GetMethod(methodName);
                //theMethod.Invoke(APx, parameteres);
                //wrapType.InvokeMember(methodName,
                //    BindingFlags.InvokeMethod | BindingFlags.Public |
                //        BindingFlags.Static,
                //    null,
                //    null,
                //    parameteres);

                return true;
            }

Any hints or help is much appreciated

svick
  • 236,525
  • 50
  • 385
  • 514
Sanju
  • 1,974
  • 1
  • 18
  • 33
  • May be usefull: http://stackoverflow.com/a/6891806/891715 http://stackoverflow.com/a/14479167/891715 http://stackoverflow.com/a/1138426/891715 – Arie Sep 20 '13 at 06:51

1 Answers1

1

The following piece of code might be somewhat long but I certainly do think that it does all that you have requested.

Let's start with the DynamicInvoke class.

public class DynamicInvoke
{
    List<object> Parameters { get; set; }
    List<string> Paths { get; set; }
    object Instance { get; set; }
    Type Type { get; set; }

    public DynamicInvoke(string path, params object[] parameters)
    {
        Parameters = parameters.ToList();

        Paths = path.Split('+').ToList();

        Type = AppDomain.CurrentDomain
                        .GetAssemblies()
                        .Where(x => x.GetName().Name == Paths[0])
                        .First()
                        .GetTypes()
                        .Where(x => x.FullName == Paths[1])
                        .First();

        Instance = Activator.CreateInstance(Type, Parameters.ToArray());
    }

    public T DynamicPropertyGet<T>()
    { 
        return (T)Type.GetProperty(Paths[2]).GetValue(Instance, null);            
    }

    public void DynamicPropertySet(object value)
    {
        Type.GetProperty(Paths[2]).SetValue(Instance, value, null);
    }

    public T DynamicMethodInvoke<T>(params object[] parameters)
    { 
        return (T)Type.GetMethods()
                      .Where(x => x.Name == Paths[2] && AreAllEqual(x, parameters))                          
                      .First()
                      .Invoke(Instance, parameters);
    }

    bool AreAllEqual(MethodInfo method, params object[] parameters)
    {
        var p1 = method.GetParameters().Select(x => x.ParameterType);
        var p2 = parameters.Select(x => x.GetType());

        var except1 = p1.Except(p2).ToList().Count;
        var except2 = p2.Except(p1).ToList().Count;

        return (except1 > 0 || except2 > 0) ? false : true;
    }
}

As you can see, it contains four properties which will hold all the relevant values we will need to invoke the required properties and methods.

In its constructor you simply pass as its first parameter the path to the property or the method you need to invoke. The rest are the required parameters that you might need to instantiate the type on which the property or the method will be invoked. The constructor will then parse the path parameter and provide you with the appropriate instance.

You can then either invoke the DynamicPropertyGet() method to get the value from some property, DynamicPropertySet(object) method to set the value of some property, or the DynamicMethodInvoke(params object[]) to invoke a method. The generic parameters are used to designate the types of the instances that will be returned.

The AreAllEqual(MethodInfo, params object[]) method is simply here to decide which overloaded method to invoke, depending on the parameter count and type.

The following is our test class. You can notice that it defines both properties and overloaded methods.

class People
{
    public string Name { get; set; }

    public People()
    {
        Name = "Billy";
    }

    public People(string name)
    {
        Name = name;
    }

    public string CallMe()
    {
        return Name;
    }

    public string CallMe(string value)
    {
        return value;
    }

    public void NoReturn()
    {
        Console.WriteLine("nothing");
    }
}

You can now test this approach with the following lines of code.

class Program
{
    static void Main()
    {
        var path = "Types+Types.People+Name";
        var path2 = "Types+Types.People+CallMe";
        var path3 = "Types+Types.People+NoReturn";

        var instance1 = new DynamicInvoke(path);
        var instance2 = new DynamicInvoke(path, "Jill");
        var instance3 = new DynamicInvoke(path2);
        var instance4 = new DynamicInvoke(path2, "Johnny");
        var instance5 = new DynamicInvoke(path3);

        instance1.DynamicPropertySet("Tom");

        sc.WriteLine(instance1.DynamicPropertyGet<string>());
        sc.WriteLine(instance2.DynamicPropertyGet<string>());

        sc.WriteLine(instance3.DynamicMethodInvoke<string>());
        sc.WriteLine(instance4.DynamicMethodInvoke<string>("Timmy"));

        instance5.DynamicMethodInvoke<object>();

        Console.Read();
    }
}

The paths to the properties and methods are split in three parts using the "+" sign. The 1st part is the name of the assembly you want to use. the 2nd part is the full name of the type you will instantiate. While the 3rd part is the name of the method or the property you want to invoke.

You should also notice that each instance variable holds an instance of your type specified in the path and you can modify its properties multiple times by simply invoking the above mentioned methods.

EDIT: Inner property access example.

The following is a situation that you seem to be dealing with.

class University
{
    public Faculty Faculty { get; set; }

    public University()
    {
        Faculty = new Faculty();
    }
}

class Faculty
{
    public string Name { get; set; }

    public Faculty()
    {
        Name = "MIT";
    }
}

Let's say you have a University class, and you have a Faculty class. You can see that you have a Faculty property, of type Faculty, defined in the University class. You can also notice that the Faculty class has a Name property. This property represents your "AnalyzeFilesFixe‌​dSampleRate" property of type double.

To reach this property, you simply have to execute the following lines of code.

var path = "Types+Types.University+Faculty";

var instance = new DynamicInvoke(path);

Consolec.WriteLine(instance.DynamicPropertyGet<Faculty>().Name);
Mario Stopfer
  • 541
  • 3
  • 8
  • Hey Mario, Thanks for the hints .If i am not wrong your approach creates individual instances for each path (Correct me if i am wrong). In my case i will he having an instance of class already (which implements a huge number of interfaces) and i would like to invoke a particular method or set/get a particular property in this object using the strings i pass . – Sanju Sep 21 '13 at 08:05
  • I tried to Create a instance by passing the string as you explained but i get an error "Constructor on type 'AudioPrecision.API.APx500' not found.". My string parameter is as below var path = "AudioPrecision.API+AudioPrecision.API.APx500+ActiveMeasurementIndex"; I can create the instance of the object APx500 directly using AudioPrecision.API.APx500 apx = new AudioPrecision.API.APx500(); and can access the property "ActiveMeasurementIndex" using apx.ActiveMeasurementIndex; – Sanju Sep 21 '13 at 08:09
  • Ok, atleast i was able to get rid of the exception i mentioned in the above comment ,using 'var instance1 = new DynamicInvoke(path,(object)null);' Now i can access the properties immediatly defined in the class APx500 but not the properties and methods defined in the interfaces implemented by the Class APx500. Eg.' var path = "AudioPrecision.API+AudioPrecision.API.APx500+ActiveMeasurementIndex"; ' works but 'var path = "AudioPrecision.API+AudioPrecision.API.APx500.ActiveMeasurement+AnalyzeFilesFixedSampleRate"; 'does not – Sanju Sep 21 '13 at 08:20
  • What exactly is "ActiveMeasurement"? Is it a type defined within "APx500" class or a property? – Mario Stopfer Sep 21 '13 at 15:01
  • Its public property implementation of an Interface class having some more properties and methods and AnalyzeFilesFixe‌​dSampleRate" is a public property of type Double with in this interface. Sample code is APx.ActiveMeasurement.AnalyzeFilesFixedSampleRate – Sanju Sep 22 '13 at 05:48
  • If it is a property then this path, "AudioPrecision.API+AudioPrecision.API.APx500.ActiveMeasurement+AnalyzeFilesFixe‌​dSampleRate", that you mentioned, will not work because you placed "ActiveMeasurement" within the type part. You should place it after the second "+" to indicate that it is a property. – Mario Stopfer Sep 22 '13 at 13:23
  • But since you also have an inner property, you will have to do it this way. Your first path will now be "AudioPrecision.API+AudioPrecision.API.APx500+ActiveMeasurement". Now that you have the instance of the "APx500" class, simply access its "ActiveMeasurement" as you did before and close the Generic type T in the DynamicPropertyGet() method with the type of "ActiveMeasurement" type. After you have done so, this method will return an instance of that type and you will be able to access the "AnalyzeFilesFixe‌​dSampleRate" property from it. I have also edited my answer with an example. – Mario Stopfer Sep 22 '13 at 13:24
  • Thanks for the hints Mario, I see your soultions is in general an answer to the problem i have but ,there were multiple things that i had to take care ,Based on your ideas ,i came up with my own solutions without using the generic part . – Sanju Oct 01 '13 at 09:41