openFrameworks(以下oF)のiOS版をXcode6(6.1)で使おうとしたら全然すんなり行かなかったのでメモ。できるだけC++11で使いたいのですが、後述のとおりMacにおいてoFを完全C++11で使うことは難しく、現状できる半C++11状態での使い方をまとめます。まずMac/iOS版oFでビルド設定の類をいじる際に知っておきたい基本知識を先に挙げておきます。
- XcodeでのoFアプリは2プロジェクト構成になっている(oF自体のプロジェクトと自分が作るアプリのプロジェクト)ので、設定によっては両プロジェクトの設定を書き換える/揃える必要がある
- Xcodeにおいてビルドに関する設定は1プロジェクトにつき「project」と「target」という2段階がある。設定を変えたと思ってもビルド結果が変わらない気がしたときはこのあたりを確認すべし(stackoverflow参考)
注意: Validate Project SettingsのPerform Changesはしない
XcodeでoFをビルドしていると、通るか通らないかに関わらず、oF側/アプリ側の両プロジェクトで「Validate Project Settings」というwarning表示が出ることがありますが、基本的にこれは放置の方がよいです。warning自体はXcode(というかMac/Apple)の推奨設定にプロジェクト設定を更新しませんか?という案内ですが、oFはoF側でビット幅など一部の設定を指定(限定)しているので、下手に変えるとビルドが通らなくなります。
修正1: Deployment Targetを上げる
公式ページからダウンロードしたoF(iOS版)を解凍し、例えばExampleのaudioInputExampleのXcode projectを立ち上げビルドしてみると、早速通りません。
ld: -pie can only be used when targeting iOS 4.2 or later
clang: error: linker command failed with exit code 1 (use -v to see invocation)
これは書いてあるとおりアプリ側のDeployment Targetがデフォルトで3.1になっているのが原因。適当に自分の都合のよいバージョンまで上げればOKです(私は7.0にしてます)。
作業2: undefined symbolを足す
Xcode6にするとリンク時に以下のように怒られます。
ld: warning: ignoring file ../../../libs/FreeImage/lib/ios/freeimage.a, missing required architecture i386 in file ../../../libs/FreeImage/lib/ios/freeimage.a (2 slices)
ld: warning: ignoring file ../../../libs/glu/lib/ios/glu-ios.a, missing required architecture i386 in file ../../../libs/glu/lib/ios/glu-ios.a (2 slices)
Undefined symbols for architecture i386:
"_fwrite$UNIX2003", referenced from:
_WriteProc(void*, unsigned int, unsigned int, void*) in freeimage.a(FreeImageIO.o-i386)
LibRaw::dcraw_thumb_writer(char const*) in freeimage.a(libraw_cxx.o-i386)
_opj_image_create in freeimage.a(image.o-i386)
_png_default_write_data in freeimage.a(pngwio.o-i386)
LibRaw::ppm_thumb() in freeimage.a(dcraw_common.o-i386)
LibRaw::jpeg_thumb_writer(__sFILE*, char*, int) in freeimage.a(dcraw_common.o-i386)
LibRaw::write_ppm_tiff() in freeimage.a(dcraw_common.o-i386)
...
"_mktime$UNIX2003", referenced from:
LibRaw::get_timestamp(int) in freeimage.a(dcraw_common.o-i386)
LibRaw::parse_rollei() in freeimage.a(dcraw_common.o-i386)
LibRaw::parse_riff() in freeimage.a(dcraw_common.o-i386)
"_strerror$UNIX2003", referenced from:
Iex::throwErrnoExc(std::string const&, int) in freeimage.a(IexThrowErrnoExc.o-i386)
"_strtod$UNIX2003", referenced from:
_png_handle_sCAL in freeimage.a(pngrutil.o-i386)
ld: symbol(s) not found for architecture i386
clang: error: linker command failed with exit code 1 (use -v to see invocation)
これについてはググればたくさん情報が出てくるとおり、 当該関数の定義をどこかに作ってやればOKです。main.mmなどアプリ側のソースにアプリ毎に追加するか、addons/ofxiOS/src/app/ofAppiOSWindow.mmなどoF側のソースに共通設定として追加しましょう。後者なら一度やれば全アプリに適用でき、また暫定対応がアプリ側に残らなくて良いと思います。私は以下をofAppiOSWindow.mm冒頭に追加しました。
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
extern "C"{
size_t fwrite$UNIX2003( const void *a, size_t b, size_t c, FILE *d ){
return fwrite(a, b, c, d);
}
char* strerror$UNIX2003( int errnum ){
return strerror(errnum);
}
time_t mktime$UNIX2003(struct tm * a){
return mktime(a);
}
double strtod$UNIX2003(const char * a, char ** b){
return strtod(a, b);
}
}
作業3: oFでできるだけのC++11機能を使う
一般にMac+clang+XcodeにおいてC++11の機能を有効にするには、build setting(1)C++ Language DialectをC++11に、(2)C++ Standard Libraryをlibc++に、すればよいのですが、oFを使うアプリでこれをやるとコンパイルエラーとリンクエラーの嵐になります。理由はoFが古いgccで作った(=libstdc++を使う)コンパイル済みライブラリとリンクする仕様だからであり、完全解決はoFから提供されるライブラリが新しくなるまでお預けです。このあたりの詳しい事情はこちらなどを参照してください。
ではoFでできるだけC++11化することを考えましょう。その場合のXcodeのbuild settingは以下のようになり、gcc4.2時点でのC++11が有効になります。これはoF側のプロジェクトとアプリ側のプロジェクト両方で設定してください。
- Language DialoctをGNU++11(-std=gnu-c++11)
- C++ Standard Libraryをlibstdc++(GNU C++ standard library)
ただし、この設定にしてoFアプリのビルドを通すにはoF側のソースを数カ所直す必要があります。
-
ofType.h: 冒頭のinclude設定を以下のようにコメントアウトする(この修正は本記事で想定する環境以外では逆にビルドを通りにくくする修正ですので注意)
//#if (_MSC_VER) //#include <memory> //#else #include <tr1/memory> // import smart pointers utils into std namespace std { //#if __cplusplus<201103L using std::tr1::shared_ptr; using std::tr1::weak_ptr; using std::tr1::enable_shared_from_this; //#endif using std::tr1::static_pointer_cast; using std::tr1::dynamic_pointer_cast; using std::tr1::const_pointer_cast; using std::tr1::__dynamic_cast_tag; } //#endif
-
ofAppiOSWindow.mm: 116行のUIApplicationMainの第一引数をnilから0にする
UIApplicationMain(0, nil, nil, [NSString stringWithUTF8String:appDelegateClassName.c_str()]);
-
ofxiOSMapKit.mm: 114行の代入文にきちんとMKMapTypeへのキャストを明示する
mapView.mapType = (MKMapType)type;
ここまでがんばってやるとC++11の半分くらいの機能が使えるようになります。知っている範囲では以下のような機能です(デフォルトオプションのままで使えるものを含んでいるかも)。ちなみにinitializer_listとconstexprは使えませんでした。
- tr1にあるヘッダの利用: memory, array, cstdint, type_traits, などなど(randomもつかえるが、実際にはまともに機能しない: 参考)
- decltype
- auto
- range based for