Xcodeで書いたCocoaアプリケーションをGNUstepで動かすメモ
コードを書く際の注意
まずWriting portable code - GNUstepWikiに書いてあることはだいたい守る。 FoundationやAppKit以外の、OSXに特有のライブラリを使わないということ。
その他に気をつけることとしては、
- Xcodeが自動生成するコードが
NSApplicationDelegateプロトコル(formal protocol)を使っている場合があるが、GNUstepにはこのプロトコルはないので、#ifndef GNUSTEPなどで対処する。- GNUstepには
GSAppDelegateProtocolというプロトコルがあるが、コメントによればこれはドキュメント用に宣言されているだけで、アプリケーションが実装する必要はないとのこと。 GSAppDelegateProtocolに@optional指定つければNSApplicationDelegateになりそうだが
- GNUstepには
- Interface BuilderでUIを作る際に、Auto Layoutを使わないようにする。デフォルトでは「Interface Builder Document」の「Use Auto Layout」チェックがついていて、生成されるxib中で
NSLayoutConstraintが使われてしまう。OSX上でもNSLayoutConstraintはかなり新しいAPIのはずだが、どうしてこうなった。
など。
ビルドシステム
GNUstep Makeを使うのが素直だろう。つまり、Xcodeのプロジェクトとは別にMakefileを書く。GNUstep MakeのMakefileの書き方はこのへんを参照。
ただし、上のリンク先に書いてない重要なことがいくつかあって、まずInterface Builderで作ったxibを指定すること。どう書けばいいかと言うと、アプリケーション名(APP_NAME)を例えばHogeとすると
Hoge_RESOURCE_FILES = Hoge/en.lproj/MainMenu.xib
Hoge_MAIN_MODEL_FILE = MainMenu
のような記述を入れておく。こうするとmakeした時にMainMenu.xibがアプリケーションバンドルにコピーされていい感じになる。
ここまでの手順で、簡単なアプリケーションはビルドできるはず。運が良ければ問題なく動作するかもしれない。
Info.plist
Xcodeで開発した時はInfo.plistというというファイルにアプリケーションの情報(名前とか作者とかアイコンとか)をいろいろ書いたが、GNUstepの場合はどうか?
まあ実際にビルドしてみるとアプリケーションバンドル内にInfo-gnustep.plistというファイルができていて、中には"Automatically generated"とか書かれている。アプリケーションアイコンを設定したり、アプリケーションメニューの「Info…」で出てくるダイアログにアプリケーションの情報を載せるにはInfo-gnustep.plistに書き出される内容をカスタマイズする必要があるわけだが、その方法には2種類ある。
- Makefile中で変数を定義する。さっきの
*_MAIN_MODEL_FILEと同様。CFBundleIconFile(*_APPLICATION_ICON)やNSPrincipalClass(*_PRINCIPAL_CLASS)など。 - テンプレートとなるInfo.plistファイルを作って、
make時に自動生成される分と合わせてマージさせる。上記以外のキー/値はこの方法を使う。テンプレートとなるInfo.plistファイルの名前は「HogeInfo.plist」で、GNUmakefileと同じディレクトリに置いておけば勝手に拾ってくれる。
この辺はまともなドキュメントがなさそうだが、辛うじて見つけられたのはこのへんとかこのへん。まあGNUstep Makeのソースコード(core/make/Instance/application.makeなど)を読むべきかもしれない。
あと今回はLinux(Ubuntu)でしか試していないので、Windowsで同じようにうまくいくのかは知らない。
Cなどで書かれたプログラムからHaskellを使う(メモ)+Xcodeのプロジェクトに組み込むときのメモ
ある種の処理を書く時にHaskellは便利だが、プログラムの他の部分はCなどの他の言語で書きたい場合がある。 そこで、Haskellで動的ライブラリ(.soや.dylib)を作り、他のプログラムから呼び出せるようにしたいと思った。
Haskell側ではforeign exportで関数をエクスポートすればよい。
foreign exportの入ったモジュールをコンパイルすると*_stub.h,*_stub.cというファイルが生成される…と思ったら現在のGHCでは_stub.cの方は生成されない?
C側のプログラムからは、生成された_stub.hを#includeすればHaskellで書かれた関数のC言語でのプロトタイプが見える…のだが、この_stub.hからはHsFFI.hというHaskell処理系の(?)ヘッダで、単体のライブラリとして配布する際に依存性があると邪魔だ。なので、自前でヘッダファイルを書くか、Haskellで書かれた関数をラップする関数をCで書くかするといいと思う。
また、Haskellの関数を使う前にHaskellのランタイムを初期化する必要がある。
Calling Haskell from C - HaskellWikiや8.2. Using the FFI with GHCを参考に初期化ルーチンを書こう。
__attribute__((constructor))や__attribute__((destructor))をつけておけばライブラリの使い手でいちいち初期化ルーチンを呼ぶ必要がなくて楽かもしれない。
ライブラリのコードとしてはCのコードとHaskellのコード両方が含まれる。どっちもまとめてGHCに渡してしまうといいだろう:
ghc -O2 --make -no-hs-main -optl '-dynamiclib' -o libHogehoge.dylib Hogehoge.hs Higehige.c
当初試したときは、リンク時に_environが見つからないというようなエラーが出たが、この問題はGHCの最新バージョンでは直っているらしく、Haskell Platform 2012.4(GHC 7.4.2)をインストールしたら解決した。
自分の場合は、ライブラリを呼ぶ側のプログラムはCocoaアプリケーションで、Xcodeで開発している。それで、Xcodeのビルドボタンを押すとHaskellのライブラリも一緒にビルドされるようにしたい。 ビルド中に上記のコマンドラインが実行されるようにしたいのだが、Haskell用のBuild Ruleを追加して…というのは難しそうだったのでMakefileを書いてExternal Build Systemとしてターゲットを作った。
Makefileの内容はこんな感じで:
PRODUCT = $(BUILT_PRODUCTS_DIR)/libHogehoge.dylib all: $(PRODUCT) $(PRODUCT): Hogehoge.hs Higehige.c $(BUILT_PRODUCTS_DIR) ghc -c -O2 --make -outputdir $(DERIVED_FILE_DIR) -o $@ Hogehoge.hs ghc -O2 --make -no-hs-main -optl '-dynamiclib' -outputdir $(DERIVED_FILE_DIR) -o $@ Hogehoge.hs Higehige.c install_name_tool -id @executable_path/libHogehoge.dylib $@ $(BUILT_PRODUCTS_DIR): mkdir -p $@
$(BUILT_PRODUCTS_DIR)や$(DERIVED_FILE_DIR)はXcodeがコマンドを呼び出す際に設定してくれる環境変数。
Higehige.cから_stub.hを参照する場合、.hsと.cを一度にghcに渡すと_stub.hが存在しない場合エラーになるようで、先に.hsだけをコンパイルして_stub.hが確実に存在するようにしている。なんだか無駄なようで、自分のやり方が悪いのかもしれない。
ライブラリを.app内に含めることを考えて、リンク後にinstall_nameを設定している。
Xcode側の設定は、External Build Toolとして作ったターゲット(仮にHigehigeとする)の、DirectoryにMakefileがあるディレクトリを設定しておく。ソースコード内の位置指定に$(SRCROOT)が使える。
また、プロジェクトのファイルとして、生成されるlibHigehige.dylibを追加しておく。ビルドしてできたファイルをXcodeに追加して、プロパティーのLocationをRelative to Build Productsに設定すればいい感じになるだろう。
主となるアプリケーションの設定としては、Build PhasesのTarget DependenciesにHigehigeを追加しておく。 それから、先に追加したlibHigehige.dylibをリンクするように設定する。 あと、ビルド後にlibHigehige.dylibをアプリケーションバンドル内にコピーするBuild Phaseを追加しておく。
我ながらかなり読みにくい文章になってしまった。CからHaskellを呼ぶ話と、XcodeでExternal Build Toolを使ってファイルをビルドする話が混ざっている気がする。 しかし、読みやすい文章を心がけたりスクリーンショットを載せたりしてがんばると、ただでさえ半年間更新していないブログの更新がますます億劫になってしまうのは間違いない。だからこのぐらいの適当さでいいのだ、ということにしておく。
テキストファイルのエンコーディング(com.apple.TextEncoding)を設定するコマンドを作った
Mac OS Xでは、テキストファイルのエンコーディングを拡張属性com.apple.TextEncodingに格納できる。テキストエディットなどでそのファイルを開くと、ファイルがcom.apple.TextEncodingで指定されたエンコーディングで読み出される。誤判定の多いエンコーディングの自動判別機能に頼る必要は無い。
テキストエディットなどでファイルを作成すればこの拡張属性は自動的に設定されるのだが、外部から取得したファイルなどにはこの拡張属性は設定されていないことが多い。そこで、任意のファイルに拡張属性com.apple.TextEncodingを設定するコマンドを書いた→https://github.com/minoki/setTextEncoding
使い方は
$ setTextEncodingエンコーディング名ファイル(複数可)…
とすればよい。CFStringEncodingの値はエンコーディング名から自動で求めてくれる。また、エンコーディングの表記にぶれがあったとしても、
$ setTextEncoding euc-jp
euc-jp;2336
$ setTextEncoding eucjp
euc-jp;2336
$ setTextEncoding eucjp
euc-jp;2336
のようにIANA名に規格化される。
入力ソースを設定するコマンドを作った
Mac OS Xの入力ソース(ことえりやGoogle日本語入力など)をCUIで設定(有効/無効/選択)できるコマンドを作った→https://github.com/minoki/InputSourceSelector
入力ソースをいじるには、Mac OS X 10.5以降であればCarbonのText Input Source Servicesを叩けばいい。入力ソースのID(例:com.apple.inputmethod.Kotoeri.Roman)から入力ソースを取得するにはTISCreateInputSourceListを呼び出す。この関数は名前の通り入力リストのリストを返すが、第一引数にプロパティリストを渡す事で入力ソースを絞り込む事ができる。帰ってきたCFArrayRefから入力ソース(TISInputSourceRef型)を取得しTISEnableInputSourceやTISSelectInputSourceを呼び出せば、それぞれ入力ソースを有効化、選択できる。
プログラム中でTISCreateInputSourceListから帰ってきた入力ソースの配列を走査するのにFast Enumerationを使っているが、TISInputSourceRef型はObjective-Cのid型と互換性がないため、ループを
for (TISInputSourceRef inputSource in inputSources) {
...
}
と書くことはできない。しかし、__attribute__((NSObject))を使えばid互換の型として扱ってくれるようだ。
for (TISInputSourceRef __attribute__((NSObject)) inputSource in inputSources) {
...
}
さて、このコマンドの使い方だが、
$ InputSourceSelector list
と打てば入力ソースのIDと名前の一覧が表示される。list-enabledとすれば現在有効になっている入力ソース飲みが表示される。これで調べたIDに対して
$ InputSourceSelector enable (調べたID)
と打てばその入力ソースが有効化される(右上のメニューの入力ソース一覧に現れる)。また、
$ InputSourceSelector select (調べたID)
と打てばその入力ソースが現在の入力ソースとして選択される。
Asymptoteで立体の輪郭を描く(2)
前回、Asymptoteを使用して球を描いた。その時の図をよく見ると、点 \((0,0,1)\) が球の輪郭に接しているのが分かる。視点がXY平面の上側にあることを考えると、これはおかしいのではないか。実際、次のようにコードを変更して再描画すると、描かれる輪郭と球の輪郭が一致しないことが分かる:
// 球(回転体)の輪郭を描く
// longitudinalbackpenは球の下半分のペンを指定するようだ
draw(s,m=11,longitudinalbackpen=currentpen+solid);
ソースコードの該当部分はbase/solids.asyの280行目以降で定義されているrevolution.longitudinal()なのでこれを修正すればいいのだが、そんな面倒なことをしなくても立体の輪郭の描画にrevolution.silhouette()を用いれば良い。
// 球(回転体)の輪郭を描く
// longitudinalbackpenは球の下半分のペンを指定するようだ
draw(s.silhouette());
draw(s,m=11,longitudinalpen=nullpen,longitudinalbackpen=nullpen);
ただしこの場合
warning [2Dsilhouette]: silhouette routine is intended only for 2d projections
という警告が出る。本来はやはり前回のようにdraw()を使って描画するべきなのだろう。しかし、なぜsilhouette()は問題ないのにlongitudinal()はバグ持ちなのだろうか。もしかして、これは意図した動作だったりするのだろうか。謎だ。