2013年12月30日月曜日

C++11 と obj-c

別に obj-c を使ってるわけでもなんでもないのですけど、前回の記事で cocoa のコードをつらつら書いてるときに”拡張子を .mm にして clang に食わすとその中身は C++ と obj-c が混在しまくってても OK"というのを知りました。
オブジェクトの互換性はなく、 obj-c のクラスを C++ で継承したりその逆はできないみたいですが、それ以外のことは一通りできており、 C++ で設計、記述したクラスに実に自然に mac の GUI を被せることができて結構感動したものです。
もうこれでくだらない glue を沢山書かなくていいんだ!

それはそれで大変素晴らしいことなのですが、それでは C++11 ではどうなのかというところが気になりました。
気になる事は試してみましょう。

まず、 clang で C++11 を有効にするには -std=c++11 をつけてコンパイルします。
更に、 C++11 の新しい標準ライブラリを使うには -stdlib=libc++ をつけます。
libstdc++ とかではありません。

cmake でいうとこうです。
   set(CMAKE_CXX_FLAGS "-std=c++11 -stdlib=libc++")
ただ一つ問題があります。
cmake の project に .m が混在しているとこれはエラーになってしまいます。
.mm だけならば問題ありません。 project を別けるかしましょう。
無論、自分で makefile を書くならば suffix ルールを別けるだけで済みます。
まぁ、全部 .mm にしちゃいましょう。

これで準備は完了です。
界面に関わらないことであれば、概ね C++11 の機能が使えるようです。

クロージャ

C++ のコンテキストでクロージャを呼び出すことは難なくできました。
これを obj-c のメソッドに渡すことを考えてみましょう。
Test::foobar というクロージャを受け取って呼び出す obj-c のクラスを考えます。

Test* test = [Test alloc];
const char* msg = "Hello";
[test foobar:[msg](){printf ("%s\n", msg);}];
太字がクロージャであります。
Test の実装はこんな感じ。
@interface Test : NSObject
@end

@implementation Test
- (void) foobar:(std::function< void(void) >)closure
{
  printf("call begin\n");
  closure();
  printf("call end\n");
}
@end
結果は以下の通り。

call begin
Hello
call end
問題なく受け渡すことができました。

__attribute__

クロージャが使えるならかなり色々なことができそうです。
界面に関わるうち、構文上どうにもならなそうなもの以外(例えば obj-c メソッドのシグネチャでrvalue 制約や型推論を使う等)は一通り使えそうです。

構文上厳しそうなものといえば [[]] による __attribute__ です。
GNU 拡張の __attribute__ ディレクティブを、 clang は理解しますが、折角仕様に盛り込まれたのだからそっちを使ったほうがいいです(もっとも、 no_inline だけは効いてくれたことがないですが)。
でもこいつは [] で括る obj-c の文法と相性が悪そうです。
    float vec4 [[align(4)]] [1];
    float vec8 [[align(8)]] [1];
    float vec16 [[align(16)]] [1];
    printf("vec4:%p vec8:%p vec16:%p\n", vec4, vec8, vec16);
一応コンパイルは通りました。ですが実行結果は以下の通り。
vec4:0x7fff5fbfee78 vec8:0x7fff5fbfee74 vec16:0x7fff5fbfee70
手元の clang では残念ながら [[align(x)]] 自体が動いていないようです。
gcc-4.9 では大丈夫だったように思うのですが。

まぁ、使えるんじゃないですかね?(鼻をほじりながら)

2013年12月27日金曜日

cmake で Mac のアプリケーションをビルド

働きたくないでござる!
絶対に IDE で働きたくないでござる!!

……というわけで、 mac 用のアプリも CUI ベースで開発してビルドも make で行いたいというような話です。
マルチプラットフォームでビルドしたいので cmake を使います。

mac のバイナリ(?)である bundle は、要するに何の変哲もないディレクトリで中身にお作法があります。
ここにはアプリケーションの実行形式とアプリケーションの MANIFEST である InfoList.plist 、そしてメインウインドウを記述する .nib ファイルが最低限必要です。
それ以外にも、必要に応じて so や、リソースファイルなどが必要になるでしょう。

こうしたものは Xcode を使えば特に気にすることもなく出来てしまうのですが、そもそも Xcode 使いたくないじゃん?というようなところに動機があります。

