Generate Python Wrapper For C# Methods Using Reflection

When you need to do the same thing over and over again then stop doing it manually. Automate it or generate a script (it depends exactly what you're working on) that automates the process for you. In this article, I am going to share and talk about something similar.

We will generate a Python wrapper around C# methods using reflection methods of .NET framework.  But, before demonstrating this solution, we will analyze the "actual need" of writing the Python wrapper and why the heck C# is depending upon something else to complete a job.

Probably, you have got an idea what should be the prerequisites of this article, but if you haven't yet, then, read them below.

Prerequisites

I tend to write quite straightforward articles (only when it comes to programming), and the only prerequisite is to understand the problem or need of this topic before looking into its solution or copying and pasting the code which I’ll be sharing here.  

IronPython as a bridge between C# and RIDE 

There could be many scenarios when you will need to wrap the actual logic written in C# using any bridge framework and make C# methods, classes, and other pieces of your code understandable for the entirely different environment. Months ago, I needed to use CLR as a bridge between C# and C++ to make them know and understand each other. It was a desktop-based chat application for a company's internal use whose server was written in C++ and the client in C# (WPF). At that point, there were many things of C++ which were needed to get wrapped in CLR because both languages differ quite a lot from each other (C# is purely managed language and C++ is known as unmanaged one). At that point, I understood the concept of wrapping one language, i.e., using some other language for another language. Ahh, quite clumsy to visualize it. See the diagram below.
C#

The above diagram shows that C# doesn’t know RIDE scripts and so does the RIDE script which doesn’t know C#. They both have totally different syntax and structure but they both know IronPython and that’s the middle source which will help these two to interact and get familiar with each other. 

After a few months, during some other project, I needed to make my RIDE scripts understand C#, which wasn’t possible without using a common framework which they both know. In this case, IronPython did the job. No matter how much complex and logical your C# methods are, it will easily be understood by your automation scripts using a Python wrapper using IronPython. Since it is just a wrapper, it wraps the things either simplest or the most complicated ones.

Referring the above figure, your automation RIDE scripts call C# methods but for the cause, it doesn’t understand C#, it will interact with Python which has wrapped these C# methods. (Both, the RIDE scripts and C# interact via Python code, thus we are using Python as the bridge.)

Here, I have two methods written in C#.

  1. class wrapper {  
  2.         public static void sayHello(int message)  
  3.         {  
  4.             Console.WriteLine("Say Hello to the outside World!");  
  5.         }  
  6.   
  7.         public static bool getInfo(string greeting, int age, float price)  
  8.         {  
  9.             Console.WriteLine($"Greetings= {greeting}, Age= {age},  Price={price}");  
  10.             return true;  
  11.         }  
  12.   
  13. }  

Looking at these two methods, you might be thinking they are very simple and subjective to what we tend to achieve directly; then instead of doing all the hassle of writing the wrapper (importing C# classes and assemblies into python project and many many more things), isn’t it better to just write the same logic again in the Python to use and get the required functionality in the RIDE Automation scripts, right?

 Like following.

  1. def ipsayHello(message)   
  2.        print(message)  
  3.   
  4. def ipgetInfo(greeting, age, price):  
  5.       print("greeting =", greeting, "age =", age, "price =", price)  

Things are good by now, but any guess what will you do if you have some functions which do tedious and linked jobs in integration with other classes present in different layers of your project? Would you write the same code ‘As it is’ again in Python to make these all methods reachable to your automation scripts (RobotFramework in our case)? Of course, you won't ever do that. That’s the reason we write Python wrapper just to wrap whatever is written in the C# code. I hope I can make things visually clear to you. 

In the diagram below, you can see the example of some cumbersome and dependent function calls which need to be wrapped before saying hello to the world of automation.
structure  
In the above type of structure (which is most common in the large projects), the single function call is dependent and linked with so many internal calls; so now, you tell me isn’t it better to write just the wrapper using Python instead of these all logic stuff itself?

Python wrapper and C# methods

I will not provide the coverage of deeper level details here but to learn the ground concepts, let's go through a quick exercise in which I’ll make a C# project, then will write a Python script to import the DLL and Classes, and call the methods of it. 

Here, we have a ClassLibrary called DLL.dll, in which we have a class called wrapper that has multiple static methods inside it to be called in the Python project.

To begin the things, we will import the C# DLL into our Python file and then, we will import the required classes (it’s better to use a single interface class instead of importing multiple classes in the python code, I’ll talk about it momentarily) .

  1. using System;  
  2. namespace DLL  
  3. {  
  4.     class wrapper  
  5.     {  
  6.         public static string method1()  
  7.         {  
  8.             return "This method returns string, directly or maybe after some manipulations.";  
  9.         }  
  10.         public static int method2()  
  11.         {  
  12.             Console.WriteLine("I am Int, how someone can forget including me in in any sample application ^_^");  
  13.             return 1 * 2 * 3 * 4 * 5;  
  14.         }  
  15.         public static double method3()  
  16.         {  
  17.             Console.WriteLine("Trust me, I am frequetly useable but in complex calculations you need me always ^_^");  
  18.             return (0.1 * 5) + (8 / 9) - (34 ^ 2) * 0.01;  
  19.         }  
  20.     }  
  21.   
  22. import clr  
  23. #Adding reference to class library!  
  24. clr.AddReference('DLL.dll')  
  25.  
  26. #importing specific class from this namespace, here!  
  27. from DLL import wrapper  
  28.  
  29. # calling functions using class name, since these all are static!  
  30. def pmethod1():  
  31.      str = wrapper.method1()  
  32.   
  33. def pmethod2():  
  34.      inte = wrapper.method2()  
  35.   
  36. def pmethod3():  
  37.     doub= wrapper.method3()  

Generate a Python Wrapper using Reflection in C#

Here is the code which will generate a Python wrapper for the C# methods. Here, I have used reflection for this purpose. First, look at the code then we will talk a bit deeper chunk by chunk.

  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Diagnostics;  
  4. using System.IO;  
  5. using System.Linq;  
  6. using System.Text;  
  7.   
  8. namespace GeneratePythonWrapper  
  9. {  
  10.     class Program  
  11.     {  
  12.   
  13.   
  14.         static void Main(string[] args)  
  15.         {  
  16.             writeType(typeof(Helpers));  
  17.             Console.WriteLine("Done.");  
  18.        
  19.             Console.Read();  
  20.         }  
  21.   
  22.         private static string wrapperName = "wrapper";  
  23.         private static string fileName = @"GeneratedScript.py";  
  24.   
  25.         private static void writeType(Type type)  
  26.         {  
  27.             using (var fileStream = new FileStream(fileName, FileMode.OpenOrCreate))  
  28.             {  
  29.   
  30.                 //var newLine = ToBytes("\r\n");  
  31.                 fileStream.SetLength(0);  
  32.                 fileStream.Flush();  
  33.                 var functions = type.GetMethods();  
  34.                 foreach (var function in functions)  
  35.                 {  
  36.                     int indent = 0;  
  37.                     if (function.IsStatic)  
  38.                     {  
  39.                         var name = $@"def ip{function.Name}(";  
  40.                         fileStream.Write(ToBytes(name), 0, ToBytes(name).Length);  
  41.                         string _parameter = "";  
  42.                         var parameters = function.GetParameters();  
  43.                         foreach (var parameter in parameters)  
  44.                         {  
  45.                             if (parameter.ParameterType == typeof(string) || parameter.ParameterType == typeof(int) || parameter.ParameterType == typeof(float))  
  46.                             {  
  47.                                 // String parameter  
  48.                                 _parameter += parameter.Name;  
  49.                             }  
  50.                             else if (parameter.ParameterType == typeof(string[]) || parameter.ParameterType == typeof(List<string>))  
  51.                             {  
  52.                                 _parameter += "*" + parameter.Name;  
  53.                             }  
  54.   
  55.                             if (parameter != parameters.Last())  
  56.                             {  
  57.                                 _parameter += ",";  
  58.                             }  
  59.                             else  
  60.                             {  
  61.                                 _parameter += "):" + Environment.NewLine;  
  62.                             }  
  63.                         }  
  64.   
  65.                         fileStream.Write(ToBytes(_parameter), 0, ToBytes(_parameter).Length);  
  66.                         indent = 4;  
  67.                         fileStream.Write(ToBytes("    "), 0, ToBytes("    ").Length);  
  68.                         var call = $"{wrapperName}.{function.Name}({_parameter.Replace("):" + Environment.NewLine, "")})" +  
  69.                                     Environment.NewLine;  
  70.                         fileStream.Write(ToBytes(call), 0, ToBytes(call).Length);  
  71.                     }  
  72.   
  73.                 }  
  74.             }  
  75.         }  
  76.   
  77.         private static byte[] ToBytes(string data)  
  78.         {  
  79.             return Encoding.ASCII.GetBytes(data);  
  80.         }  
  81.     }  
  82.   
  83.     public class Helpers  
  84.     {  
  85.         public static void sayHello(int message)  
  86.         {  
  87.             Console.WriteLine("Say Hello to the outside World!");  
  88.         }  
  89.   
  90.         public static bool getInfo(string greeting, int age, float price)  
  91.         {  
  92.             Console.WriteLine($"Greetings= {greeting}, Age= {age}, Price={price}");  
  93.             return true;  
  94.         }  
  95.   
  96.         public static string greetPerson(string[] name)  
  97.         {  
  98.             return "Hello, " + name + "!";  
  99.         }  
  100.     }  
  101. }  

Instead of doing things statistically using the techniques of parsing data and so many other ways, we can use reflection. Reflection makes the things way too dynamic and helps us decide what to do at the runtime. For instance, once we can know the typeof of any class and then use .NET capabilities of reflection, we can know its properties, methods, and other information at the runtime without hard-coding the logic individually. Since Reflection makes thing much general, it is not recommended all the time but when you know the needs exactly.

Here, in our example (in the code above), we have passed the typeof our Helper class to the WriteType method. On the function call, it will have the type of this class and then, we can manipulate and navigate through its properties and methods quite efficiently.

The WriteType method is the core method which is doing the actual job of generating a python wrapper. It accepts the type of the Helper Class and inside the function, it is manipulating methods of this class one by one as we want it to. You can know every detail which is associated with these methods of a class like their return type, passed arguments, nature, and scope of the functions etc. To make things clear and less complicated for this demo I have kept these things limited here.

After performing the basic files stream operations we have got all the methods of the Helper class using type.GetMethods() and then we have iterated through each function in a way that at first, we are checking either the function is an instance function or static, then on the basis of its return type and the number of argument and types of argument, we are forming the Python script to write in the file we have created at the start of this WriteType method. We have appended ip to differ the function name in the python wrapper than the actual C# method.

Enclosing Thoughts

This article will not be too helpful to those who aren’t very familiar with Automation. The concepts and specifically, terms like IronPython, RobotFramework, RIDE etc. are not widely known but being used for years in the domain of Automation.

So If you’re up to automate any application (either desktop, mobile or web) and your platform and language choice is somehow related to .NET and python then this article will suit your requirement up to much-extended level. Though the scope of the sample code I have written here to generate the Python script is not broad enough to cover all the bits and pieces you would want to consider and cover while writing your generator, but the basic idea is to highlight a solution to make automation a smart automation.

X

Build smarter apps with Machine Learning, Bots, Cognitive Services - Start free.

Start Learning Now