一、C++中调用python接口

在线手册:https://docs.python.org/3/c-api/intro.html

Windows环境下
python安装时提供了给C++调用的头文件及库文件。
C++中引用头文件 include <Python.h>,放在所有标准引用之前。
头文件目录库文件目录添加到工程属性。
调用python提供的API,传入模块名、函数名、函数参数(封装成PyObject的形式)
获取返回值并解析成C++理解的数据结构

重要接口:

C++的基础数据类型都要封装成PyObject对象,才能被python识别

  • void Py_Initialize(void) 初始化,首先调用
  • void Py_Finalize(void) 反初始化,结束时调用

    变量转化

  • PyObject* Py_BuildValue(const char*, …) 新建变量
  • PyObject* PyString_FromString(char* moduleName) C字符串转换为str
  • PyObject* PyLong_FromDouble(char* moduleName) long转换为double
  • 以此类推

    执行

  • PyRun_SimpleString(const char*) 高层调用python语句
  • PyObject* PyImport_Import(PyObject* pModule) 从模块名获取模块
  • PyObject* PyObject_GetAttrString(PyObject* pModule, char* funcName) 从模块获取函数指针
  • int PyCallable_Check(PyObject* pFunc) 检测函数有效性
  • PyObject* PyTuple_New(int argNum) 创建元组,大小与函数参数个数一致
  • int PyTuple_SetItem(PyObject* pArgs, Py_ssize_t argNum, PyObject* pValue) 设置元组值
  • PyObject* PyObject_CallObject(PyObject* pFunc, PyObject* pArgs) 传入函数及参数,执行,获取返回值

    返回值解析

  • int PyArg_Parse(PyObject* pVlaue, const char* format, …)
  • int PyArg_ParseTuple(PyObject *arg, char *format, …)
  • PyArg_ParseTuple用法:

    int ok;
    int i, j;
    long k, l;
    char *s;
    int size;
    
    ok = PyArg_ParseTuple(args, ""); /* No arguments */
            /* Python call: f() */
    ok = PyArg_ParseTuple(args, "s", &s); /* A string */
            /* Possible Python call: f('whoops!') */
    ok = PyArg_ParseTuple(args, "lls", &k, &l, &s); /* Two longs and a string */
            /* Possible Python call: f(1, 2, 'three') */
    ok = PyArg_ParseTuple(args, "(ii)s#", &i, &j, &s, &size);
            /* A pair of ints and a string, whose size is also returned */
            /* Possible Python call: f((1, 2), 'three') */
    {
        char *file;
        char *mode = "r";
        int bufsize = 0;
        ok = PyArg_ParseTuple(args, "s|si", &file, &mode, &bufsize);
        /* A string, and optionally another string and an integer */
        /* Possible Python calls:
           f('spam')
           f('spam', 'w')
           f('spam', 'wb', 100000) */
    }
    {
        int left, top, right, bottom, h, v;
        ok = PyArg_ParseTuple(args, "((ii)(ii))(ii)",
                 &left, &top, &right, &bottom, &h, &v);
        /* A rectangle and a point */
        /* Possible Python call:
           f(((0, 0), (400, 300)), (10, 10)) */
    }
    {
        Py_complex c;
        ok = PyArg_ParseTuple(args, "D:myfunction", &c);
        /* a complex, also providing a function name for errors */
        /* Possible Python call: myfunction(1+2j) */
    }
    

    调用实例:

    C++部分:

    #include <Python.h>
    
    int main(int argc, char *argv[])
    {
        PyObject *pName, *pModule, *pFunc;
        PyObject *pArgs, *pValue;
        int i;
    
        if (argc < 3) {
            fprintf(stderr,"Usage: call pythonfile funcname [args]\n");
            return 1;
        }
        
    	//初始化
        Py_Initialize();
        
        // 指定py文件目录
        PyRun_SimpleString("import sys");
        PyRun_SimpleString("sys.path.append('./')"); 
        
        // 变量转换 模块名
        pName = PyString_FromString(argv[1]);
        /* Error checking of pName left out */
    
    	// 根据模块名引入模块
        pModule = PyImport_Import(pName);
        Py_DECREF(pName);
    
        if (pModule != NULL) {
        	// 从模块中获取函数
            pFunc = PyObject_GetAttrString(pModule, argv[2]);
            /* pFunc is a new reference */
    
            if (pFunc && PyCallable_Check(pFunc)) {
            	// 创建参数元组
                pArgs = PyTuple_New(argc - 3);
                for (i = 0; i < argc - 3; ++i) {
                    pValue = PyInt_FromLong(atoi(argv[i + 3]));
                    if (!pValue) {
                        Py_DECREF(pArgs);
                        Py_DECREF(pModule);
                        fprintf(stderr, "Cannot convert argument\n");
                        return 1;
                    }
                    // 设置参数值
                    /* pValue reference stolen here: */
                    PyTuple_SetItem(pArgs, i, pValue);
                }
                // 函数执行
                pValue = PyObject_CallObject(pFunc, pArgs);
                Py_DECREF(pArgs);
                if (pValue != NULL) {
                    printf("Result of call: %ld\n", PyInt_AsLong(pValue));
                    Py_DECREF(pValue);
                }
                else {
                    Py_DECREF(pFunc);
                    Py_DECREF(pModule);
                    PyErr_Print();
                    fprintf(stderr,"Call failed\n");
                    return 1;
                }
            }
            else {
                if (PyErr_Occurred())
                    PyErr_Print();
                fprintf(stderr, "Cannot find function \"%s\"\n", argv[2]);
            }
            Py_XDECREF(pFunc);
            Py_DECREF(pModule);
        }
        else {
            PyErr_Print();
            fprintf(stderr, "Failed to load \"%s\"\n", argv[1]);
            return 1;
        }
        // 反初始化
        Py_Finalize();
        return 0;
    }
    

    Python部分 multiply.py

    def multiply(a,b):
        print "Will compute", a, "times", b
        c = 0
        for i in range(0, a):
            c = c + b
        return c
    

    控制台执行

    ~$ call multiply multiply 3 2
    Will compute 3 times 2
    Result of call: 6
    

    拓展

    1. 如何调用Python类中的函数

    PyObject* pClass;
    PyObject* pDict;
    PyObject* pInstance;
    PyObject* pClassArgs;
    PyObject* pResults;
    
    //拿到pModule里的所有类和函数定义
    pDict = PyModule_GetDict(pModule); 
    //找到名为Executor的类
    pClass = PyDict_GetItemString(pDict, "Executor"); 
    //设置类初始化需要的参数
    //初始化需要当前文件夹下的配置文件config.txt ?
    pClassArgs = Py_BuildValue("(s)", "./config.txt"); 
    //初始化Executor,建立实例pInstance
    pInstance = PyInstance_New(pClass, pClassArgs, NULL); 
    // 执行pInstance.func(12345)
    pResults = PyObject_CallMethod(pInstance, "func", "(i)", 12345); 
    

    2. C++格式码 / Python类型 / C++类型 对应表

    FormatCode Python C++
    s str char*
    z str / None char* / Null
    i int int
    l long long
    c str char
    d float double
    D complex Py_Complex*
    O (any) PyObject*
    S str PyStringObject*

    3. 使用第三方库 Boost.Python 调用Python模块

    在线手册:
    https://www.boost.org/doc/libs/1_72_0/libs/python/doc/html/tutorial/tutorial/embedding.html

    实例:
    此函数用于初始化渲染管线,从python中获取渲染管线,保存在python对象和C++对象中。

    void initialize_python3_render_pipeline(EViewerRC& render_ctx)
    {
        namespace bp = boost::python;
        PYModule* pyvm = &singleton<PYModule>::instance();
    
        try
        {
            pyvm->pyPipelineModule = bp::import("PYPipeline");
        }
        catch (boost::python::error_already_set)
        {
            ::OutputDebugStringA(parse_python_exception().c_str());
            return;
        }
    
        bp::list py_pipeline_list;
        try
        {
            bp::object obj = pyvm->pyPipelineModule.attr("get_pipeline_array");
            py_pipeline_list = bp::call<bp::list>(obj.ptr());
        }
        catch (boost::python::error_already_set)
        {
            ::OutputDebugStringA(parse_python_exception().c_str());
        }
    
        for (size_t i = 0, iCOUNT = bp::len(py_pipeline_list); i < iCOUNT; ++i)
        {
            try
            {
                std::string str_pipeline = bp::extract<std::string>(py_pipeline_list[i]);
                bp::object class_pipeline = pyvm->pyPipelineModule.attr(str_pipeline.c_str());
                bp::object py_RGBPipeline = class_pipeline();
                IPipeline* pyPipeline = bp::extract<IPipeline*>(py_RGBPipeline);
    
                pyvm->pipeline_pyobj_array.push_back(py_RGBPipeline);
                pyvm->pipeline_array.push_back(pyPipeline);
                render_ctx.pipeline_table_.push_back(pyPipeline);
            }
            catch (boost::python::error_already_set)
            {
                ::OutputDebugStringA(parse_python_exception().c_str());
            }
        }
    }
    


    二、C++中开启python进程

    实例:UE4中C++调用python模块实现Switchboard进程

    Windows环境下
    UE4中用C++调用python模块的思路是:
    通过调用bat脚本来开启子进程,bat脚本中会调用python.exe执行python脚本。
    调用顺序为:C++ >> bat脚本 >> python.exe >> py文件

    具体方法

    1. 编写python模块程序

    包含以下文件目录:
    switchboard文件夹包含所有要执行的python脚本
    venv文件夹包含执行python需要的环境文件、解释器等

    2. 编写调用python的bat脚本


    switchboard.bat内容如下:

    @echo off
    setlocal
    
    REM pushd and %CD% are used to normalize the relative path to a shorter absolute path.
    pushd "%~dp0..\..\..\..\.."
    set _engineDir=%CD%
    set _enginePythonPlatformDir=%_engineDir%\Binaries\ThirdParty\Python3\Win64
    set _pyVenvDir=%_engineDir%\Extras\ThirdPartyNotUE\SwitchboardThirdParty\Python
    popd
    
    call:main
    
    endlocal
    goto:eof
    
    ::------------------------------------------------------------------------------
    :main
    
    REM This provides a transition from standalone installations of Python for
    REM Switchboard to the venv-based setup using the engine version of Python.
    if exist "%_pyVenvDir%" (
        if not exist "%_pyVenvDir%\Scripts" (
            rd /q /s "%_pyVenvDir%" 2>nul
        )
    )
    
    if not exist "%_pyVenvDir%" (
        call:setup_python_venv
    )
    
    call:start_sb
    
    goto:eof
    
    ::------------------------------------------------------------------------------
    :setup_python_venv
    
    1>nul 2>nul (
        mkdir "%_pyVenvDir%"
    )
    
    1>"%_pyVenvDir%\provision.log" 2>&1 (
        set prompt=-$s
        echo on
        call:setup_python_venv_impl
        echo off
    )
    
    echo.
    goto:eof
    
    ::------------------------------------------------------------------------------
    :setup_python_venv_impl
    
    pushd "%_pyVenvDir%"
    call:echo "Working path; %CD%"
    
    call:echo "1/5 : Setting up Python Virtual Environment"
    call "%_enginePythonPlatformDir%\python.exe" -m venv "%_pyVenvDir%"
    
    call "%_pyVenvDir%\Scripts\activate"
    
    call:echo "2/5 : Installing PySide2"
    python.exe -m pip install -Iv pyside2==5.15.0
    
    call:echo "3/5 : Installing python-osc"
    python.exe -m pip install -Iv python-osc==1.7.4
    
    call:echo "4/5 : Installing requests"
    python.exe -m pip install -Iv requests==2.24.0
    
    call:echo "5/5 : Installing six"
    python.exe -m pip install -Iv six==1.15.0
    
    call deactivate
    
    popd
    goto:eof
    
    ::------------------------------------------------------------------------------
    :echo
    1>con echo %~1
    goto:eof
    
    ::------------------------------------------------------------------------------
    :start_sb
    
    call "%_pyVenvDir%\Scripts\activate"
    start "Switchboard" pythonw.exe -m switchboard
    

    3. C++获取bat脚本绝对路径及其工作路径,并创建进程

    1. UI界面点击Switchboard按钮,触发响应函数OnLaunchSwitchboardClicked
    2. OnLaunchSwitchboardClicked中获取bat脚本路径及工作路径
    3. OnLaunchSwitchboardClicked中调用RunProcess函数
    4. RunProcess中调用FPlatformProcess::CreateProc创造进程
    5. FPlatformProcess::CreateProc中使用WINAPI开启子进程
    CreateProcessW(
        _In_opt_ LPCWSTR               lpApplicationName, // 应用程序名
        _Inout_opt_ LPWSTR             lpCommandLine, // 命令行字符串
        _In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes, // 进程安全属性
        _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, // 线程安全属性
        _In_ BOOL                      bInheritHandles, // 是否继承父进程属性
        _In_ DWORD                     dwCreationFlags, // 创建标志
        _In_opt_ LPVOID                lpEnvironment, // 指向新环境块的指针
        _In_opt_ LPCWSTR               lpCurrentDirectory, 指向当前目录名的指针
        _In_ LPSTARTUPINFOW            lpStartupInfo, // 传递给新进程的信息
        _Out_ LPPROCESS_INFORMATION    lpProcessInformation // 新进程返回的信息
        );
    

    具体实现如下:

  • Engine
  • Plugins
  • VirtualProduction
  • Switchboard
  • Source
  • SwitchboardEditor
  • Private
  • SwitchboardMenuEntry.cpp – line 181
  • OnLaunchSwitchboardClicked函数:

    void OnLaunchSwitchboardClicked()
    	{
    		FString SwitchboardPath = GetDefault<USwitchboardEditorSettings>()->SwitchboardPath.Path;
    		if (SwitchboardPath.IsEmpty())
    		{
    			SwitchboardPath = FPaths::ConvertRelativePathToFull(FPaths::EnginePluginsDir() + FString(TEXT("VirtualProduction")));
    			SwitchboardPath /= FString(TEXT("Switchboard")) / FString(TEXT("Source")) / FString(TEXT("Switchboard"));
    		}
    
    #if PLATFORM_WINDOWS
    		FString Executable = SwitchboardPath / TEXT("switchboard.bat");
    #elif PLATFORM_LINUX
    		FString Executable = SwitchboardPath / TEXT("switchboard.sh");
    #endif
    		FString Args = GetDefault<USwitchboardEditorSettings>()->CommandlineArguments;
    
    		const FString PythonPath = GetDefault<USwitchboardEditorSettings>()->PythonInterpreterPath.FilePath;
    		if (!PythonPath.IsEmpty())
    		{
    			Executable = PythonPath;
    			Args.InsertAt(0, TEXT("-m switchboard "));
    		}
    		
    		// !!! 在这里创建进程 !!!
    		if (RunProcess(Executable, Args, SwitchboardPath))
    		{
    			UE_LOG(LogSwitchboardPlugin, Display, TEXT("Successfully started Switchboard from %s"), *SwitchboardPath);
    		}
    		else
    		{
    			const FString ErrorMsg = TEXT("Unable to start Switchboard! Check the log for details.");
    			FPlatformMisc::MessageBoxExt(EAppMsgType::Ok, *ErrorMsg, TEXT("Error starting Switchboard"));
    		}
    	}
    

    RunProcess函数:

    bool RunProcess(const FString& InExe, const FString& InArgs, const FString& InWorkingDirectory = TEXT(""))
    	{
    		const bool bLaunchDetached = false;
    		const bool bLaunchHidden = false;
    		const bool bLaunchReallyHidden = false;
    		const int32 PriorityModifier = 0;
    		const TCHAR* WorkingDirectory = InWorkingDirectory.IsEmpty() ? nullptr : *InWorkingDirectory;
    		uint32 PID = 0;
    		
    		// !!! 在这里创建进程 !!!
    		FProcHandle Handle = FPlatformProcess::CreateProc(*InExe, *InArgs, bLaunchDetached, bLaunchHidden, bLaunchReallyHidden, &PID, PriorityModifier, WorkingDirectory, nullptr);
    		
    		int32 RetCode;
    		if (FPlatformProcess::GetProcReturnCode(Handle, &RetCode))
    		{
    			return RetCode == 0;
    		}
    		return FPlatformProcess::IsProcRunning(Handle);
    	}
    

    FWindowsPlatformProcess::CreateProc函数

    FProcHandle FWindowsPlatformProcess::CreateProc( const TCHAR* URL, const TCHAR* Parms, bool bLaunchDetached, bool bLaunchHidden, bool bLaunchReallyHidden, uint32* OutProcessID, int32 PriorityModifier, const TCHAR* OptionalWorkingDirectory, void* PipeWriteChild, void * PipeReadChild)
    {
    	//UE_LOG(LogWindows, Log,  TEXT("CreateProc %s %s"), URL, Parms );
    
    	// initialize process creation flags
    	uint32 CreateFlags = NORMAL_PRIORITY_CLASS;
    	if (PriorityModifier < 0)
    	{
    		CreateFlags = (PriorityModifier == -1) ? BELOW_NORMAL_PRIORITY_CLASS : IDLE_PRIORITY_CLASS;
    	}
    	else if (PriorityModifier > 0)
    	{
    		CreateFlags = (PriorityModifier == 1) ? ABOVE_NORMAL_PRIORITY_CLASS : HIGH_PRIORITY_CLASS;
    	}
    
    	if (bLaunchDetached)
    	{
    		CreateFlags |= DETACHED_PROCESS;
    	}
    
    	// initialize window flags
    	uint32 dwFlags = 0;
    	uint16 ShowWindowFlags = SW_HIDE;
    	if (bLaunchReallyHidden)
    	{
    		dwFlags = STARTF_USESHOWWINDOW;
    	}
    	else if (bLaunchHidden)
    	{
    		dwFlags = STARTF_USESHOWWINDOW;
    		ShowWindowFlags = SW_SHOWMINNOACTIVE;
    	}
    
    	if (PipeWriteChild != nullptr || PipeReadChild != nullptr)
    	{
    		dwFlags |= STARTF_USESTDHANDLES;
    	}
    
    	// initialize startup info
    	STARTUPINFO StartupInfo = {
    		sizeof(STARTUPINFO),
    		NULL, NULL, NULL,
    		(::DWORD)CW_USEDEFAULT,
    		(::DWORD)CW_USEDEFAULT,
    		(::DWORD)CW_USEDEFAULT,
    		(::DWORD)CW_USEDEFAULT,
    		(::DWORD)0, (::DWORD)0, (::DWORD)0,
    		(::DWORD)dwFlags,
    		ShowWindowFlags,
    		0, NULL,
    		HANDLE(PipeReadChild),
    		HANDLE(PipeWriteChild),
    		HANDLE(PipeWriteChild)
    	};
    
    	bool bInheritHandles = (dwFlags & STARTF_USESTDHANDLES) != 0;
    
    	// create the child process
    	FString CommandLine = FString::Printf(TEXT("\"%s\" %s"), URL, Parms);
    	PROCESS_INFORMATION ProcInfo;
    
    	// !!! 在这里创建进程 !!!
    	if (!CreateProcess(NULL, CommandLine.GetCharArray().GetData(), nullptr, nullptr, bInheritHandles, (::DWORD)CreateFlags, NULL, OptionalWorkingDirectory, &StartupInfo, &ProcInfo))
    	{
    		DWORD ErrorCode = GetLastError();
    
    		TCHAR ErrorMessage[512];
    		FWindowsPlatformMisc::GetSystemErrorMessage(ErrorMessage, 512, ErrorCode);
    
    		UE_LOG(LogWindows, Warning, TEXT("CreateProc failed: %s (0x%08x)"), ErrorMessage, ErrorCode);
    		if (ErrorCode == ERROR_NOT_ENOUGH_MEMORY || ErrorCode == ERROR_OUTOFMEMORY)
    		{
    			// These errors are common enough that we want some available memory information
    			FPlatformMemoryStats Stats = FPlatformMemory::GetStats();
    			UE_LOG(LogWindows, Warning, TEXT("Mem used: %.2f MB, OS Free %.2f MB"), Stats.UsedPhysical / 1048576.0f, Stats.AvailablePhysical / 1048576.0f);
    		}
    		UE_LOG(LogWindows, Warning, TEXT("URL: %s %s"), URL, Parms);
    		if (OutProcessID != nullptr)
    		{
    			*OutProcessID = 0;
    		}
    
    		return FProcHandle();
    	}
    
    	if (OutProcessID != nullptr)
    	{
    		*OutProcessID = ProcInfo.dwProcessId;
    	}
    
    	::CloseHandle( ProcInfo.hThread );
    
    	return FProcHandle(ProcInfo.hProcess);
    }
    

    来源:wyk023

    物联沃分享整理
    物联沃-IOTWORD物联网 » C++调用python的方法

    发表评论