1

To make shure I maintain an ABI, I use classes with an explicit vtable.

in myinterface/import.h

class MyInterface
    {
    public:
        void doStuff()
            {m_vt->doStuff(this);}

        class Vtable
            {
            friend class MyInterface;
            public:
                bool init(void* module);
                size_t abiVersionTagGet() const
                     {return abi_version_tag;}

            private:
                typedef void (*DoStuffFunc)(MyInterface* obj);
                size_t abi_version_tag;
                DoStuffFunc doStuff;
            //etc
            };
    private:
         Vtable* m_vt;
    };

The function pointers inside the vtable refers to a dynamic library exporting functions like

in myinterface/export.h:

 class MyInterface;

 extern "C"
     {
     void EXPORT MyInterface_1doStuff(MyInterface* object);
     }

in myinterfaceimpl.cpp (or what ever it is called)

 #include "myinterface/export.h"

 void MyInterface_1doStuff(MyInterface* object)
      {
      MyInterfaceImpl* _this=(MyInterfaceImpl*)object;
      // ...
      }

Now I have 2 include files to maintain for each interface. How do I make it easier to maintain such structure.

EDIT: The Pimpl "solution" below, does not solve the problem since it also relies on an automatic vtable.

user877329
  • 6,717
  • 8
  • 46
  • 88
  • 1
    Since it looks like you are doing this in Windows, why not use ATL or atleast COM to do this? – user93353 Apr 20 '13 at 13:12
  • @user93353 It needs to be portable. – user877329 Apr 20 '13 at 13:13
  • 2
    To keep the code literally as you already have it, but avoid mistakes like setting parameters differently in each header, I would use the preprocessor (add a new header with the functions listed in a macro, then use a foreach macro in each current header to write the boilerplate). Of course this would only bring it down to 2 places to maintain (you'd still need to do the cpp manually). I doubt you can bring it down further without making a huge mess. – Dave Apr 20 '13 at 14:06

2 Answers2

1

As requested in the comments, here is an example of using a macro to get half-way there;

Before I begin, the downsides:

  • As with any preprocessor hack, it's ugly;
  • I've used GCC's swallow-the-comma extension;
  • This won't figure out the decorated function call automatically (not least because that will depend on the compiler);
  • This probably won't play nice with your IDE's intellisense, and will make it harder for developers to find function prototypes (unless you pre-expand the header with gcc -E before distributing it. If you do that, you should modify the loop macros to include trailing newlines).

So let's start with the ugly. Hide this away in a header (I've tried to use functions which could be reused elsewhere);

#define EXPAND(a) a
#define ARGS_COUNT__(\
_96,_95,_94,_93,_92,_91,_90,_89,_88,_87,_86,_85,_84,_83,_82,_81,\
_80,_79,_78,_77,_76,_75,_74,_73,_72,_71,_70,_69,_68,_67,_66,_65,\
_64,_63,_62,_61,_60,_59,_58,_57,_56,_55,_54,_53,_52,_51,_50,_49,\
_48,_47,_46,_45,_44,_43,_42,_41,_40,_39,_38,_37,_36,_35,_34,_33,\
_32,_31,_30,_29,_28,_27,_26,_25,_24,_23,_22,_21,_20,_19,_18,_17,\
_16,_15,_14,_13,_12,_11,_10, _9, _8, _7, _6, _5, _4, _3, _2, _1,\
N,...) N
#define ARGS_COUNT_(...) ARGS_COUNT__(__VA_ARGS__,\
96,95,94,93,92,91,90,89,88,87,86,85,84,83,82,81,\
80,79,78,77,76,75,74,73,72,71,70,69,68,67,66,65,\
64,63,62,61,60,59,58,57,56,55,54,53,52,51,50,49,\
48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,\
32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,\
16,15,14,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
#define ARGS_HEAD(a,...) a
#define ARGS_TAIL(a,...) (__VA_ARGS__)

