使用C++类成员函数作为C回调函数

问题描述 投票:0回答:8

我有一个C库,需要注册一个回调函数来自定义一些处理。回调函数的类型为

int a(int *, int *)
.

我正在编写类似于以下的C++代码,并尝试注册一个C++类函数作为回调函数:

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() 
{

}

编译器抛出以下错误:

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

我的问题:

  1. 首先是否可以像我尝试做的那样注册一个 C++ 类成员函数,如果可以的话如何? (我在http://www.parashift.com/c++-faq-lite/mixing-c-and-cpp.html读到32.8。但在我看来它并不能解决问题)
  2. 有替代/更好的方法来解决这个问题吗?
c++ c interop callback
8个回答
51
投票

如果成员函数是静态的,则可以这样做。

A 类的非静态成员函数具有

class A*
类型的隐式第一个参数,对应于 this 指针。这就是为什么只有当回调的签名也具有
class A*
类型的第一个参数时才可以注册它们。


40
投票

如果成员函数不是静态的,您也可以执行此操作,但需要更多工作(另请参阅将 C++ 函数指针转换为 c 函数指针):

#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;
}

这个例子在编译的意义上是完整的:

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

您将需要

c++11
标志。在代码中您可以看到调用了
register_with_library(func)
,其中
func
是动态绑定到成员函数
e
的静态函数。


6
投票

问题在于方法!=函数。编译器会将您的方法转换为类似的内容:

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

所以,你肯定不能传递它,因为类实例不能作为参数传递。一种解决方法是将方法设为静态,这样它将具有良好的类型。但它不会任何类实例,并访问非静态类成员。

另一种方法是声明一个带有指向第一次初始化的 A 的静态指针的函数。该函数仅将调用重定向到该类:

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

然后就可以注册回调函数了。


5
投票

嗯...如果你在 win32 平台上,总会有令人讨厌的 Thunking 方式...

Win32 中的 Thunking:简化对非静态成员函数的回调

这是一个解决方案,但我不建议使用它。
它有一个很好的解释,很高兴知道它的存在。


1
投票

在此解决方案中,我们有一个模板类 将静态方法作为回调提供给“c 函数”。 这个类持有一个“普通”对象(带有一个名为callback()的成员函数,最终将被调用)。

一旦定义了您的类(此处为 A),就可以轻松使用它:

int main() {

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

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

  callACFunctionPtr( fun );

  callACFunctionPtr( o.callback );

} // ()

完整示例:

#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 );

} // ()

0
投票

使用成员函数的问题在于它需要一个对象来执行操作 - 而 C 不了解对象。

最简单的方法是执行以下操作:

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

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

0
投票

对于 2022 年遇到该问题的任何人,我将提供一种符合最初要求的新方法。 我欢迎任何有关此解决方案的反馈,并希望这可以帮助任何遇到该主题的人。

首先我们需要了解核心问题(正如一些已经强调的那样)是非静态方法(可以访问对象的成员等)需要访问“this”,即对象的实例指针。然而,由于我们希望我们的函数是回调,因此我们无法修改它的调用方式。这就是为什么我们需要一个可以访问其对象的“this”指针的函数。

我的解决方案是在运行时修改虚拟函数克隆的代码并将其地址作为回调函数传递,然后一旦调用该函数就能够解析其分配的对象指针。这个虚拟对象是在包装器中模板化的,因此它可以适应任何所需的签名。

首先这是我的存储库链接,以防将来代码更新(https://github.com/Ezarkei/BindFunctorToChttps://gitlab.com/Ezarkei/BindFunctorToC

所以这是如何使用它:

    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

然后是更详细的例子:

#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
}

最后是存储库上可用的绑定器代码(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

0
投票

这个旧答案仍然适用: FLTK 回调不会接受我的函数指针 细节可能有点不同。但它有效。

© www.soinside.com 2019 - 2024. All rights reserved.