Linux や cygwin にも対応できるよう、 gnu make をベースにビルドすることを考えます。
makefile を最初から書く覚悟のある人は、バンドルの規則に従ってターゲット書けばいいだけの話ですのでそうしてもらうとして、ここでは cmake を使って makefile や好みにあわせて .xproj なども作れるようにしておきましょう。

CMakeLists.txt を書く

framework の追加

まずは framework を追加しましょう。ぶっちゃけ GUI でチマチマ追加するより簡単ですよ。

cmake_minimum_required(VERSION 2.8)
project(cocoa.app)

if (APPLE)
   add_executable(cocoa MACOSX_BUNDLE main.mm app.mm)
   include_directories ( /System/Library )
   find_library(COCOA_LIBRARY Cocoa)
   find_library(APPKIT_LIBRARY AppKit)
   find_library(APP_SERVICES_LIBRARY ApplicationServices )
   mark_as_advanced (COCOA_LIBRARY
                     APPKIT_LIBRARY
                     APP_SERVICES_LIBRARY)
   set(EXTRA_LIBS ${COCOA_LIBRARY} ${APPKIT_LIBRARY} ${APP_SERVICES_LIBRARY})
   target_link_libraries(cocoa ${EXTRA_LIBS})
endif (APPLE)

小文字は cmake の組み込み関数やディレクティブです。
この場合は project として cocoa.app というバンドル名を指定します。この CMakeLists.txt のルートですので、この場合はバンドル名とするのが自然と思います。さて、これは実行形式の名前とは無関係です。

実行形式は通常 project 名とは無関係で、任意の名前です。(一つのバンドルに複数の実行形式があってかまわないので)
実行形式は、ターゲットとして、  add_executable() で宣言されます。
これは極めて重要な指定で、これを行った以降でないと設定できない属性が沢山あります。実際は依存関係を記述する都合上、後方に書かれることが多いですが、ここでは解りやすさのためには真っ先に書いています。
ここでは特に、 MACOSX_BUNDLE としていることに注意してください。

mac 用の環境を切り離すのには if (APPLE) を使います。
find_library() で framework を探し、 mark_as_advanced() に設定します。
そのリストを target_link_libraries() で(上で宣言した)実行形式にリンクしましょう。
find_library() で指定するのは /System/Library/Frameworks/ 以下にある *.framework のワイルドカードにマッチする名前です。
さてここまでは簡単です。次にバンドルに突っ込むリソース類のレシピを記述します。

InfoList.plist の追加

バンドルに絶対欠かせないものの一つに InfoList.plist があります。こいつはいわゆる MANIFEST です。
バンドルは shell でさっくり実行形式をロードするのとは異なり、 Mac のシステムサービスを経由して実行されるので、これの記述は重要です。

絶対に欠かせないものは
  • 実行形式の場所
  • nib の名前
  • NSPrincipal クラスの名前
です。これがないとバンドルはシステムのロンチサービスに蹴られてエラーになります。
他にも固有識別子やバンドル自体の名前など、重要そうなものがあります。

これらは基本的には cmake 固有の変数に値を設定することで勝手に InfoList.plist が作られるのですが、なぜか nib と NSPrincipal クラスの名前だけ、 cmake 固有の変数がありません。
従ってそのままだとバンドルができても、ロンチサービスに蹴られてしまいます。ありがちですね。
すぐ思いつく対策は、あらかじめ作り込んだ InfoList.plist を用意しておいてビルドのポストコマンドでコピーすることですが、それだとカスタマイズ性がよくありません。

そこでカスタム用の変数を宣言しましょう。カスタム変数は要するに cmake の置換文字列機能です。この場合は、 MACOSX_BUNDLE_INFO_PLIST プロパティを使って、ベースとなる InfoList.plist のテンプレートを指定し、 その中身を任意の変数で置換すればよいわけです。
set_target_properties(cocoa PROPERTIES MACOSX_BUNDLE_INFO_PLIST Info-CMake.plist)
まず一点。プロパティは変数とは違います。変数がプロセッシングの過程で置換されるだけのものだとしたら、プロパティは cmake の振る舞いを変えることができます。
cmake --help-property-list で一覧を見て、 cmake --help-property でその意味を調べられます。 MACOSX で grep すると以下のようになりました。

