visual-c++mingwcalling-conventionbinary-compatibility

Is this interface binary compatible between MSVC and mingw?


I am working on a library that allows its users (other libraries residing in the same process) to exchange data buffers and streams. The library has to be usable from both MSVC and mingw code (more compatibility doesn't hurt, but is not strictly necessary). In order to achieve this, the core functionality should be provided from a small, compiler-compatible interface that can be hidden later by a convenience layer that is compiled with the client code.

A challenging aspect of the library is that it has to be extensible, so that clients can provide their own buffer and stream implementations, but the core library interface must remain stable once it is released. If you are interested in further background, you can read about it in the forum thread discussion.

I have tried to learn about problems of binary compatibility between compilers, but since I am new to this topic I would be interested in comments on my result. I am not interested in standards-defined behaviour here (the structs probably fail that test), only in compatibility between mingw and MSVC and maybe other compilers if conveniently possible.

In particular, will the structs be compatible? They uniformly consist of function pointers, so I don't think padding will be an issue. Additionally, is the stdcall convention necessary here, or would cdecl work just as well? Could I leave it unspecified then since both compilers will default to cdecl? Should I? Here is what I have right now:

#include <stdint.h>

typedef struct {
        uint32_t (__stdcall *read)(void*, uint8_t*, uint32_t);
        void (__stdcall *write)(void*, const uint8_t*, uint32_t);
        uint32_t (__stdcall *getBytesLeft)(void*);
        uint8_t (__stdcall *destroy)(void*);
} SharedStreamInterface;

typedef struct {
        uint32_t (__stdcall *read)(void*, uint8_t*, uint32_t);
        void (__stdcall *write)(void*, const uint8_t*, uint32_t);
        uint32_t (__stdcall *getBytesLeft)(void*);
        uint8_t (__stdcall *destroy)(void*);

        uint32_t (__stdcall *getreadpos)(void*);
        uint32_t (__stdcall *getwritepos)(void*);
        uint32_t (__stdcall *getlength)(void*);
        void (__stdcall *setreadpos)(void*, uint32_t);
        void (__stdcall *setwritepos)(void*, uint32_t);
        void (__stdcall *setlength)(void*, uint32_t);
} SharedBufferInterface;

extern "C" {
        // Functions applicable for both buffers and streams
        __stdcall uint32_t readData(uint32_t id, uint8_t* data, uint32_t size);
        __stdcall void writeData(uint32_t id, const uint8_t* data, uint32_t size);
        __stdcall uint32_t getBytesLeft(uint32_t id);
        __stdcall void destroyStreamOrBuffer(uint32_t id);
        __stdcall uint8_t streamOrBufferExists(uint32_t id);

        // Functions only applicable for buffers
        __stdcall uint32_t getReadPos(uint32_t id);
        __stdcall uint32_t getWritePos(uint32_t id);
        __stdcall uint32_t getLength(uint32_t id);
        __stdcall void setReadPos(uint32_t id, uint32_t pos);
        __stdcall void setWritePos(uint32_t id, uint32_t pos);
        __stdcall void setLength(uint32_t id, uint32_t length);
        __stdcall uint8_t bufferExists(uint32_t id);

        // Adding new buffers/Streams
        __stdcall uint32_t addStream(SharedStreamInterface *interface, void *stream);
        __stdcall uint32_t addBuffer(SharedBufferInterface *interface, void *buffer);
}

Edit: The project this was meant for has been on hold for a while now and probably needs a lot of rethinking if it's ever unshelved again. I'm leaving the question up though, because I'm still interested in the answer.


Solution

  • Yes, they will be compatible. That's the beauty with structs. As long as you don't introduce padding issues (which indeed wouldn't be the case here as you correctly pointed out), or in C++ add functionality to the structs that will result in - compiler-specific - vtable layouts, this will be always compatible.

    You will also notice that from the Windows headers the C declarations of COM interfaces use structs in much the same way you do.

    Side-note: the SharedStreamInterface::destroy member begs the question whether there is also one to "create" such a stream. You may want to share that as well. But your mileage may vary ...

    As for the question of the calling convention, both __cdecl and __stdcall should work across the binaries, however I would always prefer __stdcall for another reason: it is compatible with more "languages" (i.e. tools) than __cdecl.

    For style: use a #define to declare the calling convention explicitly (as you do) similar to WINAPI from the Windows headers.