使用PyRun_File()/ PyObject_CallFunction()时,如何将Objective C实例传递给PyObjC Python函数?

时间:2014-02-24 13:18:36

标签: python pyobjc

我使用PyObjC在Objective C应用程序中嵌入Python,在ObjC端手动设置Python环境(即不使用py2app)。我在一个单独的线程(在ObjC端)启动Python,调用PyRun_File(),然后调用PyObject_CallFunction()启动它做某事(特别是更新一些菜单,注册和处理菜单)回调)。 Python函数启动辅助线程的运行循环,并且回调类实例挂起。所有这一切都有效。我可以将基本数据(如字符串)传递给初始Python函数而不会出现问题,菜单操作也可以按照我的意愿运行。

我想为Python函数提供一个预先实例化的委托实例,以便于配置(从Objective C开发人员的角度来看)。如何将Objective C实例(我的委托)传递给Python函数?可能吗?我缺少桥梁功能吗?我需要手动创建一个适当配置的PyObject吗?我应该在Python端做什么样的转换以确保delegate.methods()可以从Python中使用并且代理可以正常工作?我应该注意哪些内存管理问题(桥的两边)?

我正在使用Python 2.7,PyObjC 2.5并以OSX 10.6为目标。如果解决方案特别要求,可以考虑更改其中任何一项。 TIA。

2 个答案:

答案 0 :(得分:2)

确保桥的无问题使用的最简单方法是确保您使用的委托方法不使用C数组作为参数或返回值,并且不使用可变参数签名(“...”)。此外,确保所有传递引用参数(例如常用的“NSError **”参数)都标有“in”,“out”或“inout”以指示传递的方向值。这可确保网桥可以从Objective-C运行时获取所需的所有信息。

将预构建对象传递给Python代码有两种选择:

  1. 创建一个返回对象的类方法

  2. 使用PyObjC API为Objective-C对象创建python代理。

  3. 后者使用内部PyObjC API(也被框架包装器使用),并可能在未来版本的PyObjC中中断。也就是说,我没有积极的计划来打破我在这里描述的解决方案。

    首先确保Objective-C编译器可以使用正确版本的“pyobjc-api.h”和“pyobjc-compat.h”。

    使用#include "pyobjc-api.h"使API可用。

    在初始化Python解释器之后调用“PyObjC_ImportAPI”,但在使用任何其他PyObjC函数之前。

    使用“pyValue = PyObjC_IdToPython(objcValue)”为Objective-C对象创建Python表示。

答案 1 :(得分:0)

好吧,我找到了一个的答案,虽然它让我觉得有些脆弱。注意事项适用于应用程序的打包(确保您可以在所有平台上访问正确的Python)以及使用ctypes。

This answer和PyObjC以及Python API文档一样有帮助。

在ObjC方面,例如:

#import "Delegate.h"
Delegate *delegate = [[Delegate alloc] init];

// ... Appropriate PyObjC initialisation, then
PyObject *mainModule = PyImport_AddModule("__main__");
PyObject *globals = PyModule_GetDict(mainModule);
PyRun_File(mainFile, (char *)[[mainFilePath lastPathComponent] UTF8String], Py_file_input, globals, globals);

// Get a reference to the delegate instance
uintptr_t delegate_pointer = (uintptr_t)delegate;

// Get something callable in the Python file...
PyObject *pFunc = PyObject_GetAttrString(mainModule, "myCallable");
if (pFunc && PyCallable_Check(pFunc)) {
    // ... and call it with an appropriately boxed reference to our delegate
    PyObject_CallFunctionObjArgs(pFunc, PyInt_FromLong(delegate_pointer), nil);
}

//   It's also possible to instantiate a Python class and call a method on it directly from ObjC:
//
//   PyObject *klass = PyObject_GetAttrString(mainModule, "PythonClass");
//   # Here I assume an NSObject subclass, hence alloc().init().  Pure Python instance instantiation will of course differ
//   PyObject *instance = PyObject_CallMethod(PyObject_CallMethod(klass, "alloc", nil), "init", nil);
//   PyObject_CallMethodObjArgs(mgr, PyString_FromString([@"myMethod" cStringUsingEncoding:NSUTF8StringEncoding]), PyInt_FromLong(delegate_pointer), nil);

在Python方面,myMethod()(或myCallable()):

def myCallable(delegateID):
    _objc = ctypes.PyDLL(objc._objc.__file__)
    _objc.PyObjCObject_New.restype = ctypes.py_object
    _objc.PyObjCObject_New.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_int]
    delegate = _objc.PyObjCObject_New(delegateID, 0, 1)

    # Call a method on the ObjectiveC delegate, let off fireworks, etc.
    delegate.aMethod()

希望能帮助别人。

关于是否有更好的替代方案,桥接API是否有办法在ObjC方面完成所有操作,以及内存,我仍然有一些突出的问题;如果有需要,我会问不同的问题。