Click here to Skip to main content
Click here to Skip to main content

Tagged as

Go to top

Easy Binary Compatible Interfaces Across Compilers in C++ - Part 0 of n: Introduction and a Sneak Preview

, 30 Dec 2012
Rate this:
Please Sign up or sign in to vote.
The problem of using a C++ library compiled with Compiler A, from a program compiled with Compiler B has been a problem for a while. This is especially true on Windows where Visual C++ generally breaks binary compatibility from release to release.

The problem of using a C++ library compiled with Compiler A, from a program compiled with Compiler B has been a problem for a while. This is especially true on Windows where Visual C++ generally breaks binary compatibility from release to release. Shipping a library for Windows involves shipping several versions for Visual C++ as well now often for mingw gcc.

Some of the problems C++ has in regards to binary compatibility across different compilers are:name mangling,object layout, exception support.

There are several ways to get around this.

There are whole books written on COM, so I won’t try to go into too many details. A brief overview in regards to the binary interface is here. The basic idea is that you define an interface like this.

Interface Definition 

struct Interface;
struct InterfaceVtable{
    int (*Function1)(struct Interface*);
    int (*Function2)(struct Interface*, int);
};
 
struct Interface{
    struct InterfaceVtable* pTable;
 
};

It can be used like this

Using an Interface 

struct Interface* pInterface = GetInterfaceSomehow();
int a = pInterface->pTable->Function1(pInterface);

Implementing an interface like this is painful and will be left as an exercise to the reader Smile.

Fortunately, (and by design), Microsoft Visual C++ and most Windows C++ compilers will generate something compatible to the above with an abstract base class using pure virtual functions.

Inteface using C++ (MSVC)

struct InterfaceCpp{
    virtual int Function1() = 0;
    virtual int Function2(int) = 0;
};

You can implement and use like this

struct InterfaceImplementation:public InterfaceCpp{
    virtual int Function1(){return 5;}
    virtual int Function2(int i){return 5 + i;}
};
 
InterfaceImplementation imp;
InterfaceCpp* pInterfaceCpp = &imp;
std::cout << pInterfaceCpp->Function2(5) << std::endl;

The reason for this, is that the version with function pointers was doing a vtable and a vptr by hand and this version is letting the compiler do it. For more information about vtable and vptr see the excellent article by Dan Saks in Dr. Dobbs.

While the above solution works on Windows (generally), this is not guaranteed to always work A more general cross-platform solution is presented in Matthew Wilson’s Imperfect C++ in chapters 7 and 8. He basically provides a way and macros that allow you to define the above structure manually (ie define your own vtables).

By using either COM style interfaces with compilers that have a compatible vtable layout or rolling your own, you can have cross-compiler binary compatible interfaces. However, you do not have

  • Exceptions
  • Due to not having exceptions, you often have to use error codes and thus do not have real return values.
  • Standard C++ types such as vector and string (use arrays and const char*)

In fact, in an article explaining why Microsoft created C++/CX Jim Springfield stated one of the problems with COM even with libraries such ATL was “There is no way to automatically map interfaces from low-level to a higher level (modern) form that throws exceptions and has real return values.”

During this series of posts, I will discuss the development of a C++11 library that has the following benefits

  • Able to use std::string and std::vector as function parameters and return values
  • Use exceptions for error handling
  • Compatible across compilers – able to use MSVC to create .exe and g++ to create .dll on Windows, and g++ for executable and clang++ to create .so on Linux
  • Works on Linux and Windows
  • Written in Standard C++11
  • No Macro magic
  • Header only library

As we progress we will talk about some of the disadvantages and areas for improvements and possible alternatives. Here is how we would define an interface DemoInterface. Note jrb_interface is the namespace of the library.

using namespace jrb_interface;
 
template<bool b>
struct DemoInterface
:public define_interface<b,4>
{
    cross_function<demointerface,0,int(int)> plus_5;
 
    cross_function count_characters;
 
    cross_function say_hello;
 
    cross_function(std::string)>
        split_into_words;
 
    template
DemoInterface(T t):DemoInterface::base_t(t),
        plus_5(t), count_characters(t),say_hello(t),split_into_words(t){}
};