MACOSX_BUNDLE
MACOSX_BUNDLE_INFO_PLIST
MACOSX_FRAMEWORK_INFO_PLIST
MACOSX_PACKAGE_LOCATION

困ったときに役立つと思いますので、頭に入れておきましょう。

もう一つ気をつけなければならないのは、 自分のテンプレートを指定したらもう cmake 固有のテンプレートはもう使われないというところです。
なのでどこから適当に拾ってきた InfoList.plist からテンプレートを作ると、ここで使わなかった置換変数は使われなくなります。これではカスタマイズ性が下がります。

ここでは cmake が持っているテンプレートを参考にしたほうがよいでしょう。
cmake のテンプレートは、

/Applications/CMake\ 2.8-12.app/Contents//share/cmake-2.8/Templates/AppleInfo.plist

などにあると思います。
これを使うと以下のような plist が出来上がります。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>CFBundleDevelopmentRegion</key>
        <string>English</string>
        <key>CFBundleExecutable</key>
        <string>${MACOSX_BUNDLE_EXECUTABLE}</string>
        <key>CFBundleGetInfoString</key>
        <string>${MACOSX_BUNDLE_INFO_STRING}</string>
        <key>CFBundleIconFile</key>
        <string>${MACOSX_BUNDLE_ICON}</string>
        <key>CFBundleIdentifier</key>
        <string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
        <key>CFBundleInfoDictionaryVersion</key>
        <string>6.0</string>
        <key>CFBundleLongVersionString</key>
        <string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string>
        <key>CFBundleName</key>
        <string>${MACOSX_BUNDLE_NAME}</string>
        <key>CFBundlePackageType</key>
        <string>APPL</string>
        <key>CFBundleShortVersionString</key>
        <string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
        <key>CFBundleSignature</key>
        <string>????</string>
        <key>CFBundleVersion</key>
        <string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
        <key>CSResourcesFileMapped</key>
        <true/>
        <key>LSRequiresCarbon</key>
        <true/>
        <key>NSHumanReadableCopyright</key>
        <string>${MACOSX_BUNDLE_COPYRIGHT}</string>
        <key>NSMainNibFile</key>
        <string>${MACOSX_BUNDLE_NSMAIN_NIB_FILE}</string>
        <key>NSPrincipalClass</key>
        <string>${MACOSX_BUNDLE_NSPRINCIPAL_CLASS}</string>
</dict>
</plist>
せっかく DTD がありますので、必須の要素がどれかなどは DTD か Apple のドキュメントを参照してください。
しかしながら、一点問題があります。
CFBundleExecutable の値を、デフォルトの cmake のテンプレートはターゲット名から自動で埋めてくれるのですが、カスタムテンプレートを使うとどうもその機能が動かなくなってしまいます。従って、 MACOS_BUNDLE_EXECUTABLE は明示的に指定するようにしましょう。(なくっても、バンドル名が 実行形式名.app なら勝手にそれを実行してくれるようですが)  
   set(MACOSX_BUNDLE_EXECUTABLE "cocoa")
   set(MACOSX_BUNDLE_INFO_STRING "${PROJECT_NAME}")
   set(MACOSX_BUNDLE_GUI_IDENTIFIER "com.example")
   set(MACOSX_BUNDLE_LONG_VERSION_STRING "${PROJECT_NAME} Version ${VERSION}")
   set(MACOSX_BUNDLE_BUNDLE_NAME ${PROJECT_NAME})
   set(MACOSX_BUNDLE_SHORT_VERSION_STRING ${VERSION})
   set(MACOSX_BUNDLE_BUNDLE_VERSION ${VERSION})
   set(MACOSX_BUNDLE_COPYRIGHT "Copyright 2013.")
   set(MACOSX_BUNDLE_NSMAIN_NIB_FILE "MainMenu")
   set(MACOSX_BUNDLE_NSPRINCIPAL_CLASS "NSApplication")
   set_target_properties(cocoa PROPERTIES MACOSX_BUNDLE_INFO_PLIST Info-CMake.plist)
まとめるとこんな感じでしょうか。
InfoList.plist については以上です。

.nib などのリソース類

