使用来自C ++项目(Visual Studio)的参数调用Python函数 [英] Calling Python function with parametrs from C++ project (Visual Studio)

查看:76
本文介绍了使用来自C ++项目(Visual Studio)的参数调用Python函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要在Windows平台下从我的C ++项目中调用实现为Python(3.6)函数的管道.文件" experiment_test.py "中的函数" function_name "以文本字符串作为输入参数,并返回另一个文本字符串作为结果.我尝试使用以下代码,但无法正常运行–库 shutil 编解码器 makedirs 等中的python函数无法正常工作.

C ++代码(精简版):

  std :: string文本,结果;PyObject * pName,* pModule,* pDict,* pFunc,* pArgs,* pValue;Py_Initialize();pName = PyUnicode_FromString("experiment_test");pModule = PyImport_Import(pName);pDict = PyModule_GetDict(pModule);pFunc = PyDict_GetItemString(pDict,"function_name");pArgs = PyTuple_New(1);pValue = PyUnicode_FromString(Text.c_str());PyTuple_SetItem(pArgs,0,pValue);如果(PyCallable_Check(pFunc)){pValue = PyObject_CallObject(pFunc,pArgs);如果(pValue!= NULL){结果= PyUnicode_AsUTF8(pValue);Py_DECREF(pValue);}否则返回假;}//...Py_Finalize(); 

Python代码(精简版):

 #!/usr/local/bin/python3进口壁垒导入编解码器从os import makedirs从os导入路径从操作系统导入取消链接从子流程导入调用def function_name():名称=工作文件/当前文本"如果不是path.exists('working_files'):makedirs('working_files')如果path.exists('result.txt'):取消链接('result.txt')使用codecs.open(name +'.txt','w',encoding ='utf-8')为f:f.write(文字)#...返回结果 

因此Python不会生成任何新文件.我尝试通过在 Py_Initialize(); 之后调用 PyRun_SimpleString("import shutil"); 等来导入C ++中的Python模块,但这无济于事.

我该怎么办?

解决方案

我尝试用给定的intel复制问题,但这是不可能的,所以我创建了一个小示例(尽可能接近问题中的描述)-也称为

