所謂二進制兼容就是在作版本升級(也多是Bug fix)庫文件的時候,沒必要要作從新編譯使用這個庫的可執行文件或使用這個庫的其餘庫文件,同時能保證程序功能不被破壞。
固然,這只是一個現象級描述,其實在一些簡單的例子裏,假設咱們導出一個C++類,在調用時,第三方仍然不須要從新編譯能夠運行。以下面例子:html
//導出類 class __declspec(dllexport) FastString { public: FastString(); ~FastString(); size_t length() { return 0; } private: unsigned char *m_bytes; }
int main() { FastString fStr; size_t len = fStr.length(); printf("fast string length %d\n", len); _getch(); return 0; }
若是咱們給導出類加上一個虛函數設計模式
virtual boole isEmpty(); // 位於 length 方法以前
從新編譯FastString.dll,而後直接運行test.exe,發現仍然能打印出fast string length 0
,而且沒有運行錯誤。
因此按照上面所說,FastString.dll是二進制兼容的。然而不是的!由於它增長了一個虛函數,致使FastString實例增長了一個虛函數表(是一個void **
指針),那爲何運行的時候沒有錯誤呢?參考這個問題:SO- why new virtual function will not break binary compatibility per phenomenon? 安全
因此嚴格來說,二進制兼容是保證在版本升級的狀況下,對象實例的內存佈局沒有發生變化。app
打個比方,若是庫A升級沒有作到二進制兼容,那麼全部依賴它的程序(或庫)都須要要從新編譯才能應用A庫的新版本,不然會出現各類未知異常,其直接現象就是程序莫名其妙的掛掉。 函數
譬如像Qt這種使用率很廣的程序庫,若是每次版本升級都須要第三方使用者從新編譯源程序,我想確定是不少人不肯意的。佈局
template <typename T> class Grid {}
變動爲 template <typename t, typenameContainer=vector> class Grid{}
。enum
的值。把enum Color { Red = 3};
改成Red = 4
,這會形成錯位。固然,因爲enum
自動排列取值,添加enum
項也是不安全的,除非是在末尾添加。關於更多的 Do's and Don'ts,能夠閱讀KDE的兩篇wiki: Policies/Binary Compatibility Issues With C++, Policies/Binary Compatibility Examples
COM (Component object model) 組件對象模型是微軟提出的一個偉大想法,它實際上是一個規範,而且是二進制規範,也就是說只要遵循這個規範,任何語言、任何平臺均可以相互調用相應組件。 操作系統
COM涉及到幾個概念:設計
coclass - component object class,簡單來講就是COM組件提供給使用者的接口類,這些類其實都是都繼承 IUnkown
接口的抽象類,裏面都是純虛函數。這個IUnknown
包含三個方法:指針
AddRef
- 增長對象引用計數Release
- 減小引用計數,若是計數爲0,則銷燬QueryInterface
- 根據GUID來查到對象COM組件還涉及到註冊表,它能夠註冊到操做系統的註冊表中,這樣就算當前這個組件DLL物理位置與運行文件不在同一個目錄,也能夠加載並獲取DLL的導出對象或者函數。更多瞭解能夠看 CodeProject - Introduction to COM - What It Is and How to Use It。
那爲何能夠說COM能保證二進制兼容呢? code
其實經過上面兩個概念能夠有點思緒,所謂二進制兼容對於C++ 來講就是要保證第三方使用DLL提供的接口對象時,保證內存佈局不會改變,或者說不會影響。對於C++來講,對象內存佈局的主要包括:
對於COM實現來講,由於是經過GUID來獲取對象,而且這些對象都是由接口來提供的實例化(抽象類不能建立實例,這些實例都是繼承的子類實現),就像 caller ----> coclass (interface) --create--> instance 這樣調用。
因爲 instance 是在COM組件類(DLL)實例化以及釋放,因此其內存佈局對於 caller 來講是沒有影響的。
D指針模式其實和上面COM的方式有點相似,可是它沒有COM那麼複雜。咱們用一個例子來講明爲何D指針模式能作到二進制兼容。
假設你的class Foo
裏定義了一個前置聲明類FooPrivate
class FooPrivate;
而且把D指針放在private區
private: FooPrivate *d;
FooPrivate
類能夠徹底在class實現的地方定義(通常是 *.cpp),例如:
class FooPrivate { public: FooPrivate() : m1(0), m2(0) {} int m1; int m2; QString s; }
而後你所要作的就是在Foo的構造函數或者 init
方法裏建立 private 數據
d = new FooPrivate();
而且在析構函數裏 delete 掉
delete d;
固然,在不少時候,咱們可能不想讓D指針被修改,或者被複制致使咱們失去了它的控制權,最後致使內存泄漏。因此不少時候咱們會把D指針聲明爲 const
,即
private: FooPrivate* const d;
這樣就能夠容許第三方去修改D指針指向的內容,可是不能修改這個指針的指向目標。
當這樣實現後,咱們全部的數據操做都是經過class Foo 的成員方法來作,例如:
QString Foo::string() const { return d->s; } void Foo::setString( const QString& s ) { d->s = s; }
從上面能夠看到,D指針的實現形式其實也是把數據區域隱藏,只經過方法的調用形式來操做。這樣當咱們須要修改 Foo 成員變量,對於第三方來講是沒有影響的,由於這個成員變量是在 FooPrivate 實例裏。
和COM相關: