一、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++类型 对应表

FormatCodePythonC++
sstrchar*
zstr / Nonechar* / Null
iintint
llonglong
cstrchar
dfloatdouble
DcomplexPy_Complex*
O(any)PyObject*
SstrPyStringObject*

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);
}
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