Commits

Ronald Oussoren committed 7bdd386

'in' and 'inout' arguments can now by objc.NULL. This value tells the bridge
to pass a NULL pointer to the objc method, instead of a pointer to a value.

TODO: unittests for the objects return by objc.loadBundleFunctions, these
should inherit the same functionality.

Comments (0)

Files changed (8)

Lib/objc/test/test_NULL.py

+import unittest
+import objc
+
+setSignature = objc.setSignatureForSelector
+setSignature("OCTestNULL", "callOut:", "i@:o^i")
+setSignature("OCTestNULL", "callList:andInOut2:", "i@:@^i")
+setSignature("OCTestNULL", "callList:andInOut:", "i@:@N^i")
+setSignature("OCTestNULL", "callList:andIn:", "i@:@n^i")
+setSignature("OCTestNULL", "callList:andOut:", "i@:@o^i")
+setSignature("OCTestNULL", "on:callList:andInOut:", "i@:@@N^i")
+setSignature("OCTestNULL", "on:callList:andIn:", "i@:@@n^i")
+setSignature("OCTestNULL", "on:callList:andOut:", "i@:@@N^i") # 'N' is by design
+setSignature("OCTestNULL", "on:callOut:", "v@:@N^i") # 'N' is by design
+
+from objc.test.NULL import *
+
+class TestNULL (unittest.TestCase):
+    def testNULL(self):
+        self.assert_(hasattr(objc, 'NULL'))
+        self.assertEquals(repr(objc.NULL), 'objc.NULL')
+        self.assertRaises(TypeError, type(objc.NULL))
+
+
+class TestNullArgumentsHelper (objc.lookUpClass("NSObject")):
+
+    def callList_andInOut_(self, lst, value):
+        lst.append(repr(value))
+        if value is objc.NULL:
+            return (13, value)
+        else:
+            return (13, value * 2)
+
+    callList_andInOut_ = objc.selector(callList_andInOut_,
+            signature="i@:@N^i")
+
+    def callList_andInOut2_(self, lst, value):
+        lst.append(repr(value))
+        if value is objc.NULL:
+            return 29
+        else:
+            return 29
+    callList_andInOut2_ = objc.selector(callList_andInOut2_,
+            signature="i@:@^i")
+
+    def callList_andIn_(self, lst, value):
+        lst.append(repr(value))
+        return 26
+
+    callList_andIn_ = objc.selector(callList_andIn_,
+            signature="i@:@n^i")
+
+    def callList_andOut_(self, lst):
+        lst.append("Nothing here")
+        return (27, 99)
+    callList_andOut_ = objc.selector(callList_andOut_,
+            signature="i@:@o^i")
+
+    def callOut_(self):
+        return 441;
+    callOut_ = objc.selector(callOut_, signature='v@:o^i')    
+
+class TestNULLArguments (unittest.TestCase):
+    def testCallInOutNULL(self):
+        obj = OCTestNULL.alloc().init()
+
+        v = []
+        rv = obj.callList_andInOut_(v, 42)
+        self.assertEquals(v, ["42"])
+        self.assertEquals(rv, (12, 21))
+
+        v = []
+        rv = obj.callList_andInOut_(v, objc.NULL)
+        self.assertEquals(v, ["NULL"])
+        self.assertEquals(rv,  (12, objc.NULL))
+
+    def testCallInOutNULL2(self):
+        # If nothing is specified the bridge assumes the argument behaves
+        # like an 'in' argument.
+
+        obj = OCTestNULL.alloc().init()
+
+        v = []
+        self.assertRaises(ValueError, obj.callList_andInOut2_, v, 42)
+        self.assertEquals(v, [])
+
+        v = []
+        rv = obj.callList_andInOut2_(v, objc.NULL)
+        self.assertEquals(v, ["NULL"])
+        self.assertEquals(rv,  12)
+
+    def testCallInNULL(self):
+        obj = OCTestNULL.alloc().init()
+
+        v = []
+        rv = obj.callList_andIn_(v, 42)
+        self.assertEquals(v, ["42"])
+        self.assertEquals(rv, 24)
+
+        v = []
+        rv = obj.callList_andIn_(v, objc.NULL)
+        self.assertEquals(v, ["NULL"])
+        self.assertEquals(rv,  24)
+
+
+    def testCalledInOutNULL(self):
+        helper = OCTestNULL.alloc().init()
+        obj = TestNullArgumentsHelper.alloc().init()
+
+        v = []
+        rv = helper.on_callList_andInOut_(obj, v, 42)
+        self.assertEquals(v, ['42'])
+        self.assertEquals(rv, (13, 84))
+
+        v = []
+        rv = helper.on_callList_andInOut_(obj, v, objc.NULL)
+        self.assertEquals(v, ['objc.NULL'])
+        self.assertEquals(rv, (13, objc.NULL))
+
+    def testCalledInNULL(self):
+        helper = OCTestNULL.alloc().init()
+        obj = TestNullArgumentsHelper.alloc().init()
+
+        v = []
+        rv = helper.on_callList_andIn_(obj, v, 42)
+        self.assertEquals(v, ['42'])
+        self.assertEquals(rv, 26)
+
+        v = []
+        rv = helper.on_callList_andIn_(obj, v, objc.NULL)
+        self.assertEquals(v, ['objc.NULL'])
+        self.assertEquals(rv, 26)
+
+    def testCalledOutNULL(self):
+
+        helper = OCTestNULL.alloc().init()
+        obj = TestNullArgumentsHelper.alloc().init()
+
+        v = []
+        rv = helper.on_callList_andOut_(obj, v, 42)
+        self.assertEquals(v, ['Nothing here'])
+        self.assertEquals(rv, (27, 99))
+
+        v = []
+        rv = helper.on_callList_andOut_(obj, v, objc.NULL)
+        self.assertEquals(v, ['Nothing here'])
+        self.assertEquals(rv, (27, objc.NULL))
+
+        rv = helper.on_callOut_(obj, 42)
+        self.assertEquals(rv, 441)
+
+        rv = helper.on_callOut_(obj, objc.NULL)
+        self.assertEquals(rv, objc.NULL)
+
+    def dont_testCalledOutNULL(self):
+        """
+        XXX: I'm not happy about these semantics!
+
+        Current semantics: called method doesn't know about the NULL argument,
+        the result from Python is ignored.
+
+        New semantics:
+        - If the last argument is 'out' use new semantics, otherwise keep
+          current semantics
+        - If function has an optional last param stuf this with objc.NULL if
+          the argument is NULL, otherwise don't provide
+        - If the functioin has a required last param: stuff with objc.NULL or
+          None
+        """
+
+
+    def dont_testCallOutNULL(self):
+        """
+        Call a method with an 'out' argument with an additional method
+
+        - if not objc.NULL: raise TypeError
+        - argument should be NULL in objC
+        - result should be objc.NULL
+        """
+
+        
+
+
+if __name__ == "__main__":
+    unittest.main()

