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 on 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 the .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 of 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 an 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 a purely managed language, and C++ is known as an 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 that 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 that they both know. In this case, IronPython did the job. No matter how much complex and logical your C# methods are, they 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 the simplest or the most complicated ones.

Referring to the above figure, your automation RIDE scripts call C# methods but 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#.

using System;
class wrapper
{
    public static void sayHello(int message)
    {
        Console.WriteLine("Say Hello to the outside World!");
    }
    public static bool getInfo(string greeting, int age, float price)
    {
        Console.WriteLine($"Greetings = {greeting}, Age = {age}, Price = {price}");
        return true;
    }
}

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.

using System;
namespace YourNamespace
{
    class Program
    {
        static void ipsayHello(string message)
        {
            Console.WriteLine(message);
        }
        static void ipgetInfo(string greeting, int age, double price)
        {
            Console.WriteLine("greeting = " + greeting + " age = " + age + " price = " + price);
        }
        static void Main(string[] args)
        {
            ipsayHello("Hello, world!");
            ipgetInfo("Hi", 25, 19.99);
        }
    }
}

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 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 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 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).

using System;
namespace DLL
{
    class wrapper
    {
        public static string method1()
        {
            return "This method returns string, directly or maybe after some manipulations.";
        }
        public static int method2()
        {
            Console.WriteLine("I am Int, how someone can forget including me in any sample application ^_^");
            return 1 * 2 * 3 * 4 * 5;
        }
        public static double method3()
        {
            Console.WriteLine("Trust me, I am frequently useable but in complex calculations you need me always ^_^");
            return (0.1 * 5) + (8 / 9) - (34 ^ 2) * 0.01;
        }
    }
}
import clr
# Adding reference to class library!
clr.AddReference('DLL.dll')
# Importing specific class from this namespace, here!
from DLL import wrapper
# Calling functions using class name, since these all are static!
def pmethod1():
    str = wrapper.method1()
def pmethod2():
    inte = wrapper.method2()
def pmethod3():
    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

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
namespace GeneratePythonWrapper
{
    class Program
    {
        static void Main(string[] args)
        {
            writeType(typeof(Helpers));
            Console.WriteLine("Done.");
            Console.Read();
        }
        private static string wrapperName = "wrapper";
        private static string fileName = @"GeneratedScript.py";
        private static void writeType(Type type)
        {
            using (var fileStream = new FileStream(fileName, FileMode.OpenOrCreate))
            {
                fileStream.SetLength(0);
                fileStream.Flush();
                var functions = type.GetMethods();
                foreach (var function in functions)
                {
                    int indent = 0;
                    if (function.IsStatic)
                    {
                        var name = $"def ip{function.Name}(";
                        fileStream.Write(ToBytes(name), 0, ToBytes(name).Length);
                        string _parameter = "";
                        var parameters = function.GetParameters();
                        foreach (var parameter in parameters)
                        {
                            if (parameter.ParameterType == typeof(string) || parameter.ParameterType == typeof(int) || parameter.ParameterType == typeof(float))
                            {
                                _parameter += parameter.Name;
                            }
                            else if (parameter.ParameterType == typeof(string[]) || parameter.ParameterType == typeof(List<string>))
                            {
                                _parameter += "*" + parameter.Name;
                            }

                            if (parameter != parameters.Last())
                            {
                                _parameter += ",";
                            }
                            else
                            {
                                _parameter += "):" + Environment.NewLine;
                            }
                        }
                        fileStream.Write(ToBytes(_parameter), 0, ToBytes(_parameter).Length);
                        indent = 4;
                        fileStream.Write(ToBytes("    "), 0, ToBytes("    ").Length);
                        var call = $"{wrapperName}.{function.Name}({_parameter.Replace("):" + Environment.NewLine, "")})" +
                                   Environment.NewLine;

                        fileStream.Write(ToBytes(call), 0, ToBytes(call).Length);
                    }
                }
            }
        }

        private static byte[] ToBytes(string data)
        {
            return Encoding.ASCII.GetBytes(data);
        }
    }

    public class Helpers
    {
        public static void sayHello(int message)
        {
            Console.WriteLine("Say Hello to the outside World!");
        }
        public static bool getInfo(string greeting, int age, float price)
        {
            Console.WriteLine($"Greetings= {greeting}, Age= {age}, Price= {price}");
            return true;
        }
        public static string greetPerson(string[] name)
        {
            return "Hello, " + name

Instead of doing things statistically using the techniques of parsing data and so many other ways, we can use reflection. Reflection makes things way too dynamic and helps us decide what to do at the runtime. For instance, once we can know the type 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 things much more 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 WriteTypemethod is the core method that 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 the 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 file stream operations, we have got all the methods of the Helper class using type.GetMethods()and then we 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 arguments and types of argument. We are forming the Python script to write in the file we created at the start of thisWriteTypemethod. We have appended to differentiate the function name in the Python wrapper from 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 automating 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 a 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, the basic idea is to highlight a solution to make automation a smart automation.


Similar Articles