Objective-Cの勉強を始めようと思って、適当にネットで漁ったHelloWorldをgccでコンパイルしたら上手く動かない。
で、調べ始めたらObjective-C/NeXT/OpenStep/MacOSXの歴史とかUniversalBinaryとかにまで広がってしまいましたが、ようやく収拾がついたのでまとめます。
環境:
Mac OS X (Lion), 10.7.2 Intel Core i5 64 ビットカーネルと拡張機能:有効 Xcode 4.2 $ gcc --version i686-apple-darwin11-llvm-gcc-4.2 (GCC) 4.2.1 ¥ (Based on Apple Inc. build 5658) (LLVM build 2336.1.00)
helloworld.m:
#import <objc/Object.h> #import <stdio.h> @interface HelloWorld : Object { } -(void) hello; @end @implementation HelloWorld -(void) hello { printf("Hello, Objective-C!\n"); } @end int main(int argc, char *argv[]) { HelloWorld *o = [[HelloWorld alloc] init]; [o hello]; [o free]; return 0; }
コンパイル:
[msakamoto:ex01]$ gcc -o helloworld helloworld.m -lobjc helloworld.m: In function ‘main’: helloworld.m:19: warning: ‘HelloWorld’ may not respond to ‘+alloc’ helloworld.m:19: warning: (Messages without a matching method signature helloworld.m:19: warning: will be assumed to return ‘id’ and accept helloworld.m:19: warning: ‘...’ as arguments.) helloworld.m:19: warning: no ‘-init’ method found helloworld.m:21: warning: ‘HelloWorld’ may not respond to ‘-free’
64bit化に伴い、レガシーなObjectクラスの実装については64bit用のバイナリでは提供されなくなったようです(Appleからの明確なアナウンス情報が見つからなかった)。このため、"objc/Object.h"で定義されている"Object"クラスのallocメソッドなどが見つからないため、上記のようなwarningになっているようです。エラーではなくwarningというのも面白いけど・・・。
gccはデフォルトではホストマシンのアーキテクチャでバイナリを生成します。今回は64bitカーネルで動作しているため、64bit用のライブラリとリンクするようにビルドされた結果、上述のような状況に陥ったようです。
素直にNSObjectを使いましょう。仮にMac以外のプラットフォームでのコンパイルを考慮したとしても、最近であればlibFoundationと組み合わせればNSObjectなども普通に使える・・・らしい、です。
helloworld2.m:
#import <Foundation/NSObject.h> #import <stdio.h> @interface HelloWorld : NSObject { } -(void) hello; @end @implementation HelloWorld -(void) hello { printf("Hello, Objective-C!\n"); } @end int main(int argc, char *argv[]) { HelloWorld *o = [[HelloWorld alloc] init]; [o hello]; [o release]; return 0; }
コンパイル&実行:
$ gcc -o helloworld2 helloworld2.m -framework Foundation $ ./helloworld2 Hello, Objective-C!
レガシーなObjectクラスの実体は、/usr/lib/libobjc.A.dylib に含まれています。今回はIntel版のMacOSXということもあり、i386とx86_64の2つのバイナリが含まれたUniversalBinaryになっていました。
$ file libobjc.A.dylib libobjc.A.dylib: Mach-O universal binary with 2 architectures libobjc.A.dylib (for architecture x86_64): Mach-O 64-bit dynamically linked shared library x86_64 libobjc.A.dylib (for architecture i386): Mach-O dynamically linked shared library i386
このうち、i386版の方にはObjectのallocなど各種実装が含まれていました。
i386版:
[msakamoto:lib]$ nm -arch i386 libobjc.A.dylib | fgrep "[Object" 00017950 t +[Object allocFromZone:] 0001791c t +[Object alloc] 000179f0 t +[Object class] 00017f63 t +[Object conformsTo:] 00018092 t +[Object descriptionForInstanceMethod:] 000179d7 t +[Object free] 0001789b t +[Object initialize] 0001823c t +[Object instanceMethodDescFor:] 00017beb t +[Object instanceMethodFor:] 00017b1c t +[Object instancesRespondTo:] 0001799e t +[Object name] 000178c8 t +[Object new] ...
一方、x86_64版にはObjectクラスの実装がほとんど含まれていません。
$ nm -arch x86_64 libobjc.A.dylib | fgrep "[Object" 000000000001d3d7 t +[Object class] 000000000001d3ce t +[Object initialize] (これだけ。)
以下の記事より、推測ですが32bitバイナリは"__OBJC2__"マクロ無しでビルドされ、64bitバイナリでは"__OBJC2__"マクロ有りでビルドされているのかも・・・いやそうとも限らないか・・・真相は調べきれませんでした。
長くなりましたが、i386版のライブラリにObjectの実装があるならi386版でビルドすればよさそうです。
$ gcc -arch i386 -o helloworld helloworld.m -lobjc $ ./helloworld Hello, Objective-C!
・・・というわけで、OpenStep以降独自に発展していったOpenStep -> YellowBox -> Cocoaの血脈にNSObjectは含まれている次第。
ただしサポート自体はMacOSX 10.5(Leopard)までは続いていたようで、似たような状況「<objc/Object.h>を使いたいけどうまくいかない・・・」というのは10.6以降に発生しているみたいです。この辺りは丁度OS全体の64bit化への切り替えとも重なっており、それによりObjectの実装に関連した問題が表面化したのかもしれません。なんとも分かりませんが・・・。ただ、似たような現象について取り上げられたスレッドや記事の殆どで、「Mac OS 10.5とかの昔のバージョンでは動いていたのに、10.6とか最新のOSXだと動かない!」というような内容が書かれています。
NSObjectのコードを格納しているライブラリを探してみます。まず、helloworld2がロードするライブラリを確認します。
$ otool -L helloworld2 helloworld2: /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation ¥ (compatibility version 300.0.0, current version 833.20.0) /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 159.1.0) /usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0) /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation ¥ (compatibility version 150.0.0, current version 635.15.0)
Foundationファイルを調べてみます。
$ nm /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation ... 0000000000116df4 t +[NSObject(NSObject) implementsSelector:] 0000000000002b89 t +[NSObject(NSObject) initialize] 0000000000116d85 t +[NSObject(NSObject) instancesImplementSelector:] 00000000000944f2 t +[NSObject(NSObject) load] 0000000000116e6b t +[NSObject(NSObject) poseAsClass:] 000000000002ce54 t +[NSObject(NSObject) setVersion:] 000000000002ca96 t +[NSObject(NSObject) version] ...
CoreFoundationについても確認してみます。
$ nm /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation | less ... 0000000000049320 t +[NSObject alloc] 000000000012fbc0 t +[NSObject allowsWeakReference] 000000000012fbe0 t +[NSObject autorelease] 0000000000055fd0 t +[NSObject class] 000000000009b4b0 t +[NSObject conformsToProtocol:] 000000000009b2f0 t +[NSObject copyWithZone:] 000000000012fc00 t +[NSObject copy] ...
Foundationは"NSObject(NSObject)"という表記になりましたが、CoreFoundationは"NSObject"のままです。
細い差異が多少あるものの、この辺のライブラリにNSObjectなどのCocoa Foundationの実体が収められているようです。
他:
各種Wikipediaへのショートカット