閱讀721 返回首頁    go 技術社區[雲棲]


C++、Java、Objective-C、Swift 二進製兼容測試

鑒於目前動態庫在iOS App中使用越來越廣泛,二進製的兼容問題可能會成為一個令人頭疼的問題。本文主要對比一下C++、Java、Objecive-C和Swift的二進製兼容問題。

iOS端動態庫使用情況

  1. iOS 8開始支持App使用動態庫。
  2. 蘋果對提交的App的__TEXT__段大小是有限製的,很多巨無霸App容易超出這個限製。iOS9之前每個架構的__TEXT__段比較小,iOS9放大到了500MB。詳細情況請看:To submit an app for review
  3. 開源庫隻能通過Podfile做源碼引入,源碼依賴,編譯非常慢。
  4. 可持續構建也需要基於蘋果的環境,比如使用Mac Pro/Mac Mini構建。Mac Pro比較昂貴,Mac mini性能不行,構建一次需要花費大量時間。
  5. 大型App為了加快編譯速度,可以維護自己的私有倉庫,把依賴的庫盡量編譯成Framework,加快編譯速度。
  6. Swift目前必須基於動態庫開發。
  7. 基於動態庫構建App,升級一個動態庫需要將整個依賴樹編譯一遍。尤其是一些頻繁變動的基礎組件,比如視覺組件的改動,牽一發而動全身。

測試環境

C++、Java、OC和Swift分別實現Foo這個基類,然後再實現Bar這個子類,main則使用Bar類打印成員變量的信息。給Foo類添加成員變量member0,重新編譯Foo(make foo && ./main),Bar和main不變,然後觀察執行結果。

代碼地址:binary_compatibility_test

LLDB一點有用的調試技巧。更多的調試功能,請參看:The LLDB Debugger

br set -f main.cpp -l 17 //在main.m:17打斷點
br set -f main.cpp -n main //在main.m:main函數打斷點
x/10a *(void**)bar //將bar對象的虛函數打印出來

測試結果

  1. C++會出現錯位,但是沒有崩潰。二進製也是比較脆弱的。
  2. Java能正常工作。
  3. OC能正常工作。OC非常適合基於動態庫的組件方式。
  4. Swift構造Bar對象就會崩潰。現狀讓我們非常頭疼。
warning: (x86_64) libBar.dylib unable to load swift module 'Bar' (failed to get module 'Bar' from AST context:
error: missing required module 'Foo'
)
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=2, address=0x1000a7a98)
  * frame #0: 0x00007fff90419fd1 libobjc.A.dylib`realizeClass(objc_class*) + 1155
    frame #1: 0x00007fff9041d26d libobjc.A.dylib`_class_getNonMetaClass + 127
    frame #2: 0x00007fff9041d053 libobjc.A.dylib`lookUpImpOrForward + 232
    frame #3: 0x00007fff9041cad4 libobjc.A.dylib`_objc_msgSend_uncached + 68
    frame #4: 0x00000001000a4df5 libBar.dylib`type metadata accessor for Bar at Bar.swift:0
    frame #5: 0x0000000100001584 main`main at main.swift:7
    frame #6: 0x00007fff90d0f235 libdyld.dylib`start + 1
    frame #7: 0x00007fff90d0f235 libdyld.dylib`start + 1

結果分析

  1. C++的設計沒有考慮到二進製兼容的問題,所以兼容很一般。
  2. Java的二進製兼容非常完美,對象成員改變,方法增刪,都不會輕易導致二進製兼容問題。詳細情況請參看:Chapter 13. Binary Compatibility
  3. OC使用方法和屬性都使用消息派發,增加和刪除方法,移動方法的順序,都不會導致問題;另外對成員變量的改變做了支持,所以二進製兼容完美。
  4. 作為一種嶄新的語言,Swift的二進製兼容最差,匪夷所思啊。

另外大家討論的時候也提到C++虛函數改變順序會不會出問題。針對這個問題我驗證了一下,確認C++虛函數表裏麵函數的順序完全取決於函數在頭文件中聲明的順序。

比如Foo有func1和func2兩個虛函數,調換func1和func2的順序,不重新編譯main。在main裏麵調用func2,實際上會調用到func1。

#include "Foo.h"

using namespace std;

int main()
{
    Foo *foo = new Foo();
    foo->func2();
    delete foo;

    return 0;
}   
$ lldb main
(lldb) target create "main"
Current executable set to 'main' (x86_64).
(lldb) br set -f main.cpp -l 17
Breakpoint 1: where = main`main + 65 at main.cpp:17, address = 0x0000000100000f11
(lldb) run
Process 11179 launched: '/Users/henshao/binary_compatibility_test/C++/main' (x86_64)
Process 11179 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x0000000100000f11 main`main at main.cpp:17
   14       delete bar;*/
   15   
   16       Foo *foo = new Foo();
-> 17       foo->func2();
   18       delete foo;
   19   
   20       return 0;
(lldb) x/10a *(void**)foo
0x1000900f0: 0x000000010008eff0 libfoo.dylib`Foo::func2() at Foo.cpp:10
0x1000900f8: 0x000000010008ee40 libfoo.dylib`Foo::func1() at Foo.cpp:6
0x100090100: 0x000000010008f050 libfoo.dylib`Foo::~Foo() at Foo.cpp:15
0x100090108: 0x000000010008f070 libfoo.dylib`Foo::~Foo() at Foo.cpp:15
0x100090110: 0x00007fff999b9bb8 libc++abi.dylib`vtable for __cxxabiv1::__class_type_info + 16
0x100090118: 0x000000010008ff00 libfoo.dylib`typeinfo name for Foo
0x100090120: 0x0000000000000000
0x100090128: 0x0000000000000000
0x100090130: 0x0000000000000000
0x100090138: 0x0000000000000000

參考文章

  1. C++ ABI Compliance Checker
  2. Objective-C類成員變量深度剖析
  3. Non Fragile ivars
  4. Objc源碼
  5. Swift庫二進製接口(ABI)兼容性研究

最後的最後

Golang也是一門讚新的語言,我非常好奇它對二進製兼容這塊是怎麼考慮的,所以歡迎廣大有為青年補充一個Golang版本。

最後更新:2017-10-23 15:34:05

  上一篇:go  重寫hashcode,玩兒壞HashMap
  下一篇:go  Java微服務開發指南(譯)-- 集群管理、失敗轉移和負載均衡的實踐