PyObjC and returning 'out' parameters (i.e. NSError **)
Asked Answered
D

2

4

I'm implementing an ObjC protocol as a mix-in for a PyObjC class.

This protocol defines an 'out' parameter.

I am unable to find any good documentation on how a Python class which implements an ObjC protocol defining this is to behave.

I've found this mailing list thread but the suggestion in there does not work. They say to return a Python list with the method's return value as the first item and the out parameter as the second.

I've tried this and all I get is an exception when calling from ObjC (<type 'exceptions.ValueError'>: depythonifying 'char', got 'tuple').

It seems PyObjC strictly adheres to the ObjC protocol in depythonifying method arguments, which is nice but it doesn't help me trying to modify an out parameter.

This is the ObjC protocol:

#import <Foundation/Foundation.h>

@protocol TestProtocol <NSObject>

- (BOOL)testMethod:(NSError **)error;

@end

This is the Python class implementing this protocol:

from Foundation import *
import objc

NSObject = objc.lookUpClass("NSObject")
TestProtocol = objc.protocolNamed("TestProtocol")

class TestClass(NSObject, TestProtocol):

    def testMethod_(self, error):
        return True, None

QUESTION: How do I return an ObjC out parameter in Python?

Delanadelancey answered 4/12, 2010 at 12:6 Comment(0)
G
5

This question's old, so I don't know if you've sorted it out by now, but you haven't answered it, and I was fussing with getting out parameters from Obj-C to Python a short while ago. There are a few things that you should definitely try/look into:

First and simplest is: when you make that stub in Objective-C with the implementation in Python, you've got to specify that the (NSError **) is an out parameter:

@interface MyObject : NSObject
- (BOOL)testMethod:(out NSError **)error;
@end

I've used this successfully to call a custom Obj-C method from Python. I believe the Python side doesn't get the right metadata otherwise.

There's also adding a signature decorator to your Python method definition. You can specify that one of the parameters is an out parameter in the sig. This PyObjC doc gives details.

@objc.signature('@@:io^@')

The other thing is (and you may have figured this out on your own by now) that if you don't want to do the Objective-C stub, you may be able to generate your own metadata and stick a .bridgesupport file into your project. The only thing (the big thing!) that I'm not sure of is how to make sure that this file actually gets read. The metadata itself is really easy to write -- Apple supplies a command-line utility called gen_bridge_metadata, and you could do it by hand (look at /System/Library/Frameworks/Python.framework/Versions/2.6/Extras/lib/python/PyObjC/AppKit/PyObjC.bridgesupport). Theres a man page for that utility, and man 5 BridgeSupport is also informative. Another Apple doc you should read is: Generating Framework Metadata.

UPDATE for future readers: I've found the functions objc.registerMetaDataForSelector and objc.parseBridgeSupport, both of which allow you to add metadata for your methods, using either Python dicts (the former function) or the XML format described in the BridgeSupport man page (the latter). Examples of using registerMetaData... are available in the pyobjc source at: pyobjc/pyobjc-core/PyObjCTest/test_metadata*, which I discovered via this pyobjc-dev mailing list thread.

Gooding answered 11/3, 2011 at 9:40 Comment(0)
A
4

I've tried this and all I get is an exception when calling from ObjC (: depythonifying 'char', got 'tuple').

PyObjC generally passes the error back as the second element of a tuple.

Something like:

response, error = object.someMethod_error_(foo) # leave off the error
if not response:
    # process error

Saw your update...

You are either going to have to dive into the metadata bridge and figure out how to apply some metadata at runtime to your implementation to make it work.

Or, possibly much easier, is to subclass from a compiled Objective-C stub class.

I.e. try this:

@interface AbstractFoo : NSObject
@end

@implementation AbstractFoo
- (BOOL) myMethod: (int) arg error: (NSError **) anError;
{
    return YES;
}
@end

Then, you should be able to subclass AbstractFoo from the Python side and it might "just work".

Maybe.

(Sorry -- been a while since I've been this deep in PyObjC)

Aver answered 4/12, 2010 at 21:19 Comment(4)
"PyObjC generally passes the error back as the second element of a tuple." Yes that is what my Python method (testMethod_) is doing. But PyObjC does not adhere to this rule when depythonifying back in ObjC land, and subsequently throws an exception. The confusion from all answers on the Internet I've seen, seems to stem from the distinction between calling ObjC from Python versus calling Python from ObjC. I am interested in the latter. One does not behave like the other.Delanadelancey
Thanks bbum. I tried to create an ObjC stub class and inherit from that in Python, like you suggested but I get the same exception ("depythonifying 'char', got 'tuple'"). Do you know of any places where I can find some documentation on how to even get started in messing with bridge technologies?Delanadelancey
ugh -- bummer. The best bet is to read the source. This is one of several reasons why I steer people away from using any of the bridges to do Cocoa development unless you really really need to intermingle a library from the desired language.... Sorry I couldn't be of more help.Aver
Plenty helpful. Thanks a tonne!Delanadelancey

© 2022 - 2024 — McMap. All rights reserved.