SwiftでC++コードを動かそう

C++

世の中はCなんだよなあ

Swiftはたしかに便利な言語である。ところが、Swiftは「高級すぎる」プログラミング言語なのだ。

プログラミング言語に対して「高級」というのはまあ簡単に言えばやりたいことが最初から実装されていて、例えば配列の最大値をとるのにlet max = Array.max()みたいなので簡単にとってこれるような言語のことを指す。

Swiftはもちろん高級言語だし、Pythonやその他も標準ライブラリが充実しており高級言語といえるだろう。

それらの高級言語とは別に低級言語というのが存在する。アセンブラやILなどまあいろいろあるのだが、最も有名なのはC言語だろう。

低級言語の顔をした高級言語とも、高級言語の顔をした低級言語とも言われる、ようするになんでもできてしまうプログラミング言語なのだ。

そして、多くのUNIXライブラリはCで開発されている。なんでもできるのだからこれは当然だろう。

便利なライブラリないかなあって思ったら「それがCで書かれたものでした」というのは少なくない。小さなプロジェクトならいいが、大きいものを一人でいちいちSwiftで書き直していたら人生が何回あったって足りない。

要するに、Cで書かれたライブラリをSwiftから実行できる仕組みがほしいのだ。

なんでCライブラリを使いたいのか

というのも、やねうら王エンジンをiOSで動かしたいと思ったため。やねうら王はWindowsだけでなくUNIXもサポートしており、過去にUbuntuでビルドしたこともある。

UNIXとmacOS、そしてiOSは異なるプラットフォームであるもののWindowsよりはかなり近い。ビルドが成功する可能性は高い。

