在C++中,对于C API回调,使用静态成员函数指针是安全的/可移植的吗?

In C++, is it safe/portable to use static member function pointer for C API callbacks?

在C++中,对于C API回调,使用静态成员函数指针是安全的/可移植的吗?静态成员函数的abi是否与c函数相同?


它是不安全的每一个C++标准。如本公告所述:

A C callback function implemented in C++ must be extern"C". It may seem to work as a static function in a class because class-static functions often use the same calling convention as a C function. However, doing that is a bug waiting to happen (see comments below), so please don't - go through an extern"C" wrapper instead.

根据马丁·约克在回答中的评论,在某些平台上尝试这样做存在现实问题。

使您的cABI回调cx1〔0〕。

编辑:从标准中添加一些支持报价(强调我的报价):

3.5"程序和链接":

After all adjustments of types (during which typedefs (7.1.3) are replaced by their definitions), the types specified by all declarations referring to a given object or function shall be identical, except that declarations for an array object can specify array types that differ by the presence or absence of a major array bound (8.3.4). A violation of this rule on type identity does not require a diagnostic. [3.5/10]

[Note: linkage to non-C++ declarations can be achieved using a linkage-specification (7.5). ] [3.5/11]

7.5"联动装置规范":

... Two function types with different language linkages are distinct types even if they are otherwise identical. [7.5/1]

因此,如果使回调的代码使用回调的C语言绑定,那么回调目标(在C++程序中)也必须如此。


在对其他问题进行搜索和几次休息之后,我找到了一个清晰简洁的答案(无论如何,对于标准语言来说):

Calling a function through an expression whose function type has a language linkage that is different from the language linkage of the function type of the called function's definition is undefined. [5.2.2/1]

我仍然认为,在一个基本层面上,使用C++中的文本来定义C编译器编译的C库的行为是有问题的,确切地说,中间语言互操作如何工作是非常具体的;然而,这是我认为最接近的标准(当前)希望定义这样的中介。方法。

特别是,这是未定义的行为(并且不使用C库,因此不会出现问题):

1
2
3
4
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)中包含语言链接:

1
2
3
4
extern"C" typedef void F();
void call(F* pf) { pf(); }
extern"C" void f();
int main() { call(f); }

或者可以写:

1
2
3
4
5
6
extern"C" {
typedef void F();
void f();
}
void call(F* pf) { pf(); }
int main() { call(f); }


对于所有我知道的Windows C++编译器来说,答案是肯定的,但是语言标准中没有任何东西可以保证这一点。但是,我不想让它阻止你,这是使用C++实现回调的一种非常常见的方式,但是你可能发现你需要声明静态函数为WiAPI。这是从我自己的旧线程库中获取的:

1
2
3
4
class Thread {
   ...
   static DWORD WINAPI ThreadFunction( void * args );
};

这里是TE Windows线程API使用的回调。


ABI并没有被C或C++标准所覆盖,即使C++确实通过EDCOX1 0给出了"语言链接"。因此,ABI基本上是特定于编译器/平台的。这两个标准都有许多,许多事情需要实现,这就是其中之一。

因此,编写100%可移植代码或切换编译器是不可能的,但允许供应商和用户在其特定产品中具有相当大的灵活性。这种灵活性允许更多的节省空间和时间的项目,这些方式不需要标准委员会预先预测。

据我所知,ISO的规则不允许标准每10年一次以上(但可以有各种出版物,如C++的TC1和Tr1)。此外,还有一个想法(我不确定这是否来自于ISO,是由C委员会,甚至是其他地方推行的)是"提炼/标准化现有实践,而不是进入左领域,并且有许多现有的实践,其中一些存在冲突。