When we break binary compatibility

I got the impression that whenever you do one of them:

  • Add a new public virtual method virtual void aMethod();
  • Add a new public non-virtual method void aMethod();
  • Implementation of a publicly available pure virtual method from the virtual void aMethod override; interface virtual void aMethod override;

It actually violated binary compatibility, which means that if the project was built on a previous version of the DLL, now it will not be able to load it if new methods are available.

From what I tested with Visual Studio 2012, none of this breaks anything. Dependency Walker did not report an error, and my test application called 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 that I found was that MyClass implemented a purely virtual interface and from my executable file, I called one of the purely virtual methods, and then I added a new purely virtual method before it was used by my executable. In this case, the Dependency Walker did not report any error, but at run time it actually called the wrong method.

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

In executable file

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

Then I change the interface without restoring my executable

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

Now a quiet call to bar() instead of foo() .

Can all my three assumptions be made?

EDIT:

Doing this

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

Exec

 MyClass wTest; wTest.saySomething(); 

Then rebuild the DLL as follows:

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

Calls the corresponding saySomething()

+6
source share
2 answers

Interruption of binary compatibility does not always mean that the DLL does not load, in many cases you will get memory corruption, which may or may not be immediately obvious. It depends a lot on the features of what you changed, and how everything was and is now laid out in memory.

Binary compatibility between DLLs is a tricky question. Let's start by looking at your three examples:

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

This will almost certainly lead to undefined behavior, it depends a lot on the compiler, but most compilers will use some form of vtable for virtual methods, so adding new ones will change the layout of this table.

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

This is normal for a global function or member function. A member function is essentially just a global function with the hidden argument 'this'. It does not change the location of the memory.

  • Implementation of a publicly available pure virtual method from the virtual void aMethod override; interface virtual void aMethod override;

This will not exactly trigger undefined behavior, but as you find it will not do what you expect. The code that was compiled against the previous version of the library will not know that this function has been redefined, therefore it will not call a new implementation, it will continue to call the old impl. This may or may not be a problem depending on your use case; it should not cause any other side effects. However, I think your mileage may vary depending on which compiler you use. Therefore, it is best to avoid this.

What will stop loading the DLL is if you somehow change the signature of the exported function (including changing the parameters and scope) or if you delete the function. Since the dynamic linker cannot find it. This only applies if the function in question is used, since the linker only imports the functions referenced in the code.

There are also many ways to break binary compatibility between dlls that are beyond the scope of this answer. In my experience, they usually follow the theme of resizing or arranging something in memory.

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

+8
source

C ++ does not say.

Visual Studio typically follows COM rules, allowing you to add virtual methods at the end of your derived class, if they are not overloads.

Any non-static data element will also change the binary layout.

Non-virtual functions do not affect binary compatibility.

Templates create a huge mess due to name manipulation.

The best way to maintain binary compatibility is to use the pimpl idiom and the nvi idiom quite liberally.

+3
source

All Articles