为什么只有静态成员函数才能作为回调函数?

为什么只有静态成员函数才能作为回调函数?

作者:BlogUpdater |  时间:2020-10-02 |  浏览:72 |  评论已关闭 条评论

隐藏的this指针
对于非静态成员函数来说,它携带了一个隐藏的”this”指针,这导致它不能满足Win32回调函数签名的要求,这样的结果就是:一个非静态成员函数,不能作为一个合法的Win32回调函数。
幸运的是,几乎所有的回调函数都提供了一些方法来感知调用上下文。你可以将这个”this”指针作为一个上下文环境来重构代码,下面是一个例子:

有一些回调函数签名将它的第一个参数作为指示上下文的参数(也叫做”引用数据”)。凑巧的是,这个隐藏的”this”指针也正好是第一个参数。回顾我们之前关于调用约定的文章,可以知道对于成员函数的__stdcall调用约定也刚好匹配我们预期的栈布局。下面我们看一个WAITFORTIMERCALLBACK的例子:

请注意:”thiscall”调用约定不能匹配上面的场景,但是两个”__stdcall”却可以。幸运的是,编译器足够聪明,它能识别这个this指针,并对上面的静态成员函数s_ThreadProc进行优化。

如果你有机会观察一下编译器为s_ThreadProc生成的汇编代码,则你会发现:函数的实现实际上就是一个跳转指令,因为编译器已经意识到了两种不同的调用约定,所以这里不需要做任何的翻译工作,只是跳转即可。

有些人可能想着更进一步:直接对CreateThread的第二个参数强制转换为LPTHREAD_START_ROUTINE,这样就可以完全不需要定义s_ThreadProc这个函数了。我强烈建议不这么做:因为我看到很多人都曾在转换函数指针这个上吃过亏(关于这个议题,我后面会专门讲一讲)。

总结
在上面的代码中,虽然我们考虑到了两个__stdcall调用约定,但是我们并没有依赖它们。如果调用约定的巧合不能像预期那样的工作,则代码依然会正常工作。这个对于将代码移植到其他架构的时候很重要,特别是当这个架构没有上面所述的调用约定的巧合的时候。

最后
Raymond Chen的《The Old New Thing》是我非常喜欢的博客之一,里面有很多关于Windows的小知识,对于广大Windows平台开发者来说,确实十分有帮助。
本文来自:《Why do member functions need to be “static” to be used as a callback?》

评论已关闭。