从C++项目调用带参数的Python函数(Visual Studio)

inkz8wg9  于 2023-05-24  发布在  Python
关注(0)|答案(1)|浏览(175)

我需要在Windows平台下从我的C项目中调用一个实现为Python(3.6)函数的管道。文件“experiment_test.py”中的函数“function_name”将文本字符串作为输入参数,并返回另一个文本字符串作为结果。我尝试了下面的代码,但它不能正常工作-来自库shutilcodecsmakedirs等的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不会生成新文件。我尝试在C++中通过调用**PyRun_SimpleString(“import shutil”)导入Python模块;etc afterPy_Initialize();”””但它没有帮助。
我做错了什么?

3pmvbmvn

3pmvbmvn1#

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

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

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

    • Python3.5.4x64 *
    • VStudio 2015社区版 *

结构包括:

    • VStudio * 项目/解决方案
  • 源文件(* main00.cpp *(从 * main.cpp * 重命名,但不想重新做所有的屏幕截图(包含它)))
    • Python * 模块(* experiment_test. py *)
  • 测试文件(* test_file. txt *)
  • main00.cpp *:
#include <iostream>
#include <string>

#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() {
    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 * 内容-在 * Debug * 模式下构建时链接 * Release**Python * lib( python35.lib )的(蹩脚)解决方案( gainarie *)(与 * python35_d. lib * 相反)-请阅读下文
  • 正如我所说,尝试简化代码(摆脱 * PyCallable_Check * 测试)
  • 很容易注意到代码是用***C***风格编写的,尽管它使用了***C++***编译器
  • 由于 * Python API *([Python.Docs]: Embedding Python in Another Application)(扩展/嵌入)使用指针,请确保测试 * NULL * s,否则很有可能出现 * SegFault Access Violation *)
  • 新增了[Python. Docs]:引用计数-void Py_DECREF(PyObject * o)语句以避免内存泄漏
  • Build(compile/link)/Run选项(显然,你已经越过了这些,因为你能够运行你的程序,但我还是要列出它们-当然这里有一些快捷方式,当处理多个这样的项目时):
  • 有关构建 * Win PE * 的详细信息,请查看[SO]: LNK2005 Error in CLR Windows Form (@CristiFati's answer)
  • 为了在构建依赖于 * Python * 的项目时加快过程,我创建了一个 * VStudio用户宏 (称为e.g. Python35Dir *-类似于下图,指向我的 * Python 3.5 * 安装 * 目录 *)

    • 备注**:
  • 路径("* c:\Install\x64\Python\Python\3.5 *")指向从官方网站下载的安装
  • 显然,对于 * 032bit *,必须相应地设置路径(到 * 32bit**Python *)
  • 此路径包含(如预期)* Release * 版本,只要我不需要进入 * Python * 代码(并且只要我不浪费内存-因为(当在 * Debug * 模式下构建应用程序时)我的 *. exe * 中有2个 * C运行时 *-检查下面的链接以查看篡改 * MSVC运行时 UCRT * s)时会发生什么),这就很好:
  • [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)
  • 对于 * 例外 * 情况,我在 * Debug * 模式下构建了 * Python * 并获得了二进制文件,但这不是我的第一选择,因为它需要更改设置(路径)
  • 编译:

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

  • 联系方式:

让 * VStudio * 知道 * Python * lib文件的位置(如果只需要 * pythonxx *. lib *(PYTHONCORE),则不需要任何额外的东西,因为 * Python * 代码默认包含 * PYTHONCORE *;否则,其余的都应该在[MS.Learn]: .Lib Files as Linker Input中指定:

  • 运行/调试-let:
    • VStudio * 知道 * Python * 运行时**python35.dll***( PYTHONCORE )的位置( %PATH %*)
  • 加载的 * Python * 运行时知道其他模块的位置(* %PYTHONPATH %*)

  • experiment_test. py *:
import codecs
import os
import shutil

def function_name(file_name):
    print("Py - arg: '{:s}'".format(file_name))
    if not os.path.isfile(file_name):
        return file_name
    with open(file_name, mode="rb") as f:
        content = f.read().decode()
        print("Py - Content len: {:d}, Content (can spread across multiple lines): '{:s}'".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_SetPythonHome***设置在 * Py_Initialize * 之前。

虽然它很旧,但[SO]: What files are required for Py_Initialize to run? (@CristiFati's answer)可能包含一些有用的信息

相关问题