mike-neckのブログ

Java or Groovy or Swift or Golang

Swift Package Manager プロジェクト にて CPP(C++) の関数を Swift から呼び出す

f:id:mike_neck:20180609045321p:plain

一つ前のエントリーの続き。

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.hppinclude ディレクトリーにないので、相対パスで取り込みをおこなう。

#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++ を使っていることが認識される。