在C++中,为C API回调使用静态成员函数指针是安全/可移植的吗?静态成员函数的ABI是否与C函数相同?
根据C++标准,这是不安全的.正如指出该SO发帖:
在C++中实现的AC回调函数必须是extern"C".它似乎在类中作为静态函数工作,因为类静态函数通常使用与C函数相同的调用约定.但是,这样做是一个等待发生的错误(请参阅下面的评论),所以请不要 - 通过外部"C"包装来代替.
根据Martin York在该答案中所做的评论,在某些平台上尝试这样做存在实际问题.
进行C ABI回调extern "C"
.
编辑:从标准中添加一些支持引号(强调我的):
3.5"计划和联系":
在对类型进行所有调整之后(在其中typedefs(7.1.3)被其定义替换),引用给定对象或函数的所有声明指定的类型应该是相同的,除了数组对象的声明可以指定数组类型是否存在主阵列界限(8.3.4).违反此规则的类型标识不需要诊断.[3.5/10]
[注意:使用链接规范(7.5)可以实现与非C++声明的链接.] [3.5/11]
和
7.5"连杆规格":
......具有不同语言联系的两种函数类型是不同的类型,即使它们在其他方面是相同的.[7.5/1]
因此,如果进行回调的代码使用C语言绑定进行回调,那么回调目标(在C++程序中)也必须如此.
在搜索并在攻击其他问题的几次休息之后,我找到了一个清晰简洁的答案(无论如何,为了标准):
通过表达式调用函数,该表达式的函数类型具有与被调用函数定义的函数类型的语言链接不同的语言链接,这是未定义的.[5.2.2/1]
我仍然认为它是在最基本的层面问题的使用从C++标准文本定义与C编译器编译C库的行为,究竟该如何互操作性中介的工作原理是很实现特定的; 然而,这是我认为最接近标准可以(目前)希望定义这种互动.
特别是,这是未定义的行为(并且不使用C库,因此不会出现问题):
void call(void (*pf)()) { pf(); } // pf() is the UB extern "C" void f(); int main() { call(f); } // though I'm unsure if a diagnostic is required for call(f)
Comeau确实给出了诊断call(f)
(尽管即使不需要诊断也能做到这一点).
这不是未定义的行为,并显示了如何在函数指针类型中包含语言链接(通过typedef):
extern "C" typedef void F(); void call(F* pf) { pf(); } extern "C" void f(); int main() { call(f); }
或者可以写成:
extern "C" { typedef void F(); void f(); } void call(F* pf) { pf(); } int main() { call(f); }
对于我所知道的所有Windows C++编译器,答案是肯定的,但语言标准中没有任何内容可以保证这一点.但是,我不会让你停止,这是使用C++实现回调的一种非常常见的方法 - 你可能会发现你需要将静态函数声明为WINAPI.这是从我自己的旧线程库中获取的:
class Thread { ... static DWORD WINAPI ThreadFunction( void * args ); };
这是te Windows线程API的回调使用.