マカーかつ ぺちばー な僕としてはPHPにもPerlのCamelBones、PythonのPyObjC、RubyのRubyCocoaのようにスクリプトからCocoaを叩けるようなものがあれば嬉しいのですが、存在しない。しかもそれらと同等のものをつくるのはちょっと大変。
ならば次善の策として、エクステンションとしてCocoaの欲しい部分だけつまみ食いする方法を考えてみました。
Objective-C (以下ObjC) はCにSmallTalk風の皮を被せた言語で、Cで作成したバイナリとは相互にリンク可能です。つまりObjCでPHPエクステンションを書くことも可能なはず。しかしGNU autotoolsを使うPHPエクステンションのビルドシステムではCFLAGSやLDFLAGSをあれこれいじってみたけど、うまくビルドできませんでした。
そもそもなぜautotoolsを使っているかというと可搬性のためですが、今回はMac OS X縛り (Darwin以外のObjC実装は気にしないことのする) なのでプラットフォームごとの差違に気を遣う必要がほとんどありません。そこで手書きMakefileでのビルドに挑戦。うまくいったのでソースコード一式を晒します。
Mac OS X 10.4.10 (ppc & i386)、PHP 5.2.4 & 6.0-devで動作確認しました。
今回のものはただHello Worldを返すだけのObjCである意味が全くない代物ですが、WebKitやCore Imageを使ったものが作れれば、と思います。
myobjc.m (本体)
#import <Foundation/NSAutoreleasePool.h> #import <Foundation/NSString.h> // for boolean_e in php/main/snprintf.h #undef YES #undef NO #define NSYES (BOOL)1 #define NSNO (BOOL)0 #import <php.h> #import <SAPI.h> #import <Zend/zend.h> #import <Zend/zend_extensions.h> static PHP_FUNCTION(myobjc_helloworld) { id pool = [[NSAutoreleasePool alloc] init]; id ustr = [NSString stringWithUTF8String:"Hello, World!"]; #ifdef IS_UNICODE zstr str; unsigned int len; if (UG(unicode)) { // Expecting UChar to be uint16_t and expecting NSUnicodeStringEncoding // to be UTF-16 (machine's native byte order). str.u = (UChar *)[ustr cStringUsingEncoding:NSUnicodeStringEncoding]; len = [ustr length]; } else { str.s = (char *)[ustr UTF8String]; len = [ustr lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; } RETVAL_TEXTL(str, len, 1); #else ZVAL_STRINGL(return_value, (char *)[ustr UTF8String], [ustr lengthOfBytesUsingEncoding:NSUTF8StringEncoding], 1); #endif [pool release]; } static zend_function_entry myobjc_functions[] = { PHP_FE(myobjc_helloworld, NULL) { NULL, NULL, NULL } }; static zend_module_entry myobjc_module_entry = { STANDARD_MODULE_HEADER, "myobjc", myobjc_functions, NULL, NULL, NULL, NULL, NULL, "0.0.1", STANDARD_MODULE_PROPERTIES }; ZEND_GET_MODULE(myobjc)
Makefile (-undefined suppress -flat_namespace を -undefined dynamic_lookup に変更)
PHP_CONFIG ?= php-config MODULE_SUFFIX := .so EXTENSION_DIR := $(shell $(PHP_CONFIG) --extension-dir) CC ?= cc CFLAGS += -Wall -std=c99 -fno-common -fPIC CPPFLAGS += $(shell $(PHP_CONFIG) --includes) CPPFLAGS += $(shell $(PHP_CONFIG) --configure-options 2> /dev/null | awk -f icu-dir.awk) OBJECTS := myobjc.o all: myobjc$(MODULE_SUFFIX) .m.o: $(CC) $(CFLAGS) $(CPPFLAGS) -c $< myobjc$(MODULE_SUFFIX): $(OBJECTS) $(CC) -bundle -undefined dynamic_lookup -framework Foundation $^ -o $@ clean: rm -f $(OBJECTS) myobjc$(MODULE_SUFFIX) install: myobjc$(MODULE_SUFFIX) mkdir -p $(EXTENSION_DIR) cp myobjc$(MODULE_SUFFIX) $(EXTENSION_DIR) uninstall: rm -f $(EXTENSION_DIR)/myobjc$(MODULE_SUFFIX) .PHONY: clean uninstall
icu-dir.awk (PHP6用make補助スクリプト)
{ for (i = 0; i < NF; i++) { if ($i ~ /^--with-icu-dir=.+/) { sub(/^--with-icu-dir=/, "", $i); printf("-I%s/include", $i); } } }