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

Posted

技术标签:

【中文标题】使用 C++ 类成员函数作为 C 回调函数【英文标题】:Using a C++ class member function as a C callback function 【发布时间】:2010-11-03 07:03:09 【问题描述】:

我有一个 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*)’.

我的问题:

    首先,是否可以像我尝试的那样注册一个 C++ 类成员函数,如果可以,怎么做? (我在http://www.parashift.com/c++-faq-lite/mixing-c-and-cpp.html 阅读了 32.8。但我认为它并不能解决问题) 是否有替代/更好的方法来解决这个问题?

【问题讨论】:

【参考方案1】:

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

A 类的非静态成员函数有一个class A* 类型的隐式第一个参数,它对应于this 指针。这就是为什么只有在回调的签名也有class A*类型的第一个参数时才能注册它们。

【讨论】:

是的。该解决方案有效。让我困惑的是编译器没有显示错误 int (A::)(A , int, int*)' does not match ‘int ()(int, int*)' 它确实做到了,但是通过放置 (A::) 这意味着该函数是 A 类的一部分,从那里意味着“this”指针。 我只是好奇...这是标准中规定的吗?我只是浏览了关于类的部分,并没有找到这个。尽管如此,非常有趣。我只是不认为每个编译器都必须以这种方式处理非静态成员函数。 @Methos,说成员函数有一个隐式的第一个参数并不意味着这个参数真的存在。这意味着从概念上讲,它就在那里。 @Tom,标准称之为“隐式对象参数”,它的类型是 A& 用于非常量成员函数,A const& 用于 const 成员函数,A volatile& 用于 volatile ......等等在。这是一个参考,而“this”是一个指针——主要是因为历史。调用成员函数的对象称为“隐含对象参数”。为了解决重载问题,隐式对象参数被视为隐藏的第一个参数 - 但这一切都只是概念性的,没有什么真正必须存在的【参考方案2】:

如果成员函数不是静态的,你也可以这样做,但它需要更多的工作(另见Convert C++ function pointer to c function pointer):

#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 的静态函数。

【讨论】:

酷!我一直想知道如何做到这一点。 @Jacko。嗯......那是关于负责堆栈清理的被调用者/调用者,不是吗?我不知道...我忘记了关于 Windows 的一切。 :-) 如何做到这一点是线程安全的?我在这里发布了问题:***.com/questions/41198854/… 非常感谢您的回答! @AnnevanRossum 您的解决方案很棒,但我遇到了一个问题,即尝试创建两个这样的回调,而第二个会覆盖第一个。我在***.com/q/66474621/2725742 发布了关于像这样分离“静态包装器”所需的最小更改。【参考方案3】:

问题在于方法 != 函数。编译器会将你的方法转换成这样的:

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

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

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

int callback( int *j, int *k )

    static A  *obj = new A();
    a->(j, k);

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

【讨论】:

什么是 C++ 中的“方法”?这个词在 C++ 标准中从未出现过一次。 @Aconcagua,我想你知道,但这里是你问题的答案:***.com/questions/8596461/… 函数成员(“方法”)绝对是一个函数。 (确实)有一个附加参数这一事实并不能使它成为一个非函数对象。 @AlexisWilke 更重要的是前两个 cmets 到参考答案。此外,第二段(“可互换性”)将暗示“功能!=功能”。乍一看,它可能看起来像分裂的头发,但我不得不以艰难的方式学习它(轻微的误解导致严重的错误)明确定义的重要性。所以推导出两个重要的规则: 1. 不要使用没有明确定义的术语! 2. 不要将新定义与现有定义并行使用。 a-&gt;(j, k);,您是否错过了输入e【参考方案4】:

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

Thunking in Win32: Simplifying callbacks to non-static member functions

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

【讨论】:

【参考方案5】:

在这个解决方案中,我们有一个模板类 将静态方法作为回调提供给“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 );

 // ()

【讨论】:

【参考方案6】:

使用成员函数的问题是它需要一个对象来执行 - 而 C 不知道对象。

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

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

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

【讨论】:

所以你的意思是不要让它成为一个成员函数? 在这种情况下,是的。 IMO 使用独立函数所提供的更大的简单性超过了所涉及的封装不足。 这是假设他的e函数不需要访问this

以上是关于使用 C++ 类成员函数作为 C 回调函数的主要内容,如果未能解决你的问题,请参考以下文章

如何实现类的成员函数作为回调函数

C++ 成员函数作为外部库的回调函数

如何使用 C++ 成员函数作为 C 框架的回调函数

如何使用 C++ lambda 将成员函数指针转换为普通函数指针以用作回调

使用类成员的 C++ 函数回调并在 main 中运行

将非托管方法作为回调传递给托管 C++/CLI 类