[英]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.