7

一段时间以来,我一直在使用自定义构建来替代 virtualenv,这非常棒。构建需要更长的时间,但它确实有效,而且永远不会搞砸。

其中一部分是在一个简单的 python 包装器中,它将一些特定的文件夹添加到库路径中,我发现这非常有用。它的代码很简单:

#include <stdio.h>
#include <n/text/StringUtils.h>
#include <Python.h>

int main(int argc, char *argv[]) {

  /* Setup */
  Py_SetProgramName(argv[0]);
  Py_Initialize();
  PySys_SetArgv(argc, argv);

  /* Add local path */
  PyObject *sys = PyImport_ImportModule("sys");
  PyObject *path = PyObject_GetAttrString(sys, "path");

  /* Custom path */
  char *cwd = nrealpath(argv[0]);
  char *libdir = nstrpath(cwd, "python_lib", NULL);
  PyList_Append(path, PyString_FromString(libdir));
  free(cwd);
  free(libdir);

  /* Run the 'main' module */
  int rtn = Py_Main(argc, argv); // <-- Notice the command line arguments.
  Py_Finalize();

  return rtn;
}

那么,迁移到 python3 是不是很好?所以...

我尽职尽责地将 PyString_FromString() 的调用替换为 PyByte_FromString() 并尝试重新编译,但它引发了错误:

/Users/doug/env/src/main.c:8:21: error: incompatible pointer types passing 'char *' to parameter of type 'wchar_t *' (aka 'int *')
      [-Werror,-Wincompatible-pointer-types]
  Py_SetProgramName(argv[0]);
                    ^~~~~~~
/Users/doug/projects/py-sdl2/py3/include/python3.3m/pythonrun.h:25:45: note: passing argument to parameter here
PyAPI_FUNC(void) Py_SetProgramName(wchar_t *);
                                            ^
/Users/doug/env/src/main.c:10:23: error: incompatible pointer types passing 'char **' to parameter of type 'wchar_t **' (aka 'int **')
      [-Werror,-Wincompatible-pointer-types]
  PySys_SetArgv(argc, argv);
                      ^~~~
/Users/doug/projects/py-sdl2/py3/include/python3.3m/sysmodule.h:12:47: note: passing argument to parameter here
PyAPI_FUNC(void) PySys_SetArgv(int, wchar_t **);
                                              ^
/Users/doug/env/src/main.c:24:27: error: incompatible pointer types passing 'char **' to parameter of type 'wchar_t **' (aka 'int **')
      [-Werror,-Wincompatible-pointer-types]
  int rtn = Py_Main(argc, argv);
                          ^~~~
/Users/doug/projects/py-sdl2/py3/include/python3.3m/pythonrun.h:148:45: note: passing argument to parameter 'argv' here
PyAPI_FUNC(int) Py_Main(int argc, wchar_t **argv);
                                            ^
3 errors generated.
make[2]: *** [CMakeFiles/python.dir/src/main.c.o] Error 1
make[1]: *** [CMakeFiles/python.dir/all] Error 2
make: *** [all] Error 2

从错误中可以看出,使用的是 wchar_t 而不是 char *。

你应该如何使用这个api?

我看到有一些这样做的例子,例如: http ://svn.python.org/projects/python/tags/r32rc2/Python/frozenmain.c

严重地?

我的 29 行程序必须变成一个充满#ifdefs 的 110 行怪物?

是我误会了,还是python3 c api真的变得这么难用了?

当然,我错过了一些明显的便利功能,它以一种简单、便携和跨平台的方式为你做到这一点?

4

7 回答 7

7

官方推荐的转换方式charwchar_t使用Py_DecodeLocale. 像这样:

wchar_t *program = Py_DecodeLocale(argv[0], NULL);
Py_SetProgramName(program);
于 2016-03-03T03:21:02.777 回答
3

我自己花了很多时间寻找答案。我拼凑了其他人的评论,并构建了这个片段来将 char** argv 转换为 wchar_t** 类型:

