pthom / litgen

litgen: a pybind11 automatic generator for humans who like nice code and API documentation. Also a C++ transformer tool
https://pthom.github.io/litgen
GNU General Public License v3.0
36 stars 3 forks source link

Issue with functions that have function pointers as parameters #10

Open renautomation opened 2 months ago

renautomation commented 2 months ago

In my code, I have stuff like this:

typedef retCode_t (*netReqCallback_t)(const netReq_t event,
                                                             const std::string req,
                                                             const std::string data,
                                                             const int32_t n);

...

class MyClassGateway {
public:
...
    retCode_t regNetReqCallback(const netReqCallback_t callback);

Now, when I use litgen to expose regNetReqCallback to Python, the build is successful but in Python I get the following error:

>>> ret = regw.regNetReqCallback(network_callback)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: regNetReqCallback(): incompatible function arguments. The following argument types are supported:
    1. (self: MyClass._MyClass.MyClassGateway, callback: retCode_t (netReq_t, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int)) -> MyClass._MyClass.retCode_t

Invoked with: <MyClass._MyClass.MyClassGateway object at 0x7f30dc0091f0>, <function network_callback at 0x7f30dbfed6c0>

Did you forget to `#include <pybind11/stl.h>`? Or <pybind11/complex.h>,
<pybind11/functional.h>, <pybind11/chrono.h>, etc. Some automatic
conversions are optional and require extra headers to be included
when compiling your pybind11 module.
>>>

Now, when in the past I worked with manual pybind11 module creation, I fixed this issue by using the approach suggested here: https://stackoverflow.com/questions/74480093/pybind11-pass-c-style-function-pointer-as-a-parameter

But how can I use the same approach by using litgen?

pthom commented 2 months ago

hello, I am the author of this SO question.

Concerning your issue, you will have to manually write a wrapper that uses a std::function, and use bindings for this wrapper (you could exclude also the original one via one of the exclude options)

renautomation commented 2 months ago

ahahah I didn't note that you were the author of that question... the related solution helped me so much!

Regarding your litgen solution, is the following procedure what you are suggesting?

Procedure:

If so, how can I tell litgen to generate the code for the wrapper function specified into the pybind11 module, instead of the original function in the header file processed by litgen.write_generated_code_for_files function?

pthom commented 2 months ago

Do not add your wrapper in the cpp pybind wrapper module, add it in an additional header that you tell litgen to also parse

renautomation commented 1 month ago

ok, coming back to this topic, I created a wrapper in a separated file Wrapper.h, that wraps the class member function regNetReqCallback that has the function pointer as parameter:

retCode_t regNetReqCallbackWrapper(MyClassGateway &self, std::function<std::remove_pointer_t<netReqCallback_t>> stdCallback) {
    static std::function<std::remove_pointer_t<netReqCallback_t>> callback = std::move(stdCallback);
    return self.regNetReqCallback(
        [](const netReq_t event, 
           const std::string req, 
           const std::string data, 
           const int32_t n) -> retCode_t {
                return callback(event, req, data, n);
        });
}

After adding a couple of litgen options, it is now able to build and run on Python side, however I have to call the function like this:

ret = regNetReqCallbackWrapper(my_class_gateway_object, network_callback)

I'm wondering if there is a way for litgen, in the pybind module, to include the wrapper directly into the MyClassGateway::regNetReqCallback binding, so that in Python I could do:

ret = my_class_gateway_object.regNetReqCallbackWrapper(network_callback)

When in the past I worked with manual implementation of pybind11 module, I did it in this way:

py::class_<MyClassGateway>(m, "MyClassGateway")
    .def(py::init([]()  {
        < ... some code ... >
    }),
    .def("regNetReqCallback", [](MyClassGateway &self, std::function<std::remove_pointer_t<netReqCallback_t>> stdCallback) {
        static std::function<std::remove_pointer_t<netReqCallback_t>> callback = std::move(stdCallback);
        return self.regNetReqCallback(
            [](const netReq_t event, const std::string req, const std::string data, const int32_t n) -> retCode_t {
                return callback(event, req, data, n);
            });
    }, py::arg("callback"))

but now I don't know how to instruct litgen to do that for me (if possible). I ask also because, for another class, I have a similar situation, i.e. function pointers as parameters, directly in the class constructor, so I really would like to instruct litgen to do that.

pthom commented 1 month ago

litgen will never be able to understand

std::function<std::remove_pointer_t<netReqCallback_t>>

This is advanced C++ dark black magic

You should use std::function with concrete params.