#define FOREACH(macro,list) FOREACH_(ARGS_COUNT_ list,macro,list)
#define FOREACH_(n,macro,list) FOREACH__(n,macro,list)
#define FOREACH__(n,macro,list) FOREACH_##n(macro,list)
#define FOREACH_1(macro,list) EXPAND(macro EXPAND list)
#define FOREACH_2(macro,list) EXPAND(macro ARGS_HEAD list) FOREACH_1(macro,ARGS_TAIL list)
#define FOREACH_3(macro,list) EXPAND(macro ARGS_HEAD list) FOREACH_2(macro,ARGS_TAIL list)
#define FOREACH_4(macro,list) EXPAND(macro ARGS_HEAD list) FOREACH_3(macro,ARGS_TAIL list)
#define FOREACH_5(macro,list) EXPAND(macro ARGS_HEAD list) FOREACH_4(macro,ARGS_TAIL list)
#define FOREACH_6(macro,list) EXPAND(macro ARGS_HEAD list) FOREACH_5(macro,ARGS_TAIL list)
#define FOREACH_7(macro,list) EXPAND(macro ARGS_HEAD list) FOREACH_6(macro,ARGS_TAIL list)
#define FOREACH_8(macro,list) EXPAND(macro ARGS_HEAD list) FOREACH_7(macro,ARGS_TAIL list)
#define FOREACH_9(macro,list) EXPAND(macro ARGS_HEAD list) FOREACH_8(macro,ARGS_TAIL list)
#define FOREACH_10(macro,list) EXPAND(macro ARGS_HEAD list) FOREACH_9(macro,ARGS_TAIL list)
#define FOREACH_11(macro,list) EXPAND(macro ARGS_HEAD list) FOREACH_10(macro,ARGS_TAIL list)
#define FOREACH_12(macro,list) EXPAND(macro ARGS_HEAD list) FOREACH_11(macro,ARGS_TAIL list)
#define FOREACH_13(macro,list) EXPAND(macro ARGS_HEAD list) FOREACH_12(macro,ARGS_TAIL list)
#define FOREACH_14(macro,list) EXPAND(macro ARGS_HEAD list) FOREACH_13(macro,ARGS_TAIL list)
#define FOREACH_15(macro,list) EXPAND(macro ARGS_HEAD list) FOREACH_14(macro,ARGS_TAIL list)
#define FOREACH_16(macro,list) EXPAND(macro ARGS_HEAD list) FOREACH_15(macro,ARGS_TAIL list)
#define FOREACH_17(macro,list) EXPAND(macro ARGS_HEAD list) FOREACH_16(macro,ARGS_TAIL list)
#define FOREACH_18(macro,list) EXPAND(macro ARGS_HEAD list) FOREACH_17(macro,ARGS_TAIL list)
#define FOREACH_19(macro,list) EXPAND(macro ARGS_HEAD list) FOREACH_18(macro,ARGS_TAIL list)
#define FOREACH_20(macro,list) EXPAND(macro ARGS_HEAD list) FOREACH_19(macro,ARGS_TAIL list)
#define FOREACH_21(macro,list) EXPAND(macro ARGS_HEAD list) FOREACH_20(macro,ARGS_TAIL list)
#define FOREACH_22(macro,list) EXPAND(macro ARGS_HEAD list) FOREACH_21(macro,ARGS_TAIL list)
#define FOREACH_23(macro,list) EXPAND(macro ARGS_HEAD list) FOREACH_22(macro,ARGS_TAIL list)
#define FOREACH_24(macro,list) EXPAND(macro ARGS_HEAD list) FOREACH_23(macro,ARGS_TAIL list)
// etc. You need to repeat this up to the maximum number of loops you will need.
// maybe somebody can find a way to split the list in half, in which case
// the number of these would be dramatically reduced (n -> log_2(n))

So what is that? Well the ARGS_COUNT_ stuff does as the name suggests; it counts how many arguments you give it. That's used by the FOREACH function, which is used like this:

#define MY_MACRO(a,b,c) int t##a = b + c;
FOREACH( MY_MACRO, ((1,2,3),(4,5,6)) )

(notice all the brackets around the parameters). That would produce:

int t1 = 2 + 3;
int t4 = 5 + 6;

Cool.

So let's use it for what you're doing. First we'll make a big list of the important bits: (this should go in a header which is included by both of your headers)

// return type, function name, function parameters, parameter pass-through
#define MY_FUNCTIONS ( \
  (void, doStuff, _1doStuff, (), ()), \
  (int, someOtherFunction, _1someOtherFunction, (int a, float b), (a,b)) \
)

