You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在C++中嵌入Python且不暴露源码?求返回值获取方案

解决C++嵌入Python并隐藏源码的方案

看起来你已经做了不少功课,核心需求就是在C++中调用Python逻辑、不暴露Python源码、打包成可直接调用的二进制格式(exe/dll/lib),同时还要能获取Python脚本的返回结果。下面我结合你提到的工具,给出几个可行的方案,每个方案都包含源码隐藏、返回值获取和打包的细节:

一、用C/Python API嵌入编译后的Python字节码(最灵活)

Python的.py脚本可以编译成.pyc(字节码文件),这种文件是二进制格式,直接反编译难度比源码大很多。你可以把.pyc嵌入到C++程序中,通过Python C API加载执行,完全不需要分发源码。

步骤:

  1. 编译Python脚本为字节码
    用Python自带的py_compile模块编译:

    import py_compile
    py_compile.compile("your_script.py")
    

    或者用compileall批量编译目录下的脚本:

    python -m compileall your_script_dir
    

    编译后会生成__pycache__/your_script.cpython-XX.pyc(XX是Python版本号)。

  2. 将字节码嵌入C++程序
    把.pyc文件转换成C字节数组(可以用xxd工具:xxd -i your_script.pyc > script_data.h),然后在C中引入该头文件。

  3. 调用字节码并获取返回值
    示例代码:

    #include <Python.h>
    #include "script_data.h"  // 包含转换后的字节码数组
    
    // 导出给其他C++程序调用的函数(如果要打包成dll)
    extern "C" __declspec(dllexport) const char* RunPythonScript() {
        static char result_buf[1024] = {0};
        Py_Initialize();
        if (!Py_IsInitialized()) {
            strcpy(result_buf, "Python初始化失败");
            return result_buf;
        }
    
        // 从字节数组加载编译后的代码对象
        PyObject* code_obj = PyMarshal_ReadObjectFromString(
            reinterpret_cast<char*>(your_script_pyc), your_script_pyc_len
        );
        if (!code_obj) {
            PyErr_Print();
            strcpy(result_buf, "加载字节码失败");
            Py_Finalize();
            return result_buf;
        }
    
        // 获取__main__模块的命名空间
        PyObject* main_module = PyImport_AddModule("__main__");
        PyObject* main_dict = PyModule_GetDict(main_module);
    
        // 执行代码并获取结果
        PyObject* result = PyEval_EvalCode(code_obj, main_dict, main_dict);
        if (result) {
            // 把Python对象转换成C++字符串(根据你的返回类型调整)
            if (PyUnicode_Check(result)) {
                const char* res_str = PyUnicode_AsUTF8(result);
                strncpy(result_buf, res_str, sizeof(result_buf)-1);
            } else if (PyLong_Check(result)) {
                sprintf(result_buf, "%lld", PyLong_AsLongLong(result));
            }
            Py_DECREF(result);
        } else {
            PyErr_Print();
            strcpy(result_buf, "脚本执行出错");
        }
    
        Py_DECREF(code_obj);
        Py_Finalize();
        return result_buf;
    }
    
    int main() {
        // 测试调用
        const char* res = RunPythonScript();
        printf("Python返回结果:%s\n", res);
        return 0;
    }
    
  4. 打包成exe/dll

    • 用Visual Studio编译时,链接Python的库文件(pythonXX.lib),如果要打包成独立exe,可以静态链接Python(需要编译Python的静态库),或者把Python的dll一起打包。
    • 如果编译成dll,其他C++程序可以通过LoadLibrary和GetProcAddress调用RunPythonScript函数获取返回值。

源码隐藏进阶:

如果担心.pyc被反编译,可以用第三方工具(比如pyarmor)加密字节码,然后在C++中加载时先解密字节码,再传给Python API执行。


二、用pybind11嵌入Python(现代轻量)

pybind11是比Boost.Python更轻量的绑定库,语法更简洁,适合现代C++。同样可以嵌入字节码实现源码隐藏,并且更容易处理C++和Python之间的类型转换。

步骤:

  1. 编译Python脚本为字节码(同方案一)

  2. 嵌入字节码并调用
    示例代码:

    #include <pybind11/embed.h>
    #include <vector>
    #include <cstring>
    
    namespace py = pybind11;
    
    extern "C" __declspec(dllexport) double RunPythonCalc(double a, double b) {
        try {
            py::scoped_interpreter guard{};  // 自动管理Python解释器生命周期
    
            // 嵌入的字节码(假设已经转换成vector)
            std::vector<unsigned char> pyc_data = { /* 你的pyc字节码 */ };
            py::bytes pyc_bytes(reinterpret_cast<char*>(pyc_data.data()), pyc_data.size());
            
            // 加载字节码
            py::object code = py::module_::import("marshal").attr("loads")(pyc_bytes);
            py::object main_namespace = py::module_::import("__main__").attr("__dict__");
            
            // 执行脚本(假设脚本里有add(a,b)函数)
            py::eval_code(code, main_namespace, main_namespace);
            py::object add_func = main_namespace["add"];
            
            // 调用函数并获取返回值
            double result = add_func(a, b).cast<double>();
            return result;
        } catch (const py::error_already_set& e) {
            printf("Python错误:%s\n", e.what());
            return -1;
        }
    }
    
    int main() {
        double res = RunPythonCalc(3.5, 2.5);
        printf("计算结果:%.2f\n", res);
        return 0;
    }
    
  3. 打包
    用CMake或者VS编译,链接pybind11和Python库,同样可以编译成exe或dll。pybind11支持静态编译,方便打包成独立二进制。


