| /*********************************************************** |
| Copyright 1991-1995 by Stichting Mathematisch Centrum, Amsterdam, |
| The Netherlands. |
| |
| All Rights Reserved |
| |
| Permission to use, copy, modify, and distribute this software and its |
| documentation for any purpose and without fee is hereby granted, |
| provided that the above copyright notice appear in all copies and that |
| both that copyright notice and this permission notice appear in |
| supporting documentation, and that the names of Stichting Mathematisch |
| Centrum or CWI not be used in advertising or publicity pertaining to |
| distribution of the software without specific, written prior permission. |
| |
| STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO |
| THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND |
| FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE |
| FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
| ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT |
| OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| |
| ******************************************************************/ |
| |
| /* Objective-C interface for NeXTStep */ |
| /* Tested with NeXTStep 3.3 on Intel and Sparc architectures */ |
| |
| /* Original author: Jon M. Kutemeier */ |
| /* Revamped and maintained by: Guido van Rossum */ |
| |
| /* XXX To do: |
| - bug??? x.send('name', []) gives weird error |
| - rename functions from objc_* to ObjC_* |
| - change send(sel, [a, b, c]) to send(self, a, b, c) |
| - call back to Python from Objective-C |
| */ |
| |
| /* Python header file */ |
| #include "Python.h" |
| |
| /* NeXT headers */ |
| #include <sys/param.h> |
| #include <mach-o/rld.h> |
| #include <objc/objc.h> |
| #include <objc/objc-runtime.h> |
| #import <remote/NXProxy.h> |
| |
| /* Distinguish between ObjC classes and instances */ |
| typedef enum { |
| OBJC_CLASS, |
| OBJC_INSTANCE, |
| } ObjC_Typecode; |
| |
| /* Exception raised for ObjC specific errors */ |
| static PyObject *ObjC_Error; |
| |
| /* Python wrapper about ObjC id (instance or class) */ |
| typedef struct { |
| PyObject_HEAD |
| id obj; |
| ObjC_Typecode type; |
| int owned; |
| } ObjCObject; |
| |
| /* Corresponding Python type object */ |
| staticforward PyTypeObject ObjC_Type; |
| |
| /* Corresponding Python type check macro */ |
| #define ObjC_Check(o) ((o)->ob_type == &ObjC_Type) |
| |
| /* Create a new ObjCObject */ |
| static ObjCObject * |
| newObjCObject(obj, type, owned) |
| id obj; |
| ObjC_Typecode type; |
| int owned; |
| { |
| ObjCObject *self; |
| |
| self = PyObject_NEW(ObjCObject, &ObjC_Type); |
| if (self == NULL) |
| return NULL; |
| |
| self->obj = obj; |
| self->type = type; |
| self->owned = owned; |
| |
| return self; |
| } |
| |
| static void |
| objc_sendfree(self) |
| ObjCObject *self; |
| { |
| if (self->obj) |
| self->obj = (id)objc_msgSend(self->obj, SELUID("free")); |
| } |
| |
| /* Deallocate an ObjCObject */ |
| static void |
| objc_dealloc(self) |
| ObjCObject *self; |
| { |
| if (self->owned) |
| objc_sendfree(self); |
| PyMem_DEL(self); |
| } |
| |
| /* Return a string representation of an ObjCObject */ |
| static PyObject * |
| objc_repr(self) |
| ObjCObject *self; |
| { |
| char buffer[512]; |
| char *p = buffer; |
| if (self->obj == nil) |
| p = "<Objective-C nil>"; |
| else { |
| char *t; |
| switch (self->type) { |
| case OBJC_CLASS: t = "class"; break; |
| case OBJC_INSTANCE: t = "instance"; break; |
| default: t = "???"; break; |
| } |
| sprintf(buffer, "<Objective-C %s %s at %lx>", |
| NAMEOF(self->obj), t, (long)(self->obj)); |
| } |
| return PyString_FromString(p); |
| } |
| |
| /*** ObjCObject methods ***/ |
| |
| /* Call an object's free method */ |
| static PyObject * |
| objc_free(self, args) |
| ObjCObject *self; |
| PyObject *args; |
| { |
| if (!PyArg_ParseTuple(args, "")) |
| return NULL; |
| objc_sendfree(self); |
| } |
| |
| /* Send a message to an ObjCObject. |
| The Python call looks like e.g. obj.send('moveTo::', [arg1, arg2]) |
| which translates into Objective-C as [obj moveTo: arg1 : arg2] */ |
| static PyObject * |
| objc_send(self, args) |
| ObjCObject *self; |
| PyObject *args; |
| { |
| char *methodname; |
| char *margBuff = NULL; |
| PyObject *retobject = NULL; |
| PyObject *arglist; |
| id receiver, obj; |
| char *type; |
| SEL sel; |
| Method meth; |
| unsigned int margCount, margSize; |
| int offset, i; |
| |
| if (!PyArg_ParseTuple(args, "sO!", &methodname, &PyList_Type, &arglist)) |
| return NULL; |
| |
| /* Get the method descriptor from the object */ |
| |
| receiver = self->obj; |
| sel = SELUID(methodname); |
| |
| switch(self->type) { |
| case OBJC_CLASS: |
| meth = class_getClassMethod(receiver->isa, sel); |
| break; |
| case OBJC_INSTANCE: |
| meth = class_getInstanceMethod(receiver->isa, sel); |
| break; |
| default: |
| PyErr_SetString(ObjC_Error, |
| "receiver's type is neither instance not class!?!?"); |
| return NULL; |
| } |
| |
| if (!meth) { |
| PyErr_SetString(ObjC_Error, "receiver has no method by that name"); |
| return NULL; |
| } |
| |
| /* Fill in the argument list, type-checking the arguments */ |
| |
| margCount = method_getNumberOfArguments(meth); |
| |
| if (PyList_Size(arglist) + 2 != margCount) { |
| PyErr_SetString(ObjC_Error, |
| "wrong number of arguments for this method"); |
| return NULL; |
| } |
| |
| margSize = method_getSizeOfArguments(meth); |
| margBuff = PyMem_NEW(char, margSize+1); |
| if (margBuff == NULL) |
| return PyErr_NoMemory(); |
| |
| method_getArgumentInfo(meth, 0, &type, &offset); |
| marg_setValue(margBuff, offset, id, receiver); |
| |
| method_getArgumentInfo(meth, 1, &type, &offset); |
| marg_setValue(margBuff, offset, SEL, sel); |
| |
| for (i = 2; i < margCount; i++) { |
| PyObject *argument; |
| method_getArgumentInfo(meth, i, &type, &offset); |
| |
| argument = PyList_GetItem(arglist, i-2); |
| |
| /* scan past protocol-type modifiers */ |
| while (strchr("rnNoOV", *type) != 0) |
| type++; |
| |
| /* common type checks */ |
| switch(*type) { |
| |
| /* XXX The errors here should point out which argument */ |
| |
| case 'c': |
| case '*': |
| case 'C': |
| if (!PyString_Check(argument)) { |
| PyErr_SetString(ObjC_Error, "string argument expected"); |
| goto error; |
| } |
| break; |
| |
| case 'i': |
| case 's': |
| case 'I': |
| case 'S': |
| case 'l': |
| case 'L': |
| case '^': |
| if (!PyInt_Check(argument)) { |
| PyErr_SetString(ObjC_Error, "integer argument expected"); |
| goto error; |
| } |
| break; |
| |
| case 'f': |
| case 'd': |
| if (!PyFloat_Check(argument)) { |
| PyErr_SetString(ObjC_Error, "float argument expected"); |
| goto error; |
| } |
| break; |
| |
| } |
| |
| /* convert and store the argument */ |
| switch (*type) { |
| |
| case 'c': /* char */ |
| marg_setValue(margBuff, offset, char, |
| PyString_AsString(argument)[0]); |
| break; |
| |
| case 'C': /* unsigned char */ |
| marg_setValue(margBuff, offset, unsigned char, |
| PyString_AsString(argument)[0]); |
| break; |
| |
| case '*': /* string */ |
| marg_setValue(margBuff, offset, char *, |
| PyString_AsString(argument)); |
| break; |
| |
| case 'i': /* int */ |
| marg_setValue(margBuff, offset, int, |
| PyInt_AsLong(argument)); |
| break; |
| |
| case 'I': /* unsigned int */ |
| marg_setValue(margBuff, offset, unsigned int, |
| PyInt_AsLong(argument)); |
| break; |
| |
| case 's': /* short */ |
| marg_setValue(margBuff, offset, short, |
| PyInt_AsLong(argument)); |
| break; |
| |
| case 'S': /* unsigned short */ |
| marg_setValue(margBuff, offset, unsigned short, |
| PyInt_AsLong(argument)); |
| break; |
| |
| case 'l': /* long */ |
| marg_setValue(margBuff, offset, long, |
| PyInt_AsLong(argument)); |
| break; |
| |
| case 'L': /* unsigned long */ |
| marg_setValue(margBuff, offset, unsigned long, |
| PyInt_AsLong(argument)); |
| break; |
| |
| case 'f': /* float */ |
| marg_setValue(margBuff, offset, float, |
| (float)PyFloat_AsDouble(argument)); |
| break; |
| |
| case 'd': /* double */ |
| marg_setValue(margBuff, offset, double, |
| PyFloat_AsDouble(argument)); |
| break; |
| |
| case '@': /* id (or None) */ |
| if (ObjC_Check(argument)) |
| marg_setValue(margBuff, offset, id, |
| ((ObjCObject *)(argument))->obj); |
| else if (argument == Py_None) |
| marg_setValue(margBuff, offset, id, nil); |
| else { |
| PyErr_SetString(ObjC_Error, "id or None argument expected"); |
| goto error; |
| } |
| break; |
| |
| case '^': /* void * (use int) */ |
| marg_setValue(margBuff, offset, void *, |
| (void *)PyInt_AsLong(argument)); |
| break; |
| |
| case ':': /* SEL (use string or int) */ |
| if (PyInt_Check(argument)) |
| marg_setValue(margBuff, offset, SEL, |
| (SEL)PyInt_AsLong(argument)); |
| else if (PyString_Check(argument)) |
| marg_setValue(margBuff, offset, SEL, |
| SELUID(PyString_AsString(argument))); |
| else { |
| PyErr_SetString(ObjC_Error, |
| "selector string or int argument expected"); |
| goto error; |
| } |
| break; |
| |
| case '#': /* Class (may also use int) */ |
| if (ObjC_Check(argument) && |
| ((ObjCObject *)argument)->type == OBJC_INSTANCE) |
| marg_setValue(margBuff, offset, Class *, |
| (Class *)((ObjCObject *)argument)->obj); |
| else if (PyInt_Check(argument)) |
| marg_setValue(margBuff, offset, Class *, |
| (Class *)PyInt_AsLong(argument)); |
| else { |
| PyErr_SetString(ObjC_Error, |
| "ObjC class object required"); |
| goto error; |
| } |
| break; |
| |
| default: |
| PyErr_SetString(ObjC_Error, "unknown argument type"); |
| goto error; |
| |
| } |
| } |
| |
| /* Call the method and set the return value */ |
| |
| type = meth->method_types; |
| |
| while (strchr("rnNoOV", *type)) |
| type++; |
| |
| switch(*type) { |
| |
| /* Cast objc_msgSendv to a function returning the right thing */ |
| #define MS_CAST(type) ((type (*)())objc_msgSendv) |
| |
| case 'c': |
| case '*': |
| case 'C': |
| retobject = (PyObject *)PyString_FromString( |
| MS_CAST(char *)(receiver, sel, margSize, margBuff)); |
| break; |
| |
| case 'i': |
| case 's': |
| case 'I': |
| case 'S': |
| retobject = (PyObject *)PyInt_FromLong( |
| MS_CAST(int)(receiver, sel, margSize, margBuff)); |
| break; |
| |
| case 'l': |
| case 'L': |
| case '^': |
| retobject = (PyObject *)PyInt_FromLong( |
| MS_CAST(long)(receiver, sel, margSize, margBuff)); |
| break; |
| |
| case 'f': |
| retobject = (PyObject *)PyFloat_FromDouble( |
| MS_CAST(float)(receiver, sel, margSize, margBuff)); |
| break; |
| |
| case 'd': |
| retobject = (PyObject *)PyFloat_FromDouble( |
| MS_CAST(double)(receiver, sel, margSize, margBuff)); |
| break; |
| |
| case '@': |
| obj = MS_CAST(id)(receiver, sel, margSize, margBuff); |
| if (obj == nil) { |
| retobject = Py_None; |
| Py_INCREF(retobject); |
| } |
| else if (obj != receiver) |
| retobject = (PyObject *)newObjCObject(obj, OBJC_INSTANCE, 0); |
| else { |
| retobject = (PyObject *)self; |
| Py_INCREF(retobject); |
| } |
| break; |
| |
| case ':': |
| retobject = (PyObject *)PyInt_FromLong( |
| (long)MS_CAST(SEL)(receiver, sel, margSize, margBuff)); |
| break; |
| |
| case '#': |
| retobject = (PyObject *)PyInt_FromLong( |
| (long)MS_CAST(Class *)(receiver, sel, margSize, margBuff)); |
| break; |
| |
| #undef MS_CAST |
| |
| } |
| |
| error: |
| PyMem_XDEL(margBuff); |
| return retobject; |
| } |
| |
| /* List of methods for ObjCObject */ |
| static PyMethodDef objc_methods[] = { |
| {"send", (PyCFunction)objc_send, 1}, |
| {"free", (PyCFunction)objc_free, 1}, |
| {NULL, NULL} /* sentinel */ |
| }; |
| |
| /* Get an attribute of an ObjCObject */ |
| static PyObject * |
| objc_getattr(self, name) |
| ObjCObject *self; |
| char *name; |
| { |
| PyObject *method; |
| |
| /* Try a function method */ |
| method = Py_FindMethod(objc_methods, (PyObject *)self, name); |
| if (method != NULL) |
| return method; |
| PyErr_Clear(); |
| |
| /* Try an instance variable */ |
| if (strcmp(name, "obj") == 0) |
| return PyInt_FromLong((long)self->obj); |
| if (strcmp(name, "type") == 0) |
| return PyInt_FromLong((long)self->type); |
| if (strcmp(name, "owned") == 0) |
| return PyInt_FromLong((long)self->owned); |
| if (strcmp(name, "name") == 0) |
| return PyString_FromString(NAMEOF(self->obj)); |
| if (strcmp(name, "__members__") == 0) |
| return Py_BuildValue("[sss]", "name", "obj", "owned", "type"); |
| |
| PyErr_SetString(PyExc_AttributeError, name); |
| return NULL; |
| } |
| |
| /* The type object */ |
| static PyTypeObject ObjC_Type = { |
| PyObject_HEAD_INIT(&PyType_Type) |
| 0, /*ob_size*/ |
| "objc", /*tp_name*/ |
| sizeof(ObjCObject), /*tp_basicsize*/ |
| 0, /*tp_itemsize*/ |
| /* methods */ |
| (destructor)objc_dealloc, /*tp_dealloc*/ |
| 0, /*tp_print*/ |
| (getattrfunc)objc_getattr, /*tp_getattr*/ |
| 0, /*tp_setattr*/ |
| 0, /*tp_compare*/ |
| (reprfunc)objc_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, 0, 0, 0, /*xxx1-4*/ |
| "Objective-C id wrapper", /*tp_doc*/ |
| }; |
| |
| |
| |
| /*** Top-level functions ***/ |
| |
| /* Max #files passed to loadobjectfile() */ |
| #define MAXRLD 128 |
| |
| /* Load a list of object files */ |
| static PyObject * |
| objc_loadobjectfiles(self, args) |
| PyObject *self; /* Not used */ |
| PyObject *args; |
| { |
| NXStream *errorStream; |
| struct mach_header *new_header; |
| const char *filenames[MAXRLD+1]; |
| long ret; |
| char *streamBuf; |
| PyObject *filelist, *file; |
| int listsize, len, maxLen, i; |
| |
| if (!PyArg_ParseTuple(args, "O!", &PyList_Type, &filelist)) |
| return NULL; |
| |
| listsize = PyList_Size(filelist); |
| |
| if (listsize > MAXRLD) { |
| PyErr_SetString(ObjC_Error, "more than 128 files in list"); |
| return NULL; |
| } |
| |
| errorStream = NXOpenMemory(NULL, 0, NX_WRITEONLY); |
| |
| for (i = 0; i < listsize; i++) { |
| file = PyList_GetItem(filelist, i); |
| |
| if (!PyString_Check(file)) |
| { |
| PyErr_SetString(ObjC_Error, |
| "all list items must be strings"); |
| return NULL; |
| } |
| |
| filenames[i] = PyString_AsString(file); |
| } |
| |
| filenames[listsize] = NULL; |
| |
| ret = objc_loadModules(filenames, errorStream, NULL, &new_header, NULL); |
| |
| /* extract the error messages for the exception */ |
| |
| if(ret) { |
| NXPutc(errorStream, (char)0); |
| |
| NXGetMemoryBuffer(errorStream, &streamBuf, &len, &maxLen); |
| PyErr_SetString(ObjC_Error, streamBuf); |
| } |
| |
| NXCloseMemory(errorStream, NX_FREEBUFFER); |
| |
| if(ret) |
| return NULL; |
| |
| Py_XINCREF(Py_None); |
| return Py_None; |
| } |
| |
| static PyObject * |
| objc_lookupclass(self, args) |
| PyObject *self; /* Not used */ |
| PyObject *args; |
| { |
| char *classname; |
| id class; |
| |
| if (!PyArg_ParseTuple(args, "s", &classname)) |
| return NULL; |
| |
| if (!(class = objc_lookUpClass(classname))) |
| { |
| PyErr_SetString(ObjC_Error, "unknown ObjC class"); |
| return NULL; |
| } |
| |
| return (PyObject *)newObjCObject(class, OBJC_CLASS, 0); |
| } |
| |
| /* List all classes */ |
| static PyObject * |
| objc_listclasses(self, args) |
| ObjCObject *self; |
| PyObject *args; |
| { |
| NXHashTable *class_hash = objc_getClasses(); |
| NXHashState state = NXInitHashState(class_hash); |
| Class classid; |
| PyObject *list; |
| |
| if (!PyArg_ParseTuple(args, "")) |
| return NULL; |
| |
| list = PyList_New(0); |
| if (list == NULL) |
| return NULL; |
| |
| while (NXNextHashState(class_hash, &state, (void**)&classid)) { |
| ObjCObject *item = newObjCObject(classid, OBJC_CLASS, 0); |
| if (item == NULL || PyList_Append(list, (PyObject *)item) < 0) { |
| Py_XDECREF(item); |
| Py_DECREF(list); |
| return NULL; |
| } |
| Py_INCREF(item); |
| } |
| |
| return list; |
| } |
| |
| /* List of top-level functions */ |
| static PyMethodDef objc_class_methods[] = { |
| {"loadobjectfiles", objc_loadobjectfiles, 1}, |
| {"lookupclass", objc_lookupclass, 1}, |
| {"listclasses", objc_listclasses, 1}, |
| {NULL, NULL} /* sentinel */ |
| }; |
| |
| /* Initialize for the module */ |
| void |
| initobjc() |
| { |
| PyObject *m, *d; |
| |
| m = Py_InitModule("objc", objc_class_methods); |
| d = PyModule_GetDict(m); |
| |
| ObjC_Error = PyString_FromString("objc.error"); |
| PyDict_SetItemString(d, "error", ObjC_Error); |
| |
| if (PyErr_Occurred()) |
| Py_FatalError("can't initialize module objc"); |
| |
| #ifdef WITH_THREAD |
| objc_setMultithreaded(1); |
| #endif |
| } |