It's a little ugly that we need to repeat the parameters with and without types, but I couldn't think of a tidy alternative.

So now the loops:

class MyInterface {
public:
#define ADD_THIS(...) (this,##__VA_ARGS__)
#define MY_PASSTHROUGH(ret,name,fancy,args,pass) ret name args{m_vt->name ADD_THIS pass;}
    FOREACH(MY_PASSTHROUGH,MY_FUNCTIONS)

    class Vtable {
        // ...
    private:
        size_t abi_version_tag;
#define ADD_THIS_P(...) (MyInterface*,##__VA_ARGS__)
#define MY_FUNCDEF(ret,name,fancy,args,pass) typedef ret (*name##Func)ADD_THIS_P args; name##Func name;
        FOREACH(MY_FUNCDEF,MY_FUNCTIONS)
    };
private:
     Vtable* m_vt;
};

And in the other header:

class MyInterface;
extern "C" {
#define ADD_THIS_P(...) (MyInterface*,##__VA_ARGS__)
#define MY_CFUNC(ret,name,fancy,args,pass) ret EXPORT MyInterface##fancy ADD_THIS_P args;
    FOREACH(MY_CFUNC,MY_FUNCTIONS)
}
Dave
  • 44,275
  • 12
  • 65
  • 105
  • Christ... Also Zeus, Buddha, Satan, etc. – Nik Bougalis Apr 26 '13 at 22:51
  • I'll never repent! Yeah it's pretty horrific. I can't remember where I found that `FOREACH` macro, but I've had a few uses for it over the years. – Dave Apr 26 '13 at 23:05
  • I'm not in the camp of "evil macros are evil" - sometimes macros work and work *well*. Is this case one of those times? Maybe, maybe not. At any rate, my comment was more along the lines of "wow... that's gonna take a lot of caffeine to process. I best put on my thinking pants." – Nik Bougalis Apr 26 '13 at 23:07
  • Found the (or at least an) original `FOREACH` macro: http://stackoverflow.com/a/5958315/1180785 – Dave Apr 26 '13 at 23:36
  • @Dave It looks like some sort of IDL compiler is the way to go. – user877329 Aug 07 '15 at 16:54
0

I'm not sure I fully understand you requirement, but here goes. I suggest you use the pimpl idiom and declare a common interface class such as:

class IInterface
{
public:
    class impl;
    impl* pimpl;
public:
    void DoSomthing();
    void DoSomethingElse(int a);
    void DoSomthingInteresting(const char*);
    ~IInterface();
};

Now have every dynamic library contain a factory function to create this interface for you:

IInterface* InterfaceFactory();

The internal implementation from each dynamic library will be different but ABI compatibility will be maintained. Inside the dynamic library you would do this:

struct IInterface::impl
{
    void DoSomething()
    {
        std::cout << "I did it!" << "\n";
    }

    void DoSomethingElse(int a)
    {
        std::cout << "The answer is: " << a << "\n";
    }

    void DoSomthingInteresting(const char* s)
    {
        std::cout << "The question is: " << s << "\n"; 
    }
};

void IInterface::DoSomthing()
{
    pimpl->DoSomething();
}

void IInterface::DoSomethingElse(int a)
{
    pimpl->DoSomethingElse(a);
}

void IInterface::DoSomthingInteresting(const char* s)
{
    pimpl->DoSomthingInteresting(s);
}

IInterface::~IInterface()
{
    delete pimpl;
}

IInterface* InterfaceFactory()
{
    IInterface* MyInterface = new IInterface();

    MyInterface->pimpl = new IInterface::impl();

    return MyInterface;
}
Peter R
  • 2,865
  • 18
  • 19
  • 1
    It's not clear to me why this should solve the problem, since binaries created by different compilers / compiler versions are not guaranteed to be compatible with respect to the layout of their classes. I guess this is the reason the method is implemented through a non-member function in the code given in the question!? – JohnB Apr 20 '13 at 14:06
  • Hmmm. You're right this is not the answer for compatibility across compilers and compiler versions. – Peter R Apr 20 '13 at 15:05
  • @JohnB Also, I want to know exactly where everything is in the vtable. – user877329 Apr 20 '13 at 16:02
  • @PeterR Actually, the ABI will break if the interface changes and I have no tag to check it against. – user877329 Apr 20 '13 at 16:17