12

I have a Python backend running machine learning algorithms. I want to use the same backend for both an Excel plugin (C#) and a website. I want both interfaces to send my training data (thousands of lines of numbers in arrays) to the same Python application and retrieve the results in the form of another array up to a few thousand lines.

The website would fetch data from a SQL database and send that data to Python, while the Excel plugin would take the data that is in the current worksheet and send that data to Python. I need to be able to create numpy arrays in Python before continuing to process the data. Note that the website would be running on the same machine where the Python application resides. I still haven't decided what I will use to code the website, but I was leaning towards Node.js.

I have done some research and found a few options:

1- Named pipes
2- Sockets
3- RPC server such as gRPC or XML-RPC.
4- Writing the data to a file and reading it back in Python
5- Web Service

Note: I would need the Python "server" to be stateful and keep the session running between calls. So I would need to have a kind of daemon running, waiting for calls.

Which one would you experts recommend and why? I need flexibility to handle several parameters and also large arrays of numbers. Using IronPython is not an option because I am running Keras on Python, which apparently does not support IronPython.

denfromufa
  • 5,610
  • 13
  • 81
  • 138
Vincent L
  • 699
  • 2
  • 11
  • 25

5 Answers5

6

I had the same problem recently. I used a named pipe to transport data from python to my c# server, hope it helps you.

Python:

import win32pipe, win32file

class PipeServer():
    def __init__(self, pipeName):
        self.pipe = win32pipe.CreateNamedPipe(
        r'\\.\pipe\\'+pipeName,
        win32pipe.PIPE_ACCESS_OUTBOUND,
        win32pipe.PIPE_TYPE_MESSAGE | win32pipe.PIPE_READMODE_MESSAGE | win32pipe.PIPE_WAIT,
        1, 65536, 65536,
        0,
        None)
    
    #Carefull, this blocks until a connection is established
    def connect(self):
        win32pipe.ConnectNamedPipe(self.pipe, None)
    
    #Message without tailing '\n'
    def write(self, message):
        win32file.WriteFile(self.pipe, message.encode()+b'\n')

    def close(self):
        win32file.CloseHandle(self.pipe)


t = PipeServer("CSServer")
t.connect()
t.write("Hello from Python :)")
t.write("Closing now...")
t.close()

For this code to work you need to install pywin32 (best choice is from binarys): https://github.com/mhammond/pywin32

C#-Server:

using System;
using System.IO;
using System.IO.Pipes;

class PipeClient
{
    static void Main(string[] args)
    {
        using (NamedPipeClientStream pipeClient =
            new NamedPipeClientStream(".", "CSServer", PipeDirection.In))
        {

            // Connect to the pipe or wait until the pipe is available.
            Console.Write("Attempting to connect to pipe...");
            pipeClient.Connect();

            Console.WriteLine("Connected to pipe.");
            Console.WriteLine("There are currently {0} pipe server instances open.",
               pipeClient.NumberOfServerInstances);
            using (StreamReader sr = new StreamReader(pipeClient))
            {
                // Display the read text to the console
                string temp;
                while ((temp = sr.ReadLine()) != null)
                {
                    Console.WriteLine("Received from server: {0}", temp);
                }
            }
        }
        Console.Write("Press Enter to continue...");
        Console.ReadLine();
    }
}
Coronon
  • 327
  • 5
  • 13
  • The pipe name most be equal the NamedPipeClientStream and in the win32Pipe.CreateNamedPipe – José González Aug 06 '21 at 19:16
  • Absolutely amazing answer, works pretty much right out of the box! Only problem was I got some cryptic win32 exception about "parameter is incorrect" until I removed the log statement with pipeClient.NumberOfServerInstances – pete Jan 17 '22 at 03:40
  • Is it possible for the client to send info or args to the server to influence what the server sends back? – pete Jan 17 '22 at 04:43
  • I found the answer to my question in another stackoverflow answer. We need to specify PipeDirection.InOut as well as, on the server side, specify the pipe to allow read and write. Then, something like: StreamWriter sw = new StreamWriter(pipeClient); sw.WriteLine("Waiting asdf"); sw.Flush(); pipeClient.WaitForPipeDrain(); – pete Jan 17 '22 at 07:06
3

You can use Python for .NET (Python.NET). It may require some changes to your code, but then it should work very well, once everything is in good shape.

Python.NET allows two-way communication between CPython and CLR.

denfromufa
  • 5,610
  • 13
  • 81
  • 138
Pacific Bird
  • 286
  • 1
  • 3
  • 13
  • 1
    Thanks! I figured if IronPython was not supported by Keras (the machine learning framework I am using), that Python for .NET wouldn't either. You seem to be doing some Neural Network stuff as well, have you ever tried Python for .NET with Keras/Tensorflow? It seems like the latest version is for Python 2.7. – Vincent L Mar 21 '17 at 23:08
  • @VincentLavoie No I haven't!. I'm actually programming the whole program in Java with Processing. I'm kinda a noob with non-C based languages. If that doesn't work than you could try IPC connections. I recommend [this post](http://stackoverflow.com/questions/23374854/simplest-way-to-communicate-between-python-and-c-sharp-using-ipc) to help you get started with these. Though I don't personally know much about them so some research may be required on your end. – Pacific Bird Mar 21 '17 at 23:16
  • 1
    After reading on Python for .NET, it seems to be for people to use CLR .NET objects in Python, which is not what I need right now. I need to be able to call Python scripts from C# and passing arrays and other values to the Python script. Maybe I didn't understand the way it works? Documentation is not very extensive and development seems to have stopped a long time ago. – Vincent L Mar 22 '17 at 01:15
  • Check out those IPC connections that I told you about in my last post. They seem to be what you need. The post linked asked almost your exact question and got a better answer than mine. – Pacific Bird Mar 22 '17 at 13:27
  • You are giving 5 year old link to Python.NET project. Python.NET allows bi-directional interop. The active development is in https://github.com/pythonnet/pythonnet – denfromufa Mar 26 '17 at 02:45
  • @denfromufa thank you! I found this link by some quick research and I did not know this was not the current link. Thank you for pointing that out! – Pacific Bird Mar 26 '17 at 03:12
1

Let me give you a neat and quick recipe, in the form of example code.

There are basically two ways to tie python in the backend of C# (or a C# winform app or gui or something similar).

Method1: Iron Python. In this method you install a .net package in your visual studio called IronPython. I would not prefer this, because assuming your machine learning model uses keras or a lot of other libraries. It would be another quest to get you installations ready and working in IronPython. And most importantly, it is not as good as your common virtual env or conda environment.

Method2: (The Good Method): Create a Custom Process in your C# that takes arguments from your GUI, knows the path to your script and your python env. Using all these things, it calls your python code exactly the way you would call it in your terminal and pass arguments to it.

Now the tasty example code (I have used this simple trick and it always helps make my black screen python stuff look good with the cover of C# apps).

Python Part

import sys
a = sys.argv[1]
b = sys.argv[2] 
print("The Sum = ", float(a)+float(b))

The C# Part So here is the python process/function that you need to call on the click event of your sum button in the application

static void PythonProcess()
        {
            //1) Create Process Info
            var psi = new ProcessStartInfo();
            
            //Conda Env Path
            psi.FileName = @"C:\Users\jd\.conda\pkgs\py\python.exe";

            //2) Provide Script and the Arguments
            var script = @"C:\Users\jd\Desktop\script.py";
            var a = "15";
            var b = "18";

            psi.Arguments = $"\"{script}\" \"{a}\" \"{b}\"";

            //3) Process Configuration
            psi.UseShellExecute = false; 
            psi.CreateNoWindow = true;
            psi.RedirectStandardOutput = true;
            psi.RedirectStandardError = true;

            //4) Execute Process and get Output.
            var errors = "";
            var results = "";

            using(var process = Process.Start(psi))
            {
                errors = process.StandardError.ReadToEnd();
                results = process.StandardOutput.ReadToEnd();
            }

            //5) Display Output
            Console.WriteLine("ERRORS: ");
            Console.WriteLine(errors);
            Console.WriteLine();
            Console.WriteLine("RESULTS: ");
            Console.WriteLine(results);
        }
Janzaib M Baloch
  • 1,305
  • 2
  • 5
  • 8
0

Calling Python from C# is easily possible via Pyrolite where your Python code is running as a Pyro4 server. It should be fast enough to handle "large arrays of numbers" however you didn't specify any performance constraints.

Irmen de Jong
  • 2,739
  • 1
  • 14
  • 26
0

I had the same issue and seem to end up with named pipes. Here is a nice example of how to set it up to talk C# => Python, assuming C# is the server. It can use the same way to talk back or just Python.net to call directly through CLR as shown here. I use the latter.

vlad
  • 184
  • 2
  • 5