最後にして大物です。
リソースは bundle.app/Contents/Resources 以下にある雑多なものですが、 .nib のようにウインドウシステムレベルで重要なものも含まれます。
雑多なリソースは依存を書いておけば勝手にコピーされますが、 nib に関してはそうはいきません。 .xib から .nib へのコンパイルなどはカスタムコマンドを定義してビルドのポストフェイズに走らせる必要があります。

これについては以下のようにするのがよいようです。

   set(XIB MainMenu)
   set_source_files_properties(
       ${MACOSX_RESOURCE_FILES}
       # 任意のリソース群 (.nib は除く)
       PROPERTIES
       MACOSX_PACKAGE_LOCATION Resources)

   find_program(IBTOOL ibtool HINTS "/usr/bin" "${OSX_DEVELOPER_ROOT}/usr/bin")

   add_custom_command (TARGET cocoa PRE_BUILD
       COMMAND mkdir -p ${PROJECT_NAME}/Contents/Resources)
   add_custom_command (TARGET cocoa POST_BUILD
           COMMAND ${IBTOOL} --errors --warnings --notices --output-format human-readable-text
           --compile ${PROJECT_NAME}/Contents/Resources/${XIB}.nib
           ${XIB}.xib
           COMMENT "Compiling ${XIB}.xib -> ${MODULE}/Contents/Resources/${XIB}.nib")
新たな変数が登場していますが、全部デフォルトでよいです。
基本は set_source_files_properties() でリソースファイルの MACOSX_PACKAGE_LOCATION を指定してやることと、 ibtool を探してビルドのポストフェイズに明示的にコンパイルするようにしてやることです。
.xib はこの場合一つですが、複数ある場合は foreach でリスト要素ごとに add_custom_command() でポストフェイズのコマンドを追加してやる必要があります。

パスを自分で指定しなおしてやったりと少々冗長なところもありますが汎用の関数だし、これでいいのです。

重要じゃありませんが、 find_program() も find_library() と同様よくつかう関数です。

さて、問題はどうやって .xib を用意するか、です。
XML エディタで作ってもいいですが、なんせ結構煩雑ですし、ドキュメントも DTD すらもありません。
ノードを削っていって最小限の .xib を作るというのも面白いところですが、結構でかいファイルですんでここは Xcode 様にご登場願いましょう。
一個作って使い回しましょう。直したいところだけ XML エディタで直しゃーいいじゃないですか。
ibtool は結構便利なプロセッサですが、真面目に UI を作ろうと思ったらどうしたって Interface Builder を使うことになりますから。

ではそういったところでお疲れさん。

2013年12月25日水曜日

XBA ハイブリッド

メリークリスマス!
プレゼントにいいイヤフォンを探して試聴してきたのでメモ。

XBA のハイブリッド型を見て来ました。

XBA-H1:

ダイナミックとフルレンジ BA のハイブリッド。
音が籠るけれど、全体的に自然で、低音が効きすぎるとも金物がうるさいとも思わず、値段とのバランスもとてもいいと思いました。
17000 円くらい。


XBA-H2:

音の籠もりがブーストされた感。
正直言って 25000 円もするイヤフォンとは思えないです。9000 円くらいでもっといいのあると思います。
中音重視で選んだので、まぁ中音は癖もなくよく聞こえるかなと思ったんですけど、聞く曲によっては 1500 円のイヤフォンと違わないんじゃないかこれ。
XBA-H3:

ダイナミックと BA 2 ドライバらしいです。……それにしてはデカイ。
音の籠もりはやや軽減され、バランスはこの中では一番いいです。
でも中音がやや痩せたような部分も気になりました。総合して 32000 円も取るような品質か?
自分で使うならギリギリこれだけど、別に面白くもないし、 XBA-40 のほうが全然好き。
ドライバが重過ぎるのも、人に使わせるものとしては使用シーンを限定しすぎていてちょっと難しい。
でもケーブルにウレタンが入っていて耳にかけられるようになっているのはちょっといい。

お勧めは XBA-1H です。
以前の XBA シリーズがどれも尖ってる印象ですが、かなり丸くなった感じですね。値段とのバランスも優れていると思います。
ドライバも小さくてとても軽い。
特にロックばっかり聞いているとかじゃないなら、オールマイティに使える良いイヤフィンだと思います。