I’m in the process of writing a lightweight interface in Objective-C that is capable of executing python scripts and passing data back and forth between Objective-C and Python. I’ve looked into PyObjC and ObjP and neither are what I’m looking for (and since I’m developing for iOS <= 6.0.1 PyObjC won’t compile do to the heavy use of NSMapTable’s).
So basically I created a Python Type in Objective-C called “ObjC_Class” (creative, no?) and I want this Python object to be nearly analogous to an ObjC object. So I decided to override the __getattr__ function of the class so I can access arbitrary methods and properties of the ObjC equivalent of that class.
Here is the code:
static PyObject * ObjC_Class_getattro(ObjC_Class *self, PyObject *name)
{
NSString *attrName = [NSString stringWithCString:PyString_AsString(name) encoding:NSUTF8StringEncoding];
NSLog(@"Calling Object: %@", self->object);
if([self->object respondsToSelector:NSSelectorFromString(attrName)])
{
methodName = attrName;
//PyObject* (*fpFunc)(PyObject*,PyObject*) = ObjC_Class_msg_send;
PyMethodDef methd = {[attrName UTF8String],ObjC_Class_msg_send,METH_VARARGS,[attrName UTF8String]};
PyObject* pyName = PyString_FromString(methd.ml_name);
PyObject* pyfoo = PyCFunction_NewEx(&methd,(PyObject*)self,pyName);
Py_DECREF(name);
return pyfoo;
}
else
{
return name;
}
}
Now, it works perfectly fine when I say:
Example #1
from ObjC import ObjC_Class
new = ObjC_Class('UIView')
new.backgroundColor("asdf", 42, "some_random_string") # This does not crash
But when I run:
Example #2
from ObjC import ObjC_Class
new = ObjC_Class('UIView')
moreStuff = "some_random_string" # or "42" or [1,2,3] or anything else...
new.backgroundColor("asdf", 42, moreStuff) # !!! This does crash
It crashes saying:
error: address doesn't contain a section that points to a section in a object file
I’ve seen this error when I try calling nonexistent functions but I can’t imagine why the first would work but the second wouldn’t.
Here is the implementation of the ObjC_Class_msg_send function:
static PyObject* ObjC_Class_msg_send(PyObject *self, PyObject *args)
{
NSLog(@"Entering...");
NSMutableArray *tmp = [[NSMutableArray alloc] init];
for(int i=0;i<PyTuple_Size(args);i++)
{
[tmp addObject:Py_to_ObjC(PyTuple_GetItem(args, i))];
}
NSLog(@"Object: %@, Method Name: %@, args: %@", ((ObjC_Class*)self)->object, methodName, tmp);
methodName = @"";
return PyString_FromString("Did it actually work!?!?!");
}
When I run the example that passes a python variable into the function it crashes before ObjC_Class_msg_send is even called (but after ObjC_Class_getattro returns its value).
(Oh and please excuse the sloppy code… I’m working on getting a simple proof-of-concept running before allocating too much time for this project)
Something I failed to mention: My ObjC_Class has an element named ‘object’ which is of type ‘id’ which is what stores the reference to the Objective-C object that the Python object is representing…
One more side note: I’m linking against Python 2.6(.3?) which has been mostly statically-linked
UPDATE
I’ve managed to get it to stop crashing by defining fpFunc as static removing fpFunc altogether… I’m not even sure why I had it in there (stupid copy+paste…):
static PyObject * ObjC_Class_getattro(ObjC_Class *self, PyObject *name)
{
NSString *attrName = [NSString stringWithCString:PyString_AsString(name) encoding:NSUTF8StringEncoding];
NSLog(@"Calling Object: %@", self->object);
if([self->object respondsToSelector:NSSelectorFromString(attrName)])
{
methodName = attrName;
//static PyObject* (*fpFunc)(PyObject*,PyObject*) = ObjC_Class_msg_send; // static now
PyMethodDef methd = {[attrName UTF8String],ObjC_Class_msg_send,METH_VARARGS,[attrName UTF8String]};
PyObject* pyName = PyString_FromString(methd.ml_name);
PyObject* pyfoo = PyCFunction_NewEx(&methd,(PyObject*)self,pyName);
Py_DECREF(name);
return pyfoo;
}
else
{
return name;
}
}
But… Now Python is throwing the error (when I pass a python variable in as an argument, i.e.: Example #2 above):
SystemError: Objects/methodobject.c:120: bad argument to internal function
🙁 I’ve never seen this one before…
Well this is embarrassing… I found the issue (in case anybody else is having the same problem(s)). I traced the error through the python source and noticed that the error was being thrown here:
So I thought, “Maybe my PyMethodDef methd is out of scope (or something) by the time Python takes a look at it” (idk if Python does different processing on functions that contain arguments that are variables than on functions that have const arguments which cause the former to be delayed somewhere up the pipeline…).
So I pulled methd out of the function and declared it as static (this sounds familiar…) and voila! It works beautifully. Here is the updated code:
Now, if you’ll excuse me, I’m going to go study ‘static’.