はじめに
Swift 5.9
から C++ interoperability
という機能が入りました。
これにより、C++
と Swift
に互換性ができ、直接呼び出せるようになりました!
詳細は WWDC2023
の動画を見てると面白いと思います。
では、何が変わったのかを見ていきましょう!
実装
まずは従来の方法を紹介します。
(Xcode でアプリ開発をしている前提で話します)
Swift 5.9 未満で C++ を扱う
今までは3通りの方法がありました。(他にあればコメントくださいmm)
-
C
でブリッジする(C++ の機能 extern "C" を使う) -
Objective-C++
でブリッジする -
SPM
からmodule.modulemap
で公開する
1つずつ見ていきましょう。
① C でブリッジする(C++ の機能 extern "C"
を使う)
前提として Swift
は C
と互換性があります。
Objective-C
と同じ要領で、C
のコードをヘッダーに追加して呼び出すだけです。
- Swift から C を呼び出す簡単な例
以下簡単な例を用意しました。
まず、C
のヘッダーと実行部分を用意します。
#ifndef CMethod_h
#define CMethod_h
void run_from_c(const char *str);
#endif /* CMethod_h */
実装は何かを出力する簡単な処理を入れておきます。
#include "CMethod.h"
#include "stdio.h"
void run_from_c(const char *str)
{
printf("%s\n", str);
}
最後にアプリのヘッダーに追加します。
#import "CMethod.h"
あとは Swift
で呼び出すだけです。
run_from_c("Hello, World! from C".cString(using: .utf8))
// 出力結果: "Hello, World! from C"
とても簡単です!
- Swift から C 経由で C++ を呼び出す簡単な例
C
を経由する(C
として振る舞う)ことで C++
は先の方法と同じ要領で呼び出すことができます。
使うのは extern "C"
という C++
の言語機能で、簡単に言えば C++
上でコードを C言語
として扱うようにさせるもので、これを使って実行します。
以下は簡単な例です。
まず、C++
のヘッダーと実行部分を用意します。
ヘッダーファイルで extern "C"
を使用します。
#ifndef CppMethod_hpp
#define CppMethod_hpp
#include <stdio.h>
#ifdef __cplusplus
extern "C" {
#endif
void run_from_cpp();
#ifdef __cplusplus
}
#endif
#endif /* CppMethod_hpp */
メソッド自体(今回はrun_from_cpp
)を __cplusplus
で囲ってしまうと、Swift
側で認識できなくなるので注意してください。
こちらでも、文字を出力する簡単な実装を入れておきます。
#include "CppMethod.hpp"
#include "iostream"
void run_from_cpp() {
std::cout << "Hello, World! from C++";
}
最後にアプリのヘッダーに追加します。
#include
なことに注意してください。
#include "CppMethod.hpp"
あとは Swift
で呼び出すだけです。
run_from_cpp()
// 出力結果: "Hello, World! from C++"
とても簡単でしたが、これにはいくつかの制約があります。例えば C
から C++
を呼び出す場合は、C++
側で対応する型を使用する必要があったりします。
② Objective-C++ でブリッジする
① は少しテクニックのような感じでしたが、こちらはシンプルに Objective-C++
を経由するものになります。
- Swift から Objective-C++ 経由で C++ を呼び出す簡単な例
まずは、先ほどと同じように C++
のヘッダーと実行部分を用意します。
今回はクラスにしてみます。
#ifndef CppClass_hpp
#define CppClass_hpp
#include <stdio.h>
class CppClass {
public:
void run(void);
};
#endif /* CppClass_hpp */
#include "CppClass.hpp"
#include "iostream"
void CppClass::run(void) {
std::cout << "Hello, World! from Objective-C++";
}
文字列を出力するだけの簡単なメソッドを持ったクラスです。
次にブリッジする部分を作ります。
#ifndef CppClassWrapper_hpp
#define CppClassWrapper_hpp
#include <stdio.h>
#import <Foundation/Foundation.h>
@interface CppClassWrapper : NSObject
- (void) run_from_objective_cpp;
@end
#endif /* CppClassWrapper_hpp */
#include "CppClassWrapper.h"
#include "CppClass.hpp"
@implementation CppClassWrapper {
CppClass *wrapper;
}
-(id)init {
wrapper = new CppClass();
return self;
}
-(void)dealloc {
delete wrapper;
}
-(void)run_from_objective_cpp {
wrapper->run();
}
@end
#include "CppClass.hpp"
で C++
のクラスを参照しています。
また、C++
のクラスを保持できるように、クラスにしています。
あとは、ヘッダーに Objective-C++
のヘッダーを追加して参照できるようにしましょう。
#import "CppClassWrapper.h"
これで完了です!
let wrapper = CppClassWrapper()
wrapper.run_from_objective_cpp()
// 出力結果: "Hello, World! from Objective-C++"
少し記述する量は増えましたが、基本的には橋渡しをしているだけでシンプルな実装できます。
③ SPM
から module.modulemap
で公開する
方法 ①と② をそのまま SPM
上に組み込むことで C++
を呼び出すことは可能です。
それを使用せずとも、module.modulemap
を使用することで、Objective-C++
のブリッジを使用する必要がなくなります。
module.modulemap とは?
Module Map は Bridging Header の上位互換で、Objective-C または C で書かれたライブラリの場合は、Modules ディレクトリの中に module.modulemap というファイルを設定することで、Swift 側からシンボルにアクセスできるようにします。
Swift が登場して以降の Xcode で Objective-C 製のライブラリなどをビルドした場合は自動的に module.modulemap ファイルが生成されます。
(引用:iOS開発におけるライブラリ、SDK、フレームワークなど)
以下は簡単な例です。
module ExampleCppLibrary [system] {
header "ExampleCppLibrary.hpp"
export *
}
同じような感じなので実装は割愛します。
少し調べたら出てくる & 生成AI(ChatGPTなど)に聞いてください笑
Swift 5.9 以上で C++ を扱う
ここまでは、C++
を Swift
上で動かすための従来の方法を紹介してきました。
今までの方法(特に①, ② )は ブリッジするコードが必要となり、若干の手間がありました。
C++ interoperability
では、これが必要なくなり、直接 C++
を呼ぶことができます!
- プロジェクト上にある C++ を参照する
C++
のヘッダーと実行部分を用意します。
ここでは、先ほど作成したコード例とほぼ同じものを使用して進めます。
コード例はこちら:CppClass.hpp, CppClass.cpp
#ifndef CppClass_hpp
#define CppClass_hpp
#include <stdio.h>
class CppClass {
public:
void run(void);
};
#endif /* CppClass_hpp */
#include "CppClass.hpp"
#include "iostream"
void CppClass::run(void) {
std::cout << "Hello, World! from C++";
}
これをそのままヘッダーに追加します。
#include "CppClass.hpp"
次に Xcode 側でコンパイルの設定を変更します。
interoperability の設定を C/Objective-C
-> C++/Objective-C++
にしましょう。
あとは、そのまま Swift
から呼び出すだけです。
var cppClass = CppClass()
cppClass.run()
// 出力結果: "Hello, World! from C++"
ブリッジコードが不要になり、とても簡単です!
注意点として、C
のコードはビルドできなくなります。
なので、もし C
と C++
のコードを混在させたプロジェクトがある場合は、安易にフラグを変えてしまうとビルドできなくなります。(ただし、Objective-C
はビルドできる)
その他
取り上げませんでしたが
-
C++
からSwift
を参照する -
SPM
でC++
を参照する - コマンドラインで使う
etc.
この辺はドキュメントに書いてあり、Swift
と C++
をもっと快適に、面白くできそうなことがたくさん載っています!
サンプルコード
試したサンプルを置いておきます。
説明した大部分のコードはこちらに入っています。
フラグの設定や読み込み部分は、試したいものを各自で変更してくださいませmm
関連
- Vision Pro での動作
Stack Overflow
のコメントでは Vision Pro
でビルドできないとありました。
May be I am doing something wrong, but the below code snippet from the official swift doc is working fine when the run destination is set to Mac, but when I change the run destination to Apple Vision Pro, it doesn't compile and throws some errors.
let cxxString = std.string("This is a C++ string") let swiftString = String(cxxString)
No exact matches in call to initializer So, what I get from this is that the mixing Swift and C++ doesn't support visionOS for now.
手元の Xcode 15.2
と Vision Pro
の Simulator では、紹介したサンプルコードは動作しました。C++
のユニークな型や機能を使った場合に動作するのかは分かりません。
- 興味深い記事
以下ピックアップで、紹介しませんでしたが、興味がある人は読んでみてください。
参考