wlav / cppyy

Other
402 stars 41 forks source link

Easy way to unwrap a `PyObject *`? #88

Closed torokati44 closed 1 year ago

torokati44 commented 2 years ago

I have an embedded CPython interpreter in a C++ program. Within that interpreter, I'm using Cppyy to allow interaction between the exec-ed Python scripts and the native parts of the application. Sometimes I want an evaluated Python expression to return a C++ object, but I can't get to it from the PyObject * of the wrapper returned by the interpreter.

See this little example (I had to split getWords() into a separate library otherwise the symbol was not found by Cppyy... :/ ):


words.h:

#include <vector>
#include <string>

std::vector<std::string> getWords();

words.cc:

#include "words.h"

std::vector<std::string> getWords() {
    return {"Hello", "World"};
}

main.cc:

#include <iostream>
#include <Python.h>

#include "words.h"

const char *CODE = R"(
import cppyy
cppyy.cppdef('#include "words.h"')
)";

int main() {
    Py_Initialize();

    PyObject *globals = PyDict_New();
    PyDict_SetItemString(globals, "__builtins__", PyEval_GetBuiltins());
    PyRun_String(CODE, Py_file_input, globals, globals);

    PyObject *compiled = Py_CompileString("cppyy.gbl.getWords()", "<string>", Py_eval_input);

    PyObject *result = PyEval_EvalCode(compiled, globals, globals);

    std::cout << PyUnicode_AsUTF8(PyObject_Str(result)) << std::endl;
    std::cout << PyUnicode_AsUTF8(PyObject_Str(PyObject_Type(result))) << std::endl;

    // Can I get a `std::vector<std::string> *` from `result` here?

    Py_Finalize();
}

Compile and run on Linux like this:

clang++ -shared -fPIC words.cc -o libwords.so
clang++ main.cc -L . -rpath . -l words  $(pkg-config --libs --cflags python3-embed) -o cppyy_unwrap
./cppyy_unwrap
torokati44 commented 2 years ago

My current solution involves invoking a "converter" Python function which recognizes a number of wrapped C++ types, and extracts the data from them recursively into regular Python primitives, strings, lists, dictionaries, etc. - which I can then access through their PyObject * from C++ via the appropriate CPython API for each type, and convert them back to C++ objects.

This works okay for my purposes, but is not nice, and involves an unnecessary back-and-forth conversion.

wlav commented 2 years ago

See CPyCppyy/API.h, using Instance_Check to check it's a cppyy bound instance, then Instance_AsVoidPtr to extract the pointer. (It does still subsequently require a cast on the C++ side to the actual type, but the pointer will have the correct offset to the type Python thinks it has.)

wlav commented 1 year ago

Closing as presumed clarified. If that's not the case, feel free to reopen or start a new issue.