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.
- Use extern “C” and have a C-style interface.The 2nd answer of this StackOverflow article gives an example of this technique.
- Use COM if you are on Windows
- Use Matthew Wilson’s technique from Imperfect C++ chapters 7-8.
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;
- };
Using an Interface
- struct Interface* pInterface = GetInterfaceSomehow();
- int a = pInterface->pTable->Function1(pInterface);
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;
- };
Code Snippet
- 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;
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*)
“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
Here is how we would define an interface DemoInterface. Note jrb_interface is the namespace of the library.
Code Snippet
- using namespace jrb_interface;
- template<bool b>
- struct DemoInterface
- :public define_interface<b,4>
- {
- cross_function<DemoInterface,0,int(int)> plus_5;
- cross_function<DemoInterface,1,int(std::string)> count_characters;
- cross_function<DemoInterface,2,std::string(std::string)> say_hello;
- cross_function<DemoInterface,3,std::vector<std::string>(std::string)>
- split_into_words;
- template<class T>
- DemoInterface(T t):DemoInterface<b>::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
Code Snippet
- 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::string>{
- std::vector<std::string> 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 use an interface, you construct use_interface providing the Interface as the template parameter.
Code Snippet
- // 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<std::string> words = iDemo.split_into_words("This is a test");
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
Please let me know what you think in the comments section
- John Bandela
CodeProject