LoginSignup
31
17

[Swift] Swift 5.9 と C++ の互換性

Last updated at Posted at 2024-03-02

はじめに

Swift 5.9 から C++ interoperability という機能が入りました。
これにより、C++Swift に互換性ができ、直接呼び出せるようになりました!

詳細は WWDC2023 の動画を見てると面白いと思います。

では、何が変わったのかを見ていきましょう!

実装

まずは従来の方法を紹介します。

(Xcode でアプリ開発をしている前提で話します)

Swift 5.9 未満で C++ を扱う

今までは3通りの方法がありました。(他にあればコメントくださいmm)

  1. C でブリッジする(C++ の機能 extern "C" を使う)
  2. Objective-C++ でブリッジする
  3. SPM から module.modulemap で公開する

1つずつ見ていきましょう。

① C でブリッジする(C++ の機能 extern "C" を使う)

前提として SwiftC と互換性があります。
Objective-C と同じ要領で、C のコードをヘッダーに追加して呼び出すだけです。

- Swift から C を呼び出す簡単な例

以下簡単な例を用意しました。
まず、C のヘッダーと実行部分を用意します。

CMethod.h
#ifndef CMethod_h
#define CMethod_h

void run_from_c(const char *str);

#endif /* CMethod_h */

実装は何かを出力する簡単な処理を入れておきます。

CMethod.c
#include "CMethod.h"
#include "stdio.h"

void run_from_c(const char *str)
{
    printf("%s\n", str);
}

最後にアプリのヘッダーに追加します。

App-Bridging-Header.h
#import "CMethod.h"

あとは Swift で呼び出すだけです。

.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" を使用します。

CppMethod.hpp
#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 側で認識できなくなるので注意してください。

こちらでも、文字を出力する簡単な実装を入れておきます。

CppMethod.cpp
#include "CppMethod.hpp"
#include "iostream"

void run_from_cpp() {
    std::cout << "Hello, World! from C++";
}

最後にアプリのヘッダーに追加します。
#include なことに注意してください。

App-Bridging-Header.h
#include "CppMethod.hpp"

あとは Swift で呼び出すだけです。

.swift
run_from_cpp()
// 出力結果: "Hello, World! from C++"

とても簡単でしたが、これにはいくつかの制約があります。例えば C から C++ を呼び出す場合は、C++ 側で対応する型を使用する必要があったりします。

② Objective-C++ でブリッジする

① は少しテクニックのような感じでしたが、こちらはシンプルに Objective-C++ を経由するものになります。

- Swift から Objective-C++ 経由で C++ を呼び出す簡単な例

まずは、先ほどと同じように C++ のヘッダーと実行部分を用意します。
今回はクラスにしてみます。

CppClass.hpp
#ifndef CppClass_hpp
#define CppClass_hpp

#include <stdio.h>

class CppClass {
public:
    void run(void);
};

#endif /* CppClass_hpp */
CppClass.cpp
#include "CppClass.hpp"
#include "iostream"

void CppClass::run(void) {
    std::cout << "Hello, World! from Objective-C++";
}

文字列を出力するだけの簡単なメソッドを持ったクラスです。

次にブリッジする部分を作ります。

CppClassWrapper.h
#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 */
CppClassWrapper.mm
#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++ のヘッダーを追加して参照できるようにしましょう。

App-Bridging-Header.h
#import "CppClassWrapper.h"

これで完了です!

.swift
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、フレームワークなど

以下は簡単な例です。

.modulemap
module ExampleCppLibrary [system] {
    header "ExampleCppLibrary.hpp"
    export *
}

同じような感じなので実装は割愛します。
少し調べたら出てくる & 生成AI(ChatGPTなど)に聞いてください笑

Swift 5.9 以上で C++ を扱う

ここまでは、C++Swift 上で動かすための従来の方法を紹介してきました。
今までの方法(特に①, ② )は ブリッジするコードが必要となり、若干の手間がありました。

C++ interoperability では、これが必要なくなり、直接 C++ を呼ぶことができます!

- プロジェクト上にある C++ を参照する

C++ のヘッダーと実行部分を用意します。
ここでは、先ほど作成したコード例とほぼ同じものを使用して進めます。

コード例はこちら:CppClass.hpp, CppClass.cpp
CppClass.hpp
#ifndef CppClass_hpp
#define CppClass_hpp

#include <stdio.h>

class CppClass {
public:
    void run(void);
};

#endif /* CppClass_hpp */
CppClass.cpp
#include "CppClass.hpp"
#include "iostream"

void CppClass::run(void) {
    std::cout << "Hello, World! from C++";
}

これをそのままヘッダーに追加します。

App-Bridging-Header.h
#include "CppClass.hpp"

次に Xcode 側でコンパイルの設定を変更します。

Screenshot 2023-12-27 at 22.43.37.png

interoperability の設定を C/Objective-C -> C++/Objective-C++ にしましょう。

あとは、そのまま Swift から呼び出すだけです。

.swift
var cppClass = CppClass()
cppClass.run()
// 出力結果: "Hello, World! from C++"

ブリッジコードが不要になり、とても簡単です!

注意点として、C のコードはビルドできなくなります。

なので、もし CC++ のコードを混在させたプロジェクトがある場合は、安易にフラグを変えてしまうとビルドできなくなります。(ただし、Objective-C はビルドできる)

その他

取り上げませんでしたが

  • C++ から Swift を参照する
  • SPMC++ を参照する
  • コマンドラインで使う
    etc.

この辺はドキュメントに書いてあり、SwiftC++ をもっと快適に、面白くできそうなことがたくさん載っています!

サンプルコード

試したサンプルを置いておきます。
説明した大部分のコードはこちらに入っています。

フラグの設定や読み込み部分は、試したいものを各自で変更してくださいませ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.2Vision Pro の Simulator では、紹介したサンプルコードは動作しました。C++ のユニークな型や機能を使った場合に動作するのかは分かりません。

- 興味深い記事

以下ピックアップで、紹介しませんでしたが、興味がある人は読んでみてください。

参考

31
17
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
31
17