使用成员函数指针的困境
我必须在库 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。
由于此类不是派生类,因此我无法使用虚函数。
处理这个问题的最佳方法是什么?
I have to set a function pointer in a library API, such that it calls that function whenever it needs to perform a particular action.
int (*send_processor)(char*,int);
int setSendFunctor(int (*process_func)(char*,int))
{
send_processor = process_func;
}
In my main source, I have this function defined as the member function of a class,
int thisclass::thisfunction(char* buf,int a){//code}
thisfunction depends on other functions in thisclass. So I cannot separate it out of the class.
I cannot set the pointer to the member function in setSendFunctor, since inside the API, it is not assigning it to a pointer of member function, but to a regular function pointer send_processor.
Since thisclass is not a derived class, I cannot use virtual functions.
What is the best way to handle this?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
我处理这个问题的常用方法是在我的类中创建一个静态函数,该函数采用显式的 this 指针作为参数,然后简单地调用成员函数。有时,静态函数需要严格遵循 C 库期望回调的类型签名,在这种情况下,我将这种情况下常见的
void *
强制转换为this
我确实想要指针,然后调用成员函数。如果 API 不具备提供
void *
的功能,然后将其返回给您的回调,则该 API 已损坏并需要修复。如果您无法修复它,可以使用涉及全局(或其近亲static
)变量的选项。但它们很丑。我曾经一度创建了一个相当黑客的模板系统,用于创建这些类型的变量,并将静态函数绑定到它们,然后您可以将其传递给具有此类 API 的东西。但我必须去寻找它,而且我不太相信它比普通的旧全局有任何好处。
基于该想法而不使用模板的解决方案如下所示:
然后您可以将
&InstanceBinder1::processor
传递到 C 库。您必须为需要调用 C 库的
thisClass
的每个单独实例创建一个此类的新类。这意味着这些实例的数量将在编译时确定,并且没有办法解决这个问题。My usual way of handling this issue is to create a static function in my class that takes an explicit
this
pointer as a parameter and then simply calls the member function. Sometimes the static function instead needs to closely follow the type signature the C library expects for a callback, in which case I cast thevoid *
that's common in such cases to thethis
pointer I really want, then call the member function.If the API does not have the facility for giving it a
void *
that will then be given back to your callback, that API is broken and needs to be fixed. If you can't fix it, there are options involving global (or their little cousinstatic
) variables. But they're ugly.I did, at one point, create a rather hackish templating system for creating these kinds of variables and sort of binding a static function to them that you could then pass to things that had this sort of API. But I'd have to go hunt it down and I'm not really convinced it offers any benefit over a plain old global.
A solution based on that idea without using templates would look something like this:
Then you could pass in
&InstanceBinder1::processor
to the C library.You would have to create a new class of this type for every separate instance of
thisClass
you needed the C library to call into. This means the number of those instances will be determined at compile time, and there is no way around that.不幸的是,没有办法像这样将指向成员的指针传递给 C API,因为 C api 不知道如何处理 this 指针。如果您无法更改库,那么您就会陷入困境。
如果 API 提供了一种通过库传递不透明“上下文”指针的方法(就像 Windows 中的 CreateThread 一样,操作系统将参数视为简单传递的指针大小的数字),那么您应该使用静态成员函数,并使用 this 上下文参数来传递 this 指针。在静态成员函数中,将上下文参数强制转换回指针并通过它调用成员函数。如果您可以更改该库,或者您可以让某人更改它,并且它不提供此功能,您应该添加它,因为这是在对象和 C 之间建立桥梁的最佳方式。
不幸的是,您的 API 不提供似乎提供了一种方法来做到这一点。如果您的应用程序是单线程的,并且您可以保证库不会重入或同时有多个用户,那么您可能可以将 this 指针存储在全局或类静态成员中,那么您可以这样做,并再次使用静态成员函数作为回调并通过参数进行调用。
第三种方法,也是绝对的最后手段,可能是创建“thunk”对象,这些对象是实际构建可执行代码以配置 this 指针并跳转到正确的成员函数的小对象。在具有 DEP 和其他东西的现代系统上,这是一种困难的方法,很可能不值得这么麻烦。 ATL 库在 Windows 上执行此操作,但这很困难,并且多年来给他们带来了许多安全和更新问题。
Unfortunately, there is no way to pass a pointer-to-member to a C API like this because the C api does not know how to deal with the this pointer. If you can't change the library, you're kind of stuck.
If the API provides a method to pass an opaque "context" pointer through the library (as is done with things like CreateThread in Windows, where the parameter is treated by the OS as simply a pointer sized number which is passed on) then you should use a static member function, and use this context parameter to pass your this pointer through. In the static member function, cast the context parameter back to a pointer and call the member function through it. If the library can be changed by you, or you can get someone to change it, and it doesn't offer this functionality you should add it because this is the best way to bridge between objects and C.
Unfortunately, your API doesn't appear to provide a way of doing this. If your app is single threaded, and you can guarantee there is no reentrancy or multiple users of the library at the same time then you might be able to get away with storing the this pointer in a global or class static member then you could do that, and again use a static member function as the callback and call through the parameter.
A third approach, and absolute last resort, might be to create "thunk" objects, which are small objects which actually build the executable code to configure the this pointer and jump to the correct member function. On modern systems with DEP and things this is a difficult approach which is most likely not worth the hassle. The ATL library does this on windows, but it is hard and has caused them many security and update problems over the years.
最简单的方法是制作几个设置全局变量值的函数(“调用者”和“设置者”)。您可以使用 setter 在程序中的某个点设置全局值,以便构造出您要调用其成员函数的对象。调用者将被传递给
setSendFunctor
,并且在调用时会将调用转发到全局变量的值。上面我故意不讲global的类型;它可以是任何你想要的东西(一个普通的旧函数指针、一个成员函数指针、来自 Boost 或其他库的函子,或者任何其他可能方便的东西)。
不幸的是,我认为没有全局变量就没有办法做到这一点,因为 C API 非常有限。
The simplest approach would be to craft a couple of functions (a "caller" and a "setter") that set the value of a global variable. You would use the setter to set the value of the global at such a point in your program such that the object you want to call member functions on has been constructed. The caller will be passed to
setSendFunctor
, and when called will forward the call to the value of the global variable.I have intentionally not talked about the type of the global above; that can be whatever you might want it to be (a plain old function pointer, a member function pointer, a functor from Boost or some other library, or anything else that may be convenient).
Unfortunately I don't think there's a way to do this without a global, as the C API is pretty limiting.
如果所有其他方法都失败了,并且您的库只能获取没有客户端定义数据的函数指针,并且您希望获得多个对象的回调,并且回调的生命周期重叠,那么您可以使用蹦床。
我通常这样做的方式(实际上,我只需要这样做几次,所以“正常”这样做并不常见,因为大多数库都足够理智,可以传递一些 void* 用户数据当你设置回调时)是编译一个带有硬编码指针的函数:
然后你可以检查这个函数的机器代码(在Windows中通过单步进入它并获取函数指针的值,然后检查内存在那个位置)。通常,您有几十个字节,其中硬编码指针的位置是显而易见的。更改指针值并比较它们(如果不是)。因此,对给定对象的成员函数的调用将具有相同的机器代码,但硬编码指针的字节将替换为指向接收者对象的指针的字节。
要在运行时创建一个指向
thisclass
对象的指针,请从操作系统中获取一些可写、可执行的内存(通常您会以块的形式获得这些内存,因此为蹦床创建一个管理器对象,这样您就不会每个 20 字节函数使用 4K),将这些字节复制到其中,用对象的指针替换硬编码指针的字节。现在,您有了一个指向可以调用的自由函数的指针,该函数将调用特定对象上的成员函数。If all else fails, and you have the case where your library only has the capability to take a function pointer with no client defined data, and you want to get callbacks on multiple objects, and the callback's lifecycles overlap, then you can use a trampoline.
The way I normally do this (actually, I've only ever had to do in a couple of times, so it's not common enough to be 'normally' doing it, since most libraries are sane enough to pass in some void* user data when you set a callback) is to compile a function with a hard-coded pointer in it:
You can then inspect the machine code for this function ( in Windows by stepping into it and getting the value of the function pointer, then inspecting the memory at that location ). Typically you then have a couple of dozen bytes in which the location of the hard coded pointer is obvious. Change the pointer value and diff them if it's not. So a call the member function on a given object would have the same machine code, but the bytes for the hard coded pointer would be replaced with the bytes for the pointer to the receiver object.
To create trampolines at runtime given a pointer to a
thisclass
object, grab some writeable, executable memory from the OS (usually you get this in chunks, so create a manager object for the trampolines so you don't use 4K for each 20 byte function), copy those bytes into it, replacing the bytes for the hard coded pointer with the object's pointer. You now have a pointer to a free function you can call which will call a member function on a specific object.我不知道 char* 和 int 的确切用途。如果它们是由 C API 生成的,那么您无能为力,因为您无法传递任何“上下文”信息。
如果其中一个引用了您传入的内容,并且正在回调给您,那么您可以将它们映射回原始的“this”或包含“this”加上其他一些 char* 或 int 的结构。
I do not know exactly what the char* and the int are used for. If they are generated by the C API then there is nothing you can do as you have no way of passing in any "context" information.
If one of those refers to something you have passed in, and is getting called back to you, then you can map them back to your original "this" or a struct containing "this" plus some other char* or int.