使用 C++ 项目 (Visual Studio) 中的参数调用 Python 函数

Posted

技术标签:

【中文标题】使用 C++ 项目 (Visual Studio) 中的参数调用 Python 函数【英文标题】:Calling Python function with parametrs from C++ project (Visual Studio) 【发布时间】: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 函数应将输入字符串写入文本文件,处理此文件,然后读取结果并将其返回。我的代码中的缩进是正确的,不幸的是我在这里写的时候出错了,谢谢。 【参考方案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) 语句以避免内存泄漏

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

查看[SO]: LNK2005 Error in CLR Windows Form (@CristiFati's answer) 了解构建Win PE的详细信息 为了在构建依赖于 Python 的项目时加快进程,我创建了一个 VStudio 用户宏(例如称为 Python35Dir - 就像在下图,指向我的 Python 3.5 安装目录)

注意事项

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

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

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

[SO]: When using fstream in a library I get linker errors in the executable (@CristiFati's answer) [SO]: Errors when linking to protobuf 3 on MS Visual C (@CristiFati's answer) 对于 exception 情况,我在 Debug 模式下构建了 Python 并获得了二进制文件,但这不是我的 1st 选择,因为它需要更改设置(路径)

编译:

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

最后说明

在这种情况下(简单的),使用了安装路径中的 PYTHONCORE。但在其他的(例如在另一个产品中提供)中,Python 的标准库路径必须在 Py_Initialize 之前设置,通过 Py_SetPythonHome。 虽然它很旧,但[SO]: What files are required for Py_Initialize to run? (@CristiFati's answer) 可能包含一些有用的信息

【讨论】:

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

以上是关于使用 C++ 项目 (Visual Studio) 中的参数调用 Python 函数的主要内容,如果未能解决你的问题,请参考以下文章

在 Visual Studio C++ 项目中使用 cusp

使用 Visual Studio 2010 将 libexif 编译为静态库 - 然后从 Visual C++ 项目链接

使用 Visual Studio 2012 Profiler 分析 C++

使用 C++ 项目 (Visual Studio) 中的参数调用 Python 函数

在 Visual Studio 2017 中使用 64 位 Visual C++ 工具集

在 Visual Studio 2010 中从 .NET 项目启动 C++ 项目