3
\$\begingroup\$

Below is code modified from the source code for python.exe. The motivation is to be able to build a suite of python applications that can be distributed with an installer, that share an isolated run-time, but run with their own executables so that they have proper taskbar integration, icons and task manager identifier names. The goal is to have an application suite distribution with a directory layout like this:

/application_suite
    /python_runtime
        python.exe
        python36.dll
        (and everything else in a python dir)
    /python_applications
    my_python_application1.exe
    my_python_application2.exe

To achieve this I needed executables that could run as python interpreters launching pre-determined applications but living outside the main python directory. As a packaging scheme this makes things very clean and keeps the run-time completely separate from the applications that I am distributing. An example source code for one of these executables is here:

// source code for custom my_python_application1
// requires _CRT_SECURE_NO_WARNINGS flag to compile deprecated path operations

#include "stdafx.h"
#include <string>
#include <sstream>
#include <iostream>
#include "Windows.h"
#include "Shlwapi.h"
// #include "Python.h"  // don't need this as we are dynamically loading the library of choice

#pragma comment(lib, "Shlwapi.lib")
__pragma(warning(disable:4996))  // # _CRT_SECURE_NO_DEPRECIATE

wchar_t SWITCH[] = L"-m";
wchar_t APP[] = L"my_python_application1.main";

typedef int(__stdcall *py_main_function)(int, wchar_t**);

int wmain(int argc, wchar_t **argv) {

    int newargc;
    newargc = argc + 2;

    // determine the path of the executable so we know the absolute path
    // of the python runtime and application directories
    wchar_t executable_dir[MAX_PATH];
    if (GetModuleFileName(NULL, executable_dir, MAX_PATH) == 0)
        return -1;
    PathRemoveFileSpec(executable_dir);
    std::wstring executable_dir_string(executable_dir);

    // now set the relevant environment variables so that the environment works as it is supposed to
    std::wstring python_home(L"PYTHONHOME=" + executable_dir_string + L"\\runtime");
    _wputenv(python_home.c_str());

    std::wstring python_path(L"PYTHONPATH=" + executable_dir_string + L"\\apps");
    _wputenv(python_path.c_str());

    // put the python runtime at the front of the path
    std::wstringstream ss;
    ss << "PATH=" << executable_dir << "\\runtime;" << getenv("PATH");
    std::wstring path_string (ss.str());
    _wputenv(path_string.c_str());

    wchar_t **newargv = new wchar_t*[newargc];
    newargv[0] = argv[0];
    newargv[1] = SWITCH;
    newargv[2] = APP;

    for (int i = 1; i < argc; i++) {
        newargv[i + 2] = argv[i];
    }

    // dynamically load the python dll
    std::wstring python_dll(executable_dir_string + L"\\runtime\\python36.dll");
    HINSTANCE hGetProcIDDLL = LoadLibrary(python_dll.c_str());
    py_main_function Py_Main = (py_main_function)GetProcAddress(hGetProcIDDLL, "Py_Main");

    //now call Py_Main with our arguments
    return Py_Main(newargc, newargv);
    // return Py_Main(argc, argv);

}

The main goals this code achieves are:

The requirements to achieve this behavior are the following:

  1. get the directory of the custom executable
  2. set the PYTHONHOME environment variable to %executable_dir%\runtime
  3. set the PYTHONPATH environment variable to %executable_dir%\apps so that python knows where our python packages are living. This also clears out any system wide settings so that the distribution doesn't use other python environment settings
  4. I don't know if it's necessary, but I am adding the runtime directory at the front of the path
  5. dynamically load the Py_Main function from the desired dll. Since we are not expecting the runtime to be on the path before execution, we must find the dll dynamically from %executable_dir%\runtime\python36.dll.
  6. Allows us to launch our application with files directly as command line arguments so we can set "open this file with" this application type of options.

I am very new to C/C++ programming. This code was built using Visual Studio 2017. Any feedback about the code, comments about best practices, whether or not the same code can be built with MinGW, or any other comments about important issues would be very much appreciated.

\$\endgroup\$
  • \$\begingroup\$ Hav you tried pyinstaller or some other tool for making EXE from Python code? \$\endgroup\$ – WebOrCode Jun 1 '18 at 4:59
  • \$\begingroup\$ Yes I have. Honestly this approach was easier for packaging a reasonably complex Qt Gui app with scientific computations and in addition allows me to neatly package more than one executable outside of the python runtime. The Freezer approach still requires the executables to be inside the same directory as the python.dll \$\endgroup\$ – Vince W. Jun 1 '18 at 11:08
  • \$\begingroup\$ Also the one file option at first glance would seem to be nice but it does slow down launch time and again it is packaging the entire runtime into every file which can be hundreds of MB for each application whereas the exe files here are about 100kb and the python code and Any resource files are usually in the range of 1MB \$\endgroup\$ – Vince W. Jun 1 '18 at 11:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.