wchar_t** _argv = PyMem_Malloc(sizeof(wchar_t*)*argc);
for (int i=0; i<argc; i++) {
  wchar_t* arg = Py_DecodeLocale(argv[i], NULL);
  _argv[i] = arg;
}
Py_Initialize();
PySys_SetArgv(argc, _argv);

到目前为止,它工作得很好。我已经确认我的嵌入式 Python 代码正确接收了命令行参数。

于 2019-02-26T17:36:49.223 回答
2

似乎没有简单的方法可以做到这一点。

我在下面最接近的。我会模糊地留下这个问题,希望有人会出现并告诉我超级简单的方法来做到这一点。

#include <stdio.h>
#include <Python.h>
#include <wchar.h>

int main(int argc, char *argv[]) {

  /* These have to be wchar_t */
  char *str_program_name = argv[0];
  char **str_argv = argv;

  /* For ever stupid reason, these don't need to be wchar_t * */
  char *_sys = "sys";
  char *_libdir = "lib";
  char *_path = "path";
  char *_dot = ".";

#if PY_MAJOR_VERSION >= 3
  wchar_t **_argv = nstrws_array(argc, str_argv);
  wchar_t *_program_name = nstrws_convert(str_program_name);
#else
  char **_argv = str_argv;
  char *_program_name = str_program_name;
#endif

  /* Setup */
  Py_SetProgramName(_program_name);
  Py_Initialize();

  /* Add local path */
#if PY_MAJOR_VERSION >= 3
  PyObject *sys = PyImport_ImportModule(_sys);
  PyObject *path = PyObject_GetAttrString(sys, _path);
  PyList_Append(path, PyBytes_FromString(_dot));
  PyList_Append(path, PyBytes_FromString(_libdir));
#else
  PyObject *sys = PyImport_ImportModule(_sys);
  PyObject *path = PyObject_GetAttrString(sys, _path);
  PyList_Append(path, PyString_FromString(_dot));
  PyList_Append(path, PyString_FromString(_libdir));
#endif

  /* Run the 'main' module */
  int rtn = Py_Main(argc, _argv);
  Py_Finalize();

#if PY_MAJOR_VERSION >= 3
  nstrws_dispose(argc, _argv);
  free(_program_name);
#endif

  return rtn;
}

使用:

/** Unix-like platform char * to wchar_t conversion. */
wchar_t *nstrws_convert(char *raw) {
  wchar_t *rtn = (wchar_t *) calloc(1, (sizeof(wchar_t) * (strlen(raw) + 1)));
  setlocale(LC_ALL,"en_US.UTF-8"); // Unless you do this python 3 crashes.
  mbstowcs(rtn, raw, strlen(raw));
  return rtn;
}

/** Dispose of an array of wchar_t * */
void nstrws_dispose(int count, wchar_t ** values) {
  for (int i = 0; i < count; i++) {
    free(values[i]);
  }
  free(values);
}

/** Convert an array of strings to wchar_t * all at once. */
wchar_t **nstrws_array(int argc, char *argv[]) {
  wchar_t **rtn = (wchar_t **) calloc(argc, sizeof(wchar_t *));
  for (int i = 0; i < argc; i++) {
    rtn[i] = nstrws_convert(argv[i]);
  }
  return rtn;
}

如果需要,对于 Windows 用户:

#include <windows.h>

/** Windows char * to wchar_t conversion. */
wchar_t *nstrws_convert(char *raw) {
  int size_needed = MultiByteToWideChar(CP_UTF8, 0, raw, -1, NULL, 0);
  wchar_t *rtn = (wchar_t *) calloc(1, size_needed * sizeof(wchar_t));
  MultiByteToWideChar(CP_UTF8, 0, raw, -1, rtn, size_needed);
  return rtn;
}
于 2013-08-21T09:10:35.580 回答
1

以下代码包含Hello World嵌入 Python 的 C++ 实现。

你好世界.cpp

