Objective-CでPHPエクステンションを書いてみる

マカーかつ ぺちばー な僕としてはPHPにもPerlのCamelBones、PythonPyObjCRubyRubyCocoaのようにスクリプトから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である意味が全くない代物ですが、WebKitCore 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);
		}
	}
}