简体   繁体   English

使用 C++ class 会员 function 作为 C 回调 function

[英]Using a C++ class member function as a C callback function

I have a C library that needs a callback function to be registered to customize some processing.我有一个 C 库,需要注册一个回调 function 来自定义一些处理。 Type of the callback function is int a(int *, int *) .回调 function 的类型是int a(int *, int *)

I am writing C++ code similar to the following and try to register a C++ class function as the callback function:我正在编写类似于以下的 C++ 代码,并尝试注册一个 C++ class function 作为回调 function:

class A {
  public:
   A();
   ~A();
   int e(int *k, int *j);
};

A::A()
{
   register_with_library(e)
}

int
A::e(int *k, int *e)
{
  return 0;
}

A::~A() 
{

}

The compiler throws following error:编译器抛出以下错误:

In constructor 'A::A()',
error:
 argument of type ‘int (A::)(int*, int*)’ does not match ‘int (*)(int*, int*)’.

My questions:我的问题:

  1. First of all is it possible to register a C++ class member function like I am trying to do and if so how?首先,是否可以像我正在尝试的那样注册一个 C++ class 成员 function,如果可以,该怎么做? (I read 32.8 at http://www.parashift.com/c++-faq-lite/mixing-c-and-cpp.html . But in my opinion it does not solve the problem) (我在http://www.parashift.com/c++-faq-lite/mixing-c-and-cpp.html读到 32.8。但在我看来并没有解决问题)
  2. Is there a alternate/better way to tackle this?有没有替代/更好的方法来解决这个问题?

You can do that if the member function is static.如果成员 function 是 static,则可以这样做。

Non-static member functions of class A have an implicit first parameter of type class A* which corresponds to this pointer. class A 的非静态成员函数有一个类型为class A*的隐式第一个参数,它对应于这个指针。 That's why you could only register them if the signature of the callback also had the first parameter of class A* type.这就是为什么只有当回调的签名也具有class A*类型的第一个参数时才能注册它们。

You can also do this if the member function is not static, but it requires a bit more work (see also Convert C++ function pointer to c function pointer ):如果成员 function 不是 static,您也可以执行此操作,但这需要更多工作(另请参见将 C++ function 指针转换为 c function 指针):

#include <stdio.h>
#include <functional>

template <typename T>
struct Callback;

template <typename Ret, typename... Params>
struct Callback<Ret(Params...)> {
   template <typename... Args> 
   static Ret callback(Args... args) {                    
      return func(args...);  
   }
   static std::function<Ret(Params...)> func; 
};

template <typename Ret, typename... Params>
std::function<Ret(Params...)> Callback<Ret(Params...)>::func;

void register_with_library(int (*func)(int *k, int *e)) {
   int x = 0, y = 1;
   int o = func(&x, &y);
   printf("Value: %i\n", o);
}

class A {
   public:
      A();
      ~A();
      int e(int *k, int *j);
};

typedef int (*callback_t)(int*,int*);

A::A() {
   Callback<int(int*,int*)>::func = std::bind(&A::e, this, std::placeholders::_1, std::placeholders::_2);
   callback_t func = static_cast<callback_t>(Callback<int(int*,int*)>::callback);      
   register_with_library(func);      
}

int A::e(int *k, int *j) {
   return *k - *j;
}

A::~A() { }

int main() {
   A a;
}

This example is complete in the sense that it compiles:这个例子在编译的意义上是完整的:

g++ test.cpp -std=c++11 -o test

You will need the c++11 flag.您将需要c++11标志。 In the code you see that register_with_library(func) is called, where func is a static function dynamically bound to the member function e .在代码中,您会看到调用了register_with_library(func) ,其中func是动态绑定到成员 function e的 static function。

The problem is that method.= function: The compiler will transform your method to something like that:问题是那个方法。= function:编译器会将你的方法转换成这样的东西:

int e( A *this, int *k, int *j );

So, it's sure you can't pass it, because the class instance can't be passed as argument.所以,你肯定不能传递它,因为 class 实例不能作为参数传递。 One way to work around is to make the method as static, this way it would have the good type.一种解决方法是将方法设置为 static,这样它就会具有良好的类型。 But it won't any class instance, and access to non-static class members.但它不会访问任何 class 实例,并访问非静态 class 成员。

The other way is to declare a function with a static Pointer to a A initialised the first time.另一种方法是声明一个 function 和一个 static 指向第一次初始化的 A 的指针。 The function only redirect the call to the class: function 仅将呼叫重定向到 class:

int callback( int *j, int *k )
{
    static A  *obj = new A();
    a->(j, k);
}

Then you can register the callback function.然后就可以注册回调function了。

Well...if you are on a win32 platform there is always the nasty Thunking way...好吧...如果您在 win32 平台上,总会有讨厌的 Thunking 方式...

Thunking in Win32: Simplifying callbacks to non-static member functions Win32 中的 Thunking:简化对非静态成员函数的回调

It is a solution but I don't recommend using it.这是一个解决方案,但我不建议使用它。
It has a good explanation and it is nice to know it exists.它有一个很好的解释,很高兴知道它的存在。

In this solution, we have a template class with the static method to be given to the "c function" as a callback.在此解决方案中,我们有一个模板 class 和 static 方法作为回调提供给“c 函数”。 This class holds a "ordinary" object ( with a member function named callback() which will be finally called).这个 class 持有一个“普通的” object (有一个名为 callback() 的成员 function 最终会被调用)。

Once your class (here, A) is defined, it can be easily used:一旦定义了 class(此处为 A),就可以轻松使用它:

int main() {

  Holder<A> o ( A(23, 23) );

  std::cout << o().getN() << "\n";

  callACFunctionPtr( fun );

  callACFunctionPtr( o.callback );

} // ()

Complete example:完整示例:

#include <iostream>

// ----------------------------------------------------------
// library class: Holder
// ----------------------------------------------------------
template< typename HeldObjectType >
class Holder {
public:
  static inline HeldObjectType object;

  static void callback( ) {
    object.callback();
  } // ()

  HeldObjectType &  operator() ( ) {
    return object;
  }

  Holder( HeldObjectType && obj )
  {
    object = obj;
  }

  Holder() = delete;

}; // class

// ----------------------------------------------------------
// "old" C function receivin a ptr to function as a callback
// ----------------------------------------------------------
using Callback = void (*) (void);

// ..........................................................
// ..........................................................
void callACFunctionPtr( Callback f ) {
  f();
} // ()

// ----------------------------------------------------------
// ----------------------------------------------------------
void fun() {
  std::cout << "I'm fun\n";
} // 

// ----------------------------------------------------------
// 
// Common class where we want to write the
// callback to be called from callACFunctionPtr.
// Name this function: callback
// 
// ----------------------------------------------------------
class A {
private:
  int n;

public:

  A(  ) : n( 0 ) { }

  A( int a, int b ) : n( a+b ) { }

  void callback( ) {
    std::cout << "A's callback(): " << n << "\n";
  }

  int getN() {
    return n;
  }

}; // class

// ----------------------------------------------------------
// ----------------------------------------------------------
int main() {

  Holder<A> o ( A(23, 23) );

  std::cout << o().getN() << "\n";

  callACFunctionPtr( fun );

  callACFunctionPtr( o.callback );

} // ()

The problem with using a member function is that it needs an object on which to act - and C doesnt know about objects.使用成员 function 的问题是它需要一个 object 来操作——而 C 不知道对象。

The easiest way would be to do the following:最简单的方法是执行以下操作:

//In a header file:
extern "C" int e(int * k, int * e);

//In your implementation: 
int e(int * k, int * e) { return 0; }

To anyone coming accross that issue in 2022, I will offer a new approach that is compliant with the initial request.对于 2022 年遇到该问题的任何人,我将提供一种符合最初要求的新方法。 I will welcome any feedback about this solution, and hope this can help anyone coming accross that topic.我将欢迎有关此解决方案的任何反馈,并希望这可以帮助任何遇到该主题的人。

First we need to understand that the core issue -as some highlighted already- is that a non-static method (which can access object's members etc..) need to access 'this', the object's instance pointer.首先,我们需要了解核心问题——正如一些人已经强调的那样——是非静态方法(可以访问对象的成员等)需要访问“this”,即对象的实例指针。 However since we want our function to be a callback, we cannot modify the way it will be called.然而,由于我们希望我们的 function 成为回调,我们无法修改它的调用方式。 This is why we need to have a function that can access its object's 'this' pointer.这就是为什么我们需要一个 function 可以访问其对象的“this”指针的原因。

My solution is to modify a dummy function clone's code at runtime and pass its address as the callback function, which then once called will be able to resolve its assigned object pointer.我的解决方案是在运行时修改虚拟 function 克隆的代码并将其地址作为回调 function 传递,然后一旦调用将能够解析其分配的 object 指针。 This dummy is templated in a wrapper, so it can adapt to any desired signature.该虚拟对象在包装器中进行了模板化,因此它可以适应任何所需的签名。

First here are my repos links in case the code is updated in future ( https://github.com/Ezarkei/BindFunctorToC https://gitlab.com/Ezarkei/BindFunctorToC )首先这是我的回购链接,以防将来更新代码( https://github.com/Ezarkei/BindFunctorToC https://gitlab.com/Ezarkei/BindFunctorToC

So here is how to use it:所以这里是如何使用它:

    Object instance{}; //Create an instance
    BindFunctorToC<Object> binder{instance}; //Create a binder on that instance
    void(*fPtr)(void){binder()}; //Get the C-style function pointer from the binder, here the signature is void(*)(void)
    fPtr(); //Call the C-style function pointer

Then a more detailed example:然后是一个更详细的例子:

#include "BindFunctorToC.hpp"

#include <iostream>

struct Foo {
    int operator()(std::string const &other) const noexcept { //This is our functor, the "entry point" to our object from the C-style function pointer call
    return Bar(other); //Here this functor simply forwards to a method
    }
    int Bar(std::string const &other) const noexcept { //This method is non-static and will use an object's member: _str
    std::cout << _str << ' ' << other << std::endl; //Beeing able to access _str here clearly shows that it's not a trick, we have a direct access to 'this'
    return 0;
    }
    std::string const _str{"default"};
};

static void CallBack(int(*callback)(std::string const &)) noexcept { //This is the kind of use case we want to be able to accomplish, a simple C-style function pointer is passed as parameter but it will effectively call a non-static method on an object
    callback("world"); //Here we will call foo1 instance's operator(), hence foo1's 'Bar' method
}

int main(void) {
    Foo foo1{"hello"}, foo2{"foo"}; //First we declare 2 instances of Foo, with 2 different member values so we can distinguish them well
    BindFunctorToC<Foo> binder1{foo1}, binder2{foo2}; //For every instance a binder is needed
    int(*ptr)(std::string const &){binder1()}; //We then construct a C-style function pointer with Foo's operator() signature and initialize it to binder1 function by calling binder1's operator()
    CallBack(ptr); //Here we will pass our C-style function pointer to the C api which may need it as a callback
    return binder2()("bar"); //Proof that we work on instances, first the operator() will get the C-style function pointer, then we call it and return its value to show the signatures deduction works
}

And finally the binder code available on the repos (content of BindFunctorToC.hpp):最后是 repos 上可用的活页夹代码(BindFunctorToC.hpp 的内容):

//******************************************************************************
//* Copyright (c) 2022 Ezarkei                                                 *
//*                                                                            *
//* This document is under the MIT License                                     *
//******************************************************************************

#ifndef BINDFUNCTORTOC_HPP_
#define BINDFUNCTORTOC_HPP_

#if ((defined(__i386__) || defined(__x86_64__) || defined(__arm__)) && (defined(__linux__) || defined(__linux) || defined(linux) || defined(__unix__) || defined(__unix))) || (defined(WIN32) || defined(_WIN32) || defined(__WIN32__))

#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__)
#if defined(_DEBUG) || defined(DEBUG)
#error Requires release compilation (windows)
#endif
#define __win32__
#endif

#ifdef __win32__
#define __attribute__(__)
#include <windows.h>
#else
#include <sys/mman.h>
#include <unistd.h>
#include <cstring>
#endif

#include <type_traits>
#include <stdexcept>
#include <string>

#ifdef __win32__
#define __DCL__(_) ((typename decltype(_))(_))
#else
#define __DCL__(_) (_)
#endif
#define __FLG__ 0x21626e636967616d

template<typename R> struct __TTRf__ {
    explicit __TTRf__(void) noexcept = delete;
    using _R = R &;
};

template<typename> struct __BndFcntrTC__;
template<typename R, typename T, typename ...A> struct __BndFcntrTC__<R(T::*)(A...)> {
public:
    explicit __BndFcntrTC__(T &);
    ~__BndFcntrTC__(void) noexcept;

    R(*operator()(void) const noexcept)(A...);

    R(&_mppr)(__BndFcntrTC__<R(T::*)(A...)> &, typename __TTRf__<A>::_R...) noexcept = &__MdmMppr__<>;

private:
    void __MplcDdrss__(void const *const);

    template<typename O = R> static typename std::enable_if<std::is_same<O, void>::value, void>::type __MdmMppr__(__BndFcntrTC__<R(T::*)(A...)> &, typename __TTRf__<A>::_R...) noexcept;
    template<typename O = R> static typename std::enable_if<!std::is_same<O, void>::value, O>::type __MdmMppr__(__BndFcntrTC__<R(T::*)(A...)> &, typename __TTRf__<A>::_R...) noexcept;
    static std::size_t __PgSzClcltr__(void) noexcept;
    static std::size_t __RwTmpltSzClcltr__(void) noexcept;

    static std::size_t const _flg, _pgSz, _rwTmpltSz, _sgmntSz;
    T &_trgt;
    void *_sgmnt;
};

template<typename> struct __CnstNxcptBstrct__;
template<typename R, typename T, typename ...A> struct __CnstNxcptBstrct__<R(T::*)(A...)> {
    explicit __CnstNxcptBstrct__(void) noexcept = delete;
    using _S = R(T::*)(A...);
};

template<typename R, typename T, typename ...A> struct __CnstNxcptBstrct__<R(T::*)(A...) const> {
    explicit __CnstNxcptBstrct__(void) noexcept = delete;
    using _S = typename __CnstNxcptBstrct__<R(T::*)(A...)>::_S;
};

#if __cplusplus > 201402L

template<typename R, typename T, typename ...A> struct __CnstNxcptBstrct__<R(T::*)(A...) noexcept> {
    explicit __CnstNxcptBstrct__(void) noexcept = delete;
    using _S = typename __CnstNxcptBstrct__<R(T::*)(A...)>::_S;
};

template<typename R, typename T, typename ...A> struct __CnstNxcptBstrct__<R(T::*)(A...) const noexcept> {
    explicit __CnstNxcptBstrct__(void) noexcept = delete;
    using _S = typename __CnstNxcptBstrct__<R(T::*)(A...)>::_S;
};

#endif

template<typename T> class BindFunctorToC : public __BndFcntrTC__<typename __CnstNxcptBstrct__<decltype(&T::operator())>::_S> {
public:
    explicit BindFunctorToC(T &);
};

template<typename R, typename T, typename ...A> __attribute__((noinline, unused)) void __SzClcltrE__(void) noexcept;
template<typename R, typename T, typename ...A> __attribute__((noinline, optimize(3))) typename std::enable_if<std::is_same<R, void>::value, void>::type __RwTmplt__(A...) noexcept;
template<typename R, typename T, typename ...A> __attribute__((noinline, optimize(3))) typename std::enable_if<!std::is_same<R, void>::value, R>::type __RwTmplt__(A...) noexcept;

template<typename R, typename T, typename ...A> __BndFcntrTC__<R(T::*)(A...)>::__BndFcntrTC__(T &trgt) : _trgt{trgt} {
#ifdef __win32__
    (void const *const)(_rwTmpltSz + _pgSz);
    _sgmnt = VirtualAlloc(NULL, _sgmntSz, MEM_COMMIT, PAGE_READWRITE);
    if (!_sgmnt)
        throw std::runtime_error{std::string{"VirtualAlloc error :: "} + std::to_string(GetLastError())};
#else
    _sgmnt = mmap(nullptr, _sgmntSz, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
    if (MAP_FAILED == _sgmnt)
        throw std::runtime_error{std::string{"Mmap error :: "} + strerror(errno)};
#endif
    void const *const sgmnt{(void const *)__DCL__((&__RwTmplt__<R, T, A...>))};
    std::memcpy(_sgmnt, sgmnt, _rwTmpltSz);
    __MplcDdrss__(this);
#ifdef __win32__
    unsigned long dscrd;
    if (!VirtualProtect(_sgmnt, _sgmntSz, PAGE_EXECUTE_READ, &dscrd))
        throw std::runtime_error{std::string{"VirtualProtect error :: "} + std::to_string(GetLastError())};
#else
    if (mprotect(_sgmnt, _sgmntSz, PROT_EXEC | PROT_READ))
        throw std::runtime_error{std::string{"Mprotect error :: "} + strerror(errno)};
    __builtin___clear_cache(_sgmnt, (uint8_t*)_sgmnt + _rwTmpltSz);
#endif
}

template<typename R, typename T, typename ...A> __BndFcntrTC__<R(T::*)(A...)>::~__BndFcntrTC__(void) noexcept {
#ifdef __win32__
    if (!VirtualFree(_sgmnt, 0, MEM_RELEASE))
#else
    if (munmap(_sgmnt, _sgmntSz))
#endif
        abort();
}

template<typename R, typename T, typename ...A> R(*__BndFcntrTC__<R(T::*)(A...)>::operator()(void) const noexcept)(A...) {
    return (R(*)(A...))_sgmnt;
}

template<typename R, typename T, typename ...A> void __BndFcntrTC__<R(T::*)(A...)>::__MplcDdrss__(void const *const ddrss) {
    std::size_t const tht{(std::size_t const)ddrss};
    uint8_t *ffst{nullptr}, m{0};
    for (std::size_t i{0}, j{0}, k{0}; !ffst && _rwTmpltSz > i; ++i)
        if (j[(uint8_t*)&_flg] == i[(uint8_t*)_sgmnt]) {
            if (!j++)
                k = i;
            else if (sizeof(void *volatile const) <= j)
                ffst = (uint8_t*)_sgmnt + k;
        } else if (j)
            j = 0;
    if (ffst)
        std::memcpy(ffst, &tht, sizeof(void *volatile const));
    else {
        for (std::size_t i{0}; !ffst && _rwTmpltSz > i; ++i)
            for (uint8_t l{0}; !ffst && 8 > l; l += 4)
                for (std::size_t j{0}, k{0}; _rwTmpltSz > i + j + k && 7 > j; 2 == j ? (j += 2, k = l) : ++j)
                    if (!(j % 4 ? j % 2 ? (uint8_t{(uint8_t)(j[(uint8_t *)_sgmnt + i + k] << 4)} >> 4) == uint8_t{(uint8_t)((j / 4 ? 3 : 1)[(uint8_t *)&_flg] << 4)} >> 4 : (uint8_t{(uint8_t)(j[(uint8_t *)_sgmnt + i + k] << 4)} >> 4) == (j / 4 ? 3 : 1)[(uint8_t *)&_flg] >> 4 : j[(uint8_t *)_sgmnt + i + k] == (j / 2)[(uint8_t *)&_flg]))
                        j = 7;
                    else if (6 == j) {
                        ffst = (uint8_t *)_sgmnt + i;
                        m = l;
                    }
        if (ffst)
            for (std::size_t i{0}, k{0}; 7 > i; 2 == i ? (i += 2, k = m) : ++i)
                i % 4 ? ((i[ffst + k] >>= 4) <<= 4) |= i % 2 ? uint8_t{(uint8_t)((i / 4 ? 3 : 1)[(uint8_t *)&tht] << 4)} >> 4 : (i / 4 ? 3 : 1)[(uint8_t *)&tht] >> 4 : i[ffst + k] = (i / 2)[(uint8_t *)&tht];
    }
    if (!ffst)
        throw std::runtime_error{"Failed to resolve flag offset"};
}

template<typename R, typename T, typename ...A> template<typename O> typename std::enable_if<std::is_same<O, void>::value, void>::type __BndFcntrTC__<R(T::*)(A...)>::__MdmMppr__(__BndFcntrTC__<R(T::*)(A...)> &tht, typename __TTRf__<A>::_R... __flds__) noexcept {
    tht._trgt.operator()(std::forward<A>(__flds__)...);
}

template<typename R, typename T, typename ...A> template<typename O> typename std::enable_if<!std::is_same<O, void>::value, O>::type __BndFcntrTC__<R(T::*)(A...)>::__MdmMppr__(__BndFcntrTC__<R(T::*)(A...)> &tht, typename __TTRf__<A>::_R... __flds__) noexcept {
    return tht._trgt.operator()(std::forward<A>(__flds__)...);
}

template<typename R, typename T, typename ...A> void __SzClcltrE__(void) noexcept {
    __SzClcltrE__<R, T, A...>();
}

template<typename R, typename T, typename ...A> typename std::enable_if<std::is_same<R, void>::value, void>::type __RwTmplt__(A... __flds__) noexcept {
    void *volatile const __RwTmpltRmPtr__{(void *)__FLG__};
    __BndFcntrTC__<R(T::*)(A...)> &tht{*((__BndFcntrTC__<R(T::*)(A...)> *const)__RwTmpltRmPtr__)};
    tht._mppr(tht, __flds__...);
}

template<typename R, typename T, typename ...A> typename std::enable_if<!std::is_same<R, void>::value, R>::type __RwTmplt__(A... __flds__) noexcept {
    void *volatile const __RwTmpltRmPtr__{(void *)__FLG__};
    __BndFcntrTC__<R(T::*)(A...)> &tht{*((__BndFcntrTC__<R(T::*)(A...)> *const)__RwTmpltRmPtr__)};
    return tht._mppr(tht, __flds__...);
}

template<typename R, typename T, typename ...A> std::size_t __BndFcntrTC__<R(T::*)(A...)>::__PgSzClcltr__(void) noexcept {
#ifdef __win32__
    SYSTEM_INFO nf{};
    GetSystemInfo(&nf);
    return nf.dwPageSize;
#else
    return (std::size_t)sysconf(_SC_PAGESIZE);
#endif
}

template<typename R, typename T, typename ...A> std::size_t __BndFcntrTC__<R(T::*)(A...)>::__RwTmpltSzClcltr__(void) noexcept {
    if ((std::size_t)__DCL__((&__RwTmplt__<R, T, A...>)) > (std::size_t)&__SzClcltrE__<R, T, A...>)
        abort();
    return (std::size_t)&__SzClcltrE__<R, T, A...> - (std::size_t)__DCL__((&__RwTmplt__<R, T, A...>));
}

template<typename R, typename T, typename ...A> std::size_t const __BndFcntrTC__<R(T::*)(A...)>::_flg{(std::size_t)__FLG__};

template<typename R, typename T, typename ...A> std::size_t const __BndFcntrTC__<R(T::*)(A...)>::_pgSz{__PgSzClcltr__()};

template<typename R, typename T, typename ...A> std::size_t const __BndFcntrTC__<R(T::*)(A...)>::_rwTmpltSz{__RwTmpltSzClcltr__()};

template<typename R, typename T, typename ...A> std::size_t const __BndFcntrTC__<R(T::*)(A...)>::_sgmntSz{(_rwTmpltSz / _pgSz + 1) * _pgSz};

template<typename T> BindFunctorToC<T>::BindFunctorToC(T &trgt) : __BndFcntrTC__<typename __CnstNxcptBstrct__<decltype(&T::operator())>::_S>(trgt) {
}

#ifdef __win32__
#undef __win32__
#undef __attribute__
#endif
#undef __DCL__
#undef __FLG__

#else
#error Unknown system ; supports unix(-like) (x86_64, i386, arm) and windows (x64, x32)
#endif
#endif

This old answer still applies: FLTK callbacks won't accept my function pointers The details might be a bit different.这个旧答案仍然适用: FLTK 回调不会接受我的 function 指针细节可能有点不同。 But it works.但它有效。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM