[英]Using a C++ class member function as a C callback function
我有一个 C 库,需要注册一个回调 function 来自定义一些处理。 回调 function 的类型是int a(int *, int *)
。
我正在编写类似于以下的 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()
{
}
编译器抛出以下错误:
In constructor 'A::A()',
error:
argument of type ‘int (A::)(int*, int*)’ does not match ‘int (*)(int*, int*)’.
我的问题:
如果成员 function 是 static,则可以这样做。
class A 的非静态成员函数有一个类型为class A*
的隐式第一个参数,它对应于这个指针。 这就是为什么只有当回调的签名也具有class A*
类型的第一个参数时才能注册它们。
如果成员 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;
}
这个例子在编译的意义上是完整的:
g++ test.cpp -std=c++11 -o test
您将需要c++11
标志。 在代码中,您会看到调用了register_with_library(func)
,其中func
是动态绑定到成员 function e
的 static function。
问题是那个方法。= function:编译器会将你的方法转换成这样的东西:
int e( A *this, int *k, int *j );
所以,你肯定不能传递它,因为 class 实例不能作为参数传递。 一种解决方法是将方法设置为 static,这样它就会具有良好的类型。 但它不会访问任何 class 实例,并访问非静态 class 成员。
另一种方法是声明一个 function 和一个 static 指向第一次初始化的 A 的指针。 function 仅将呼叫重定向到 class:
int callback( int *j, int *k )
{
static A *obj = new A();
a->(j, k);
}
然后就可以注册回调function了。
好吧...如果您在 win32 平台上,总会有讨厌的 Thunking 方式...
Win32 中的 Thunking:简化对非静态成员函数的回调
这是一个解决方案,但我不建议使用它。
它有一个很好的解释,很高兴知道它的存在。
在此解决方案中,我们有一个模板 class 和 static 方法作为回调提供给“c 函数”。 这个 class 持有一个“普通的” object (有一个名为 callback() 的成员 function 最终会被调用)。
一旦定义了 class(此处为 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 );
} // ()
使用成员 function 的问题是它需要一个 object 来操作——而 C 不知道对象。
最简单的方法是执行以下操作:
//In a header file:
extern "C" int e(int * k, int * e);
//In your implementation:
int e(int * k, int * e) { return 0; }
对于 2022 年遇到该问题的任何人,我将提供一种符合最初要求的新方法。 我将欢迎有关此解决方案的任何反馈,并希望这可以帮助任何遇到该主题的人。
首先,我们需要了解核心问题——正如一些人已经强调的那样——是非静态方法(可以访问对象的成员等)需要访问“this”,即对象的实例指针。 然而,由于我们希望我们的 function 成为回调,我们无法修改它的调用方式。 这就是为什么我们需要一个 function 可以访问其对象的“this”指针的原因。
我的解决方案是在运行时修改虚拟 function 克隆的代码并将其地址作为回调 function 传递,然后一旦调用将能够解析其分配的 object 指针。 该虚拟对象在包装器中进行了模板化,因此它可以适应任何所需的签名。
首先这是我的回购链接,以防将来更新代码( https://github.com/Ezarkei/BindFunctorToC https://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
}
最后是 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
这个旧答案仍然适用: FLTK 回调不会接受我的 function 指针细节可能有点不同。 但它有效。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.