In C++, is it safe/portable to use static member function pointer for C API callbacks?
It is not safe per the C++ standard. As stated in this SO posting:
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.
And according to comments made by Martin York in that answer there are real-world problems trying to do so on some platforms.
Make your C ABI callbacks extern "C"
.
Edit: Adding some supporting quotes from the standard (emphasis mine):
3.5 "Program and linkage":
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]
And
7.5 "Linkage specifications":
... Two function types with different language linkages are distinct types even if they are otherwise identical. [7.5/1]
So if the code making the callback is using C language bindings for the callback, then the callback target (in the C++ program) must as well.
After searching and several breaks while attacking other problems, I found an answer which is clear and succinct (for standardese, anyway):
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]
I still maintain that it is problematic at a fundamental level to use text from the C++ standard to define the behavior of a C library compiled with a C compiler, and exactly how that interlanguage interoperability works is very implementation-specific; however, this is the closest I think either standard can (currently) hope to define such interaction.
In particular, this is undefined behavior (and isn't using a C library so that issue doesn't arise):
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 does give a diagnostic at call(f)
(though it can do that even if the diagnostic isn't required).
This isn't undefined behavior, and shows how to include language linkage in a function pointer type (which is through a typedef):
extern "C" typedef void F();
void call(F* pf) { pf(); }
extern "C" void f();
int main() { call(f); }
Or could be written:
extern "C" {
typedef void F();
void f();
}
void call(F* pf) { pf(); }
int main() { call(f); }
For all the Windows C++ compilers that I'm aware of, the answer is yes, but nothing in the language standard guarantees this. I wouldn't let that stop you however, it's a very common way of implementing callbacks using C++ - you may find you need to declare the static functions as WINAPI however. This is taken from an old threading library of my own:
class Thread {
...
static DWORD WINAPI ThreadFunction( void * args );
};
where this is the callback use by te Windows threading API.
ABI isn't covered by either the C or C++ standards, even though C++ does give you "language linkage" through extern "C"
. Therefore, ABI is fundamentally compiler/platform specific. Both standards leave many, many things up to the implementation, and this is one of them.
Consequently, writing 100% portable code—or switching compilers—is hard to impossible, but allows vendors and users considerable flexibility in their specific products. This flexibility allows more space and time efficient programs, in ways that don't have to be anticipated in advance by the standards committees.
As I understand it, ISO's rules do not allow a standard more often than once every 10 years (but there can be various publications, such as TC1 and TR1 for C++). Plus there is the idea (I'm not sure if this comes from ISO, is carried over from the C committee, or even from elsewhere) to "distill"/standardize existing practice rather than going off into left field, and there are many existing practices, some of which conflict.