How to build a distributable .Net application that runs your CPython scripts

Al-Gassim Sharaf Addin
10 min readOct 21, 2020

Facts you should know before you get started:

  • Although IronPython works well with .Net Framework, its library is limited compared to the CPython library. Furthermore, many Python packages are not supported by IronPython.
  • Latest Visual Studio supports only certain CPython versions. Moreover, Visual Studio doesn’t build UI with Python nor does it create stand-alone executables for Python. However, using this method you’re going to be able to build .Net applications and User Interfaces with all the .Net features as well as distribute your application.
  • This solution is important because Python packages must be installed locally using pip install; therefore, when distributing your Python application it will fail if users don’t have the packages installed in their Python environment. Furthermore, although there are tools such as pynsist which are supposed to bundle everything for a distributable python application this method doesn’t work well with all packages.

In this demonstration, I’m going to use the C# programming language in Visual Studio, but the idea should work in any of the .Net Windows platform applications. Furthermore, this method can work with WinForms and WPF (Windows Presentation Foundations) if you want to create stunning Windows graphical user interface for your Python project.

In this article, I’ll use a WPF project in Visual Studio using C# to demonstrate how the CPython script results can be displayed in WPF Windows, so if you decide to use Console or any other .Net Windows project, there will have to be some changes in the code as well as references. For example, Console doesn’t use Windows UI so you’re going to have to display the results in the Console rather than a textbox.

Furthermore, in this project, we’re dealing with string input and output and converting it to int to perform simple integer addition in the Python script only for the sake of making this starting project easy, but in more complicated projects, conversion between datatypes including binary is required.

Step One: Visual Studio Solution

First, create a new project in Visual Studio:

  • Run your visual studio application
  • Then on the left side of the window under the Get Started column, click on Create a New Project
  • Select WPF App (.Net Framework) then click on Next
  • Click Create after you enter the project name

First of all, a C# class must be created to prepare the environment and it can be initialized in the startup file because it must make sure that the Python environment is ready. Therefore, each time the application is launched, the class checks if the Python environment is not ready, which is going to be the case during the first launch, then if the Python environment is not ready the class prepares the environment.

Second, create the PythonInstallation class for the Python installation:

  • In your Visual Studio solution, view the Solution Explorer. If the Solution Explorer is not on the screen, use Ctrl+Alt+L to view it.
  • Then in Solution Explorer right-click on your project name and click on “Add > New Item.” In the first column to the left click on Installed > Visual C# Item then choose Class from the column in the center of the window. Click Add after typing the class name (I chose the name PythonInstallation.cs).
  • First, get the path to where the Python environment should be. In this example, I prepare the Python environment in the base directory i.e. the place where the application executable file is.
string basePath = AppDomain.CurrentDomain.BaseDirectory;
  • The PythonInstallation class is going to have two methods. If the Python environment exists in the base directory, the first method returns true, but if it doesn’t exist it calls the second method which prepares the Python environment, and then when it is ready, it returns true for the application to start and continue its work.
  • First Method:
///<summary>
///returns true if PythonEnvironment is ready
///<para>if PythonEnvironment is not ready, it calls
///setpyEnvironment()</para>
/// </summary>
public bool SetupEnvironment()
{
if (!File.Exists(basePath + @"CPython\python.exe"))
{
return setpyEnvironment();
}
else if (File.Exists(basePath + @"CPython\python.exe"))
{
return true;
}
else
return false;
}
  • Second Method:
