3

I'm a bit confused: I have a C++ API which is supposed to be called from C code and uses __cdecl in the function declarations.

There's a vtable with function pointers like this:

void (__cdecl *funptr) (const MyStruct& obj);

references are a C++ construct, are they not? How can there be a __cdecl with references?

And finally: is __cdecl equivalent to wrapping everything in an extern "C" statement? What about references in that case?

I'm a bit confused..

user3840170
  • 26,597
  • 4
  • 30
  • 62
Dean
  • 6,610
  • 6
  • 40
  • 90
  • 1
    Yes, it does look rather sloppy. I assume whoever wrote this was counting on references always being passed as pointers. – user3840170 May 03 '22 at 11:11
  • That won't compile in C, so it can't be the whole truth. What does the actual C interface look like? – molbdnilo May 03 '22 at 11:52
  • *Where* is there such a vtable? What makes you think that the pointed-to functions are among the ones that are callable from C? Note that in general, `__cdecl` has nothing much to do with whether functions are callable from C. – John Bollinger May 03 '22 at 12:13
  • C doesn't understand vtables anyway. It doesn't matter what is in them, they're being used exclusively by the C++ code that's behind the C API. – MSalters May 03 '22 at 12:44

3 Answers3

3

These are apples and oranges.

__cdecl is a non-standard keyword used to describe one of the more common x86 ABI calling conventions (together with __stdcall) which specifies how variables are passed/stacked between caller and callee. It has nothing to do with C specifically - some historic Microsoft C compiler just used this calling convention, hence the name. Many programming languages can use this calling convention and similarly, C code doesn't have to use it.

extern "C" just means that the code should be compiled "like C" by the C++ compiler, disabling various name mangling etc used internally by the C++ compiler. It's not necessarily related to compliant C, but could as well be used when sharing code between two different C++ compilers that may use different name mangling.

Neither has anything to do with how references work. In C they will not compile, in C++ they will compile. The code you posted is C++.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • I see but how can that stuff be called by C code? Because even if I wrap stuff in extern "C" with a class reference it will still compile. Shouldn't the compiler issue an error? – Dean May 03 '22 at 11:55
  • 2
    @Dean Who said it can be called by C code? And you are compiling as C++, yes? – Lundin May 03 '22 at 11:58
  • @Dean It can't be called by C code. The code should be changed to accept a pointer instead of a reference to do so – nos May 03 '22 at 12:41
  • @Dean Do you mean it will still compile with a C++ compiler after wrapping stuff with `extern "C"` or that it will compile with a C compiler (after wrapping with `#ifdef __cplusplus` `extern "C" {` `#endif` ... `#ifdef __cplusplus` `}` `#endif`)? – Ian Abbott May 03 '22 at 12:50
  • I guess you would need to arrange for the C compiler to use a pointer instead of a reference. Something like `#ifdef __cplusplus` `extern "C" {` `#endif` `#ifdef __cplusplus` `void (__cdecl *funptr) (const MyStruct& obj);` `#else` `void (__cdecl *funptr) (const struct MyStruct *obj);` `#endif` `#ifdef __cplusplus` `}` `#endif`. – Ian Abbott May 03 '22 at 12:57
1

__cdecl is a calling convention only. It's not related to C language specifically. It specifies who is responsible for stack cleanup with function calls.

extern "C" prevents name decoration, and uses the c language calling convention.

ChrisMM
  • 8,448
  • 13
  • 29
  • 48
0

This code does indeed look rather sloppy.

The __cdecl keyword defines the calling convention: it sets where arguments are supposed to be passed (registers or memory), in what order, which side of the function call is responsible for disposing of them once the function call finishes, and what register state is supposed to be preserved across the call. The extern "C" declaration controls name mangling as well, i.e. under what name the function is visible to code outside the current translation unit. Neither declaration influences the memory representations of the arguments themselves. Collectively, all those concerns are known as the Application Binary Interface (ABI).

This means that a function signature that declares a C calling convention while simultaneously mentioning types foreign to C is not necessarily useless or meaningless. It’s of course not required that only transitively pure types (those shared between C and C++) should be passed across the C/C++ boundary: it’s expected that sometimes C code may receive opaque (to C) pointers to classes, to structures containing references inside, and so on. However, the presence of types foreign to C directly in the function signature (references, non-POD classes passed by value, non-opaque pointers to such) should be somewhat worrying. It signals whoever wrote that code probably did not think too deeply about ABI stability, or at least did not value it as much as programmer convenience.

Now, a reference is typically represented internally as a pointer, which means that on the C side, one should expect the declaration to behave the same as:

void (*funptr)(const MyStruct *obj);

This is what I think the programmer who wrote that signature had assumed. However, this is not guaranteed. While the C ABI is pretty much set in stone on each platform, the ABI of C++ has historically been much less stable. There is no telling if compilers may some day, for example, start passing const references to ‘small’ types without mutable fields as if they were by-value arguments, which might break declarations like mentioned in the question. At the moment this seems unlikely, it is but not entirely outside the realms of possibility.

user3840170
  • 26,597
  • 4
  • 30
  • 62