In this library, all interfaces are actually templates that take a bool parameter. The reason for this will become clear as we discuss the implementation in later posts.

All interfaces inherit from define_interface which takes a bool parameter (just use the bool passed in to the template) and an int parameter specifying how many functions are in the interface. If you pass in a too small number, you will get a static_assert telling you that the number is too small.

To define a function in the interface, use the cross_function template

The first parameter is the interface in this case DemoInterface. The second parameter is the 0 based position of the function. The first function is 0, the second is 1, the third 2, etc. The third and final parameter of cross_function is the signature of the function is the name style as std::function.

Finally all interfaces need a templated constructor that takes a value t and passes it on to the base class as well as each function. For convenience the define_interface template defines a typedef base_t that you can use in your constructor initializer.

To implement an interface you would do this

struct DemoInterfaceImplemention:
    public implement_interface<demointerface>{
 
        DemoInterfaceImplemention(){
 
            plus_5 = [](int i){
                return i+5;
            };
 
            say_hello = [](std::string name)->std::string{
                return "Hello " + name;
            };
 
            count_characters = [](std::string s)->int{
                return s.length();
            };
 
            split_into_words =
                [](std::string s)->std::vector{
                    std::vector ret;
                    auto wbegin = s.begin();
                    auto wend = wbegin;
                    for(;wbegin!= s.end();wend = std::find(wend,s.end(),' ')){
                        if(wbegin==wend)continue;
                        ret.push_back(std::string(wbegin,wend));
                        wbegin = std::find_if(wend,s.end(),
                            [](char c){return c != ' ';});
                        wend = wbegin;
                    }
                    return ret;
            };
 
        }
};

To implement an interface, you derive from implement_interface specifying your Interface as the template parameter. Then in your constructor you assign a lambda with the same signature you specified in the definition of the interface to each of the cross_function variables.

To use an interface, you construct use_interface providing the Interface as the template parameter.

// Assume iDemo is defined as follows
// use_interface<demointerface> iDemo = ...
int i = iDemo.plus_5(5);
 
int count = iDemo.count_characters("Hello World");
 
std::string s =  iDemo.say_hello("John");
 
std::vector words = iDemo.split_into_words("This is a test");

You then call the functions just as you would with any class object. Note the use of . instead of –>

Thank you taking the time to read this post. I hope this has piqued your interest. In future posts we will explore how we create this library, and how we can extend this library to do more. I hope you will join me.

You can find compilable code at https://github.com/jbandela/cross_compiler_call. The code has been tested on

  • Windows with compiling the executable with MSVC 2012 Milan (Nov CTP) and the DLL with mingw g++ 4.7.2
  • Ubuntu 12.10 with compiling the executable with g++ 4.7.2 and the .so file with clang++ 3.1

Instructions on how to compile are included in the README.txt file.

Please let me know what you think in the comments section

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

John Bandela
Software Developer self employed
United States United States
I started programming in Basic in the 4th grade. In 8th grade, I convinced my parents to buy me Visual C++ 1.0. I discovered I really enjoyed C++, and have been programming in it since. I attended the University of Florida and majored in Computer Science graduating with honors with a 4.0 GPA. I then attending Medical School and obtained my Doctor of Medicine degree.
 
I have used my computer skills to help me in my medical practice. I also enjoy programming in C++ just for fun, trying to discover new ways of solving problems or applying C++ to new areas. My latest interest has been in creating a component system for C++ allowing components created with 1 compiler to be easily used by another compiler.

Comments and Discussions

 
GeneralMy vote of 5 Pinmemberyanqisong1-Jan-13 22:20 
GeneralMy vote of 5 Pinmemberhardben1-Jan-13 7:38 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140916.1 | Last Updated 31 Dec 2012
Article Copyright 2012 by John Bandela
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid