How do you use the python3 c api for a command line driven app?

The official recommended way of converting from char to wchar_t is by using Py_DecodeLocale. Like this:

wchar_t *program = Py_DecodeLocale(argv[0], NULL);
Py_SetProgramName(program);

I spent a lot of time looking for the answer myself. I pieced together comments from other people, and built this snippet to convert char** argv to type 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);

So far, it works great. I've confirmed that the command line arguments are being received correctly by my embedded Python code.


Seems there's no easy way to do this.

The closest I've come to below. I'll leave the question open in the vague hopes someone will come along and show me the super easy and simple way to do this.

#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;
}

Using:

/** 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;
}

and for windows users, if required:

#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;
}

Tags:

C

Python 3.X