I am trying to embed Python inside my C++ DLL.
The idea is that the DLL, once distributed, should be sufficient and not rely on other installations and downloads.
Interestingly, the below "sort of" works, only in my solution directory, since that is where my vcpkg_installed
is.
How can I make my DLL not be required to be near vcpkg_installed
?
py_wrap.cpp (the DLL):
void assertPyInit() {
if (!Py_IsInitialized()) {
PyConfig config;
PyConfig_InitPythonConfig(&config);
// Get the executable path instead of current working directory
wchar_t exePath[MAX_PATH];
GetModuleFileNameW(NULL, exePath, MAX_PATH);
// Remove the executable name to get the directory
std::wstring exeDir = exePath;
size_t lastSlash = exeDir.find_last_of(L"\\");
if (lastSlash != std::wstring::npos) {
exeDir = exeDir.substr(0, lastSlash);
}
// Now build Python path relative to executable location
std::wstring pythonHome = exeDir + L"\\..\\..\\vcpkg_installed\\x64-windows-static-md\\x64-windows-static-md\\tools\\python3";
// Resolve the full path to eliminate .. references
wchar_t resolvedPath[MAX_PATH];
GetFullPathNameW(pythonHome.c_str(), MAX_PATH, resolvedPath, NULL);
pythonHome = resolvedPath;
std::wstring pythonLib = pythonHome + L"\\Lib";
std::wstring pythonSitePackages = pythonLib + L"\\site-packages";
std::wstring pythonDLLs = pythonHome + L"\\DLLs";
// Set the Python home directory
PyConfig_SetString(&config, &config.home, pythonHome.c_str());
// Set the module search paths
std::wstring pythonPathEnv = pythonLib + L";" + pythonSitePackages + L";" + pythonDLLs;
PyConfig_SetString(&config, &config.pythonpath_env, pythonPathEnv.c_str());
PyStatus status = Py_InitializeFromConfig(&config);
PyConfig_Clear(&config);
if (PyStatus_Exception(status)) {
PyErr_Print();
return;
}
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append(\".\")");
}
}
void MY_DLL pyPrint(const char* message) {
assertPyInit();
PyObject* pyStr = PyUnicode_FromString(message);
if (pyStr) {
PyObject* builtins = PyEval_GetBuiltins();
PyObject* printFunc = PyDict_GetItemString(builtins, "print");
if (printFunc && PyCallable_Check(printFunc)) {
PyObject* args = PyTuple_Pack(1, pyStr);
PyObject_CallObject(printFunc, args);
Py_DECREF(args);
}
Py_DECREF(pyStr);
}
}
DLLTester.cpp (client app):
#include <iostream>
#include "py_wrap.h"
int main()
{
std::cout << "Hello\n";
pyPrint("Hello from python:D !");
}
PS D: \RedactedLabs\Dev > ls AsyncDLLMQL\x64\Release\
Directory:
D: \RedactedLabs\Dev\AsyncDLLMQL\x64\Release Mode LastWriteTime Length Name ---- ------------- ------ ---- -a---- 6/11/2025 10:48 PM 476672 AsyncDLLMQL.dll -a---- 6/11/2025 10:48 PM 4297 AsyncDLLMQL.exp -a---- 6/11/2025 10:26 PM 7752 AsyncDLLMQL.lib -a---- 6/11/2025 10:48 PM 7376896 AsyncDLLMQL.pdb -a---- 6/11/2025 10:48 PM 12288 DLLTester.exe -a---- 6/11/2025 10:48 PM 790528 DLLTester.pdb -a---- 6/6/2025
4: 39 PM 56320 python3.dll -a---- 6/6/2025
4: 39 PM 7273984 python312.dll PS
D: \RedactedLabs\Dev > ls AsyncDLLMQL\x64\Release\
Directory:
D: \RedactedLabs\Dev\AsyncDLLMQL\x64\Release Mode LastWriteTime Length Name ---- ------------- ------ ---- -a---- 6/11/2025 10:48 PM 476672 AsyncDLLMQL.dll -a---- 6/11/2025 10:48 PM 4297 AsyncDLLMQL.exp -a---- 6/11/2025 10:26 PM 7752 AsyncDLLMQL.lib -a---- 6/11/2025 10:48 PM 7376896 AsyncDLLMQL.pdb -a---- 6/11/2025 10:48 PM 12288 DLLTester.exe -a---- 6/11/2025 10:48 PM 790528 DLLTester.pdb -a---- 6/6/2025
4: 39 PM 56320 python3.dll -a---- 6/6/2025
4: 39 PM 7273984 python312.dll PS
D: \RedactedLabs\Dev > ls .\scraps\
Directory:
D: \RedactedLabs\Dev\scraps Mode LastWriteTime Length Name ---- ------------- ------ ---- -a---- 6/11/2025 10:48 PM 476672 AsyncDLLMQL.dll -a---- 6/11/2025 10:48 PM 4297 AsyncDLLMQL.exp -a---- 6/11/2025 10:26 PM 7752 AsyncDLLMQL.lib -a---- 6/11/2025 10:48 PM 7376896 AsyncDLLMQL.pdb -a---- 6/11/2025 10:48 PM 12288 DLLTester.exe -a---- 6/11/2025 10:48 PM 790528 DLLTester.pdb -a---- 6/6/2025
4: 39 PM 56320 python3.dll -a---- 6/6/2025
4: 39 PM 7273984 python312.dll PS
D: \RedactedLabs\Dev > .\AsyncDLLMQL\x64\Release\DLLTester.exe Hello Hello from
python: D ! PS
D: \RedactedLabs\Dev > .\scraps\DLLTester.exe Hello Python path
configuration: PYTHONHOME= 'D:\RedactedLabs\vcpkg_installed\x64-windows-static-md\x64-windows-static-md\tools\python3' PYTHONPATH = 'D:\RedactedLabs\vcpkg_installed\x64-windows-static-md\x64-windows-static-md\tools\python3\Lib;D:\RedactedLabs\vcpkg_installed\x64-windows-static-md\x64-windows-static-md\tools\python3\Lib\site-packages;D:\RedactedLabs\vcpkg_installed\x64-windows-static-md\x64-windows-static-md\tools\python3\DLLs' program name = 'python' isolated = 0 environment = 1 user site = 1 safe_path = 0 import site = 1 is in build tree = 0 stdlib dir = 'D:\RedactedLabs\vcpkg_installed\x64-windows-static-md\x64-windows-static-md\tools\python3\Lib' sys._base_executable = 'D:\\RedactedLabs\\Dev\\scraps\\DLLTester.exe' sys.base_prefix = 'D:\\RedactedLabs\\vcpkg_installed\\x64-windows-static-md\\x64-windows-static-md\\tools\\python3' sys.base_exec_prefix = 'D:\\RedactedLabs\\vcpkg_installed\\x64-windows-static-md\\x64-windows-static-md\\tools\\python3' sys.platlibdir = 'DLLs' sys.executable = 'D:\\RedactedLabs\\Dev\\scraps\\DLLTester.exe' sys.prefix = 'D:\\RedactedLabs\\vcpkg_installed\\x64-windows-static-md\\x64-windows-static-md\\tools\\python3' sys.exec_prefix = 'D:\\RedactedLabs\\vcpkg_installed\\x64-windows-static-md\\x64-windows-static-md\\tools\\python3' sys.path = [
'D:\\RedactedLabs\\vcpkg_installed\\x64-windows-static-md\\x64-windows-static-md\\tools\\python3\\Lib',
'D:\\RedactedLabs\\vcpkg_installed\\x64-windows-static-md\\x64-windows-static-md\\tools\\python3\\Lib\\site-packages',
'D:\\RedactedLabs\\vcpkg_installed\\x64-windows-static-md\\x64-windows-static-md\\tools\\python3\\DLLs',
'D:\\RedactedLabs\\Dev\\scraps\\python312.zip',
'D:\\RedactedLabs\\vcpkg_installed\\x64-windows-static-md\\x64-windows-static-md\\tools\\python3\\DLLs',
'D:\\RedactedLabs\\vcpkg_installed\\x64-windows-static-md\\x64-windows-static-md\\tools\\python3\\Lib',
'D:\\RedactedLabs\\Dev\\scraps',
]
ModuleNotFoundError: No module named 'encodings'
Finally, my linker settings and other useful info:
$(SolutionDir)vcpkg_installed\x64-windows-static-md\x64-windows-static-md\include\python3.12
$(VcpkgInstalledDir)\x64-windows-static-md\lib
CPython needs its standard library to exist at the PYTHONHOME directory, (which you can override in the config).
If you need a standalone python interpreter to just parse python syntax (for game scripting) then you can use other implementations of python like RustPython or IronPython or JYThon or pocketpy, but you won't be able to use any C/C++ libraries like numpy.
If security is not a concern then you can do as popular apps like blender do and package the interpreter with your application and set PYTHONHOME
before initializing the interpreter with Py_InitializeFromConfig
as in the following structure.
.
└── app_root/
├── app.exe
├── python311.dll
└── python/
├── DLLs/
│ ├── _ctypes.pyd
│ └── ...
├── Lib/
│ ├── site_packages/
│ │ └── numpy, etc...
│ ├── os.py
│ └── ...
└── bin (optional)/
├── python311.dll (see note below)
└── python.exe (see note below)
If you place python311.dll
in a subdirectory then you must do extra steps to set the PATH
to its location or use LoadLibrary
instead and not link it directly.
packing python.exe
allows your user to install extra libraries using pip by doing python.exe -m pip install ...
keep in mind that users will be able to modify everything, so if security is a concern then my recommendation is not to use Python ... but you can create a custom importer and static link everything and get it to work. note that a game did this before as well as create custom byte-code for the interpreter and was still completely reverse engineered, so this is not really fool-proof.