C++调用python的方法
一、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识别
变量转化
执行
返回值解析
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脚本绝对路径及其工作路径,并创建进程
- UI界面点击Switchboard按钮,触发响应函数OnLaunchSwitchboardClicked
- OnLaunchSwitchboardClicked中获取bat脚本路径及工作路径
- OnLaunchSwitchboardClicked中调用RunProcess函数
- RunProcess中调用FPlatformProcess::CreateProc创造进程
- 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 // 新进程返回的信息
);
具体实现如下:
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