Swift C++ Interop

zonble
6 min readDec 24, 2023

--

大概在兩年前左右就約略知道 Swift 團隊在開發 Swift/C++ Interop,也就是可以在 Swift 當中呼叫 C++ 程式的能力,而到了 2023 年 WWDC 也正式發布,在 Xcode 15 當中可以混寫 Swift 與 C++。

雖然公司還有一些 side project 都用到了 C++,也一直想試試看能不能無痛混合 Swift 與 C++ 。不過,網路上看到的討論看來不多,而由於今年的工作可說是水深火熱,大概前陣子才有時間試了一下,簡單寫點心得。心得是,雖然的確可以從 Swift 呼叫 C++,但目前應該還是很難實際用在工作上。以下條列些摘要:

  • C++ 的 namespace 到了 Swift 中會變成 enum,class 到了 Swift 中會變成 struct。
  • 由於變成了 struct,所以匯入的 C++ class 會失去原本所有的繼承關係。一方面,我們不能在 Swift 中寫一個 Swift class 去繼承一個 C++ class,另外,如果一個 C++ API 要求傳入某個 C++ class,我們也不能傳入這個 class 的 subclass。
  • C++ 那端的 virtual method 不會出現在 Swift 中。
  • 一些用了C++ 11 特性的 class 或 method,像是用了 unique_ptr ,也無法出現在 Swift 端。如果一個 method 用了 shared_ptr 當做參數,那麼這個 method 就不會暴露出來,但如果把 unique_ptr 用在成員變數上,那,整個 class 都不會出現,而所有繼承這個 class 的所有 subclass 也都不會出現。
  • 如果在 C++ 這端用到了 lambda,雖然可以在 Swift 這邊看到這些API,但是 Swift 這邊好像還找不到可以相對支援的語法。

開始混合 Swift 與 C++

就像我們想混用 Swift 與 Objective-C 一樣,只要在 Swift 專案當中,選擇新增一個 C++ 檔案,Xcode 就會問你要不要加上一個 Objective-C bridging header。其實可以想成,在 Xcode 15 之後,在Swift 中可以混用 Objective-C、C++ 與 Objective-C++。

不過,真的要開始能夠用 C++,記得要去改一下 Xcode 設定裡頭的 C++ and Objective-C Interoperability 選項,把 C/Objective-C 改成 C++/Objective-C++

接下來我們來隨性寫一個 C++ class。

去 Swift 那邊,我們就可以使用 Cpp.TestClass1() 這個語法,建立一個 instance,然後呼叫他的方法與成員變數。

C++ class 的 Swift Interface

如果我們直接在 Xcode 當中,對一個 C++ header ,選擇 “Generated Interface” 這個選項,我們沒辦法直接產生想要的 Swift Interface,Xcode 15 裡頭只會是一片空白。不過,我們如果將這個 class 放在一個 SPM 的 Package 當中,就可以從 Swift 這端去看從整個 package 匯入的 interface。就跟把 Objective-C 打包成 SPM 一樣,建立好一個 SPM 之後,我們把 C++/Objectice.c 的實作,放在 Source 目錄下,然後把所有的 public header,都放在 Source 目錄下的 include 目錄中。

然後,如果你在 Swift 這端,寫了一行 import CppStudy 的程式碼,在 CppStudy 這個 Module 名稱上,鍵盤按著 command 然後用滑鼠點擊,就會進入 module interface。於是我們可以看到,CppStudy 這個 C++ class 變成了 Swift enum,TestClass1 這個 class 變成了 struct。C++ 的 std::string 到了 Swift 中變成 std.string,然後 vector…變成了一個看起來就很嚇人的型態。

另外ˋ需要注意的是,由於對 C++ 物件呼叫任何 method,都可能影響 C++ 物件本身的成員變數,所以,所有的 method 都是 mutating 的。我們在 Swift 中要使用 C++ 物件時,大概都得用 var 而不是 let。

C++ class 的繼承關係

我們來試試看,建立一個 TestClass2,繼承自 TestClass1。

到了 Swift 這邊,可以看到,TestClass1 與 TestClass2,只是兩個各別的 struct,中間已經沒有繼承關係了。

假如,我們在 C++ 這邊,建立了一個 method,要傳入 TestClass1。

到了 Swift 這邊,就會發現,明明 TestClass2 繼承自 TestClass1,但我們卻不能夠把 TestClass2 傳進去。

其他 C++ 語言特性

我們再來做點實驗。我們建立一個 method channel,然後建立傳入 shared_ptr、unique_ptr 的 method,還有將 unique_ptr 當成成員變數。

到了 Swift 會變成這樣:

我們的 virtual method 被直接打上了 unavailable,原因是目前還沒有辦法支援。至於用了 unique_ptr 的 method,以及把 unique_ptr 的 class,就直接不見了。

或許還要一段時間,Swift C++ Interop 才會更成熟。這段時間想要讓 Swift 呼叫 C++,中間大概還是得要透過一層 Objective-C。

--

--