一つ前のエントリーの続き。
C++ で作られた関数を Swift から呼び出す方法。 巷には C++ の関数を Swift から呼び出す方法に関する記事が溢れているが、
- XCode 前提で Swift Package Manager プロジェクトについて触れてない
- SPM 使っててもちょっと古い
- Bridging-Header.h を使う方法しかない
ということで、自分で書くことにした。
なお、使っている Swift Package Manager は 4.2.1 。ここに書いた方法は macOS X Mojave と Ubuntu 16.04(Docker の swift:4.2.1 イメージ) で動作を確認した。
まず、プロジェクトの構造を作る。
. ├── Package.swift ├── Sources │ ├── CppWrapper │ │ ├── Cpp │ │ │ ├── cpp_lib.cpp │ │ │ └── cpp_lib.hpp │ │ ├── cpp_bridge.cpp │ │ └── include │ │ └── cpp_bridge.hpp │ ├── CppBridge │ │ └── include │ ├── CppLib │ │ └── include │ └── SwiftCppApp │ ├── Bridging-Header.h │ └── main.swift └── Tests └── LinuxMain.swift
Package.swift
は次のようになる。
// swift-tools-version:4.2 import PackageDescription let package = Package( name: "SwiftCppApp", products: [ .executable(name: "SwiftCppApp", targets: ["SwiftCppApp"]), .library(name: "CppWrapper", targets: ["CppWrapper"]), ], dependencies: [ ], targets: [ .target(name: "CppWrapper"), .target(name: "SwiftCppApp", dependencies: ["CppWrapper"]), ], cLanguageStandard: .c11, cxxLanguageStandard: .cxx11 )
まず、 C++ のコードを用意する。これはとある ライブラリーが C++ しか提供されていなくて、それを呼び出すために一旦 C++ で書いた場合などを想定している。
この C++ のファイルは CppWrapper
の直下ではなく、 Cpp
というさらに下のディレクトリーに置くところがポイント。 CppWrapper
直下に置くと、 <string>
ヘッダーが見つからないというエラーが出てくる。
cpp_lib.hpp
#ifndef CPP_LIB_HPP #define CPP_LIB_HPP #include <string> void show(std::string const& message); #endif //CPP_LIB_HPP
cpp_lib.cpp
同じディレクトリーにおくので、ヘッダーは #include "cpp_lib.hpp"
とクオーテーションで囲んだ形式で取り込む
#include "cpp_lib.hpp" #include <string> #include <iostream> void show(std::string const& message) { std::cout << message << std::endl; }
次は C++ のラッパー部分だが、 C で読めるように翻訳する C++ で書かれたレイヤー。
cpp_bridge.hpp
#ifndef CPP_BRIDGE_HPP #define CPP_BRIDGE_HPP #ifdef __cplusplus extern "C" #endif void show_message(char const* message); #endif //CPP_BRIDGE_HPP
cpp_bridge.cpp
先程の cpp_lib.hpp
は include
ディレクトリーにないので、相対パスで取り込みをおこなう。
#include <cpp_bridge.hpp> #include <string> #include "Cpp/cpp_lib.hpp" #ifdef __cplusplus extern "C" #endif void show_message(char const* message) { std::string msg(message); show(msg); }
extern "C"
によって、 Swift には C のインターフェースとして見えるようになります(多分)。
後は C の関数を Swift から呼び出すコードを書けばコードは完成する。
import Foundation import CppWrapper let string = "hello" var message = string if var msg = message.cString(using: .utf8) { show_message(&msg); }
ビルドする際に、オプションをいくつか指定しないと失敗する。
必要なオプションは次のコマンドの通り
swift build -Xcxx -std=c++11
これを入れることで、 C++ を使っていることが認識される。