Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When do we break binary compatibility

I was under the impression that whenever you do one of these:

  • Add a new public virtual method virtual void aMethod();
  • Add a new public non-virtual method void aMethod();
  • Implement a public pure-virtual method from an interface virtual void aMethod override;

Was actually breaking binary compatibility, meaning that if a project had build on a previous version of the DLL, it would not be able to load it now that there is new methods available.

From what I have tested using Visual Studio 2012, none of these break anything. Dependency Walker reports no error and my test application was calling the appropriate method.

DLL:

class EXPORT_LIB MyClass {
public:
  void saySomething();
}

Executable:

int _tmain(int argc, _TCHAR* argv[])
{
  MyClass wTest;
  wTest.saySomething();
  return 0;
}

The only undefined behavior I found was if MyClass was implementing an pure-virtual interface and from my executable, I was calling one of the pure-virtual method and then I added a new pure-virtual method before the one used by my executable. In this case, Dependency Walker did not report any error but at runtime, it was actually calling the wrong method.

class IMyInterface {
public:
  virtual void foo();
}

In the executable

IMyInterface* wTest = new MyClass();
wTest->foo();

Then I change the interface without rebuilding my executable

class IMyInterface {
public:
  virtual void bar();
  virtual void foo();
}

It is now quietly calling bar() instead of foo().

Is it safe to do all of my three assumptions?

EDIT:

Doing this

class EXPORT_LIB MyClass {
public:
  virtual void saySomething();
}

Exec

MyClass wTest;
wTest.saySomething();

Then rebuild DLL with this:

class EXPORT_LIB MyClass {
public:
  virtual void saySomething2();
  virtual void saySomething();
  virtual void saySomething3();
}

Is calling the appropriate saySomething()

like image 817
Ceros Avatar asked May 10 '16 21:05

Ceros


People also ask

What is binary incompatibility?

Binary incompatibility releases often lead to dependency hell, rendering your users unable to update any of their libraries without breaking their application. If a new library version is binary compatible but source incompatible, the user can fix the compile errors and their application should work.

What is the meaning of the compatible in Java?

Source: Source compatibility concerns translating Java source code into class files including whether or not code still compiles at all. Binary: Binary compatibility is defined in The Java Language Specification as preserving the ability to link without error.

What is C++ abi?

As C++ evolved over the years, the Application Binary Interface (ABI) used by a compiler often needed changes to support new or evolving language features. Consequently, programmers were expected to recompile all their binaries with every new compiler release.

What breaks ABI compatibility?

ABI break happens when one binary, e.g. shared object, uses a different ABI when calling functions on another shared object. If both binaries are not ABI compatible, the interpretation of the bytes would be wrong.


1 Answers

Breaking binary compatibility doesn't always result in the DLL not loading, in many cases you'll end up with memory corruption which may or may not be immediately obvious. It depends a lot on the specifics of what you've changed and how things were and now are laid out in memory.

Binary compatibility between DLLs is a complex subject. Lets start by looking at your three examples;

  • Add a new public virtual method virtual void aMethod();

This almost certainly will result in undefined behaviour, it's very much compiler dependant but most compilers will use some form of vtable for virtual methods, so adding new ones will change the layout of that table.

  • Add a new public non-virtual method void aMethod();

This is fine for a global function or a member function. A member function is essentially just a global function with a hidden 'this' argument. It doesn't change the memory layout of anything.

  • Implement a public pure-virtual method from an interface virtual void aMethod override;

This won't exactly cause any undefined behaviour but as you've found, it won't do what you expect. Code that was compiled against the previous version of the library won't know this function has been overridden, so will not call the new implementation, it'll carry on calling the old impl. This may or may not be a problem depending on your use case, it shouldn't cause any other side effects. However I think your mileage could vary here depending on what compiler you're using. So it's probably best to avoid this.

What will stop a DLL from being loaded is if you change the signature of an exported function in any way (including changing parameters and scope) or if you remove a function. As then the dynamic linker won't be able to find it. This only applies if the function in question is being used as the linker only imports functions that are referenced in the code.

There are also many more ways to break binary compatibility between dlls, which are beyond the scope of this answer. In my experience they usually follow a theme of changing the size or layout of something in memory.

Edit: I just remembered that there is an excellent article on the KDE Wiki on binary compatibility in C++ including a very good list of do's and don'ts with explanations and work arounds.

like image 124
Robert Di Paolo Avatar answered Sep 20 '22 13:09

Robert Di Paolo