Search code examples
pythonc++pybind11

Wrapping GLFWwindow with pybind11


GLFWwindow is forward declared in glfw.h, and as such, pybind11 tells me it cant properly wrap it without a definition, for example, this:

#include <GLFW/glfw3.h>
#include <pybind11/pybind11.h>

PYBIND11_MAKE_OPAQUE(GLFWwindow);

class Application {
    GLFWwindow *window;
public:
    GLFWwindow* getWindow() {
        return window;
    }
};
namespace py = pybind11;

PYBIND11_MODULE(python_example, m) {
    py::class_<Application>(m, "Application")
    .def("window", &getWindow);
}

produces the error message:

error: invalid use of incomplete type ‘struct GLFWwindow’

I've tried a few different things with py::capsules but on the whole haven't found a solution. I pretty much need access to the window somehow, it doesn't necessarily have to be a raw pointer, but I do want to be able to pass it as an argument to things like glfwMakeContextCurrent or glfwGetKey from Python for example. So, I tried something like this:

nb::capsule get_window = []() {
    return nb::capsule(app->window);
};

void makeContextCurrent(nb::capsule a_cap) { 
    glfwMakeContextCurrent((GLFWwindow*)a_cap.data()); 
};

...

.def_prop_ro("window", []() {
    return nb::capsule((void *)app->window);
})
m.def("make_context_current", &makeContextCurrent);

But when I try to instantiate an Application and print self.window or call make_context_current(self.window), i'm getting:

print (self.window) TypeError: (): incompatible function arguments. The following argument types are supported:

  1. () -> capsule

Also, the above is nanobind, since I want to solve it with nanobind, but I figured if there was a solution in pybind11 it would also apply to nanobind.

Anyway, thanks in advance for any help or suggestions.


Solution

  • I will use simplified example here, because you don't necessarily need to use OpenGl to reproduce this problem.

    Assuming you have the forward declaration of some struct and functions that use it as an opaque ptr:

    // Opaque.h
    typedef struct MyOpaqueStr MyOpaqueStr;
    MyOpaqueStr* getStr();
    void useStr(MyOpaqueStr* str);
    

    The implementation of these functions doesn't concern us, but let's assume it's something like that for demonstration:

    #include "Opaque.h"
    
    #include <iostream>
    #include <string>
    
    struct MyOpaqueStr {
        std::string str;
    };
    
    namespace {
    MyOpaqueStr g_str{"xxx"};
    }
    
    MyOpaqueStr* getStr()
    {
        return &g_str;
    }
    
    void useStr(MyOpaqueStr* str)
    {
        std::cout << str->str << std::endl;
    }
    

    Then you can use these functions in your python bindings using python capsule (also described in pybind11 docs).

    namespace py = pybind11;
    
    PYBIND11_MODULE(test_binding, m) {
        m.doc() = "pybind11 example plugin";
        m.def("getStr", []() { return py::capsule(getStr()); }, "Gives you the opaque ptr to some struct");
        m.def("useStr", [](py::capsule cap) { useStr((MyOpaqueStr*)cap.get_pointer()); }, "Uses the opaque ptr");
    }
    

    The trick is to use lambdas that encapsulate the opaque pointer, it can be also easily applied to class methods.

    Example python usage:

    >>> import test_binding
    >>> test_binding.useStr(test_binding.getStr())
    xxx
    

    Inspired by this discussion.