在Objective-C中调用Python-C API封装时,传入Python对象导致__getattr__崩溃

5 投票
1 回答
895 浏览
提问于 2025-04-17 14:05

我正在用Objective-C写一个轻量级的接口,这个接口可以执行Python脚本,并且能在Objective-C和Python之间传递数据。我查过PyObjC和ObjP,但都不符合我的需求(而且因为我是在为iOS 6.0.1及以下版本开发,PyObjC无法编译,因为它大量使用了NSMapTable)。

所以我基本上在Objective-C中创建了一个叫“ObjC_Class”的Python类型(很有创意吧?),我希望这个Python对象能和ObjC对象几乎一样。因此,我决定重写这个类的__getattr__函数,这样我就可以访问ObjC类的任意方法和属性。

这是代码:

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;
    }
}

现在,当我这样调用时,它工作得很好:

示例 #1

from ObjC import ObjC_Class
new = ObjC_Class('UIView')
new.backgroundColor("asdf", 42, "some_random_string") # This does not crash

但是当我运行:

示例 #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

它崩溃了,提示:

error: address doesn't contain a section that points to a section in a object file

我见过这个错误,通常是因为调用了不存在的函数,但我想不明白为什么第一个能工作而第二个却不行。

这是ObjC_Class_msg_send函数的实现:

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!?!?!");
}

当我运行那个将Python变量传入函数的示例时,它在ObjC_Class_msg_send被调用之前就崩溃了(但在ObjC_Class_getattro返回值之后)。

(哦,请原谅我代码写得乱... 我正在努力让一个简单的概念验证能运行,之后再花更多时间在这个项目上)

我还没提到的一点是:我的ObjC_Class有一个名为'object'的元素,它的类型是'id',用来存储Python对象所代表的Objective-C对象的引用...

还有一点:我链接的是Python 2.6(可能是2.6.3),大部分是静态链接的。

更新 我通过将fpFunc定义为静态完全移除fpFunc,让它不再崩溃... 我甚至不太确定当初为什么要加这个(愚蠢的复制粘贴...):

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;
    }
}

但是... 现在Python抛出了错误(当我将Python变量作为参数传入时,也就是上面的示例 #2):

SystemError: Objects/methodobject.c:120: bad argument to internal function

:( 我从来没见过这个错误...

1 个回答

6

这真是尴尬……我找到了问题所在(如果有人遇到同样的问题,可以参考一下)。我通过查看Python的源代码追踪到了错误,发现错误是在这里抛出的:

PyObject *
PyCFunction_Call(PyObject *func, PyObject *arg, PyObject *kw)
{
    PyCFunctionObject* f = (PyCFunctionObject*)func;
    PyCFunction meth = PyCFunction_GET_FUNCTION(func);
    PyObject *self = PyCFunction_GET_SELF(func);
    Py_ssize_t size;

    switch (PyCFunction_GET_FLAGS(func) & ~(METH_CLASS | METH_STATIC | METH_COEXIST)) {
    case METH_VARARGS:
        if (kw == NULL || PyDict_Size(kw) == 0)
            return (*meth)(self, arg);
        break;
    case METH_VARARGS | METH_KEYWORDS:
    case METH_OLDARGS | METH_KEYWORDS:
        return (*(PyCFunctionWithKeywords)meth)(self, arg, kw);
    case METH_NOARGS:
        if (kw == NULL || PyDict_Size(kw) == 0) {
            size = PyTuple_GET_SIZE(arg);
            if (size == 0)
                return (*meth)(self, NULL);
            PyErr_Format(PyExc_TypeError,
                "%.200s() takes no arguments (%zd given)",
                f->m_ml->ml_name, size);
            return NULL;
        }
        break;
    case METH_O:
        if (kw == NULL || PyDict_Size(kw) == 0) {
            size = PyTuple_GET_SIZE(arg);
            if (size == 1)
                return (*meth)(self, PyTuple_GET_ITEM(arg, 0));
            PyErr_Format(PyExc_TypeError,
                "%.200s() takes exactly one argument (%zd given)",
                f->m_ml->ml_name, size);
            return NULL;
        }
        break;
    case METH_OLDARGS:
        /* the really old style */
        if (kw == NULL || PyDict_Size(kw) == 0) {
            size = PyTuple_GET_SIZE(arg);
            if (size == 1)
                arg = PyTuple_GET_ITEM(arg, 0);
            else if (size == 0)
                arg = NULL;
            return (*meth)(self, arg);
        }
        break;
    default:
        PyErr_BadInternalCall();
        return NULL;
    }
    PyErr_Format(PyExc_TypeError, "%.200s() takes no keyword arguments",
             f->m_ml->ml_name);
    return NULL;
}

于是我想,“也许我的PyMethodDef methd在Python查看它的时候已经超出了作用域(或者其他什么原因)” (我不知道Python对带有变量参数的函数和带有常量参数的函数是否有不同的处理方式,这可能导致前者在某个地方被延迟处理……)。

所以我把methd从函数中提取出来,并把它声明为静态(这听起来有点熟悉……),结果!它运行得非常好。这里是更新后的代码:

static PyMethodDef methd = {"blah",ObjC_Class_msg_send,METH_VARARGS,"blech"};

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* pyName = PyString_FromString(methd.ml_name);
        PyObject* pyfoo = PyCFunction_NewEx(&methd,(PyObject*)self,pyName);
        Py_DECREF(name);

        return pyfoo;
    }
    else
    {
        return name;
    }
}

现在,请原谅我,我要去学习一下‘静态’。

撰写回答