├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md └── src ├── app └── example.py └── main.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .vs 3 | .vscode 4 | *.tmp 5 | *.suo 6 | *.TMP 7 | *.log -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libs/cpython"] 2 | path = libs/cpython 3 | url = https://github.com/python/cpython.git 4 | branch = 491bbedc209fea314a04cb3015da68fb0aa63238 5 | [submodule "libs/pybind11"] 6 | path = libs/pybind11 7 | url = https://github.com/pybind/pybind11.git 8 | branch = master 9 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1) 2 | include(${CMAKE_ROOT}/Modules/ExternalProject.cmake) 3 | project (PythonEmbeddedExample) 4 | 5 | # Check for C++11 6 | set (CMAKE_CXX_STANDARD 11) 7 | 8 | # Specify build type 9 | if(NOT CMAKE_BUILD_TYPE) 10 | set(CMAKE_BUILD_TYPE "Debug") 11 | endif() 12 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "RelWithDebInfo" "MinSizeRel") 13 | 14 | if (CMAKE_BUILD_TYPE MATCHES "Debug") 15 | set(CPYTHON_BUILD_TYPE Debug) 16 | else () 17 | set(CPYTHON_BUILD_TYPE Release) 18 | endif() 19 | 20 | # Add the cpython as an external project that will be included in the build 21 | if(MSVC) 22 | if(CMAKE_CL_64) 23 | set(CPYTHON_PLATFORM x64) 24 | set(CPYTHON_BUILD_DIR amd64) 25 | else() 26 | set(CPYTHON_PLATFORM x86) 27 | set(CPYTHON_BUILD_DIR win32) 28 | endif() 29 | ExternalProject_Add(CPYTHON 30 | DOWNLOAD_COMMAND "" 31 | SOURCE_DIR ${CMAKE_SOURCE_DIR}/libs/cpython 32 | CONFIGURE_COMMAND "" 33 | BUILD_COMMAND cd ${CMAKE_SOURCE_DIR}/libs/cpython && MSBuild.exe /p:Configuration=${CPYTHON_BUILD_TYPE} /property:Platform=${CPYTHON_PLATFORM} "PCBuild/python.vcxproj" /nologo /verbosity:minimal /consoleloggerparameters:summar 34 | INSTALL_COMMAND "" 35 | TEST_COMMAND "" 36 | ) 37 | else() 38 | ExternalProject_Add(CPYTHON 39 | DOWNLOAD_COMMAND "" 40 | SOURCE_DIR ${CMAKE_SOURCE_DIR}/libs/cpython 41 | CONFIGURE_COMMAND cd ${CMAKE_SOURCE_DIR}/libs/cpython && ./configure --disable-static --enable-shared 42 | BUILD_COMMAND cd ${CMAKE_SOURCE_DIR}/libs/cpython && make 43 | INSTALL_COMMAND "" 44 | TEST_COMMAND "" 45 | ) 46 | endif() 47 | 48 | set(CPYTHON_STDLIB_DIR ${CMAKE_SOURCE_DIR}/libs/cpython/Lib) 49 | if(MSVC) 50 | set(CPYTHON_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/libs/cpython/Include ${CMAKE_SOURCE_DIR}/libs/cpython/PC) 51 | set(CPYTHON_LIBRARY_DIR ${CMAKE_SOURCE_DIR}/libs/cpython/PCBuild/${CPYTHON_BUILD_DIR}) 52 | if(CMAKE_BUILD_TYPE MATCHES "Debug") 53 | set(CPYTHON_BIN ${CMAKE_SOURCE_DIR}/libs/cpython/PCBuild/${CPYTHON_BUILD_DIR}/python38_d.dll) 54 | else() 55 | set(CPYTHON_BIN ${CMAKE_SOURCE_DIR}/libs/cpython/PCBuild/${CPYTHON_BUILD_DIR}/python38.dll) 56 | endif() 57 | else() 58 | set(CPYTHON_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/libs/cpython/Include ${CMAKE_SOURCE_DIR}/libs/cpython) 59 | set(CPYTHON_LIBRARY_DIR ${CMAKE_SOURCE_DIR}/libs/cpython) 60 | set(CPYTHON_LIBRARY python3.8m) 61 | set(CPYTHON_BIN ${CMAKE_SOURCE_DIR}/libs/cpython/libpython3.8m.so) 62 | endif() 63 | 64 | # Add the pybind11 library (optional) 65 | ExternalProject_Add(PYBIND 66 | DOWNLOAD_COMMAND "" 67 | SOURCE_DIR ${CMAKE_SOURCE_DIR}/libs/pybind11 68 | CMAKE_ARGS -DPYBIND11_TEST=OFF -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DPYTHONLIBS_FOUND=ON -DPYTHON_MODULE_EXTENSION=.lib -DPYTHON_LIBRARY=${CMAKE_SOURCE_DIR}/libs/cpython/PCBuild/win32/python38_d.lib -DPYTHON_INCLUDE_DIR=${CMAKE_SOURCE_DIR}/libs/cpython/PCBuild/win32 69 | BUILD_COMMAND cmake --build . --config ${CMAKE_BUILD_TYPE} 70 | INSTALL_COMMAND "" 71 | TEST_COMMAND "" 72 | ) 73 | 74 | set(PYBIND_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/libs/pybind11/include) 75 | 76 | # Source and header files 77 | FILE(GLOB_RECURSE SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp) 78 | FILE(GLOB_RECURSE HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/src/*.h) 79 | 80 | # Linking directories 81 | link_directories(${CPYTHON_LIBRARY_DIR}) 82 | 83 | # The target executable 84 | add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS}) 85 | 86 | # Add macros 87 | target_compile_definitions(${PROJECT_NAME} PRIVATE NOMINMAX=1) 88 | 89 | # Dependencies 90 | add_dependencies(${PROJECT_NAME} PYBIND) 91 | add_dependencies(${PROJECT_NAME} CPYTHON) 92 | 93 | # Include directories 94 | target_include_directories(${PROJECT_NAME} PRIVATE ${CPYTHON_INCLUDE_DIR}) 95 | target_include_directories(${PROJECT_NAME} PRIVATE ${PYBIND_INCLUDE_DIR}) 96 | 97 | # On MSVC build, the python library is automatically linked (crazy I know) 98 | if(NOT MSVC) 99 | target_link_libraries(${PROJECT_NAME} ${CPYTHON_LIBRARY}) 100 | endif() 101 | 102 | # Set the executable to console application if MSVC 103 | if(MSVC) 104 | set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "/SUBSYSTEM:CONSOLE") 105 | endif() 106 | 107 | # Copy Python DLL to the build folder if different 108 | add_custom_command( 109 | TARGET ${PROJECT_NAME} 110 | POST_BUILD 111 | COMMAND ${CMAKE_COMMAND} 112 | -E copy_if_different ${CPYTHON_BIN} $ 113 | ) 114 | 115 | # Copy our python sources to the build folder 116 | add_custom_command( 117 | TARGET ${PROJECT_NAME} 118 | POST_BUILD 119 | COMMAND ${CMAKE_COMMAND} 120 | -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/src/app $/app 121 | ) 122 | 123 | # Copy the Python stdlib into the build folder (needed by the embedded python) 124 | add_custom_command( 125 | TARGET ${PROJECT_NAME} 126 | POST_BUILD 127 | COMMAND ${CMAKE_COMMAND} 128 | -E copy_directory ${CPYTHON_STDLIB_DIR} $/lib 129 | ) 130 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python Embedded Example Project 2 | 3 | This is an example project using mebedded **[python 3.8](https://github.com/python/cpython)** in C++ console application using CMake. This example project also contains **[pybind11](https://github.com/pybind/pybind11)** library for easy binding between C++ and python. 4 | 5 | Tested on Windows 10 with Visual Studio 2013, 2015, and 2017 (both x86 and x64). Also tested on Ubuntu with GCC (x64). 6 | 7 | **Note this example has nothing to do with embedding C++ in python! This is the other way around! Embeding python in C++.** 8 | 9 | ## About 10 | 11 | Normally, you have a python installed in your system and then you can launch python scripts from command line. What if you want to create C++ application, but want to use python scripts (for example as modding for games)? One option is to let the user install python on their system and then install your app. No problem there, except, you are forcing the user to modify their system. Additonally, you can never guarantee that the user will install the specific python version (2.7 or 3.8) you require. Even if all of that is sorted out, the python that will get run will probably need third party packages (or you need to explicitly disable some packages), this can't be done easily. 12 | 13 | What if we can embed entire python in C++ executable? This way, the user won't have to install python on their system nor additional dependencies. Everything will be bundled up in the executable. 14 | 15 | This project comes with cpython 3.8 (as a git submodule) and pybind11. Once you build the project, your build folder will look like this: 16 | 17 | ``` 18 | python-embedded-example-project/ 19 | build/ 20 | Release/ 21 | PythonEmbeddedExample.exe 22 | app/ 23 | example.py 24 | lib/ 25 | a lot of Python standard library files 26 | ``` 27 | 28 | The script files (such as example.py) are copied to the build directory from `src/app` and the python standard libraries are copied over from git submodule located in `python-embedded-example-project/libs/cpython/Lib`. You can't get rid of those standard libraries! Python needs them to initialise. However, you could limit the standard libraries by only selectively copying over the ones you need. 29 | 30 | When the executable runs, it will load an `example` module and creates an instance of `Example` class from `example.py` file. That's all it does. 31 | 32 | **The python PATH is limited only to the app and lib folder located next to the executable.** The embedded python won't have any access to installed packages in your operating system. You will have to include them in any of those two folders. 33 | 34 | ## Requirements 35 | 36 | Visual Studio 2013 (or newer) or Linux with GCC. MinGW is sadly not supported, and clang has not been tested. 37 | 38 | ## Download 39 | 40 | Don't forget to initialise and update the submodules! The cpython is +200MB so it may take some time to during `git submodule update`. 41 | 42 | ``` 43 | git clone https://github.com/matusnovak/python-embedded-example-project 44 | cd python-embedded-example-project 45 | git submodule init 46 | git submodule update --progress 47 | ``` 48 | 49 | ## Build using Visual Studio on Windows 50 | 51 | **Please note:** that you have to explicitly specify `CMAKE_BUILD_TYPE`. If you specify Debug, then you must use Debug in your Visual Studio. You won't be able to change to Release using the dropdown list in the main menu. Python will be build using the `CMAKE_BUILD_TYPE` flag regarding the chosen configuration in Visual Studio. To change from Debug to Release, re-run the cmake and set the `CMAKE_BUILD_TYPE` to Release. 52 | 53 | ``` 54 | cd python-embedded-example-project 55 | mkdir build 56 | cd build 57 | cmake .. -G "Visual Studio 15 2017" -DCMAKE_BUILD_TYPE=Debug 58 | ``` 59 | 60 | Then open the generated solution file "PythonEmbeddedExample.sln" in the build folder. You should see the following projects: 61 | 62 | * **ALL_BUILD** - Building this will build all projects 63 | * **CPYTHON** - The embedded python project 64 | * **PYBIND** - The pybind11 library for binding 65 | * **PythonEmbeddedExample** - This is the example project 66 | 67 | Build the PythonEmbeddedExample and then run the `PythonEmbeddedExample.exe` from command line. That's all. 68 | 69 | ## Build using GCC on Linux 70 | 71 | ``` 72 | cd python-embedded-example-project 73 | mkdir build 74 | cd build 75 | cmake .. -DCMAKE_BUILD_TYPE=Debug 76 | ``` 77 | 78 | Then build the example by running: 79 | 80 | ``` 81 | make 82 | ``` 83 | 84 | The `PythonEmbeddedExample` executable will be generated. 85 | 86 | ## Changing python version 87 | 88 | At the time of the creation of this example, Python 3.8 was just released. The cpython submodule is frozen to commit `491bbedc209fea314a04cb3015da68fb0aa63238`. If you want to change python version, change the `branch` in `.gitmodules` to `branch = 2.7` or any other version. See available branches at: 89 | 90 | ## Example output 91 | 92 | Example output of the `PythonEmbeddedExample` executable. 93 | 94 | ``` 95 | Python PATH set to: C:\Users\matus\Documents\cpp\python-embedded-example-project\build\Debug\lib;C:\Users\matus\Documents\cpp\python-embedded-example-project\build\Debug\app; 96 | Importing module...Initializing class... 97 | Example constructor with msg: Hello World 98 | Got msg back on C++ side: Hello World 99 | ``` 100 | 101 | ## fatal error LNK1104: cannot open file 'python38_d.lib' 102 | 103 | This happens when you run cmake with `-DCMAKE_BUILD_TYPE=MinSizeRel` and you are compiling the solution in Visual Studio as Debug. Simply, in Visual Studio, change the configuration to the one used in `CMAKE_BUILD_TYPE`. 104 | 105 | This happens because the cpython has been built via `CMAKE_BUILD_TYPE` but your Visual Studio is looking for a debug version of the python library (or the other way around). 106 | -------------------------------------------------------------------------------- /src/app/example.py: -------------------------------------------------------------------------------- 1 | class Example: 2 | def __init__(self, msg: str): 3 | self.msg = msg 4 | print('Example constructor with msg:', self.msg) 5 | 6 | def getMsg(self): 7 | return self.msg 8 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) 2 | #define IS_WINDOWS 3 | #include // Needed by GetModuleFileNameW 4 | #else 5 | #include // Needed by readlink 6 | #endif 7 | 8 | #include 9 | #include 10 | #ifndef IS_WINDOWS 11 | #pragma GCC diagnostic push 12 | #pragma GCC diagnostic ignored "-Wdeprecated-declarations" 13 | #endif 14 | #include 15 | #include 16 | #include 17 | #ifndef IS_WINDOWS 18 | #pragma GCC diagnostic pop 19 | #endif 20 | 21 | namespace py = pybind11; 22 | using namespace py::literals; 23 | 24 | ///============================================================================= 25 | #ifdef IS_WINDOWS 26 | std::wstring getExecutableDir() { 27 | wchar_t exePath[MAX_PATH]; 28 | GetModuleFileNameW(nullptr, exePath, MAX_PATH); 29 | const auto executableDir = std::wstring(exePath); 30 | const auto pos = executableDir.find_last_of('\\'); 31 | if (pos != std::string::npos) return executableDir.substr(0, pos); 32 | return L"\\"; 33 | } 34 | #else 35 | std::wstring getExecutableDir() { 36 | char result[PATH_MAX]; 37 | ssize_t count = readlink("/proc/self/exe", result, PATH_MAX); 38 | if (count != -1) { 39 | const auto path = std::string(dirname(result)); 40 | return std::wstring(path.begin(), path.end()); 41 | } 42 | return L"/"; 43 | } 44 | #endif 45 | 46 | ///============================================================================= 47 | int main(int argc, char** argv) { 48 | (void)argc; 49 | (void)argv; 50 | 51 | // Get executable dir and build python PATH variable 52 | const auto exeDir = getExecutableDir(); 53 | #ifdef IS_WINDOWS 54 | const auto pythonHome = exeDir + L"\\lib"; 55 | const auto pythonPath = exeDir + L"\\lib;" + exeDir + L"\\app;"; 56 | #else 57 | const auto pythonHome = exeDir + L"/lib"; 58 | const auto pythonPath = exeDir + L"/lib:" + exeDir + L"/app"; 59 | #endif 60 | 61 | // Initialize python 62 | Py_OptimizeFlag = 1; 63 | Py_SetProgramName(L"PythonEmbeddedExample"); 64 | Py_SetPath(pythonPath.c_str()); 65 | Py_SetPythonHome(pythonHome.c_str()); 66 | 67 | std::wcout << "Python PATH set to: " << pythonPath << std::endl; 68 | 69 | try { 70 | py::scoped_interpreter guard{}; 71 | 72 | // Disable build of __pycache__ folders 73 | py::exec(R"( 74 | import sys 75 | sys.dont_write_bytecode = True 76 | )"); 77 | 78 | // This imports example.py from app/example.py 79 | // The app folder is the root folder so you don't need to specify app.example. 80 | // The app/example script that is being imported is from the actual build folder! 81 | // Cmake will copy the python scripts after you have compiled the source code. 82 | std::cout << "Importing module..." << std::endl; 83 | auto example = py::module::import("example"); 84 | 85 | std::cout << "Initializing class..." << std::endl; 86 | const auto myExampleClass = example.attr("Example"); 87 | auto myExampleInstance = myExampleClass("Hello World"); // Calls the constructor 88 | // Will print in the terminal window: 89 | // Example constructor with msg: Hello World 90 | 91 | const auto msg = myExampleInstance.attr("getMsg")(); // Calls the getMsg 92 | std::cout << "Got msg back on C++ side: " << msg.cast() << std::endl; 93 | } catch (std::exception& e) { 94 | std::cerr << "Something went wrong: " << e.what() << std::endl; 95 | return EXIT_FAILURE; 96 | } 97 | 98 | return EXIT_SUCCESS; 99 | } 100 | --------------------------------------------------------------------------------