注释:

  • 路径(" c:\ Install \ x64 \ Python \ Python \ 3.5 ")指向从官方网站下载的安装

  • 显然,对于 32bit ,必须相应设置路径(至 32bit Python )

  • 此路径包含(如预期的那样) Release 版本,只要我不需要进入 Python 代码(并且只要我不搞乱内存,例如(在 Debug 模式下构建应用程序时),我的 .exe中就有2个 C运行时em>-检查以下链接以查看篡改 MSVC运行时( UCRT s))会发生什么:

    • 链接:

      VStudio 了解 Python lib文件的位置(如果只有 pythonxx * .lib ( PYTHONCORE )是必需的,不需要任何额外的操作,因为 Python 代码默认包含 PYTHONCORE ;否则,所有其余部分都应在

    • 运行/调试-让:

      • VStudio 知道 Python 运行时 python35.dll ( PYTHONCORE )的位置位于(%PATH%)
      • 已加载的 Python 运行时知道其他模块的位置(%PYTHONPATH%)

experiment_test.py :

  import os进口壁垒导入编解码器def function_name(文件名):print("Py-arg:'{}'''.format(file_name))如果不是os.path.isfile(file_name):返回文件名将open(file_name,"rb")设为f:内容= f.read().decode()print("Py-Content len:{},Content(可以跨多行):'{}'".format(len(content),content))返回内容 

注释:

  • 几乎是虚拟的模块,如开头所指定的
  • 仅对 text 文件有效(对于 binary 文件, decode 将会失败)
  • 导入未使用的模块,以确保它们正常(很明显,如果这样的 import 语句成功,则所有模块都应如此)
  • stdout 上打印一些数据(以与 C ++ 端的内容匹配)
  • 位于 Python 已知的路径(上一步中的%PYTHONPATH%)
  • 具有1个参数( file_name )-与该问题中没有任何参数的参数相比,关键差异(不知道这是逻辑上的错误还是 typo 一样)

test_dir \ test_file.txt :

 第0行-虚拟第1行-gainarie 

输出( VStudio 控制台):

  Py-arg:'test_dir \ test_file.txt'Py-内容len:33,内容(可以跨多行显示):'第0行-虚拟第1行-gainarie'C(++)-Python调用:experiment_test.function_name('test_dir \ test_file.txt')返回第0行-虚拟第1行-gainarie'(len:33)好的按ENTER返回... 

最后的记录:

  • 在这种情况下(简单的一种),使用安装路径中的 PYTHONCORE .但是在其他版本中(另一产品中随附的 eg ),必须在 Py_Initialize 之前通过 Python 设置标准库路径,em> Py_SetPythonHome .
    虽然很旧,但 [SO]:运行Py_Initialize需要哪些文件?(@CristiFati的答案)可能包含一些有用的信息

I need to call a pipeline realized as a Python (3.6) function from my C++ project under the Windows platform. Function "function_name" from file "experiment_test.py" takes text string as input parameter and return another text string as the result. I try the code below but it doesn’t work properly – python functions from libraries shutil, codecs, makedirs, etc doesn’t work.

C++ code (reduced):

std::string Text,Result;
PyObject *pName, *pModule, *pDict, *pFunc, *pArgs, *pValue;
Py_Initialize();

pName = PyUnicode_FromString("experiment_test");
pModule = PyImport_Import(pName);    
pDict = PyModule_GetDict(pModule);

pFunc = PyDict_GetItemString(pDict, "function_name");

pArgs = PyTuple_New(1);
pValue = PyUnicode_FromString(Text.c_str());
PyTuple_SetItem(pArgs, 0, pValue);

if (PyCallable_Check(pFunc))
{
    pValue = PyObject_CallObject(pFunc, pArgs);
    if (pValue != NULL)
    {
        Result = PyUnicode_AsUTF8(pValue);    
        Py_DECREF(pValue);
    }
    else return false;    
}
// ...

Py_Finalize();

Python code (reduced):

#!/usr/local/bin/python3
import shutil
import codecs
from os import makedirs
from os import path
from os import unlink
from subprocess import call

def function_name():

    name = 'working_files/current_text'

    if not path.exists('working_files'):
        makedirs('working_files')
    if path.exists('result.txt'):
        unlink('result.txt')
    with codecs.open(name + '.txt', 'w', encoding='utf-8') as f:
        f.write(text)
    # ...
    return result

So no new files will be generated by Python. I tried to import Python modules in C++ by calling PyRun_SimpleString("import shutil"); etc after Py_Initialize(); but it doesn’t help.

What do I do wrong?

解决方案

I tried replicating the problem with the given intel, but it was impossible, so I created a small example (as close as possible to what's described in the question) - Also referred to as [SO]: How to create a Minimal, Reproducible Example (reprex (mcve)) (that should be included in the question BTW)

So, the problem that I'm illustrating here, is:

  • C++
    • Load the Python engine
    • Load a Python module
    • From that module, load a function which:
      • Receives a (string) argument representing a file name
      • Reads the file contents (text) and returns it
      • In case of error, simply returns the file name
    • Call that function
    • Get the function call result

I am using (on Win 10 x64 (10.0.16299.125)):

  • Python 3.5.4 x64
  • VStudio 2015 Community Edition

The structure consists of:

  • VStudio project / solution
    • Source file (main00.cpp (renamed it from main.cpp, but didn't feel like doing all the screenshots (containing it) all over again))
  • Python module (experiment_test.py)
  • Test file (test_file.txt)

main00.cpp:

#include <string>
#include <iostream>

#if defined(_DEBUG)
#  undef _DEBUG
#  define _DEBUG_UNDEFINED
#endif
#include <Python.h>
#if defined(_DEBUG_UNDEFINED)
#  define _DEBUG
#  undef _DEBUG_UNDEFINED
#endif

#define MOD_NAME "experiment_test"
#define FUNC_NAME "function_name"
#define TEST_FILE_NAME "test_dir\\test_file.txt"

using std::cout;
using std::cin;
using std::endl;
using std::string;


int cleanup(const string &text = string(), int exitCode = 1) {
    Py_Finalize();
    if (!text.empty())
        cout << text << endl;
    cout << "Press ENTER to return...\n";
    cin.get();
    return exitCode;
}


int main() {
    char c;
    string fName = TEST_FILE_NAME, result;
    PyObject *pName = NULL, *pModule = NULL, *pDict = NULL, *pFunc = NULL, *pArgs = NULL, *pValue = NULL, *pResult = NULL;
    Py_Initialize();
    pName = PyUnicode_FromString(MOD_NAME);
    if (pName == NULL) {
        return cleanup("PyUnicode_FromString returned NULL");
    }
    pModule = PyImport_Import(pName);
    Py_DECREF(pName);
    if (pModule == NULL) {
        return cleanup(string("NULL module: '") + MOD_NAME + "'");
    }
    pDict = PyModule_GetDict(pModule);
    if (pDict == NULL) {
        return cleanup("NULL module dict");
    }
    pFunc = PyDict_GetItemString(pDict, FUNC_NAME);
    if (pFunc == NULL) {
        return cleanup(string("module '") + MOD_NAME + "' doesn't export func '" + FUNC_NAME + "'");
    }
    pArgs = PyTuple_New(1);
    if (pArgs == NULL) {
        return cleanup("NULL tuple returned");
    }
    pValue = PyUnicode_FromString(fName.c_str());
    if (pValue == NULL) {
        Py_DECREF(pArgs);
        return cleanup("PyUnicode_FromString(2) returned NULL");
    }
    int setItemResult = PyTuple_SetItem(pArgs, 0, pValue);
    if (setItemResult) {
        Py_DECREF(pValue);
        Py_DECREF(pArgs);
        return cleanup("PyTuple_SetItem returned " + setItemResult);
    }
    pResult = PyObject_CallObject(pFunc, pArgs);
    Py_DECREF(pArgs);
    Py_DECREF(pValue);
    if (pResult == NULL) {
        return cleanup("PyObject_CallObject returned NULL");
    } else {
        int len = ((PyASCIIObject *)(pResult))->length;
        char *res = PyUnicode_AsUTF8(pResult);
        Py_DECREF(pResult);
        if (res == NULL) {
            return cleanup("PyUnicode_AsUTF8 returned NULL");
        } else {
            cout << string("C(++) - Python call: ") << MOD_NAME << "." << FUNC_NAME << "('" << fName << "') returned '" << res << "' (len: " << len << ")" << endl;
        }
    }
    return cleanup("OK", 0);
}

Notes:

  • The _DEBUG / _DEBUG_UNDEFINED stuff at the beginning - a (lame) workaround (gainarie) to link against Release Python lib (python35.lib) when building in Debug mode (as opposed to python35_d.lib) - read below

  • As I said, tried to simplify the code (got rid of the PyCallable_Check test)

  • It's easily noticeable that the code is written in C style, although it uses the C++ compiler

  • Since Python API ([Python.Docs]: Embedding Python in Another Application) (both extending / embedding) uses pointers, make sure to test for NULLs, otherwise there's a high chance getting segfault (Access Violation)

  • Added the [Python.Docs]: Reference Counting - void Py_DECREF(PyObject *o) statements to avoid memory leaks

  • Build (compile / link) / Run options (obviously, you got past these, since you were able to run your program, but I'm going to list them anyway - for sure there are some shortcuts here, when dealing with more than one such project):

    Notes:

    • The path ("c:\Install\x64\Python\Python\3.5") points to the installation downloaded from the official site

    • Obviously, for 32bit, the path must be set accordingly (to 32bit Python)

    • This path contains (as expected) a Release version, and this is fine as long as I don't need to get into Python code (and as long as I don't mess around with memory - as (when building my app in Debug mode) I have 2 C runtimes in my .exe - check the links below to see what happens when tampering with MSVC runtimes (UCRTs)):

    • Compile:

      Let VStudio know about the Python include files location:

    • Link:

      Let VStudio know about the Python lib files location (if only pythonxx*.lib (PYTHONCORE) is required, nothing extra needed, since PYTHONCORE is included by default by Python code; otherwise, all the rest should be specified in the [MS.Docs]: .Lib Files as Linker Input:

    • Run / Debug - let:

      • VStudio know where Python runtime python35.dll (PYTHONCORE) is located (%PATH%)
      • Loaded Python runtime know where additional modules are located (%PYTHONPATH%)

experiment_test.py:

import os
import shutil
import codecs


def function_name(file_name):
    print("Py - arg: '{}'".format(file_name))
    if not os.path.isfile(file_name):
        return file_name
    with open(file_name, "rb") as f:
        content = f.read().decode()
        print("Py - Content len: {}, Content (can spread across multiple lines): '{}'".format(len(content), content))
        return content

Notes:

  • An almost dummy module, as specified at the beginning
  • Works only with text files (decode will fail for binary files)
  • Imports modules that aren't used, to see that they are OK (that's obvious, if one such import statement succeeds, all should)
  • Prints some data on stdout (to be matched with what's on the C++ side)
  • Located in a path known by Python (%PYTHONPATH% from previous step)
  • Has 1 argument (file_name) - crucial difference compared to the one in the question which doesn't have any (don't know whether that's a logical mistake or a typo like one)

test_dir\test_file.txt:

line 0 - dummy
line 1 - gainarie

Output (VStudio console):

Py - arg: 'test_dir\test_file.txt'
Py - Content len: 33, Content (can spread across multiple lines): 'line 0 - dummy
line 1 - gainarie'
C(++) - Python call: experiment_test.function_name('test_dir\test_file.txt') returned 'line 0 - dummy
line 1 - gainarie' (len: 33)
OK
Press ENTER to return...

Final note:

这篇关于使用来自C ++项目(Visual Studio)的参数调用Python函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