Modules/objc/libffi_support.m

 			/* FALL THROUGH */
 		case _C_IN: case _C_CONST:
 			if (argtype[1] == _C_PTR) {
-				v = pythonify_c_value(argtype+2, 
+				if (*(void**)args[i+argOffset]) {
+					v = pythonify_c_value(argtype+2, 
 						*(void**)args[i+argOffset]);
+				} else {
+					v = PyObjC_NULL;
+					Py_INCREF(v);
+				}
 			} else {
 				v = pythonify_c_value(argtype+1, 
 						args[i+argOffset]);
 	void* arg;
 
 	for (i = argOffset; i < methinfo->nargs; i++) {
+
 		int error;
 		PyObject *argument;
 		const char *argtype = methinfo->argtype[i];
 
 				if (argtype[1] == _C_PTR) {
 					/* Allocate space and encode */
-					argbuf_cur = align(argbuf_cur, PyObjCRT_AlignOfType(argtype+2)); 
-					arg = argbuf + argbuf_cur;
-					argbuf_cur += PyObjCRT_SizeOfType(argtype+2);
-					byref[i] = arg;
-	  				error = depythonify_c_value (
-						argtype+2, 
-						argument, 
-						arg);
+
+					if (argument == PyObjC_NULL) {
+						byref[i] = NULL;
+						error = 0;
+
+					} else {
+						argbuf_cur = align(argbuf_cur, PyObjCRT_AlignOfType(argtype+2)); 
+						arg = argbuf + argbuf_cur;
+						argbuf_cur += PyObjCRT_SizeOfType(argtype+2);
+						byref[i] = arg;
+						error = depythonify_c_value (
+							argtype+2, 
+							argument, 
+							arg);
+					}
 
 					arglist[i] = &ffi_type_pointer;
 					values[i] = byref + i;
 			case _C_OUT:
 				if (argtype[1] == _C_PTR) {
 					arg = byref[i];
-					v = pythonify_c_value(argtype+2, arg);
+
+					if (arg == NULL) {
+						v = PyObjC_NULL;
+						Py_INCREF(v);
+					} else {
+						v = pythonify_c_value(argtype+2, arg);
+					}
 					if (!v) goto error_cleanup;
 
 					if (result != NULL) {

Modules/objc/module.m

 void 
 init_objc(void)
 {
-	PyObject *m, *d;
+	PyObject *m, *d, *v;
 
 	NSAutoreleasePool *initReleasePool = [[NSAutoreleasePool alloc] init];
 	[OC_NSBundleHack installBundleHack];
 
 	d = PyModule_GetDict(m);
 	/* use PyDict_SetItemString for the retain, non-heap types can't be dealloc'ed */
+
 	PyDict_SetItemString(d, "objc_class", (PyObject*)&PyObjCClass_Type);
 	PyDict_SetItemString(d, "objc_object", (PyObject*)&PyObjCObject_Type);
 	PyDict_SetItemString(d, "pyobjc_unicode", (PyObject*)&PyObjCUnicode_Type);
 	PyDict_SetItemString(d, "function", (PyObject*)&PyObjCFunc_Type);
 	PyDict_SetItemString(d, "IMP", (PyObject*)&PyObjCIMP_Type);
 
+	v = PyObjCInitNULL();
+	if (v == NULL) return;
+
+	if (PyDict_SetItemString(d, "NULL", v) < 0) {
+		Py_DECREF(v);
+		return;
+	}
+	Py_DECREF(v);
+
 	if (PyObjCUtil_Init(m) < 0) return;
 	if (PyObjCAPI_Register(m) < 0) return;
 	if (PyObjCIMP_SetUpMethodWrappers() < 0) return;

Modules/objc/objc-NULL.m

+/*
+ * Implementation for objc.NULL
+ */
+#include "pyobjc.h"
+
+PyObject* PyObjC_NULL = NULL;
+
+static PyObject*
+obj_repr(PyObject* self __attribute__((__unused__)))
+{
+	return PyString_FromString("objc.NULL");
+}
+
+PyTypeObject PyObjC_NULL_Type = {
+	PyObject_HEAD_INIT(&PyType_Type)
+	0,					/* ob_size */
+	"objc.NULL_type",			/* tp_name */
+	sizeof(PyObject),			/* tp_basicsize */
+	0,					/* tp_itemsize */
+	/* methods */
+	0,	 				/* tp_dealloc */
+	0,					/* tp_print */
+	0,					/* tp_getattr */
+	0,					/* tp_setattr */
+	0,					/* tp_compare */
+	(reprfunc)obj_repr,			/* tp_repr */
+	0,					/* tp_as_number */
+	0,					/* tp_as_sequence */
+	0,		       			/* tp_as_mapping */
+	0,					/* tp_hash */
+	0,					/* tp_call */
+	0,					/* tp_str */
+	0,					/* tp_getattro */
+	0,					/* tp_setattro */
+	0,					/* tp_as_buffer */
+	Py_TPFLAGS_DEFAULT,			/* tp_flags */
+ 	0,					/* tp_doc */
+ 	0,					/* tp_traverse */
+ 	0,					/* tp_clear */
+	0,					/* tp_richcompare */
+	0,					/* tp_weaklistoffset */
+	0,					/* tp_iter */
+	0,					/* tp_iternext */
+	0,					/* tp_methods */
+	0,					/* tp_members */
+	0,					/* tp_getset */
+	0,					/* tp_base */
+	0,					/* tp_dict */
+	0,					/* tp_descr_get */
+	0,					/* tp_descr_set */
+	0,					/* tp_dictoffset */
+	0,					/* tp_init */
+	0,					/* tp_alloc */
+	0,					/* tp_new */
+	0,		        		/* tp_free */
+	0,					/* tp_is_gc */
+	0,					/* tp_bases */
+	0,					/* tp_mro */
+	0,					/* tp_cache */
+	0, 					/* tp_subclasses */
+	0,					/* tp_weaklist */
+	0					/* tp_del */
+};
+
+PyObject* PyObjCInitNULL(void)
+{
+	PyObject* result;
+
+	result = PyObjC_NULL = PyObject_New(PyObject, &PyObjC_NULL_Type);
+	Py_XINCREF(PyObjC_NULL);
+
+	return result;
+}

Modules/objc/pointer-support.m

 	struct wrapper* item;
 	int r;
 
+	if (value == PyObjC_NULL) {
+		*(void**)datum = NULL;
+		return 0;
+	}
+
 	item = FindWrapper(type);
 	if (item == NULL) {
 		return -1;

Modules/objc/pyobjc.h

 PyObject* PyObjCCreateOpaquePointerType(const char* name, 
 		const char* typestr, const char* docstr);
 
+/* objc-NULL.m */
+extern PyObject* PyObjC_NULL;
+extern PyObject* PyObjCInitNULL(void);
+
 #endif
 
 #define PyObjCErr_InternalError() \

Modules/objc/test/NULL.m

+#include <Python.h>
+#include "pyobjc-api.h"
+
+#import <Foundation/Foundation.h>
+
+@interface OCTestNULL : NSObject {}
+-(int)callList:(NSMutableArray*)list andInOut:(int*)pval;
+-(int)callList:(NSMutableArray*)list andInOut2:(int*)pval;
+-(int)callList:(NSMutableArray*)list andIn:(int*)pval;
+-(int)callList:(NSMutableArray*)list andOut:(int*)pval;
+-(void)callOut:(int*)pval;
+@end
+
+@implementation OCTestNULL
+
+-(int)callList:(NSMutableArray*)list andInOut:(int*)pval
+{
+	if (pval == NULL) {
+		[list addObject: @"NULL"];
+	} else {
+		[list addObject: [NSString stringWithFormat:@"%d", *pval]];
+		*pval = *pval / 2;
+	}
+	return 12;
+}
+
+/* This is the same implementation as callList:andInOut:, the python code
+ * uses a different type string for this one.
+ */
+-(int)callList:(NSMutableArray*)list andInOut2:(int*)pval
+{
+	if (pval == NULL) {
+		[list addObject: @"NULL"];
+	} else {
+		[list addObject: [NSString stringWithFormat:@"%d", *pval]];
+		*pval = *pval / 2;
+	}
+	return 12;
+}
+
+-(int)callList:(NSMutableArray*)list andIn:(int*)pval
+{
+	if (pval == NULL) {
+		[list addObject: @"NULL"];
+	} else {
+		[list addObject: [NSString stringWithFormat:@"%d", *pval]];
+	}
+	return 24;
+}
+
+-(int)callList:(NSMutableArray*)list andOut:(int*)pval
+{
+	if (pval == NULL) {
+		[list addObject: @"NULL"];
+	} else {
+		[list addObject: @"POINTER"];
+		*pval = 99;
+	}
+
+	return 24;
+}
+
+-(int)on:(OCTestNULL*)object callList:(NSMutableArray*)list andInOut:(int*)pval
+{
+	return [object callList:list andInOut:pval];
+}
+
+-(int)on:(OCTestNULL*)object callList:(NSMutableArray*)list andIn:(int*)pval
+{
+	return [object callList:list andIn:pval];
+}
+
+-(int)on:(OCTestNULL*)object callList:(NSMutableArray*)list andOut:(int*)pval
+{
+	return [object callList:list andOut:pval];
+}
+
+-(void)callOut:(int*)pval
+{
+	*pval = 144;
+}
+
+-(void)on:(OCTestNULL*)object callOut:(int*)pval
+{
+	[object callOut:pval];
+}
+
+@end
+
+
+static PyMethodDef NULL_methods[] = {
+	        { 0, 0, 0, 0 }
+};
+
+void initNULL(void);
+void initNULL(void)
+{
+	PyObject* m;
+
+	m = Py_InitModule4("NULL", NULL_methods,
+		NULL, NULL, PYTHON_API_VERSION);
+
+	if (PyObjC_ImportAPI(m) < 0) return;
+
+	PyModule_AddObject(m, "OCTestNULL",
+	    PyObjCClass_New([OCTestNULL class]));
+}
 Version 1.3.1 (2005-04-??)
 --------------------------
 
+- You can now pass the special value ``objc.NULL`` as the value
+  for 'in' and 'inout' arguments. This tells the bridge to pass
+  a NULL pointer to the Objective-C method, instead of a pointer
+  to the value.
+
 - First stab at wrapping some Tiger frameworks:
 
   - ``AppleScriptKit``