三、用PyInstaller打包Python脚本为dll/exe,再用C++调用

你提到了Py2exe,其实PyInstaller更强大,支持把Python脚本打包成独立的dll或者exe,然后C++可以通过调用dll的导出函数或者通过管道获取exe的输出。

方案A:打包成dll

  1. 修改Python脚本,添加导出函数
    用ctypes定义导出函数:

    from ctypes import c_char_p, c_double
    
    def add(a: float, b: float) -> float:
        return a + b
    
    def get_result() -> str:
        return "Hello from Python!"
    
    # 定义导出接口(供PyInstaller识别)
    if __name__ == "__main__":
        import sys
        if sys.argv[0].endswith('.dll'):
            from ctypes import WINFUNCTYPE
            # 注册导出函数
            export_add = WINFUNCTYPE(c_double, c_double, c_double)(add)
            export_get_result = WINFUNCTYPE(c_char_p)(get_result)
    
  2. 用PyInstaller打包成dll

    pyinstaller --name python_script --dll --export-functions add,get_result your_script.py
    
  3. C++调用dll

    #include <windows.h>
    #include <iostream>
    
    typedef double(*AddFunc)(double, double);
    typedef const char*(*GetResultFunc)();
    
    int main() {
        HMODULE hDll = LoadLibraryA("python_script.dll");
        if (!hDll) {
            std::cout << "加载dll失败" << std::endl;
            return 1;
        }
    
        AddFunc add = (AddFunc)GetProcAddress(hDll, "add");
        GetResultFunc get_result = (GetResultFunc)GetProcAddress(hDll, "get_result");
    
        if (add && get_result) {
            double res = add(5.0, 3.0);
            std::cout << "加法结果:" << res << std::endl;
            std::cout << "字符串结果:" << get_result() << std::endl;
        }
    
        FreeLibrary(hDll);
        return 0;
    }
    

方案B:打包成exe,通过管道获取返回值

如果不需要直接调用函数,只是获取脚本的输出,可以把Python脚本打包成exe,C++调用exe并读取标准输出:

#include <iostream>
#include <string>
#include <windows.h>

std::string RunPythonExe(const std::string& exe_path) {
    std::string result;
    SECURITY_ATTRIBUTES sa;
    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
    sa.bInheritHandle = TRUE;
    sa.lpSecurityDescriptor = NULL;

    HANDLE hRead, hWrite;
    CreatePipe(&hRead, &hWrite, &sa, 0);
    SetHandleInformation(hRead, HANDLE_FLAG_INHERIT, 0);

    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    si.hStdOutput = hWrite;
    si.hStdError = hWrite;
    si.dwFlags |= STARTF_USESTDHANDLES;

    if (CreateProcessA(NULL, const_cast<char*>(exe_path.c_str()), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) {
        CloseHandle(hWrite);
        char buf[4096];
        DWORD bytesRead;
        while (ReadFile(hRead, buf, sizeof(buf)-1, &bytesRead, NULL) && bytesRead > 0) {
            buf[bytesRead] = '\0';
            result += buf;
        }
        CloseHandle(hRead);
        WaitForSingleObject(pi.hProcess, INFINITE);
        CloseHandle(pi.hProcess);
        CloseHandle(pi.hThread);
    }
    return result;
}

int main() {
    std::string res = RunPythonExe("your_script.exe");
    std::cout << "Python返回结果:\n" << res << std::endl;
    return 0;
}

工具对比建议

  • C/Python API:最底层,灵活度最高,但需要处理更多Python对象的内存管理,适合对性能和控制要求高的场景。
  • pybind11:现代C++风格,语法简洁,类型转换方便,比Boost.Python轻量,推荐大多数场景使用。
  • PyInstaller:快速打包,不需要写太多C++代码,适合Python逻辑复杂、快速迭代的场景,但打包后的体积可能较大。

源码隐藏注意事项

  • 永远不要分发.py源码,只用.pyc或加密后的字节码。
  • 对于敏感逻辑,可以对字节码进行自定义加密,C++加载时解密后再执行,进一步提高反编译难度。
  • 静态链接Python解释器可以减少依赖文件,同时避免暴露Python安装路径。

内容的提问来源于stack exchange,提问作者z-pc

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 21:08:10