【问题标题】:Calling Python function with parametrs from C++ project (Visual Studio)使用 C++ 项目 (Visual Studio) 中的参数调用 Python 函数
【发布时间】:2017-12-22 14:05:51
【问题描述】:

我需要在 Windows 平台下从我的 C++ 项目中调用实现为 Python (3.6) 函数的管道。文件“experiment_test.py”中的函数“function_name”将文本字符串作为输入参数,并返回另一个文本字符串作为结果。我尝试了下面的代码,但它不能正常工作——来自库 shutilcodecsma​​kedirs 等的 python 函数不起作用。

C++ 代码(精简):

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 代码(精简):

#!/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

因此 Python 不会生成新文件。我尝试通过在 Py_Initialize(); 之后调用 PyRun_SimpleString("import shutil"); 等在 C++ 中导入 Python 模块,但它没有帮助。

我做错了什么?

【问题讨论】:

  • 什么确切不起作用(错误、回溯等)?您是否尝试过直接运行 Python 模块(不添加 C++ 层)?请注意,您的代码在语法上不正确(缩进)。
  • shutil、编解码器、makedirs 中的 Python 函数在从 C++ 调用“function_name”时不起作用。如果我从命令行调用 Python 模块,它可以正常工作。没有错误发生 - C++ 接收到等于输入的结果。 Python 函数应将输入字符串写入文本文件,处理此文件,然后读取结果并将其返回。我的代码中的缩进是正确的,不幸的是我在这里写的时候出错了,谢谢。

标签: python c++


【解决方案1】:

我尝试用给定的英特尔复制问题,但这是不可能的,所以我创建了一个小例子(尽可能接近问题中描述的内容) - 也称为[SO]: How to create a Minimal, Reproducible Example (reprex (mcve))(应该包含在问题顺便说一句

所以,我在这里说明的问题是:

  • C++
    • 加载 Python 引擎
    • 加载一个 Python 模块
    • 从该模块中,加载一个函数:
      • 接收代表文件名的(字符串)参数
      • 读取文件内容(文本)并返回
      • 如果出错,只返回文件名
    • 调用该函数
    • 获取函数调用结果

我正在使用(Win 10 x64 (10.0.16299.125)):

  • Python 3.5.4 x64
  • VStudio 2015 社区版

结构组成:

  • VStudio 项目/解决方案
    • 源文件(main00.cpp重命名来自 main.cpp,但不想做所有的截图(包含它)从头再来))
  • Python 模块 (experiment_test.py)
  • 测试文件 (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);
}

注意事项

  • _DEBUG / _DEBUG_UNDEFINED 开头的东西 - 一个(蹩脚的)解决方法(gainarie)链接到 Release em> Python lib (python35.lib) 在 Debug 模式下构建时(相对于 python35_d.lib) - 阅读下文

  • 正如我所说,试图简化代码(摆脱了 PyCallable_Check 测试)

  • 很容易注意到代码是用 C 风格编写的,尽管它使用 C++ 编译器

  • 由于Python API ([Python.Docs]: Embedding Python in Another Application)(扩展/嵌入)使用指针,请确保测试NULLs,否则很有可能得到 segfault访问冲突

  • 添加了[Python.Docs]: Reference Counting - void Py_DECREF(PyObject *o) 语句以避免内存泄漏

  • 构建(编译/链接)/运行选项(显然,您已经通过了这些,因为您能够运行您的程序,但无论如何我都会列出它们 - 当然这里有一些快捷方式,当处理多个这样的项目):

    注意事项

    • 路径(“c:\Install\x64\Python\Python\3.5”)指向官网下载的安装

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

    • 此路径包含(如预期)一个 Release 版本,只要我不需要进入 Python 代码(并且只要我不会乱用内存 - 因为(在 Debug 模式下构建我的应用程序时)我的 .exe 中有 2 个 C 运行时 -检查下面的链接以了解篡改 MSVC 运行时 (UCRTs) 时会发生什么:

    • 编译:

      VStudio知道Python包含文件的位置:

    • 链接:

      VStudio 知道 Python lib 文件的位置(如果只有 pythonxx*.lib (PYTHONCORE) 是必需的,不需要额外的,因为 PYTHONCORE 默认包含在 Python 代码中;否则,应在 @987654329 中指定所有其余部分@:

    • 运行/调试 - 让:

      • VStudio 知道 Python 运行时 python35.dll (PYTHONCORE) 的位置(%PATH%)
      • 加载的 Python 运行时知道其他模块的位置 (%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

注意事项

  • 一个几乎是虚拟的模块,如开头所述
  • 仅适用于文本文件解码将失败二进制文件)
  • 导入未使用的模块,以查看它们是否正常(很明显,如果一个这样的 import 语句成功,则都应该)
  • stdout 上打印一些数据(与 C++ 端的内容相匹配)
  • 位于 Python 已知的路径中(上一步的%PYTHONPATH%
  • 有 1 个参数 (file_name) - 关键区别与问题中没有任何参数的参数相比(不知道这是逻辑错误还是一个错字就像一个)

test_dir\test_file.txt

line 0 - dummy
line 1 - gainarie

输出VStudio控制台):

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...

最后说明

【讨论】:

  • 感谢您如此详细的回答!我试图将它与我的代码一起使用,但 python 模块今天无法加载。我想,如果我找不到我的错误,我会尝试在我的机器上使用你的代码。
  • 你有机会测试吗(不仅仅是代码,还有构建过程)?如果您在执行 Python 代码时仍然遇到问题,请将 experiment_test.py 中的所有代码包装在 try/except 子句中并打印异常。
  • 抱歉让您久等了 - 我的代码当时不起作用。
  • 现在构建过程的差异真的很有趣。今天我开始重建您的解决方案(使用我的软件版本) - 但如果我在附加依赖项中编写“$(PythonDir)\libs”,我会收到链接错误。当时我停止使用“$(PythonDir)\libs\python36.lib”,稍后会继续。我会写下结果。
  • 但是您在 User Macros 下设置了 $(PythonDir) 以获得指向正确路径的正确架构,对吧?
猜你喜欢
  • 2020-10-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-13
  • 1970-01-01
相关资源
最近更新 更多