で、当然ビルドすればできるのはやねうら王の実行ファイルである。メインであるアプリの実行ファイルから別の実行ファイル(やねうら王)を呼び出すことなど可能なのだろうか?(セキュリティ的にヤバそうな気がするが

iOSアプリに実行バイナリを同梱して実行できるのか試した - Qiita
やりたかったこと Unix環境を想定したライブラリをiOSプログラミングで利用したい場合は静的ライブラリとして取り込むのが普通です。しかし、ライブラリ形式になっていないツールを輸入するのにわざわざライブラリ化するのは手間ですよね...

やはり、iOSアプリで別のバイナリを実行することはSandbox制限に引っかかってできないようだ。

まあそうしないと悪意のある実行ファイルをガンガン動かせてしまうので仕方ないだろう。となると、やはり静的ライブラリとしてコンパイルするしかなさそうだ。

しかしいきなりやねうら王のような大きいプロジェクトをやるというのは無謀である。そこで、今回はスプラトゥーン2における疑似乱数生成器を動かすことにした。

Randomクラス

スプラトゥーンの乱数のクラスは既に調べられており、C++でコード化もされているのでパクってくるだけである。

tkgstrator/StarlightSeedHack
SeedHack is the best way to fix waves for Salmon Run in the Shoal. - tkgstrator/StarlightSeedHack

いろいろあるが、必要になるのはrandom.cpp、random.htypes.hの三つだけである。

どうやらやり方は二つあるらしい

調べてみたところ、ライブラリを静的ライブラリとしてコンパイルし、それをSwfitプロジェクトに読み込ませる方法と、ソースコードをそのまま打ち込んでObjective-Cのラッパークラスでゴニョゴニョする方法があるらしい。

一応どちらも試してみたのだが、残念ながら静的ライブラリの方法はうまくいかなかった。というか、ドキュメントが少ない&古いで意味がわからなかった。

今回は、うまくいかなかったほうを解説する。

静的ライブラリとして使う

Guide to including a C library into an iOS project written in Swift
Compile C libraries that will run on multiple platforms. Have a better understanding of the role some technologies, like universal binaries and bitcode, play wh...

2019年向けの記事なのだが、コンパイルまでは通ったのだがその先がうまくいかなかった。ただ、コンパイル自体も非常に苦痛を伴った。

まず、macOSには(おそらく)デフォルトでclangが入っているのだが、こいつでビルドするとmacOS向けのビルドが出来上がってしまう。

このあたりは検証が不十分なので申し訳ないが、SDKを正しく指定することでビルドがうまくいった気がする。

xcrun -sdk iphonesimulator --show-sdk-pathとやればiOS SimulatorのSDKまでのパスが表示されるのでその値をコピペしよう。

/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.0.sdk

xcrun -sdk iphoneos --show-sdk-pathとすればiOS実機用のSDKまでのパスが表示される。が、何故かシミュレータ用のSDKでビルドがどちらも通ってしまい、iphoneos用だと通らなかった。

export SDKROOT=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.0.sdk

この値をexportしてSDKROOTを明示してやるのだ。ただ、これをやると実機とシミュレータでごとに切り替えるのがめんどくさい。Makefileから呼び出すのが正しい方法だろう。

ビルドしてみた

iOS向けであれば以下のコードでビルドできた。

 gcc -c random.cpp -target arm64-apple-ios

これで中間言語ファイルrandom.oが作成されるわけである。この中間ファイルのアーキテクチャを調べるにはxcrun lipo -info random.oとコマンドを打てば良い。

Non-fat file: random.o is architecture: arm64

すると、arm64用の中間ファイルであることがわかるわけだ。これをrandom.aにビルドすればいいわけなのだが、libtool -static random.o -o random.aで可能なのだが、これだとarm64しかサポートしていないライブラリになってしまう。

ここで気になるのは実機とシミュレータでテストをしようとすれば静的ライブラリを二つ用意しなければいけないのかという問題がある。なのでMakefileをつかってそれらを一発でビルドしてくれるようにするのである。

Makefile

いちいちどちらもビルドするのはめんどくさいので以下のようなMakefileをつくれば幸せになれると思います。

.PHONY = clean

CC = `xcrun -find clang++`
CFLAGS = -Wall -O3 -fembed-bitcode -isysroot `xcrun -sdk iphonesimulator --show-sdk-path`

# iOS13以降のみをサポート
CFLAGS += -mios-simulator-version-min=13.0 -mios-version-min=13.0

SOURCES = $(wildcard *.cpp)
UNIVERSAL_OBJECTS = $(SOURCES:%.cpp=%.o)

random.a: $(UNIVERSAL_OBJECTS)
	# creating static library
	libtool -static $^ -o random.a

%.o: %-x86_64.o %-arm64.o
	# creating universal object file for $@
	lipo -create $^ -output $@

%-x86_64.o: %.cpp
	# creating object file for $< using x86_64 architecture
	$(CC) -c $< -o $@ -target x86_64-apple-ios-simulator $(CFLAGS)

%-arm64.o: %.cpp
	# creating object file for $< using arm64 architecture
	$(CC) -c $< -o $@ -target arm64-apple-ios $(CFLAGS)

clean:
	# removing intermediate object files
	rm *.o *.a

このMakefileを作成したあとでmake -rと実行すればシミュレータ用と実機用の静的ライブラリを作成してそれらを勝手にマージしてくれます。

先ほどと同じようにxcrun lipo -info random.aを実行してみると、

Architectures in the fat file: random.a are: x86_64 arm64 

と表示されるようにx86_64とarm64の二つをサポートしたFat fileであることがわかるのである。

まとめ

今回静的ライブラリ作成につかったファイルは全て以下のGitHub Gistに公開しておきます。

結局何がうまくいかなかったかというと、このライブラリをSwiftのプロジェクトに組み込んで実行するところです。調べてもラッパークラスを作成する記事が多いのと、古いのが多いのとで全然わからんかった。

以下の記事も大変参考にさせていただきました。

全然記事の内容とは関係ないのですが、まゆしぃをプッシュしておきます。記事は以上。

コメント

  1. 匿名 より:

    最近難しい話が多い、、、

タイトルとURLをコピーしました