我必须在库API中设置一个函数指针,以便在需要执行特定操作时调用该函数。
int (*send_processor)(char*,int);
int setSendFunctor(int (*process_func)(char*,int))
{
send_processor = process_func;
}
在我的主要来源中,我将此函数定义为类的成员函数,
int thisclass::thisfunction(char* buf,int a){//code}
此函数取决于此类中的其他函数。所以我不能把它从课堂上分开。
我无法在setSendFunctor中设置指向成员函数的指针,因为在API内部,它不会将其分配给成员函数的指针,而是分配给常规函数指针send_processor。
由于此类不是派生类,因此我无法使用虚函数。
处理此问题的最佳方法是什么?
答案 0 :(得分:3)
我处理这个问题的常用方法是在我的类中创建一个静态函数,它将一个显式this
指针作为参数,然后简单地调用成员函数。有时静态函数需要紧跟C库为回调所期望的类型签名,在这种情况下,我将在这种情况下常见的void *
转换为我真正想要的this
指针,然后调用成员函数。
如果API没有为其提供void *
的功能,然后将其返回给您的回调,则该API已损坏且需要修复。如果你无法修复它,那么有些选项涉及全局(或它们的小表兄static
)变量。但他们很难看。
我曾经创建了一个相当虚伪的模板系统,用于创建这些变量,并将静态函数绑定到它们,然后您可以将它们传递给具有此类API的事物。但是我不得不去追捕它,而且我并不相信它能为普通的全球化提供任何好处。
基于该想法而不使用模板的解决方案看起来像这样:
class InstanceBinder1 {
public:
static initialize(thisClass *instance, int (thisClass::*processor)(char *buf, int a)) {
instance_ = instance;
processor_ = processor;
}
static int processor(char *instance, int a) {
instance_->*processor_(buf, a);
}
private:
static thisClass *instance_;
static int (thisClass::*processor_)(char *buf, int a);
};
然后你可以将&InstanceBinder1::processor
传递给C库。
您必须为需要C库调用的thisClass
的每个单独实例创建此类型的新类。这意味着这些实例的数量将在编译时确定,并且无法解决这个问题。
答案 1 :(得分:2)
不幸的是,没有办法将指向成员的指针传递给像这样的C API,因为C api不知道如何处理这个指针。如果你不能改变图书馆,你就会陷入困境。
如果API提供了一种方法来通过库传递不透明的“上下文”指针(就像在Windows中使用CreateThread这样的东西,操作系统将参数视为传递的指针大小的数字)那么你应该使用一个静态成员函数,并使用这个上下文参数来传递你的指针。在静态成员函数中,将context参数强制转换为指针并通过它调用成员函数。如果您可以更改库,或者您可以让某人更改它,并且它不提供此功能,则应添加它,因为这是在对象和C之间建立桥梁的最佳方式。
很遗憾,您的API似乎没有提供这样做的方法。如果您的应用程序是单线程的,并且您可以保证同时没有库的重入或多个用户,那么您可以将此指针存储在全局或类静态成员中,那么您可以这样做,并再次使用静态成员函数作为回调并通过参数调用。
第三种方法,也是绝对的最后手段,可能是创建“thunk”对象,这些对象实际上是构建可执行代码以配置this指针并跳转到正确的成员函数的小对象。在具有DEP和事物的现代系统上,这是一种难以实现的麻烦。 ATL库在Windows上执行此操作,但它很难并且多年来导致了许多安全性和更新问题。
答案 2 :(得分:1)
最简单的方法是设计一些设置全局变量值的函数(“调用者”和“设置者”)。您可以使用setter在程序中的某个点设置全局值,以便构建要调用成员函数的对象。调用者将被传递给setSendFunctor
,并且在被调用时将调用转发给全局变量的值。
我故意没有谈到上述全球的类型;这可能是你想要的任何东西(一个普通的旧函数指针,一个成员函数指针,一个来自Boost或其他库的函子,或者其他任何可能方便的东西)。
不幸的是,我不认为没有全局化就有办法做到这一点,因为C API非常有限。
答案 3 :(得分:1)
如果所有其他方法都失败了,并且您的库只能够获取没有客户端定义数据的函数指针,并且您希望在多个对象上获得回调,并且回调的生命周期重叠,那么您可以用蹦床。
我通常这样做的方式(实际上,我只需要做几次,因此通常'做'它并不常见,因为大多数库都足够理智,可以传递一些空白*设置回调时的用户数据)是编译一个带有硬编码指针的函数:
int send_processor_trampoline ( char* buf, int a )
{
return ( ( thisclass* ) 0x12345678 ) -> thisfunction ( buf, a );
}
然后,您可以检查此功能的机器代码(在Windows中通过单步执行并获取函数指针的值,然后检查该位置的内存)。通常,您有几十个字节,其中硬编码指针的位置是显而易见的。更改指针值,如果不是,则将它们区分开来。因此,调用给定对象上的成员函数将具有相同的机器代码,但硬编码指针的字节将替换为指向接收器对象的指针的字节。
要在给定指向thisclass
对象的指针的情况下在运行时创建trampolines,从操作系统中获取一些可写的可执行内存(通常是以块的形式获取,因此为trampolines创建一个管理器对象,这样你就不会对每个20字节函数使用4K),将这些字节复制到其中,用对象的指针替换硬编码指针的字节。您现在有一个指向可以调用的自由函数的指针,该函数将调用特定对象上的成员函数。
答案 4 :(得分:0)
我不确切知道char *和int的用途。如果它们是由C API生成的,那么您无法做任何事情,因为您无法传递任何“上下文”信息。
如果其中一个引用了你传入的内容,并且被回调给你,那么你可以将它们映射回原始的“this”或包含“this”的结构以及其他一些char *或int。