kkAyatakaのメモ帳。

誰かの役に立つかもしれない備忘録。

C++向けライブラリの一部実装をSwiftで行おうとして諦めた話

MacC++のプログラミングをしているとCoreFoundationでは機能が足りないことがある。これまではObjective-Cの関数をC言語スタイルの関数にラップして使用するということをよくやっていた。

今回どうせならObjective-CではなくSwiftでやれないかと試したけど、結果的にあまり上手くないなぁというところに行き着いたので、試行錯誤をまとめておく。

  • C/C++から直接Swiftを呼び出す方法は(公式には)なさげ
  • staticライブラリはアプリのビルド段階で上手く行かない
  • dynamicライブラリは作れるけど、libswiftCore.dylibやらが必要になって上手くない

Swiftを使うと言ってもC++ -> Objective-C -> SwiftとしたのでObjective-Cのレイヤーは抜けてない。将来的にどうなっていくのかはちょっと気になる。

環境

@objc publicが必要

まずはObjecive-CからSwiftを呼び出そうとしていきなり詰まる。Swift 4.2では@objc publicを明示的につけないと見つけてもくれない。

@objc public class SwiftClass {
    @objc public func hello() -> String {
        return "hello"
    }
}

Swift 3だとなくても動く。正確に理解していないからといったらそれまでだけど、古い情報がイロイロ引っかかって、そのまま動かないのは結構辛かった。

inoutは使えない

既存のAPIを参考にして書いても通らないし、Swift単体のコードとも違うので、すぐにわからなかった。

// Objective-Cに公開するメソッドにはinoutは要らない(つけられない)
@objc func getPath(buf: inout UnsafeMutablePointer<CChar>?, bufSize: UnsafeMutablePointer<CInt>?) {
    ...
}

もうちょっとエラーが語ってくれてもなぁ。慣れの問題でもあろうけど、Objective-C向けかSwift向けかってのを意識する必要があるのか。

Method cannot be marked @objc because the type of the parameter 1 cannot be represented in Objective-C

dylib版だと動くけど、libswiftCore.dylib等が必要

staticライブラはどうにもリンクが通らないので、dynamic版にしたところこちらは上手くいった。が、実行するとエラー。

dyld: Library not loaded: @rpath/libswiftCore.dylib

確かに調べてみると、Swiftで作ったアプリもバンドルの中にがっつり組み込まれている。

f:id:kkAyataka:20190319190912p:plain

これは単純にアプリケーションバンドルと同じように、@rpathが届く範囲にファイルをおいてやれば解決する。実際に試したところ上手く動く。...んだけど、わざわざ動かすのにファイルが必要になってくるのは上手くないので、ここで詰み。Swift使えてもデメリットが大きく、現時点では上手くない。

ちなみにXcodeで「Always Embed Swift Standard Libraries」をYesにしておくと自動的にコピーしてくれるらしんだけど、プロジェクトタイプによってはツールが動作せず、Warningが出てコピーされない。

f:id:kkAyataka:20190320172623p:plain
Xcode-AlwaysEmbedSwiftStandardLibraries

libswiftCoreにはstatic版もあるんだけど...

Xcode.appの中をあさると一応static版も見つかる。だけど使おうにもXcodeから変更する方法が無いっぽい(あまり調べてない)。

$ find /Applications/Xcode.app/Contents/Developer/Toolchains -name "libswiftCore.*"
...
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift_static/macosx/libswiftCore.a
...
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx/libswiftCore.dylib

Swift単体のコマンドラインツールはdylibが不要

Swift単体のコマンドラインツールはdylibいらんしなぁ...というので、ビルドログを調べてみるとstatic版(swift_static)を参照していた。

Ld /Users/ayataka/Projects/SwiftCmd/DerivedData/SwiftCmd/Build/Products/Debug/SwiftCmd normal x86_64 (in target: SwiftCmd)
    cd /Users/ayataka/Projects/SwiftCmd
    export MACOSX_DEPLOYMENT_TARGET=10.14
    ...
    -Xlinker -no_deduplicate -fobjc-arc -fobjc-link-runtime -L/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift_static/macosx
    ...

dylibのビルドの方も同じようにみてみると、こちらはdylib版を参照してる。ここの参照を変更することができれば...と思わなくはないけど。Xcodeでなくswiftcとclangを直に使ったらできるのかも。

Ld /Users/ayataka/Projects/SwiftCmd/DerivedData/SwiftCmd/Build/Products/Debug/libDynamic.dylib normal x86_64 (in target: Dynamic)
    cd /Users/ayataka/Projects/SwiftCmd
    export MACOSX_DEPLOYMENT_TARGET=10.14
    ...
    -Xlinker -no_deduplicate -fobjc-arc -fobjc-link-runtime -L/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx
    ...

まあ、ライブラリ側でlibswiftCoreにあるようなオブジェクトを個別に持つのは非効率。Swiftのコマンドラインツールが内包してしまうのも、プログラムそのものであれば重複を気にすることもないので、理にはかなった動作だとは思うけど。