Commits

Anonymous committed ace6fb0

and check in the rest of these bits

Comments (0)

Files changed (231)

+Installation:
+
+# build libffi, need to do this once only
+cd libffi
+./configure
+make
+sudo make install
+cd ..
+
+# build the bridge.  Make sure you have the "right" php-config in your path.
+# Note well, that you need to have PHP 5 for this stuff to work.
+# Also note that the version of PHP that ships with Leopard appears to have
+# had its exported symbols stripped out, making it impossible to load the extension
+make
+sudo make install
+
+# Now you can build the sample into a bundle:
+php utils/make-bundle -target-dir ~/Desktop -name 'Currency Converter' -url 'http://netevil.org/' -resources samples/converter/main.php -resources samples/converter/MainMenu.nib
+
+# and now double click the php icon on your desktop
+
+
+PHPCONFIG=php-config
+PHPCFLAGS=-DPHP_ATOM_INC -DCOMPILE_DL_OBJC -DPIC -I. `$(PHPCONFIG) --includes` -Ilibffi/include -g -Wall 
+
+all: objc.so
+
+PHPOBJC_OBJS=extension.o exception.o
+
+*.m: php_objc.h
+
+objc.so: $(PHPOBJC_OBJS) 
+	gcc -framework Foundation -framework AppKit -bundle -flat_namespace -undefined suppress -o objc.so $(PHPOBJC_OBJS) -Llibffi/.libs -lffi
+
+%.o: %.m
+	gcc $(PHPCFLAGS) -c $< -o $@
+
+shim: shim.c
+	gcc -o shim shim.c
+
+install: objc.so
+	install -m 755 objc.so `$(PHPCONFIG) --extension-dir`
+
+dist:
+	-rm -rf /tmp/php-objc
+	-mkdir /tmp/php-objc
+	cp -r *.php *.m *.h TODO README INSTALL samples Makefile utils libffi /tmp/php-objc
+	-cd /tmp/php-objc/libffi && make clean
+	-cd /tmp/php-objc/ && make clean
+	find /tmp/php-objc -type d -name .svn | xargs rm -rf
+	cd /tmp && tar cjf /tmp/php-objc.tar.bz2 php-objc
+
+clean:
+	rm *.o *.so
+
+- provide hooks for specifying types (call a php method)?
+
+- expand @objc:signature resolution code to look for standard phpdoc @param and @return
+  annotations.
+
+- expand signature resolution code to look up signatures from inherited methods/classes
+
+- naming for method names that conflict with PHP reserved words
+
+- handle more types, and better
+ - _C_BFLD ?
+ - _C_UNDEF ?
+ - _C_ARY
+ - _C_UNION
+ - _C_STRUCT
+
+/* vim:ts=4:sw=4:noet:
+ * Copyright (c) 2007, OmniTI Computer Consulting, Inc.
+ * Author: Wez Furlong, <wez@omniti.com>
+ * This source file is subject to version 3.01 of the PHP license.
+ */
+#include "php_objc.h"
+
+static zval *exception_hack = NULL;
+
+/* raise a PHP level exception as an objective-C exception */
+void php_objc_raise(zval *excep TSRMLS_DC)
+{
+	exception_hack = excep;
+	EG(exception) = NULL;
+	[[NSException exceptionWithName:@"PHPException" reason:@"" userInfo:nil] raise];
+}
+
+void php_objc_throw_NS(NSException *nse TSRMLS_DC)
+{
+	char *msg = NULL;
+
+	if ([[nse name] isEqualToString:@"PHPException"]) {
+		EG(exception) = exception_hack;
+		exception_hack = NULL;
+		[nse dealloc];
+		return;
+	}
+	
+	spprintf(&msg, 0, "%s: %s", [[nse name] UTF8String],
+		[[nse reason] UTF8String]);
+	zend_throw_exception(php_objc_excep_ce,
+		msg, 0 TSRMLS_CC);
+	efree(msg);
+	[nse dealloc];
+}
+
+/* vim:ts=4:sw=4:noet:
+ * Copyright (c) 2007, OmniTI Computer Consulting, Inc.
+ * Author: Wez Furlong, <wez@omniti.com>
+ * This source file is subject to version 3.01 of the PHP license.
+ */
+#include "php_objc.h"
+
+static zend_object_handlers php_objc_handlers;
+static PHP_FUNCTION(objc_method_handler);
+static PHP_FUNCTION(objc_alloc);
+static PHP_FUNCTION(objc_static_invoke);
+static zend_object_value php_objc_object_new(zend_class_entry *ce TSRMLS_DC);
+static int php_objc_method_call(char *method, INTERNAL_FUNCTION_PARAMETERS);
+static int php_objc_method_call_inner(char *method, int skip_arg_static, INTERNAL_FUNCTION_PARAMETERS);
+static void php_objc_wrap_id(id idval, zval *return_value);
+static int php_objc_mapping(zval *object, id *idval, int throw TSRMLS_DC);
+static const char *php_objc_type_to_ffi(const char *t, ffi_type **ffi, int isret);
+static struct objc_class *php_objc_export_class(zend_class_entry *ce TSRMLS_DC);
+static int php_objc_classHandler(const char *classname);
+static void php_objc_import_classes(TSRMLS_D);
+
+TsHashTable php_objc_imported_classes;
+TsHashTable php_objc_structs;
+static NSAutoreleasePool *my_pool = NULL;
+
+zend_class_entry
+	*php_objc_excep_ce = NULL,
+	*php_objc_NSObject_ce = NULL;
+
+void php_objc_throw(const char *fmt TSRMLS_DC, ...)
+{
+	va_list ap;
+	char *msg = NULL;
+
+#ifdef ZTS
+	va_start(ap, tsrm_ls);
+#else
+	va_start(ap, fmt);
+#endif
+	vspprintf(&msg, 0, fmt, ap);
+	va_end(ap);
+	zend_throw_exception(php_objc_excep_ce, msg, 0 TSRMLS_CC);
+	efree(msg);
+}
+
+@implementation PHPZval /* {{{ */
+- (void)dealloc
+{
+	if (val) {
+		zval_ptr_dtor(&val);
+	}
+	[super dealloc];
+}
+
+- (zval *)_phpGetZval
+{
+	return val;
+}
+
+- (BOOL)respondsToSelector:(SEL)aSelector
+{
+	struct objc_method_list *mlist;
+	void *iter = 0;
+
+	while ((mlist = class_nextMethodList(self->isa, &iter))) {
+		int i;
+		for (i = 0; i < mlist->method_count; i++) {
+			if (mlist->method_list[i].method_name == aSelector) {
+				return YES;
+			}
+		}
+	}
+
+	{
+		char *buf;
+		char *cur;
+		BOOL retval = NO;
+		zend_function *f;
+		zend_class_entry *ce;
+
+		buf = strdup(sel_getName(aSelector));
+		for (cur = buf; *cur; cur++) {
+			if (*cur == ':')
+				*cur = '_';
+			else
+				*cur = tolower(*cur);
+		}
+
+		ce = Z_OBJCE_P(self->val);
+		if (zend_hash_find(&ce->function_table,
+				buf, strlen(buf)+1, (void**)&f) == SUCCESS) {
+			retval = YES;
+		} else {
+			printf("I don't respond to %s\n", buf);
+		}
+
+		if (retval == NO) {
+			/* TODO: call a respondsToSelector method if present */
+		}
+
+		free(buf);
+		return retval;
+	}
+
+	return NO;
+}
+
+- (void)forwardInvocation:(NSInvocation *)anInvocation
+{
+	SEL sel = [anInvocation selector];
+
+	if (sel == @selector(respondsToSelector:)) {
+		BOOL b;
+		[anInvocation getArgument:&sel atIndex:2];
+		b = [self respondsToSelector:sel];
+		[anInvocation setReturnValue:&b];
+		return;
+	}
+
+#if 0
+	if ([self respondsToSelector:sel]) {
+		[anInvocation setTarget:self];
+		[anInvocation invoke];
+		return;
+	}
+#endif
+	printf("I get to forward %s!\n", sel_getName(sel));
+	{
+		char *buf;
+		char *cur;
+		zend_function *f;
+		zend_class_entry *ce;
+		int nargs = 0;
+		zval ***params;
+		zval *func_name;
+		int i;
+		TSRMLS_FETCH();
+
+		buf = estrdup(sel_getName(sel));
+		for (cur = buf; *cur; cur++) {
+			if (*cur == ':') {
+				*cur = '_';
+				nargs++;
+			} else {
+				*cur = tolower(*cur);
+			}
+		}
+		ALLOC_INIT_ZVAL(func_name);
+		ZVAL_STRING(func_name, buf, 0);
+
+		ce = Z_OBJCE_P(self->val);
+		if (zend_hash_find(&ce->function_table,
+				buf, strlen(buf)+1, (void**)&f) == SUCCESS) {
+			zval *retval = NULL;
+
+			/* now my favourite part, calling into PHP */
+			if (nargs) {
+				params = (zval***)safe_emalloc(sizeof(zval **), nargs, 0);
+				for (i = 0; i < nargs; i++) {
+					zval *arg;
+					id iarg;
+
+					[anInvocation getArgument:&iarg atIndex:(i+2)];
+
+					params[i] = (zval**)emalloc(sizeof(zval**));
+					if ([iarg isKindOfClass:[PHPZval class]]) {
+						/* we get to ref the zval direct */
+						*params[i] = [iarg _phpGetZval];
+						ZVAL_ADDREF(*params[i]);
+					} else {
+						/* wrap it in */
+						ALLOC_INIT_ZVAL(arg);
+						php_objc_wrap_id(iarg, arg TSRMLS_CC);
+						*params[i] = arg;
+					}
+					
+				}
+			} else {
+				params = NULL;
+			}
+			zend_try {
+				if (SUCCESS == call_user_function_ex(&ce->function_table,
+						&self->val, func_name, &retval, nargs, params, 1, NULL
+						TSRMLS_CC)) {
+				}
+			} zend_catch {
+			} zend_end_try();
+			if (params) {
+				for (i = 0; i < nargs; i++) {
+					zval_ptr_dtor(params[i]);
+					efree(params[i]);
+				}
+				efree(params);
+			}
+			if (EG(exception)) {
+				if (retval) {
+					zval_ptr_dtor(&retval);
+				}
+				zval_ptr_dtor(&func_name);
+				php_objc_raise(EG(exception) TSRMLS_CC);
+			}
+
+			if (retval) {
+				switch (Z_TYPE_P(retval)) {
+					case IS_BOOL:
+					case IS_LONG:
+					{
+						long l = Z_LVAL_P(retval);
+						[anInvocation setReturnValue:&l];
+						break;
+					}
+					case IS_NULL:
+					{
+						id null = 0;
+						[anInvocation setReturnValue:&null];
+						break;
+					}
+					default:
+					{
+						id iret;
+						if (php_objc_mapping(retval, &iret, 0 TSRMLS_CC)) {
+							[anInvocation setReturnValue:&iret];
+						}
+					}
+				}
+				zval_ptr_dtor(&retval);
+			}
+		}
+		zval_ptr_dtor(&func_name);
+		return;
+	}
+	
+	[super forwardInvocation:anInvocation];
+}
+
+- (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector
+{
+	char *sig;
+	struct objc_method *m;
+
+	m = class_getInstanceMethod(self->isa, aSelector);
+	if (m) {
+		sig = m->method_types;
+	} else {
+		const char *buf;
+		const char *cur;
+		int nargs = 0;
+
+		buf = sel_getName(aSelector);
+		for (cur = buf; *cur; cur++) {
+			if (*cur == ':') {
+				nargs++;
+			}
+		}
+
+		sig = alloca(nargs + 4);
+		memset(sig, '@', nargs + 3);
+		sig[nargs+3] = '\0';
+		sig[2] = ':';
+		sig[0] = 'c'; /* FIXME: need to set this correctly */
+	}
+
+	return [NSMethodSignature signatureWithObjCTypes:sig];
+}
+
++ (PHPZval*)wrapZval:(zval*)value
+{
+	PHPZval *O = [PHPZval alloc];
+	O->val = value;
+	ZVAL_ADDREF(O->val);
+	return O;
+}
+@end
+/* }}} */
+
+static int is_implied_ref_param(const char *argtype)
+{
+	if (*argtype == _C_PTR) {
+		argtype++;
+		if (*argtype == _C_VOID) return 0;
+		return 1;
+	}
+	return 0;
+}
+
+static int populate_func_entry(zend_function_entry *fe,
+	SEL sel, const char *functype, int modifiers,
+	HashTable *dup_elim TSRMLS_DC)
+{
+	int narg;
+	char methname[1024];
+	char *cur;
+	struct objc_method fake_meth;
+
+	/* we want to determine the names of the args and their types.
+	 * We do the heavy lifting by faking a regular method entry
+	 * from the method description and using objc runtime provided
+	 * APIs. */
+	memset(&fake_meth, 0, sizeof(fake_meth));
+	fake_meth.method_name = sel;
+	fake_meth.method_types = (char*)functype;
+
+	/* TODO: skip over dangerous methods like release, retain */
+
+	memset(fe, 0, sizeof(*fe));
+	fe->num_args = method_getNumberOfArguments(&fake_meth) - 2;
+	fe->flags = modifiers;
+
+	if (fe->num_args) {
+		cur = (char*)sel_getName(sel);
+
+		/* the 0th slot is actually for the return value */
+		fe->arg_info = calloc(1 + fe->num_args, sizeof(zend_arg_info));
+		fe->arg_info[0].required_num_args = fe->num_args;
+
+		for (narg = 0; narg < fe->num_args; narg++) {
+			char *next = NULL;
+			int off;
+			const char *argtype;
+
+			/* we name the arguments based on the selector
+			 * name, with the ordinal position appended.
+			 * This is because methods like performSelector have
+			 * multiple withObject parameters. */
+
+			if (cur) {
+				if (*cur == ':') {
+					/* walk past : */
+					cur++;
+				}
+				next = strchr(cur, ':');
+			}
+
+			fe->arg_info[narg].name_len = spprintf(
+					&(fe->arg_info[narg+1].name), 0, "%.*s%d",
+					next - cur, cur, narg);
+
+			cur = next;
+
+			/* what about the type? */
+			method_getArgumentInfo(&fake_meth, narg+2, &argtype, &off);
+
+			while (*argtype) {
+				if (*argtype == 'N' || *argtype == 'o' || *argtype == 'R') {
+					fe->arg_info[narg+1].pass_by_reference = 1;
+					argtype++;
+					continue;
+				}
+				if (*argtype == 'r' || *argtype == 'n' || *argtype == 'O'
+						|| *argtype == 'V') {
+					argtype++;
+					continue;
+				}
+				/* must be the actual type code now */
+				break;
+			}
+			if (!fe->arg_info[narg+1].pass_by_reference &&
+					is_implied_ref_param(argtype)) {
+				argtype++;
+				fe->arg_info[narg+1].pass_by_reference = 1;
+			}
+			while (*argtype) {
+				if (*argtype == 'r' || *argtype == 'n' || *argtype == 'O'
+						|| *argtype == 'V') {
+					argtype++;
+					continue;
+				}
+				/* must be the actual type code now */
+				break;
+			}
+
+			switch (*argtype) {
+				case _C_SEL:
+				case _C_CLASS:
+				case _C_CHR:
+				case _C_UCHR:
+				case _C_SHT:
+				case _C_USHT:
+				case _C_INT:
+				case _C_UINT:
+				case _C_LNG:
+				case _C_ULNG:
+				case _C_FLT:
+				case _C_DBL:
+				case _C_CHARPTR:
+				case 'Q':	/* unsigned long long */
+				case 'q':	/* long long */
+				case 'B':	/* C++/C99 bool */
+					/* maps to primitive types */
+					break;
+				case _C_ID:
+					/* must be any object type. We can't express
+					 * that in PHP type hints */
+					break;
+				case _C_STRUCT_B:
+					{
+						/* structured data. We only support it of the form:
+						 * {name=#}, which we can map to a class type */
+//						printf("struct type %s\n", argtype);
+//						goto cant_map;
+						break;
+					}
+				case _C_PTR:
+					{
+						/* we don't support this fellow */
+//						goto cant_map;
+						break;
+					}
+
+				case _C_UNDEF:
+					/* unknown type, perhaps a function pointer */
+					goto cant_map;
+
+				default:
+					printf("unhandled arg type %s for method %s\n", argtype, sel_getName(sel));
+					exit(0);
+			}
+		}
+	}
+
+	strcpy(methname, sel_getName(sel));
+	cur = strchr(methname, ':');
+	while (cur) {
+		*cur = '_';
+		cur = strchr(methname, ':');
+	}
+
+	if (dup_elim) {
+		zend_function_entry *findme;
+
+		if (zend_hash_find(dup_elim, methname,
+				strlen(methname), (void**)&findme) == SUCCESS) {
+			goto cant_map;
+		}
+		if (FAILURE == zend_hash_update(dup_elim, methname, strlen(methname),
+				&fe, sizeof(fe), NULL)) {
+			abort();
+		}
+	}
+	
+	fe->fname = strdup(methname);
+	fe->handler = PHP_FN(objc_method_handler);
+
+	return 1;
+cant_map:
+	/* FIXME: free up things here */
+	return 0;
+}
+
+static void add_protocol_methods(zend_function_entry **feptr,
+	struct objc_method_description_list *list, int modifiers,
+	HashTable *dup_elim TSRMLS_DC)
+{
+	int m;
+	zend_function_entry *fe;
+
+	for (m = 0; m < list->count; m++) {
+		fe = *feptr;
+
+		if (populate_func_entry(fe, list->list[m].name, list->list[m].types,
+				modifiers, dup_elim TSRMLS_CC)) {
+			*feptr = fe + 1;
+		}
+	}
+}
+
+/* Make sure we aren't defining methods that we'll pick up via inheritance */
+static void fix_ftable(zend_function_entry *ftable, zend_class_entry *ce TSRMLS_DC)
+{
+	int ftable_size = 0;
+	int i;
+	zend_function_entry *fe;
+	zend_function *func;
+	char *lname;
+
+	for (fe = ftable; fe->fname; fe++) {
+		ftable_size++;
+	}
+	for (i = 0; ftable[i].fname; i++) {
+		lname = zend_str_tolower_dup(ftable[i].fname, strlen(ftable[i].fname));
+		
+		if (zend_hash_find(&ce->function_table,
+				lname, strlen(ftable[i].fname)+1,
+				(void**)&func) == SUCCESS) {
+		
+			if ((ftable[i].flags & ZEND_ACC_STATIC) != 
+					((func)->common.fn_flags & ZEND_ACC_STATIC)) {
+
+#if 0
+				ftable[i].flags &= ~ ZEND_ACC_STATIC;
+#else
+				/*
+				printf("Nuking %s static %x doesn't match %x (inherited from %s)\n",
+					ftable[i].fname, ftable[i].flags,
+					(func)->common.fn_flags, ce->name);
+				*/
+
+				memmove(ftable + i, ftable + i + 1,
+						(ftable_size - i) * sizeof(zend_function_entry));
+				i--;
+#endif
+			}
+		}
+		efree(lname);
+	}
+	
+}
+
+static zend_class_entry *php_objc_import_interface(Protocol *p TSRMLS_DC)
+{
+	char namebuf[1024];
+	struct objc_method_description_list *imeth = NULL, *cmeth = NULL;
+	int nfuncs;
+	zend_function_entry *ftable, *curfunc;
+	zend_class_entry *ice, *ece, **pce;
+	zend_class_entry ce;
+	struct objc_protocol_list *plist_ext = NULL;
+	HashTable dup_elim;
+	char *lname;
+
+	snprintf(namebuf, sizeof(namebuf)-1, "I%s", [p name]);
+	lname = zend_str_tolower_dup(namebuf, strlen(namebuf));
+
+	/* already registered? */
+	if (zend_hash_find(EG(class_table), lname, strlen(namebuf)+1,
+				(void**)&pce) == SUCCESS) {
+		efree(lname);
+		return *pce;
+	}
+	efree(lname);
+
+	object_getInstanceVariable(p, "instance_methods", (void**)&imeth);
+	object_getInstanceVariable(p, "class_methods", (void**)&cmeth);
+	object_getInstanceVariable(p, "protocol_list", (void**)&plist_ext);
+
+	nfuncs = (imeth ? imeth->count : 0) + (cmeth ? cmeth->count : 0);
+
+	ftable = calloc(nfuncs + 1,
+			sizeof(zend_function_entry));
+
+	curfunc = ftable;
+
+	zend_hash_init(&dup_elim, 2, NULL, NULL, 0);
+
+	if (imeth) {
+		add_protocol_methods(&curfunc, imeth,
+			//ZEND_ACC_INTERFACE|
+			ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT,
+			&dup_elim
+			TSRMLS_CC);
+	}
+	if (cmeth) {
+		add_protocol_methods(&curfunc, cmeth,
+			//ZEND_ACC_INTERFACE|
+			ZEND_ACC_STATIC|
+			ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT,
+			&dup_elim
+			TSRMLS_CC);
+	}
+	zend_hash_destroy(&dup_elim);
+
+	memset(&ce, 0, sizeof(ce));
+	INIT_CLASS_ENTRY(ce, namebuf, ftable);
+	/* the INIT_CLASS_ENTRY macro uses sizeof for the name len calculation,
+	 * fix it up. */
+	ce.name_length = strlen(namebuf);
+
+	if (plist_ext) {
+		struct objc_protocol_list *plist = plist_ext;
+		while (plist) {
+			int ip;
+			for (ip = 0; ip < plist_ext->count; ip++) {
+				Protocol *ep = plist_ext->list[ip];
+				ece = php_objc_import_interface(ep TSRMLS_CC);
+				ce.interfaces = realloc(ce.interfaces, 
+					(ce.num_interfaces + 1) *
+					sizeof(zend_function_entry*));
+				ce.interfaces[ce.num_interfaces] = ece;
+				ce.num_interfaces++;
+				
+				fix_ftable(ftable, ece TSRMLS_CC);
+			}
+			plist = plist->next;
+		}
+	}
+
+	ice = zend_register_internal_interface(&ce TSRMLS_CC);
+	return ice;
+}
+
+static int count_interface_methods(zend_class_entry *iface)
+{
+	int n = iface->function_table.nNumOfElements;
+	int i;
+
+	for (i = 0; i < iface->num_interfaces; i++) {
+		n += count_interface_methods(iface->interfaces[i]);
+	}
+	return n;
+}
+
+static int import_interface_methods(zend_class_entry *ice,
+	zend_function_entry *ftable, HashTable *dup_elim
+	TSRMLS_DC)
+{
+	Bucket *b;
+	int nfuncs = 0;
+	int i;
+
+	for (i = 0; i < ice->num_interfaces; i++) {
+		int n;
+		n = import_interface_methods(
+			ice->interfaces[i],
+			ftable, dup_elim TSRMLS_CC);
+		ftable += n;
+		nfuncs += n;
+	}
+	b = ice->function_table.pListHead;
+	while (b) {
+		zend_function *f, **findme;
+		f = (zend_function*)b->pData;
+
+		if (SUCCESS == zend_hash_find(dup_elim,
+					b->arKey, b->nKeyLength-1, (void**)&findme)) {
+			/* already in there */
+		} else {
+
+			ftable->fname = strdup(f->common.function_name);
+			ftable->flags = f->common.fn_flags & ~ZEND_ACC_ABSTRACT;
+			ftable->num_args = f->common.num_args;
+			ftable->arg_info = calloc(f->common.num_args + 1,
+					sizeof(zend_arg_info));
+			ftable->arg_info[0].required_num_args = f->common.num_args;
+			memcpy(ftable->arg_info + 1,
+					f->common.arg_info,
+					f->common.num_args * sizeof(zend_arg_info));
+
+			ftable->handler = f->internal_function.handler;
+			nfuncs++;
+			ftable++;
+
+			zend_hash_update(dup_elim, b->arKey, b->nKeyLength-1,
+					ftable, sizeof(zend_function_entry*), NULL);
+//			printf("%s::%s added to ftable\n", ice->name, f->common.function_name);
+
+		}
+		b = b->pListNext;
+	}
+	
+	return nfuncs;
+}
+
+static php_objc_class *php_objc_import_class(const char *name TSRMLS_DC)
+{
+	php_objc_class c, *cls, *parent_cls;
+	zend_class_entry ce;
+	struct objc_protocol_list *plist;
+	void *iter = 0;
+	zend_class_entry **ifaces = NULL;
+	int nifaces = 0;
+	int nstatic = 0;
+	int i;
+	struct objc_method_list *mlist;
+	HashTable dup_elim;
+	char *lname;
+	zend_class_entry **pce;
+	
+	if (zend_ts_hash_find(&php_objc_imported_classes, (char*)name,
+			strlen(name), (void**)&cls) == SUCCESS) {
+		return cls;
+	}
+	lname = zend_str_tolower_dup(name, strlen(name));
+	if (zend_hash_find(EG(class_table), lname, strlen(name)+1,
+				(void**)&pce) == SUCCESS) {
+		efree(lname);
+		return NULL;
+	}
+	efree(lname);
+
+	memset(&c, 0, sizeof(c));
+
+	c.class_id = objc_getClass(name);
+	if (!c.class_id) {
+		printf("failed to import %s\n", name);
+		return NULL;
+	}
+
+	if (c.class_id->super_class) {
+		parent_cls = php_objc_import_class(c.class_id->super_class->name TSRMLS_CC);
+		if (parent_cls == NULL) {
+			/* error */
+			printf("failed to import super %s\n", c.class_id->super_class->name);
+			return NULL;
+		}
+	} else {
+		parent_cls = NULL;
+	}
+
+	nstatic = 0;
+	zend_hash_init(&dup_elim, 2, NULL, NULL, 0);
+
+	/* for each protocol, register it as an interface */
+	for (plist = c.class_id->protocols; plist; plist = plist->next) {
+		int np;
+		for (np = 0; np < plist->count; np++) {
+			zend_class_entry *ice;
+
+			ice = php_objc_import_interface(plist->list[np] TSRMLS_CC);
+
+			ifaces = realloc(ifaces, (nifaces + 1) * sizeof(zend_class_entry*));
+			ifaces[nifaces++] = ice;
+
+			/* now, manually import the function defs from the interface
+			 * and remove their abstractness.
+			 * Its a shame that we need to do this, as we are able to
+			 * do method resolution on demand.
+			 */
+			c.ftable = realloc(c.ftable,
+				(nstatic + count_interface_methods(ice) + 1) *
+				sizeof(zend_function_entry));
+			nstatic += import_interface_methods(ice, c.ftable + nstatic,
+				&dup_elim TSRMLS_CC);
+		}
+	}
+	/* due to the way that the zend engine works, we can't dynamically
+	 * resolve the static methods on the class, so we need to do this
+	 * relatively expensive bit of walking here to register our static
+	 * methods. */
+	while ((mlist = class_nextMethodList(c.class_id->isa, &iter))) {
+		int i;
+		for (i = 0; i < mlist->method_count; i++) {
+			if (mlist->method_list[i].method_name == @selector(alloc)) {
+				continue;
+			}
+			c.ftable = realloc(c.ftable,
+					(nstatic+1) * sizeof(zend_function_entry));
+			if (populate_func_entry(&c.ftable[nstatic],
+						mlist->method_list[i].method_name,
+						mlist->method_list[i].method_types,
+						ZEND_ACC_PUBLIC|ZEND_ACC_STATIC,
+						&dup_elim
+						TSRMLS_CC)) {
+				nstatic++;
+			}
+		}
+	}
+	zend_hash_destroy(&dup_elim);
+
+	/* now create a fake alloc entry, to guarantee that the scope
+	 * is set right, so that we can use that to allocate an instance
+	 * of the correct class */
+	c.ftable = realloc(c.ftable, (nstatic+3) * sizeof(zend_function_entry));
+
+	memset(&c.ftable[nstatic], 0, sizeof(zend_function_entry));
+	c.ftable[nstatic].handler = PHP_FN(objc_alloc);
+	c.ftable[nstatic].fname = strdup("alloc");
+	c.ftable[nstatic].flags = ZEND_ACC_PUBLIC|ZEND_ACC_STATIC;
+	nstatic++;
+
+	memset(&c.ftable[nstatic], 0, sizeof(zend_function_entry));
+	c.ftable[nstatic].handler = PHP_FN(objc_static_invoke);
+	c.ftable[nstatic].fname = strdup("__staticInvoke");
+	c.ftable[nstatic].flags = ZEND_ACC_PUBLIC|ZEND_ACC_STATIC;
+	nstatic++;
+
+	memset(&c.ftable[nstatic], 0, sizeof(zend_function_entry));
+	
+	for (i = 0; i < nifaces; i++) {
+		fix_ftable(c.ftable, ifaces[i] TSRMLS_CC);
+	}
+
+	if (parent_cls) {
+		fix_ftable(c.ftable, parent_cls->ce TSRMLS_CC);
+	}
+
+	memset(&ce, 0, sizeof(ce));
+	INIT_CLASS_ENTRY(ce, name, c.ftable);
+	/* the INIT_CLASS_ENTRY macro uses sizeof for the name len calculation,
+	 * fix it up. */
+	ce.name_length = strlen(name);
+	ce.create_object = php_objc_object_new;
+	ce.num_interfaces = nifaces;
+	ce.interfaces = ifaces;
+
+	if (parent_cls) {
+		c.ce = zend_register_internal_class_ex(&ce,
+			parent_cls->ce, NULL TSRMLS_CC);
+	} else {
+		c.ce = zend_register_internal_class(&ce TSRMLS_CC);
+	}
+//	printf("Importing %s\n", name);
+
+	zend_ts_hash_update(&php_objc_imported_classes, (char*)name,
+		strlen(name), (void*)&c, sizeof(c), (void**)&cls);
+	return cls;
+}
+
+static void php_objc_import_classes(TSRMLS_D)
+{
+	char *name = NULL;
+	int namelen;
+	int size = 2048;
+	struct objc_class **classes = emalloc(size * sizeof(Class*));
+	int n, i;
+
+	n = objc_getClassList(classes, size);
+	while (n >= size) {
+		if (n > size) {
+			size = n + 1;
+		} else {
+			size *= 2;
+		}
+		classes = erealloc(classes, size * sizeof(Class*));
+		n = objc_getClassList(classes, size);
+	}
+
+	for (i = 0; i < n; i++) {
+		name = (char*)classes[i]->name;
+		if (name[0] == '_' || name[0] == '%') {
+			continue;
+		}
+		if (!strcmp(name, "Object") || !strcmp(name, "List") ||
+				!strcmp(name, "Protocol")) {
+			continue;
+		}
+		php_objc_import_class(name TSRMLS_CC);
+	}
+	efree(classes);
+
+}
+
+static PHP_RINIT_FUNCTION(php_objc)
+{
+	php_objc_import_classes(TSRMLS_C);
+	return SUCCESS;
+}
+
+static PHP_FUNCTION(objc_load_bundle)
+{
+	char *name = NULL;
+	int namelen;
+	NSBundle *bundle;
+
+	if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
+			"s", &name, &namelen)) {
+		RETURN_FALSE;
+	}
+
+	bundle = [[NSBundle alloc] initWithPath:[[NSString alloc] initWithUTF8String:name]];
+	[bundle load];
+	php_objc_import_classes(TSRMLS_C);
+}
+
+static PHP_FUNCTION(objc_import_class)
+{
+	char *name = NULL;
+	int namelen;
+
+	if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
+			"s", &name, &namelen)) {
+		RETURN_FALSE;
+	}
+	php_objc_import_class(name TSRMLS_CC);
+}
+
+static PHP_FUNCTION(objc_export_class)
+{
+	char *name = NULL;
+	int namelen;
+
+	if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
+			"s", &name, &namelen)) {
+		RETURN_FALSE;
+	}
+
+	php_objc_classHandler(name);
+}
+
+static PHP_FUNCTION(objc_new)
+{
+	char *name = NULL;
+	int namelen;
+	id inst;
+	Class c;
+
+	if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
+			"s", &name, &namelen)) {
+		RETURN_FALSE;
+	}
+
+	c = objc_getClass(name);
+	if (!c) {
+		php_objc_throw("Couldn't find class %s" TSRMLS_CC, name);
+		return;
+	}
+
+NS_DURING
+	inst = [c new];
+	php_objc_wrap_id(inst, return_value TSRMLS_CC);
+NS_HANDLER
+	php_objc_throw_NS(localException TSRMLS_CC);
+NS_ENDHANDLER
+
+}
+
+static PHP_FUNCTION(NSApplicationMain)
+{
+	zval *arr;
+	char **argv = NULL;
+	int argc, i;
+	HashPosition pos;
+
+	if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
+			"a", &arr)) {
+		RETURN_FALSE;
+	}
+
+	zend_hash_internal_pointer_reset_ex(EG(class_table), &pos);
+	do {
+		zend_class_entry **pce;
+		if (FAILURE == zend_hash_get_current_data_ex(EG(class_table), (void**)&pce, &pos)) {
+			break;
+		}
+		php_objc_export_class(*pce TSRMLS_CC);
+	} while (zend_hash_move_forward_ex(EG(class_table), &pos) == SUCCESS);
+
+
+	argc = zend_hash_num_elements(Z_ARRVAL_P(arr));
+	argv = ecalloc(argc + 1, sizeof(char*));
+
+	zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(arr), &pos);
+	i = 0;
+	do {
+		zval **zv;
+		if (FAILURE == zend_hash_get_current_data_ex(Z_ARRVAL_P(arr), (void**)&zv, &pos)) {
+			break;
+		}
+		convert_to_string_ex(zv);
+		argv[i++] = Z_STRVAL_PP(zv);
+		printf("argv[%d] = %s\n", i-1, argv[i-1]);
+	} while (zend_hash_move_forward_ex(Z_ARRVAL_P(arr), &pos) == SUCCESS);
+	argv[i] = NULL;
+
+    /*
+     * NSApplicationMain on MacOS X completely ignores its arguments and 
+     * reads the argv from the shared NSProcessInfo. We *HACK* around this 
+     * by setting a (private) instance variable of the object.
+     *
+     * This code is evil. Look away if you're easily scared.
+     */
+    {
+        typedef struct {
+            @defs(NSProcessInfo)
+        } NSProcessInfoStruct;
+
+        NSMutableArray *newarglist = [[NSMutableArray alloc] init];
+        NSProcessInfo *processInfo = [NSProcessInfo processInfo];
+        char **anArg = argv;
+
+        while(*anArg) {
+            [newarglist addObject:
+                [NSString stringWithUTF8String: *anArg]];
+            anArg++;
+        }
+
+        /* Don't release the orignal arguments, because we don't know
+         * if the list is owned by the processInfo object.
+         *
+         *[((NSProcessInfoStruct *)processInfo)->arguments release]; 
+         */
+        ((NSProcessInfoStruct *)processInfo)->arguments = newarglist;
+    }
+
+	
+	NSApplicationMain(argc, argv);
+	efree(argv);
+}
+
+static zend_function_entry php_objc_functions[] = {
+	/* TODO: objc_getClassList */
+	PHP_FE(objc_import_class, NULL)
+	PHP_FE(objc_load_bundle, NULL)
+	PHP_FE(objc_export_class, NULL)
+	PHP_FE(objc_new, NULL)
+	PHP_FE(NSApplicationMain, NULL)
+	{ NULL, NULL, NULL }
+};
+
+static void php_objc_free_stg(void *object TSRMLS_DC)
+{
+	php_objc_obj *obj = (php_objc_obj*)object;
+
+	/* TODO: free more stuff */
+	if (obj->instance) {
+		NS_DURING
+			[obj->instance release];
+		NS_HANDLER
+			php_objc_throw_NS(localException TSRMLS_CC);
+		NS_ENDHANDLER
+	}
+
+	efree(obj);
+}
+
+static void php_objc_clone(void *object, void **clone_ptr TSRMLS_DC)
+{
+	/* no cloning allowed yet */
+	*clone_ptr = NULL;
+}
+
+/* adds a ref to the underlying sel for the caller */
+static int php_objc_mapping(zval *object, id *idval, int throw TSRMLS_DC)
+{
+	switch (Z_TYPE_P(object)) {
+		case IS_OBJECT: {
+			php_objc_class *cls;
+			php_objc_obj *obj;
+			zend_class_entry *ce = Z_OBJCE_P(object);
+
+			if (!php_objc_NSObject_ce) {
+				cls = php_objc_import_class("NSObject" TSRMLS_CC);
+				php_objc_NSObject_ce = cls->ce;
+				cls = NULL;
+			}
+				
+			if (!instanceof_function(ce, php_objc_NSObject_ce TSRMLS_CC)) {
+				if (zend_ts_hash_find(&php_objc_imported_classes, ce->name,
+							strlen(ce->name), (void**)&cls) == FAILURE) {
+#if 0
+					/* let's export it using our proxy */
+					*idval = [PHPZval wrapZval:object];
+					return 1;
+#else
+					if (throw) {
+						php_objc_throw("can't pass %s to Objective-C" TSRMLS_CC,
+								ce->name);
+					}
+					printf("ugh\n");
+					return 0;
+#endif
+				}
+			}
+
+			/* its an objc compatible object; pull out its instance */
+			obj = PHPOBJC(object);
+			if (obj->instance) {
+				[obj->instance retain];
+				*idval = obj->instance;
+			} else {
+					printf("ugh here (null)\n");
+				return 0;
+			}
+
+			return 1;
+		}
+		case IS_NULL:
+			*idval = 0;
+			return 1;
+		case IS_STRING: {
+			NSString *str = [[NSString alloc]
+					initWithUTF8String:Z_STRVAL_P(object)];
+			*idval = str;
+			return 1;
+		}
+	}
+	return 0;
+}
+
+static zval *php_objc_prop_read(zval *object, zval *member, int type TSRMLS_DC)
+{
+	return std_object_handlers.read_property(object, member, type TSRMLS_CC);
+#if 0
+	zval *return_value;
+
+	MAKE_STD_ZVAL(return_value);
+	ZVAL_NULL(return_value);
+	return_value->refcount = 0;
+	return_value->is_ref = 0;
+	return return_value;
+#endif
+}
+
+static void php_objc_prop_write(zval *object, zval *member, zval *value TSRMLS_DC)
+{
+	std_object_handlers.write_property(object, member, value TSRMLS_CC);
+#if 0
+	php_objc_throw("property write not supported" TSRMLS_CC);
+#endif
+}
+
+static zval *php_objc_dim_read(zval *object, zval *offset, int type TSRMLS_DC)
+{
+	zval *return_value;
+	id val = 0;
+	php_objc_obj *obj = PHPOBJC(object);
+
+	MAKE_STD_ZVAL(return_value);
+	ZVAL_NULL(return_value);
+	return_value->refcount = 0;
+	return_value->is_ref = 0;
+
+NS_DURING
+	if ([obj->instance isKindOfClass:[NSArray class]]) {
+		convert_to_long(offset);
+		val = [obj->instance objectAtIndex:Z_LVAL_P(offset)];
+	} else if ([obj->instance isKindOfClass:[NSDictionary class]]) {
+		NSString *str;
+
+		convert_to_string(offset);
+
+		str = [[NSString alloc] 
+			initWithUTF8String:Z_STRVAL_P(offset)];
+
+		val = [obj->instance valueForKey:str];
+
+		[str dealloc];
+	}
+
+	if (val) {
+		php_objc_wrap_id(val, return_value TSRMLS_CC);
+	}
+
+NS_HANDLER
+	php_objc_throw_NS(localException TSRMLS_CC);
+NS_ENDHANDLER
+
+	return return_value;
+}
+
+static void php_objc_dim_write(zval *object, zval *offset, zval *value TSRMLS_DC)
+{
+	id val = 0;
+	php_objc_obj *obj = PHPOBJC(object);
+
+NS_DURING
+	if (!php_objc_mapping(value, &val, 1 TSRMLS_CC)) {
+		return;
+	}
+
+	if ([obj->instance isKindOfClass:[NSMutableArray class]]) {
+		convert_to_long(offset);
+		[obj->instance replaceObjectAtIndex:Z_LVAL_P(offset) withObject:val];
+		[val release];
+	} else if ([obj->instance isKindOfClass:[NSMutableDictionary class]]) {
+		NSString *str;
+
+		convert_to_string(offset);
+
+		str = [[NSString alloc] 
+			initWithUTF8String:Z_STRVAL_P(offset)];
+
+		[obj->instance setObject:val forKey:str];
+
+		[str dealloc];
+	}
+
+NS_HANDLER
+	php_objc_throw_NS(localException TSRMLS_CC);
+NS_ENDHANDLER
+}
+
+static int php_objc_prop_exists(zval *object, zval *member, int has_set_exists TSRMLS_DC)
+{
+	return std_object_handlers.has_property(object, member, has_set_exists TSRMLS_CC);;
+}
+
+static void php_objc_prop_del(zval *object, zval *member TSRMLS_DC)
+{
+	return std_object_handlers.unset_property(object, member TSRMLS_CC);
+}
+
+static int php_objc_dim_exists(zval *object, zval *member, int chk_empty TSRMLS_DC)
+{
+	id val = 0;
+	php_objc_obj *obj = PHPOBJC(object);
+	int retval = 0;
+
+NS_DURING
+	if ([obj->instance isKindOfClass:[NSArray class]]) {
+		convert_to_long(member);
+		val = [obj->instance objectAtIndex:Z_LVAL_P(member)];
+	} else if ([obj->instance isKindOfClass:[NSDictionary class]]) {
+		NSString *str;
+
+		convert_to_string(member);
+
+		str = [[NSString alloc] 
+			initWithUTF8String:Z_STRVAL_P(member)];
+
+		val = [obj->instance valueForKey:str];
+
+		[str dealloc];
+	} else {
+		val = std_object_handlers.has_dimension(object, member, chk_empty TSRMLS_CC);
+	}
+
+	if (val) {
+		retval = 1;
+	}
+
+NS_HANDLER
+	php_objc_throw_NS(localException TSRMLS_CC);
+NS_ENDHANDLER
+
+	return retval;
+}
+
+static void php_objc_dim_del(zval *object, zval *offset TSRMLS_DC)
+{
+	php_objc_obj *obj = PHPOBJC(object);
+
+NS_DURING
+	if ([obj->instance isKindOfClass:[NSMutableDictionary class]]) {
+		NSString *str;
+
+		convert_to_string(offset);
+
+		str = [[NSString alloc] 
+			initWithUTF8String:Z_STRVAL_P(offset)];
+
+		[obj->instance removeObjectForKey:str];
+
+		[str dealloc];
+	} else if ([obj->instance isKindOfClass:[NSMutableArray class]]) {
+		convert_to_long(offset);
+		[obj->instance replaceObjectAtIndex:Z_LVAL_P(offset) withObject:nil];
+	} else {
+		std_object_handlers.unset_dimension(object, offset TSRMLS_CC);
+	}
+
+NS_HANDLER
+	php_objc_throw_NS(localException TSRMLS_CC);
+NS_ENDHANDLER
+}
+
+static HashTable *php_objc_props_get(zval *object TSRMLS_DC)
+{
+	return std_object_handlers.get_properties(object TSRMLS_CC);
+}
+
+static void func_dtor(void *pDest)
+{
+	zend_internal_function *f = (zend_internal_function*)pDest;
+	efree(f->function_name);
+	if (f->arg_info) {
+		efree(f->arg_info);
+	}
+}
+
+static PHP_FUNCTION(objc_method_handler)
+{
+	if (!getThis()) {
+		php_objc_method_call_inner(
+			((zend_internal_function*)EG(function_state_ptr)->function)
+			->function_name, 0,
+			INTERNAL_FUNCTION_PARAM_PASSTHRU);
+		return;
+	}
+	Z_OBJ_HANDLER_P(getThis(), call_method)(
+		((zend_internal_function*)EG(function_state_ptr)->function)
+			->function_name,
+			INTERNAL_FUNCTION_PARAM_PASSTHRU);
+}
+
+static char *php_objc_SEL_to_string(SEL s TSRMLS_DC)
+{
+	char *buf = estrdup(sel_getName(s));
+	char *cur;
+
+	cur = strchr(buf, ':');
+	while (cur) {
+		*cur = '_';
+		cur = strchr(cur, ':');
+	}
+	return buf;
+}
+
+static SEL php_objc_method_name_to_SEL(const char *name, int len)
+{
+	char buf[1024];
+	char *cur;
+
+	if (len == -1) len = strlen(name);
+	if (len >= sizeof(buf)-1) return NULL;
+	/* we borrow from pyobjc in the way that we encode the selector
+	 * into the method name; we translate underscores into : characters.
+	 */
+	strcpy(buf, name);
+	cur = strchr(buf, '_');
+	while (cur) {
+		*cur = ':';
+		cur = strchr(cur, '_');
+	}
+	return sel_registerName(buf);
+}
+
+static union _zend_function *php_objc_method_get(zval **object_ptr,
+	char *name, int len TSRMLS_DC)
+{
+	zend_internal_function f, *fptr = NULL;
+	php_objc_obj *obj;
+	union _zend_function *func;
+	zval *object = *object_ptr;
+	SEL sel;
+	Method meth;
+	char *lname = zend_str_tolower_dup(name, len);
+
+	obj = PHPOBJC(object);
+
+	/* defined in the class entry? */
+	if (SUCCESS == zend_hash_find(&Z_OBJCE_PP(object_ptr)->function_table,
+			lname, len + 1, (void**)&fptr)) {
+		/* yay! */
+
+		efree(lname);
+		func = emalloc(sizeof(*func));
+		switch (fptr->type) {
+			case ZEND_INTERNAL_FUNCTION:
+				memcpy(func, fptr, sizeof(zend_internal_function));
+				break;
+			case ZEND_USER_FUNCTION:
+				memcpy(func, fptr, sizeof(zend_op_array));
+				break;
+			default:
+				abort();
+		}
+		return func;
+	}
+	efree(lname);
+
+	if (obj->cls->method_cache == NULL ||
+			FAILURE == zend_hash_find(obj->cls->method_cache, name,
+			len, (void**)&fptr))
+	{
+		int i;
+
+		f.fn_flags = 0;
+
+		sel = php_objc_method_name_to_SEL(name, len);
+		if (!sel) {
+			php_objc_throw("Couldn't make a selector for %s\n" TSRMLS_CC, name);
+			return NULL;
+		}
+
+		meth = class_getInstanceMethod(obj->cls->class_id, sel);
+		if (!meth) {
+			meth = class_getClassMethod(obj->cls->class_id, sel);
+			if (meth) {
+				f.fn_flags |= ZEND_ACC_STATIC;
+			}
+		}
+		if (!meth) {
+			return NULL;
+		}
+
+		f.type = ZEND_OVERLOADED_FUNCTION;
+		/* two implied args are self and the selector; ignore those
+		 * as we want the actual number of args */
+		f.num_args = method_getNumberOfArguments(meth) - 2;
+		f.arg_info = calloc(f.num_args, sizeof(zend_arg_info));
+		for (i = 0; i < f.num_args; i++) {
+			f.arg_info[i].allow_null = 1;
+			
+			{
+				const char *argtype;
+				int off;
+				method_getArgumentInfo(meth, i+2, &argtype, &off);
+
+				while (*argtype) {
+					if (*argtype == 'N' || *argtype == 'o' || *argtype == 'R') {
+						f.arg_info[i].pass_by_reference = 1;
+						break;
+					}
+					if (*argtype == 'r' || *argtype == 'n' || *argtype == 'O'
+							|| *argtype == 'V') {
+						argtype++;
+						continue;
+					}
+					/* must be the actual type code now */
+					break;
+				}
+				if (!f.arg_info[i].pass_by_reference &&
+						is_implied_ref_param(argtype)) {
+					argtype++;
+					f.arg_info[i].pass_by_reference = 1;
+				}
+			}
+		}
+		
+		f.scope = obj->zo.ce;
+		f.function_name = estrndup(name, len);
+		f.handler = PHP_FN(objc_method_handler);
+
+		fptr = &f;
+
+		if (fptr) {
+			if (!obj->cls->method_cache) {
+				ALLOC_HASHTABLE(obj->cls->method_cache);
+				zend_hash_init(obj->cls->method_cache, 2, NULL, func_dtor, 0);
+			}
+			zend_hash_update(obj->cls->method_cache, name, len, &f, sizeof(f),
+				(void**)&fptr);
+		}
+	}
+
+	if (fptr) {
+		/* duplicate this into a new chunk of emalloc'd memory,
+		 * since the engine will efree it */
+		func = emalloc(sizeof(*fptr));
+		memcpy(func, fptr, sizeof(*fptr));
+		return func;
+	}
+	return NULL;
+}
+			
+static void php_objc_wrap_id(id idval, zval *return_value)
+{
+	php_objc_obj *nobj;
+	php_objc_class *cls;
+
+	if ([[idval class] conformsToProtocol:@protocol(PHPHasZval)]) {
+		zval *zv = [idval _phpGetZval];
+
+		*return_value = *zv;
+		zend_objects_store_add_ref(zv);
+		return;
+	}
+
+	nobj = ecalloc(1, sizeof(*nobj));
+	cls = php_objc_import_class(idval->isa->name TSRMLS_CC);
+	
+	nobj->cls = cls;
+	nobj->zo.ce = cls->ce;
+	nobj->instance = idval;
+	[nobj->instance retain];
+
+	Z_TYPE_P(return_value) = IS_OBJECT;
+	return_value->value.obj.handle = zend_objects_store_put(nobj,
+		NULL, php_objc_free_stg, php_objc_clone TSRMLS_CC);
+	return_value->value.obj.handlers = &php_objc_handlers;
+}
+
+static PHP_FUNCTION(objc_static_invoke)
+{
+	char *method_name;
+	int method_name_len;
+	
+	if (zend_parse_parameters(1 TSRMLS_CC, "s|",
+			&method_name, &method_name_len) == FAILURE) {
+		php_objc_throw("expected method name as first parameter" TSRMLS_CC);
+		return;
+	}
+
+	php_objc_method_call_inner(method_name, 1, INTERNAL_FUNCTION_PARAM_PASSTHRU);
+}
+
+static PHP_FUNCTION(objc_alloc)
+{
+	php_objc_class *cls;
+	php_objc_obj *obj;
+	id inst;
+	
+	if (getThis()) {
+		php_objc_throw("can't call alloc on a live instance" TSRMLS_CC);
+		return;
+	}
+	if (ZEND_NUM_ARGS()) {
+		php_objc_throw("alloc takes 0 parameters" TSRMLS_CC);
+		return;
+	}
+
+	cls = php_objc_import_class(EG(scope)->name TSRMLS_CC);
+
+NS_DURING
+	inst = objc_msgSend(cls->class_id, @selector(alloc));
+	if (inst) {
+		obj = ecalloc(1, sizeof(*obj));
+		obj->zo.ce = cls->ce;
+		obj->instance = inst;
+		[obj->instance retain];
+		obj->cls = php_objc_import_class(inst->isa->name TSRMLS_CC);
+
+		Z_TYPE_P(return_value) = IS_OBJECT;
+		return_value->value.obj.handle = zend_objects_store_put(obj,
+			NULL, php_objc_free_stg, php_objc_clone TSRMLS_CC);
+		return_value->value.obj.handlers = &php_objc_handlers;
+	}
+NS_HANDLER
+	php_objc_throw_NS(localException TSRMLS_CC);
+	NS_VOIDRETURN;
+NS_ENDHANDLER
+
+}
+
+static int php_objc_method_call(char *method, INTERNAL_FUNCTION_PARAMETERS)
+{
+	return php_objc_method_call_inner(method, 0, INTERNAL_FUNCTION_PARAM_PASSTHRU);
+}
+
+static int php_objc_method_call_inner(char *method, int skip_arg_static, INTERNAL_FUNCTION_PARAMETERS)
+{
+	zval ***args = NULL;
+	int nargs, i;
+	php_objc_obj *obj;
+	php_objc_class *cls;
+	SEL sel;
+	Method meth = NULL;
+	const char *argtype;
+	int nargs_passed;
+	int first_passed;
+	int offset, is_ref;
+	HashTable *byref_hash = NULL;
+	ffi_cif cif;
+	ffi_type *rettype, **ffi_arg_types = NULL;
+	void **ffi_args = NULL;
+	struct objc_super sup, *supptr = NULL;
+	void *rvalue = NULL;
+
+	nargs_passed = ZEND_NUM_ARGS();
+	first_passed = 0;
+	sel = php_objc_method_name_to_SEL(method, -1);
+
+	if (getThis() == NULL) {
+		obj = NULL;
+		cls = php_objc_import_class(EG(scope)->name TSRMLS_CC);
+		if (skip_arg_static) {
+			nargs_passed = ZEND_NUM_ARGS() - 1;
+			first_passed = 1;
+		}
+	} else {
+		obj = PHPOBJC(getThis());
+		cls = obj->cls;
+	}
+
+	if (obj) {
+		meth = class_getInstanceMethod(obj->cls->class_id, sel);
+	}
+	if (!meth) {
+		meth = class_getClassMethod(cls->class_id, sel);
+	}
+	if (!meth) {
+		meth = class_getClassMethod(cls->class_id->isa, sel);
+	}
+	if (!meth) {
+		php_objc_throw("couldn't resolve method %s" TSRMLS_CC, method);
+		return FAILURE;
+	}
+	nargs = method_getNumberOfArguments(meth) - 2;
+
+	if (nargs > nargs_passed) {
+		php_objc_throw("%s expects %d args but %d were passed" TSRMLS_CC,
+			method, nargs, nargs_passed);
+		return FAILURE;
+	}
+	args = (zval ***)safe_emalloc(sizeof(zval *), ZEND_NUM_ARGS(), 0);
+	zend_get_parameters_array_ex(ZEND_NUM_ARGS(), args);
+
+	ffi_arg_types = calloc(2 + nargs_passed, sizeof(ffi_type*));
+	ffi_args = calloc(2 + nargs_passed, sizeof(void*));
+
+	if (obj && obj->from_objc && EG(active_op_array) && EG(active_op_array)->scope == cls->ce) {
+		sup.receiver = obj->instance;
+		sup.class = cls->class_id->super_class;
+		supptr = &sup;
+		ffi_args[0] = &supptr;
+	} else if (obj) {
+		ffi_args[0] = &obj->instance;
+	} else {
+		ffi_args[0] = &cls->class_id;
+	}
+	ffi_arg_types[0] = &ffi_type_pointer;
+	ffi_arg_types[1] = &ffi_type_pointer;
+	ffi_args[1] = &sel;
+
+NS_DURING
+//	printf("method type: %s(%s)\n", method, meth->method_types);
+
+	ALLOC_HASHTABLE(byref_hash);
+	zend_hash_init(byref_hash, 2, NULL, NULL, 0);
+	
+	for (i = 0; i < nargs; i++) {
+		is_ref = 0;
+
+		method_getArgumentInfo(meth, i+2, &argtype, &offset);
+		php_objc_type_to_ffi(argtype, &ffi_arg_types[i+2], 0);
+		
+		while (*argtype) {
+			if (*argtype == 'N' || *argtype == 'o' || *argtype == 'R') {
+				is_ref = 1;
+				argtype++;
+				continue;
+			}
+			if (*argtype == 'r' || *argtype == 'n' || *argtype == 'O'
+					|| *argtype == 'V') {
+				argtype++;
+				continue;
+			}
+			/* must be the actual type code now */
+			break;
+		}
+
+		if (!is_ref && is_implied_ref_param(argtype)) {
+			argtype++;
+			is_ref = 1;
+		}
+
+		switch (*argtype) {
+			case _C_CHARPTR:
+				if (is_ref) {
+					php_objc_throw(
+						"references to charptr are not currently supported"
+						TSRMLS_CC);
+					return FAILURE;
+				}
+
+				convert_to_string_ex(args[i + first_passed]);
+				ffi_args[2 + i] = &Z_STRVAL_PP(args[i + first_passed]);
+				break;
+			case _C_SEL: {
+				SEL s;
+				
+				if (is_ref) {
+					php_objc_throw(
+						"references to SEL are not currently supported"
+						TSRMLS_CC);
+					return FAILURE;
+				}
+
+				convert_to_string_ex(args[i + first_passed]);
+				s = php_objc_method_name_to_SEL(
+							Z_STRVAL_PP(args[i + first_passed]),
+							Z_STRLEN_PP(args[i + first_passed]));
+
+				zend_hash_index_update(byref_hash, i, &s, sizeof(s),
+					(void**)&ffi_args[2 + i]);
+				break;
+			}
+			case _C_CLASS: {
+				Class c;
+				if (is_ref) {
+					php_objc_throw(
+						"references to CLASS are not currently supported"
+						TSRMLS_CC);
+					return FAILURE;
+				}
+
+				convert_to_string_ex(args[i + first_passed]);
+				c = objc_getClass(Z_STRVAL_PP(args[i + first_passed]));
+				zend_hash_index_update(byref_hash, i, &c, sizeof(c),
+					(void**)&ffi_args[2 + i]);
+
+				break;
+			}
+			case _C_STRUCT_B: {
+				int size;
+
+				if (is_ref) {
+					php_objc_throw(
+						"references to structures are not currently supported"
+						TSRMLS_CC);
+					return FAILURE;
+				}
+
+				size = ffi_arg_types[2 + i]->size;
+
+				/* FIXME: evil hack! */
+				convert_to_string_ex(args[i + first_passed]);
+				if (Z_STRLEN_PP(args[i + first_passed]) > size) {
+					php_objc_throw("struct parameter too big! %d expected, got %d"
+						TSRMLS_CC, size, Z_STRLEN_PP(args[i + first_passed]));
+					return FAILURE;
+				}
+				if (Z_STRLEN_PP(args[i + first_passed]) != size) {
+					printf("struct parameter size mismatch: %d expected, got %d\n",
+						size, Z_STRLEN_PP(args[i + first_passed]));
+				}
+
+				ffi_args[2 + i] = Z_STRVAL_PP(args[i + first_passed]);
+				break;
+			}
+			case _C_INT:
+			case _C_UINT:
+			{
+				int ival;
+				if (is_ref) {
+					php_objc_throw(
+						"references to int are not currently supported"
+						TSRMLS_CC);
+					return FAILURE;
+				}
+
+				convert_to_long_ex(args[i + first_passed]);
+				ival = Z_LVAL_PP(args[i + first_passed]);
+				zend_hash_index_update(byref_hash, i, &ival, sizeof(ival),
+					(void**)&ffi_args[2 + i]);
+
+				break;
+			}
+			case _C_LNG:
+			case _C_ULNG:
+				if (is_ref) {
+					php_objc_throw(
+						"references to long are not currently supported"
+						TSRMLS_CC);
+					return FAILURE;
+				}
+
+				convert_to_long_ex(args[i + first_passed]);
+				ffi_args[2 + i] = &Z_LVAL_PP(args[i + first_passed]);
+				break;
+			case _C_SHT:
+			case _C_USHT:
+			{
+				short sval;
+				if (is_ref) {
+					php_objc_throw(
+						"references to short are not currently supported"
+						TSRMLS_CC);
+					return FAILURE;
+				}
+
+				convert_to_long_ex(args[i + first_passed]);
+				sval = Z_LVAL_PP(args[i + first_passed]);
+				zend_hash_index_update(byref_hash, i, &sval, sizeof(sval),
+					(void**)&ffi_args[2 + i]);
+				break;
+			}
+			case _C_DBL:
+				if (is_ref) {
+					php_objc_throw(
+						"references to double are not currently supported"
+						TSRMLS_CC);
+					return FAILURE;
+				}
+
+				convert_to_double_ex(args[i + first_passed]);
+				ffi_args[2 + i] = &Z_DVAL_PP(args[i + first_passed]);
+				break;
+			case _C_FLT:
+			{
+				float fval;
+				if (is_ref) {
+					php_objc_throw(
+						"references to float are not currently supported"
+						TSRMLS_CC);
+					return FAILURE;
+				}
+	
+				convert_to_double_ex(args[i + first_passed]);
+				fval = Z_DVAL_PP(args[i + first_passed]);
+				zend_hash_index_update(byref_hash, i, &fval, sizeof(fval),
+					(void**)&ffi_args[2 + i]);
+				break;
+			}
+
+			case _C_CHR:
+			case _C_UCHR:
+			{
+				char cval;
+				if (is_ref) {
+					php_objc_throw(
+						"references to char are not currently supported"
+						TSRMLS_CC);
+					return FAILURE;
+				}
+				cval = Z_LVAL_PP(args[i + first_passed]);
+				zend_hash_index_update(byref_hash, i, &cval, sizeof(cval),
+					(void**)&ffi_args[2 + i]);
+				break;
+			}
+
+			case _C_ID: {
+				id idval = 0;
+
+				if (!php_objc_mapping(*args[i + first_passed],
+						&idval, 1 TSRMLS_CC)) {
+					return FAILURE;
+				}
+
+				if (is_ref) {
+					id *idptr = NULL;
+					zend_hash_index_update(byref_hash, i, &idptr, sizeof(idptr),
+						(void**)&idptr);
+					*idptr = idval;
+					ffi_args[2 + i] = idptr;
+				} else {
+					zend_hash_index_update(byref_hash, i, &idval, sizeof(idval),
+						(void**)&ffi_args[2 + i]);
+				}
+
+				break;
+			}
+
+			case _C_PTR:
+				if (is_ref) {
+					php_objc_throw(
+						"references to ptr are not currently supported"
+						TSRMLS_CC);
+					return FAILURE;
+				}
+
+				if (Z_TYPE_PP(args[i + first_passed]) == IS_NULL) {
+					id idval = 0;
+					zend_hash_index_update(byref_hash, i, &idval, sizeof(idval),
+						(void**)&ffi_args[2 + i]);
+				} else {
+					php_objc_throw("can't handle non-null pointer type %s"
+						TSRMLS_CC, argtype);
+					return FAILURE;
+				}
+				break;
+
+			default:
+				php_objc_throw("unhandled input arg type %s (is_ref=%d)"
+					TSRMLS_CC, argtype, is_ref);
+				return FAILURE;
+		}
+	}
+
+	/* support variable args by presenting them as objects */
+	for (i = nargs; i < nargs_passed; i++) {
+		ffi_arg_types[i + 2] = &ffi_type_pointer;
+
+		if (Z_TYPE_PP(args[i + first_passed]) == IS_OBJECT) {
+			php_objc_class *cls;
+			php_objc_obj *obj;
+			zend_class_entry *ce = Z_OBJCE_PP(args[i + first_passed]);
+
+			if (zend_ts_hash_find(&php_objc_imported_classes, ce->name,
+						strlen(ce->name), (void**)&cls) == FAILURE) {
+				php_objc_throw("can't pass %s to Objective-C" TSRMLS_CC,
+						ce->name);
+				return FAILURE;
+			}
+
+			obj = PHPOBJC(*args[i + first_passed]);
+			ffi_args[i + 2] = &obj->instance;
+		} else if (Z_TYPE_PP(args[i + first_passed]) == IS_NULL) {
+			id idval = 0;
+			zend_hash_index_update(byref_hash, i, &idval, sizeof(idval),
+				(void**)&ffi_args[2 + i]);
+		} else if (Z_TYPE_PP(args[i + first_passed]) == IS_STRING) {
+			/* map PHP strings to NSString */
+			NSString *str = [[NSString alloc]
+					initWithUTF8String:Z_STRVAL_PP(args[i + first_passed])];
+			zend_hash_index_update(byref_hash, i, &str, sizeof(str),
+				(void**)&ffi_args[2 + i]);
+		} else {
+			php_objc_throw("expected object for %s" TSRMLS_CC, argtype);
+			return FAILURE;
+		}
+	}
+
+	php_objc_type_to_ffi(meth->method_types, &rettype, 1);
+	ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 2 + nargs_passed,
+		rettype, ffi_arg_types);
+
+	switch (rettype->type) {
+#ifdef __i386__
+		case FFI_TYPE_FLOAT:
+		{
+			float fval;
+			if (obj && obj->from_objc) {
+				php_objc_throw("floating point return types are not currently supported for parent:: calls" TSRMLS_CC);
+			}
+			ffi_call(&cif, FFI_FN(objc_msgSend_fpret), &fval, ffi_args);
+			RETVAL_DOUBLE(fval);
+			break;
+		}
+
+		case FFI_TYPE_DOUBLE:
+		{
+			if (obj && obj->from_objc) {
+				php_objc_throw("floating point return types are not currently supported for parent:: calls" TSRMLS_CC);
+			}
+			RETVAL_DOUBLE(0);
+			ffi_call(&cif, FFI_FN(objc_msgSend_fpret), &Z_DVAL_P(return_value), ffi_args);
+			break;
+		}
+#endif
+		case FFI_TYPE_STRUCT:
+			php_objc_throw("structure return types are not currently supported" TSRMLS_CC);
+			break;
+		default: {
+			union {
+				float fval;
+				int ival;
+				unsigned int uival;
+				char cval;
+				unsigned char ucval;
+				short sval;
+				unsigned short usval;
+				id idval;
+				SEL selval;
+				Class classval;
+			} u;
+
+			argtype = meth->method_types;
+			while (*argtype) {
+				if (*argtype == 'N' || *argtype == 'o' || *argtype == 'R' ||
+						*argtype == 'r' || *argtype == 'n' || *argtype == 'O'