C func to C++ instance-member trampoline with non-bijective argument type mapping

爷,独闯天下 提交于 2019-12-08 09:16:19

问题


EDIT: I think I have cracked it, here

(Non bijective here means we may have arguments of type const char or char* both mapping to const std::string&.)*

NOTE: I've been working at this and asking similar questions for a few days now. Nevertheless, I will present a summary from scratch, as it may make the question a decent resource.

I have a C function pointer:

R_c (*CFunc)( void* self, A1, A2, ..., Ak )
CFunc slot = nullptr;

And an associated C++ method:

class Base {
    R_cxx f_cxx( X1, X2, ..., Xk );
}

I need to create a mechanism that forwards.

The C Library is going to invoke say x = slot(&someobject, 1, "two"), and my task is to create a slot function that will trampoline to:

slot( void* self, A1 a1, A2 a2 ) { 
    R_cxx ret = ((Base*)self)->f_cxx( toCxx<A1>(a1), toCXX<A2>(a2) );
    return toC<R_cxx>(ret);
}

The problem is I have close to 100 different slots, spanning maybe 20 different signatures. So I need to automate this.

I would start with a template containing a static function:

template< typename F_c, typename F_Cxx >
struct Generator {
    static Bla call( etc ) {...}
};

#define BIND_SLOT( F_c, F_Cxx ) &Generator<F_c,F_Cxx>::call
:

BIND_SLOT( table->slot35, Base::handler_35 );

Of course, that is semi-pseudocode. Actually the syntax is much more complicated, as you need to pass decltype(foofunc), foofunc into a template -- just foofunc alone is not sufficient (although there is some chance this will be fixed in C++17). Also an intermediate template layer is necessary for splitting a function signature into returntype, C++base and args. And a toCXX(T t) function would need to be overloaded to map all necessary A_k to X_k.

I thought I had this cracked yesterday thanks to this answer.

The solution was to construct the C function signature from f_cxx's signature. However I have since realised that this approach won't work for one annoying reason: Two different C-types map onto the same C++ type.

i.e. The slot-function may have a signature involving const char* and char*. But both of these get mapped onto 'const std::string&'. So that technique would fail the moment it comes across 'const std::string&' --it doesn't know whether to convert back to char* or const char*.

Hence I'm attempting to rewrite it, this time using the signature of the slot function, instead of the cxx member function.

However, this is extremely complex code, and I'm struggling.


回答1:


  1. The address of the slot is not a valid template parameter. I did not take the time to determine why, but in the code below I removed it from the parameter list. It was not being used in the example code.

  2. Your first decltype has an extra Table. in it which makes the expression invalid. Removing this allows all the arguments to be seen. The expression became Table.table.tp_getattr before this was removed, which was invalid.

  3. Your Return class was using the return type of the C function, but you specialized it on the return type of the C++ function.

  4. Some of your helpers are doing things like returning a reference to a temporary or returning a pointer when a reference was expected. I cleaned those up somewhat as well, but note that it leaks memory.

Updated code (compiles on g++ 4.7.3):

#include <iostream>
#include <typeinfo>
#include <utility>

struct PyObject
{
    PyObject(int i_) : i{i_} {}
    int i;
};

struct Object
{
    // avoid implicit conversion
    explicit Object(PyObject* p_) : p{p_} { 
        std::cout << "Converting PyObject: " << p->i << std::endl; 
    }

    PyObject* ptr() const {
        return p;
    }  
private:
    PyObject* p;
};

struct Table {
    typedef PyObject*   (*getattrfunc)  (PyObject *, char *     );
    typedef PyObject*   (*getattrofunc) (PyObject *, PyObject * );
    typedef int         (*setattrfunc)  (PyObject *, char *     , PyObject *);
    typedef int         (*setattrofunc) (PyObject *, PyObject * , PyObject *);

    getattrfunc tp_getattr;
    setattrfunc tp_setattr;
    getattrofunc tp_getattro;
    setattrofunc tp_setattro;
} table{};

class Base {
public:
    Object  getattr( const std::string& s )                     { std::cout << "Base::getattr" << std::endl; return Object{ new PyObject(42) }; }
    int     setattr( const std::string& s, const Object& value ){ std::cout << "Base::setattr" << std::endl; return 666; }
};

class Final : public Base {
public:
    Object getattr( const std::string& s ){ std::cout << "Final::getattr" << std::endl; return Object{ new PyObject(43) }; }
} final{};

// helpers
template<typename T, typename U>       U&& mapperC2CXX( T&& t )         { return std::forward<U&&>(t); }
template<typename T>    const std::string& mapperC2CXX(       char* t ) { return *new std::string(t); }
template<typename T>    const std::string& mapperC2CXX( const char* t ) { return *new std::string(t); }
template<typename T>    const std::string& mapperC2CXX( PyObject*   p ) { return *new Object{p}; }

template<typename T> 
struct Return {
    static T&& cvalue(T&& t) { return std::forward<T>(t); }
    static T cerr()        { return T(-1); }
};
template<>
struct Return<Object> {
    static PyObject* cvalue(const Object& ob)   { return ob.ptr(); }
    static PyObject* cerr()                     { return (PyObject*)nullptr; }
};

// function generator
template<typename Fc, typename Target, Target target>
struct Generate;

template <  typename R      , typename ...Arg       ,
            typename RTarg  , typename ...TargArg   , RTarg(Base::*target)(TargArg...) >
struct Generate< R(*)(PyObject*, Arg...)    ,
                 RTarg(Base::*)(TargArg...) , target >
{
    static Base* cxxbase_for(PyObject* pyob) {
        return (Base*)&final; // cheat for now!
    }

    static R call( PyObject* self, Arg... carg) 
    {
        try
        {
            RTarg r_cxx = (cxxbase_for(self)->*target) (mapperC2CXX<Arg>(carg)...);
            return Return<RTarg>::cvalue(r_cxx);
        }
        catch (...)
        {
            std::cout << "CAUGHT" << std::endl;
            return Return<R>::cerr();
        }
    }   
};

#define BIND(c_slot, cxx_target) c_slot = & Generate< decltype(c_slot),   decltype(&cxx_target), &cxx_target >::call;

int main()
{
    BIND( table.tp_getattr, Base::getattr );

    // test -- imagine C library fires this
    PyObject* self = (PyObject*)&final;
    PyObject* pyob = table.tp_getattr( self, (char*)"someattribute" );

    std::cout << pyob->i << std::endl;
}


来源:https://stackoverflow.com/questions/27866483/c-func-to-c-instance-member-trampoline-with-non-bijective-argument-type-mappin

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!