7

I have a C library that uses a struct of function pointers for callbacks. The callbacks will be called from C code.

extern "C" {
typedef struct callbacks_t {
    void (*foo) (const char*);
    int  (*bar) (int);  
} callbacks_t;
}// extern C

What kinds of C++ functions can I safely place in those function pointers to be called from the C library? Static member functions? Fully specified template functions? Non-capturing Lambdas?

g++ seemingly lets me use all of the above, but I question the safety when different calling conventions and language bindings are used for C and C++ functions.

  • 1
    _"but I question the safety when different calling conventions and language bindings are used for C and C++ functions"_ There aren't any differences like these when everything was consistently compiled for the target environment. – πάντα ῥεῖ Apr 29 '16 at 15:16
  • 3
    Static member functions, specialized template functions and non-capturing lambdas can't be given extern "C" calling convention so formally that's ungood. But in practice it depends on the compiler. And as a matter of in-practice, the main problem isn't that it won't work, but that some compilers can produce silly-warnings about the formal. – Cheers and hth. - Alf Apr 29 '16 at 15:20
  • 2
    @Cheersandhth.-Alf, that shouldn't matter. The C library should be able to call a function whose name is mangled by the C++ compiler without any issues. – R Sahu Apr 29 '16 at 15:24
  • 4
    @RSahu: It's not about name mangling, it's about calling convention. – Cheers and hth. - Alf Apr 29 '16 at 15:27
  • @Cheersandhth.-Alf, just curious. Are there platforms which use different calling conventions for C vs C++? – R Sahu Apr 29 '16 at 15:31
  • 4
    @RSahu: I never heard of one. The one compiler that reportedly (when this was discussed in clc++ once) issues silly-warnings was for Solaris, I believe. So I think it's in the same class of issue as the possibility of one's complement or sign-and-magnitude representation of signed integers. – Cheers and hth. - Alf Apr 29 '16 at 15:33

2 Answers2

7

I have a C library that uses a struct of function pointers for callbacks. The callbacks will be called from C code.

A C library only understands C. So you can only pass back things that are explicitly supported and understood by C.

Since there is no definition of calling conventions for any function types in C++ you can not explicitly pass back C++ functions. You can only pass back C function (those explicitly declared with extern "C") and guarantee that they are compatible.

Like all undefined behavior this may seem to work (like passing back normal C++ functions or static members). But like all undefined behavior it's allowed to work. You just can't guarantee that it's actually correct or portable.

extern "C" {
    typedef struct callbacks_t {
        void (*foo) (const char*);
        int  (*bar) (int);  
    } callbacks_t;

    // Any functions you define in here.
    // You can set as value to foo and bar.

}// extern C

What kinds of C++ functions can I safely place in those function pointers to be called from the C library?

Static member functions?

No. But this happens to work on a lot of platforms. This is a common bug and will bite people on some platforms.

Fully specified template functions?

No.

Non-capturing Lambdas?

No.

g++ seemingly lets me use all of the above,

Yes. But it is assuming that you are passing to objects that are built with the C++ compiler (which would be legal). As C++ code will use the correct calling convention. The problem is when you pass these things to a C library (pthreads springs to mind).

Ruslan
  • 18,162
  • 8
  • 67
  • 136
Martin York
  • 257,169
  • 86
  • 333
  • 562
  • A number of years ago you mentioned encountering a C++compiler that had a calling convention for static members that was different than the C calling convention: http://stackoverflow.com/questions/1738313/c-using-class-method-as-a-function-pointer-type/1738425#comment-1618920 I know it was a long time ago, but I think that any details you might be able to recall about that would be very interesting. – Michael Burr Apr 29 '16 at 21:41
  • 1
    This was during my time at VERITAS (4 Jobs ago). I was in charge of the build system that built the software on 25 compiler/OS variants. Unfortunately I can't remember which one it was. But that experience is basically what makes me stay away from anything that is close to unspecified or undefined behavior. – Martin York Apr 30 '16 at 15:05
3

In general, unless you use cast, you should trust g++.

While it is true that none of the function types you mention can be exported to use from within C, this is not what you're asking. You are asking about what functions you can pass as a function pointer.

To answer what you can pass, I think it is more constructive to understand what you cannot pass. You cannot pass anything that requires additional arguments not explicitly stated in the arguments list.

So, no non-static methods. They need an implicit "this". C will not know to pass it. Then again, the compiler won't let you.

No capturing lambdas. They require an implicit argument with the actual lambda body.

What you can pass are function pointers that require no implicit context. As a point of fact, you went ahead and listed them:

  • Function pointer. It doesn't matter whether it is a standard function or a template, so long as the template is fully resolved. This is not a problem. Any syntax you write that results in a function pointer will automatically fully resolve the template.
  • Non capturing lambdas. This is a special work-around introduced by C++11 when it introduced lambdas. Since it is possible to do so, the compiler does the explicit conversion required to make it happen.
  • Static methods. Since they are static, they do not get passed an implicit this, so they are okay.

The last one bears expanding on. Many C callback mechanisms get a function pointer and a void* opaq. The following is a standard and fairly safe of using them with a C++ class:

class Something {
  void callback() {
    // Body goes here
  }

  static void exported_callback(void *opaq) {
    static_cast<Something*>(opaq)->callback();
  }
}

And then do:

Something something;

register_callback(Something::exported_callback, &something);

Edited to add: The only reason this works is because the C++ calling convention and the C calling convention are identical when no implicit arguments are passed. There is a difference in name mangling, but it is not relevant when you pass a function pointer, as the sole purpose of name mangling is to allow the linker to find the correct function's address.

Had you tried that trick with a callback expecting, e.g., an stdcall or pascal calling convention, this scheme would fall flat on its face.

This, however, is not unique to static methods, lambdas and template functions. Even a standard function would fail under those circumstances.

Sadly, when you define a function pointer to an stdcall type, gcc ignores you:

#define stdcall __attribute__((stdcall))
typedef stdcall void (*callback_type)(void *);

results in:

test.cpp:2:45: warning: ‘stdcall’ attribute ignored [-Wattributes]
 typedef stdcall void (*callback_type)(void *);
Shachar Shemesh
  • 8,193
  • 6
  • 25
  • 57
  • 1
    This is very much an in-practice view. It's not wrong as such. But in the formal one can't mix calling conventions with well-defined behavior. I think you should mention also the formal. It's worth knowing about. – Cheers and hth. - Alf Apr 29 '16 at 18:04
  • 1
    This is all wrong. You can't pass any C++ function to a C code via a function pointer. This is because the calling conventions for both languages are unspecified by the standard. The only thing you can pass from C++ to C and guarantee it works are C function (explicitly declared with `extern "C"`). Will it break maybe maybe not like all undefined behavior it may seem to work perfectly on your platform. – Martin York Apr 29 '16 at 20:28
  • @LokiAstari This is the answer I was expecting and was afraid of. I went ahead and wrote little `extern "C"` shims for all my callbacks that forward to my C++ functions. The C++ functions get inlined inside the shim, so there's no overhead, just some annoyance having to do it this way. – user1526370 Apr 29 '16 at 21:16