1

I'm working on a C++ library and was wondering about compatibility within it's own API and ABI. I stumbled upon this (Creating Library with backward compatible ABI that uses Boost) which gave some helpful tips.

The problem I'm facing is trying to create a C interface that doesn't use STL/Boost and somehow get the same functionality as the backend which does use them. Some of my interface functions require allowing the user to bind function pointers. Normally I'd use boost bind/function for this but now I can't.

An example to illustrate the problem:

PRIVATE/INTERNAL:

class TestClass
{
public: 
    typedef boost::function<void (void)> FuncType;

public:
    TestClass( FuncType f ) : m_Func(f)
    {
    }

    void operator()()
    {
        m_Func();
    }

private:
    boost::function<void (void)> m_Func;
};

void* createTestClass(...)
{
    TestClass* fooHandle = new TestClass(boost::bind(...));
    return fooHandle;
}

PUBLIC INTERFACE:

extern "C" void* createTestClass(...);

CLIENT'S CODE:

void doSomething()
{
}

class DoStuffClass
{
public:
    void Stuff() {}
};

createTestClass(&doSomething);

DoStuffClass stuff;
createTestClass(&DoStuffClass::Stuff, &stuff);

I can probably make this work for simple function pointers but for member functions is there a way? What would I put in the ... sections?

Cheers!

Community
  • 1
  • 1
Firefly
  • 282
  • 5
  • 13
  • If you're going to put C++ on both sides, why the C api? – André Caron Feb 13 '12 at 05:04
  • So that my library has a binary compatible interface and so that I don't have to force users of my library to use a specific version of boost, and so I don't have to compile a version of my library for every compiler. All of these reasons are outlined in the link I provided and the sub link mentioned in that which is [link](http://chadaustin.me/cppinterface.html) – Firefly Feb 13 '12 at 05:28

1 Answers1

2

If you're going to try to define your own delegates to wrap C++ member functions, free functions etc., I suggest you take a look at "5 years later, is there something better than the “Fastest Possible C++ Delegates”?. The discussion is quite nice and the accepted answer proposes a neat trick to reduce all callbacks to a pair of void* and some free function R(void*,...) that invokes a member function or free function and whatnot.

I've used a similar technique to implement wrappers for Win32 API callbacks. Here's an example (take a look at the Timer::function<> and Timer::method<> inner types). Using this technique, you can expose a C interface like:

/* type of callback functions passed through the C interface.
   Obviously, C++ templates won't be of any help here, you need
   to typedef all the function signatures. */
typedef void(*Callback)(void*,int, int);

/* Example function that uses one of the callbacks. */
extern "C" void* createTestClass(Callback,void*);

// Enhanced interface for passing object methods bound to 
// a specific instance instead of the basic C-style callback.
#ifdef __cplusplus
    template<typename T, void(T::M)(int,int)>
    inline
    void * createTestClass2(T& object,method<T,M> method)
    {
        // '&object' converts to 'void*' and 'method' object
        // converts to 'void(void*,int,int)' function pointer.
        return createTestClass(&object, method);
    }
#endif

The role of the proposed mechanism is just generation of the void(void*,int,int) functions that wrap member function calls so that they have the right signature. Everything else is just plain C function pointers and can be freely passed to your C API while preserving the ABI.

Just be careful exceptions don't leak from your C++ callback functions. See "Passing C++ exceptions across a C API boundary" for a more detailed discussion of the topic.


Update (working example)

Here's a full working example (tested with Visual Studio 2008).

library.h (public interface for C or C++ clients with stable ABI)

#ifndef _library_h_
#define _library_h_

#ifdef __cplusplus
extern "C" {
#endif

typedef int(*Callback)(void*,int,int);

void* createTestClass(Callback,void*);

#ifdef __cplusplus
}
#endif

#endif /* _library_h_ */

library.c (library implementation)

This demonstrates a C implementation, but you could have a C++ file instead and use std::function<>, boost::function<> or other to use the callback.

#include "library.h"

void* createTestClass(Callback callback, void* context)
{
    callback(context, 1, 2);
    return 0;
}

library.hpp (enhanced interface for C++ clients, builds on public ABI)

This is the enhanced interface for C++ clients. It adds support for using member functions instead of plain int(void*,int,int) free functions. If you only plan to support C++ clients, then this can be in the same file as your C-style interface.

#ifndef _library_hpp_
#define _library_hpp_

#include "library.h"

template<typename T, int(T::*M)(int,int)>
class MethodCallback
{
    static int wrapper(void* context, int x, int y)
    {
        return (static_cast<T*>(context)->*M)(x, y);
    }
public:
    operator Callback () const
    {
        return &wrapper;
    }
};

template<typename T, int(T::*M)(int,int)>
void* createTestClass(T& object, MethodCallback<T,M> method)
{
    return createTestClass(static_cast<Callback>(method), static_cast<void*>(&object));
}

#endif //  _library_hpp_

main.cpp (test program)

Here's a simple demo on using the interface:

#include "library.hpp"
#include <iostream>

namespace {

    class Foo
    {
    public:
        int bar(int x, int y)
        {
            std::cout
                << "Foo::bar(" << x << "," << y << ")"
                << std::endl;
            return 1;
        }
    };

}

int main(int, char**)
{
    Foo foo;
    void* instance = ::createTestClass
        (foo, MethodCallback<Foo, &Foo::bar>());
    // ...
}

You should expect this program to output Foo::bar(1, 2) since the registered callback function will invoke foo.bar(1,2).

Community
  • 1
  • 1
André Caron
  • 44,541
  • 12
  • 67
  • 125
  • Are you able to provide a simplified working example of your technique? I can't seem to get any of the above to compile. I haven't tried the accepted answer from the other link as it seemed a bit more complex than what you proposed. Also, will any of these techniques allow me to pass the object/method into a boost::bind? I use boost internally and only require the callback wrapping on the interface so I can pass the function/member pointers into my library to create boost::function/bind's. So I need minimal as possible on the C interface so it's not cluttered and doesn't have dependencies. – Firefly Feb 13 '12 at 22:43
  • I ended up getting something to work using the accepted answer you linked. I can convert the callback into a boost::function internally like this: void* createTestClass(Callback callback) { boost::function f = callback; TestClass* foo = new TestClass(f); return foo; } – Firefly Feb 13 '12 at 23:51
  • @Firefly: what compiler are you using? The member function pointer as a template argument requires a pretty recent compiler (e.g. Visual Studio 2005 does not support them). – André Caron Feb 14 '12 at 01:13
  • I'm using VS2008. I tried compiling your code above and at first it complained that _method_ wasn't defined - so I looked at your code on git and created _method_ but then it gave me some other errors. This sort of template magic is a bit above me so I'm no doubt just doing something wrong. – Firefly Feb 14 '12 at 01:35
  • Ok, I just compiled a sample demonstration, I'm posting it right now. – André Caron Feb 14 '12 at 02:47
  • Ah awesome! Thanks for taking the time to do that. Are there any disadvantages to the method you propose compared to the other more templated method? Your method look easier to understand :) – Firefly Feb 14 '12 at 04:07
  • @Firefly: my simplified method does not provide a common type for storing both functions and methods. However, since you only intend to use this method to generate function pointers and actually store the values as `boost::function<>` on the other end, you don't need all of the extra features of the other library. – André Caron Feb 14 '12 at 05:10