///<summary>
///Unzips the Python installation.
///Stops the application from running until
///the Python installation is ready.
///Uses (Tasks) and returns boolean
/// </summary>
private bool setpyEnvironment()
{
if (!Directory.Exists(Environment.ExpandEnvironmentVariables(basePath + @"CPython\")))
{
try
{
//Asks from the user to wait for Python packages to be copied
//before the application starts
MessageBox.Show("Please wait for the Python packages to be copied", "Python packages", MessageBoxButton.OK, MessageBoxImage.Information);
//Use tasks to make sure the Zipfile is properly extracted
Task unzipTask = new Task(() =>
{
try
{
var zipPath = @".\CPython\python.zip";
ZipFile.ExtractToDirectory(zipPath, basePath);
}
catch (Exception exp)
{
MessageBox.Show(exp.Message);
}
});
try
{
unzipTask.Start();
unzipTask.Wait();
if (unzipTask.IsCompleted)
{
//Notify the user that the Python environment is ready
MessageBox.Show("Python packages are ready", "Success!", MessageBoxButton.OK, MessageBoxImage.Information);
return true;
}
return true;
}
catch (Exception e)
{
if (unzipTask.IsCanceled == true || unzipTask.IsFaulted == true)
{
Directory.Delete(basePath + @"CPython\");
unzipTask.Dispose();
MessageBox.Show(unzipTask.Exception.Message, e.Message);
Application.Current.Shutdown();
return false;
}
else
return false;
}
}
catch (Exception exp)
{
MessageBox.Show(exp.Message + String.Format("{0}Please restart the software. Error in trying to copy the packages", Environment.NewLine), "Error!");
Application.Current.Shutdown();
return false;
}
}
else
return false;
}
  • Next, determine what startupfile the project is using by checking the StartupUri value in App.xml file from the Solution Explorer. Then go to this startup file and in the constructor initiate the PythonInstallation class.
PythonInstallation py = new PythonInstallation();
bool ready = py.SetupEnvironment();
if (ready = false)
{
Application.Current.Shutdown();
}

Third, create the CPythonScripts class:

The methods in the CPythonScripts differ in how the data is being sent and received to and from Python and C#.

  • First Method — no input and no output:
    The first method is simple, all we need to do is run the Python script, so the method will have one parameter which is for the path to the Python script.
  • Second Method — Input and no output:
    The second method is going to receive two parameters. The first parameter is the path to the Python script and the second parameter is the input to the Python script to receive. In this method, the script is supposed to be able to read input from the process. However, this method doesn’t need to return anything because the script has no output.
  • Third Method — no Input but there’s an output:
    This is when we run the Python script, it does what it’s supposed to do then it returns the result to the C#. This method has one parameter which is the path to the python script and it returns the output.
  • Fourth Method — Input and Output:
    This method has two parameters and returns the output. The first parameter is the path to the script file and the second parameter is the input.
  • The code is too long to paste it here. You can download the CPythonScript class from the Github repository.

Create a form in your WPF window to run the script:

It is possible now to expand this process and use it in so many ways including sending and receiving data from databases, handling files, and so on; however, to make the demonstration easy all is needed is a form to send and receive data from the Python script.

  • First, open your WPF XAML window and add two textboxes, a checkbox, and a button to browse and run the script. Like in the XAML code below:
<TextBox x:Name="txtBox" HorizontalAlignment="Left" Height="87" Margin="59,10,0,0" TextWrapping="Wrap" Text=""  VerticalAlignment="Top" Width="179"/>
<TextBox x:Name="txtBoxOutput" HorizontalAlignment="Left" Height="87" Margin="59,108,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="179"/>
<Button x:Name="btnRun" Content="Run script" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Margin="157,240,0,0" Click="btnRun_Click"/>
<CheckBox x:Name="chkBox" Content="Receive output" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="59,215,0,0"/>
<Label Content="Input" HorizontalAlignment="Left" Margin="10,47,0,0" VerticalAlignment="Top"/>
<Label Content="Output" HorizontalAlignment="Left" Margin="10,135,0,0" VerticalAlignment="Top"/>
  • The input textbox is to type the input for the python script, the output textbox is to display the result from the python script, and the checkbox is to check whether the script is supposed to return a result.
  • Finally, the button is to browse for the python script and call the proper method from the CPythonScripts class.

Add the C# code-behind for the WPF form:

  • If you noticed in the XAML code above the Button has a Click event property btnRun_Click which uses the following code in the code-behind:
RunPythonScript runPy = new RunPythonScript();
OpenFileDialog openFileDialog = new OpenFileDialog();
if (openFileDialog.ShowDialog() == true)
{
script = openFileDialog.FileName;
if (!script.EndsWith(".py"))
{
MessageBox.Show("Only select python scripts, please!");
}
}
if (script != "")
{
if (txtBox.Text != "")
{
if (chkBox.IsChecked.GetValueOrDefault())
{
txtBoxOutput.Text = runPy.PythonScriptIO(script, txtBox.Text);
}
else
{
if (runPy.PythonScriptInput(script, txtBox.Text))
{
txtBoxOutput.Text = "running script was successful";
}
}
}
else
{
if (chkBox.IsChecked.GetValueOrDefault())
{
txtBoxOutput.Text = runPy.PythonScriptOutput(script);
}
else
{
if (runPy.PythonScript(script))
{
txtBoxOutput.Text = "running script was successful";
}
}
}
}

Step Two: Python installation

Now that the code in the Visual Studio is ready to run python scripts. There are two things that need to be prepared outside of Visual Studio. The first is the Python environment which is going to be embedded into the Visual Studio solution. The second thing is the code that must be in the Python script to read input and to return output.

The Python Environment

  • Download the embeddable zip file of the CPython version that you are going to use (https://www.python.org/downloads/windows/)
  • Once the embeddable zip file has been downloaded. Extract the zip file to a new folder, and then in the extracted folder, you’re going to have to do two things:
  • The first is to uncompress the PythonXY.zip into the same folder, delete the zipped file (or rename it to PythonBackup.zip), and then rename the PythonXY folder to PythonXY.zip. This is important to be able to install packages, and because the Python executable recognizes this as “landmark file” instead of using the system’s environment variables.
  • The second is to edit the pythonXY._pth file in notepad, remove the hashcode ‘#’ from import site, and then save and close the file. This is because this file will enable the isolated mode, and adding “import site” will make Python search for its library (and your site-packages folder) in the same directory.
  • The third is to create a folder called Scripts in the same extracted folder. Which is necessary for pip to install packages.

Get PIP:

Dowload https://bootstrap.pypa.io/get-pip.py . Either by using a downloader or by going to this page and then in your browser look for the Save Page as control (which is different from browser to browser. In Chrome, click on the three vertical dots > Tools > Save Page as..) then save the get-pip.py the same directory as the Python.exe folder which we extracted.

Finally, you need to use the specific python which we downloaded to get pip to be able to install the packages into the embeddable Python installation.

  • First, copy the path of the extracted folder.
  • Open your Command Prompt (either by searching for it in the Windows start menu or by using the keyboard shortcut Windows Command Key + R then type cmd and press enter.)
  • Once in Command Prompt type in cd /D and paste the path you just copied then press enter. For example, if the extracted folder is in “D:\python-3.9.0-embed-amd64\” and the command prompt is in “C:\Users\Happy” then typing cd /D “D:\python-3.9.0-embed-amd64\” is going to take me to the extracted folder. This is an example code:
cd /D "D:\python-3.9.0-embed-amd64\"
  • Now that the cmd is in the same path as the Python.exe in the extracted folder. Type python get-pip.py

Install Python packages:

After preparing the paths, setting the library, installing pip, and creating the Scripts folder, you’re now going to be able to use pip to install packages to your embeddable Python installation:

  • Using the Command Prompt change the command directory to where the Python.exe folder is and then type cd Scripts
  • Once in the Scripts folder use the command
pip install →→→type$here$the$package$name$instead←←←

Compress the Python Folder

After installing all the packages that you need to use for your python scripts. Compress the folder that we extracted and make sure you rename it to python.zip which is the same name we used in the Visual Studio PythonInstallation class.

Add python.zip to your Visual Studio project

Go back to your Visual Studio project, and in the Solution Explorer, right-click on the project name > Add > New Folder > CPython. Then right-click on the CPython folder > Add > Existing Item … then browse for the python.zip file. Next, in the Solution Explorer select the python.zip file in the CPython folder, right-click and select properties. Change the build action into “Content” and the Copy to Output Directory “Copy if newer.” Does this mean your application will be able to update to a newer version of Python? Yes, but the same process in preparing the Python installation needs to be done. Additionally, this means we need to create a class for updates (but this is beyond the scope of this article.)

Step Three: Run the CPython scripts in your WPF C# application

Before you start running your CPython scripts in your WPF Window, you need to make sure to do the following:

  • Only use modules i.e. “import module” in your python script if you’ve already installed it into the embeddable Python installation. An easy way to make sure that it is working is to use the embeddable python we’ve been using in the command prompt changing the directory to where the python.exe is located and then enter python -c “import →→→yourmodulename←←←;” if it says module not found then you’ve not installed the package using pip properly.
  • To receive input from C# use:
input = sys.stdin.read()
  • To return output from Python use:
sys.stdout.write(→→→output←←←)
  • At this moment we’re dealing with strings only, and this means we’re going to have to convert the input and output into the required data type. For example, if we input a number ‘3’ it is going to be read as string, so when we want to add it to another number in python we’re going to have to use cast it into int. Similarly, if we want to return a number then we’re going to have to cast it into string.
x = sys.stdin.read()
add = int(x) + 3
sys.stdout.write(str(add))

Simple example:

Open notepad and insert the following code:

import sys
x = sys.stdin.read()
y = int(x)+3
sys.stdout.write(str(y))

Then save as pythonscript.py and make sure you change save as type to “All files”

Test this application:

Run your WPF windows application in your Visual Studio solution (Ctrl + F5) and in the form add to the input any number, press on the checkbox to receive the output, and click on the button Run Script. The dialog will open for you to browse to the directory where you saved the script, select the pythonscript.py, and click open. Immediately Python runs the script and the result appears in the output textbox.

This is a simple WPF application that runs a CPython script. In this video user inputs 5, checks the checkbox to receive output, and then clicks on the Run Script button. A dialog opens for the user to select the python script, and then immediately the output appears in the output textbox. 3+5 = 8, 8 is the output.

Link to my Github repository:

--

--

Al-Gassim Sharaf Addin

Graduate of Information Technology BS.c. I’m skilled in computers but I aim to become more and more professional in my IT skills as a developer.