#include <cstdlib>
#include <stdexcept>

#include <Python.h>

int main(int argc, char* argv[])
{
    wchar_t** wargv = new wchar_t*[argc];
    
    for(int i = 0; i < argc; i++)
    {
        wargv[i] = Py_DecodeLocale(argv[i], nullptr);
        if(wargv[i] == nullptr)
        {
            return EXIT_FAILURE;
        }
    }
    
    Py_SetProgramName(wargv[0]);

    Py_Initialize();
    
    PySys_SetArgv(argc, wargv);
    
    PyRun_SimpleString("import sys");
    PyRun_SimpleString("print('Hello World!', sys.argv)");
    
    Py_Finalize();
    
    for(int i = 0; i < argc; i++)
    {
        PyMem_RawFree(wargv[i]);
        wargv[i] = nullptr;
    }

    delete[] wargv;
    wargv = nullptr;
    
    return 0;
}

安装编译的先决条件*:

$ sudo apt install build-essential pkg-config python3 python3-dev 

编译源代码

$ g++ HelloWorld.cpp `pkg-config python3-embed --libs --cflags` -o HelloWorld

运行

$ ./HelloWorld
$ # Hello World! ['./HelloWorld']
$
$ ./HelloWorld hi
$ # Hello World! ['./HelloWorld', 'hi']
于 2020-08-17T10:11:10.353 回答
0

我发现这可以在主函数中将 char* 转换为 wchar_t* :

wchar_t progname[FILENAME_MAX + 1];
mbstowcs(progname, argv[0], strlen(argv[0]) + 1);
Py_SetProgramName(progname);

如果您使用的是 unix,请使用:

#include "sys/param.h"
于 2013-12-31T01:21:59.277 回答
0

Py_DecodeLocale 返回一个指向新分配的宽字符串的指针,使用 PyMem_RawFree() 释放内存。所以不需要定义 wchar_t** _argv = PyMem_Malloc(sizeof(wchar_t*)*argc); 下面是我的代码:

#include <iostream>
#include <string>
#include "hello-time.h"
#include "main/hello-greet.h"
#include "pybind11/embed.h"

namespace py = pybind11;

int main(int argc, char** argv) {
  std::string who = "world";
  if (argc > 1) {
    who = argv[1];
  }
  std::cout << get_greet(who) << std::endl;
  print_localtime();

  int py_argc = 9;
  py::scoped_interpreter guard{};
  wchar_t* wargv[py_argc];
  std::string py_argv[py_argc] = {"convert.py",
                                  "--input",
                                  "resnet_v1_50_inference.pb",
                                  "--inputs",
                                  "input:0",
                                  "--outputs",
                                  "resnet_v1_50/predictions/Reshape_1:0",
                                  "--output",
                                  "resnet50.onnx"};
  for (int i = 0; i < py_argc; i++) {
    wargv[i] = Py_DecodeLocale(py_argv[i].c_str(), nullptr);
    if (wargv[i] == nullptr) {
      fprintf(stderr, "Fatal error: cannot decode py_argv[%d]\n", i);
      exit(1);
    }
  }

  PySys_SetArgv(py_argc, wargv);

  py::module_ sys = py::module_::import("sys");
  py::print(sys.attr("path"));
  print_localtime();
  py::object convert = py::module_::import("tf2onnx.convert");
  py::object do_convert = convert.attr("main");
  do_convert();
  py::print(py::module::import("sys").attr("argv"));

  for (int i = 0; i < py_argc; i++) {
    PyMem_RawFree(wargv[i]);
    wargv[i] = nullptr;
  }
  return 0;
}
于 2021-03-10T09:44:50.323 回答
-1

这可能是错误的做法,绝不会更少:

Py_SetProgramName((wchar_t*)argv[0]);

此修复程序阻止了我的代码抱怨,尚未对其进行测试以了解它如何处理 args,但至少它可以编译..

于 2013-08-15T05:32:35.293 回答