Converting Swift / ObjC pointers to python

I'm using python to write a bunch of scripts that dothings with CoreGraphics. I'm getting stuck when the API documentation uses pointers.


For instance:

  for filename in sys.argv[1:]: 
     pdf = CGPDFDocumentCreateWithURL(CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, filename, len(filename), False)) pages = CGPDFDocumentGetNumberOfPages(pdf) info = CGPDFDocumentGetInfo(pdf) print pages, info
     pages = CGPDFDocumentGetNumberOfPages(pdf)
     info = CGPDFDocumentGetInfo(pdf)


The pages variable comes out as an integer, with the number of pages. But info gives me:

PyObjCPointer created: at 0x7f8944f2c200 of type {CGPDFDictionary=}^{CGPDFDocument=}


Similarly, the Swift documentation for CGPDFDocumentGetVersion has this:

func CGPDFDocumentGetVersion( 
     _ document: CGPDFDocument?, 
     _ majorVersion: UnsafeMutablePointer<Int32>, 
     _ minorVersion: UnsafeMutablePointer<Int32> 
)


and I have no idea how to do this in python.

Accepted Reply

There’s two questions here, somewhat related. Let’s take each in turn.

Once you remove all the guff the Objective-C declaration for

CGPDFDocumentGetInfo
is as follows.
CGPDFDictionaryRef CGPDFDocumentGetInfo(CGPDFDocumentRef document);

It returns an opaque

CGPDFDictionaryRef
object. To get information from that, you have to call the various routines in
<CoreGraphics/CGPDFDictionary.h>
. To continue your example:
>>> CGPDFDictionaryGetCount(info)
9

Now, this isn’t directly helpful because most of CGPDFDictionary routines return values by reference. And that brings us to your second question.

The Objective-C declaration for

CGPDFDocumentGetVersion
is as follows.
void CGPDFDocumentGetVersion(
    CGPDFDocumentRef cg_nullable document,
    int * majorVersion,
    int * minorVersion
);
majorVersion
and
minorVersion
are ‘out’ parameters. However, it seems that the bridge is treating them as general pointer parameters.
>>> CGPDFDocumentGetVersion.__metadata__()['arguments'][1]['type']
'^i'
>>> CGPDFDocumentGetVersion.__metadata__()['arguments'][2]['type']
'^i'

That means that the usual trick of passing in

None
for an ‘out’ parameter doesn’t work.

I’m not sufficiently au fait with PyObjC’s metadata infrastructure to offer any suggestions for how to fix this. I recommend you raise this via the support channel associated with that tool.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Replies

There’s two questions here, somewhat related. Let’s take each in turn.

Once you remove all the guff the Objective-C declaration for

CGPDFDocumentGetInfo
is as follows.
CGPDFDictionaryRef CGPDFDocumentGetInfo(CGPDFDocumentRef document);

It returns an opaque

CGPDFDictionaryRef
object. To get information from that, you have to call the various routines in
<CoreGraphics/CGPDFDictionary.h>
. To continue your example:
>>> CGPDFDictionaryGetCount(info)
9

Now, this isn’t directly helpful because most of CGPDFDictionary routines return values by reference. And that brings us to your second question.

The Objective-C declaration for

CGPDFDocumentGetVersion
is as follows.
void CGPDFDocumentGetVersion(
    CGPDFDocumentRef cg_nullable document,
    int * majorVersion,
    int * minorVersion
);
majorVersion
and
minorVersion
are ‘out’ parameters. However, it seems that the bridge is treating them as general pointer parameters.
>>> CGPDFDocumentGetVersion.__metadata__()['arguments'][1]['type']
'^i'
>>> CGPDFDocumentGetVersion.__metadata__()['arguments'][2]['type']
'^i'

That means that the usual trick of passing in

None
for an ‘out’ parameter doesn’t work.

I’m not sufficiently au fait with PyObjC’s metadata infrastructure to offer any suggestions for how to fix this. I recommend you raise this via the support channel associated with that tool.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Many thanks. I think you're right that it just may not be possible in python. Though some people have said it might be possible using named tuples and classes that ... eat themselves. Beyond my pay grade, I think.


Any idea what/where/who the PyObjC support channel is?

Any idea what/where/who the PyObjC support channel is?

There’s a bunch of resources listed at the bottom of the PyObjC home page. I don’t have a specific recommendation (I’ve use PyObjC a lot but I’ve never had to ask for help at this level).

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

I've not installed anything beyond what comes with OS X. Is the PyObjC you've linked to the same as or different from the PyObjC stuff that is part of OS X?

I always thought the two were different things.


EDIT: Reading those pages, they seem to imply that the solution would be something like this:


majorVersion, minorVersion = Quartz.CGPDFDocumentGetVersion_document_majorVersion_minorVersion_(pdf_doc, None, None)


But I get an error:

in __getattr__

raise AttributeError(name)

Is the PyObjC you've linked to the same as or different from the PyObjC stuff that is part of OS X?

macOS includes a bundled copy of the standard PyObjC, although I doubt it’s the latest version. You can determine exactly which version by looking at the

pyobjc
in the Darwin source that corresponds to the OS you’re running.

Reading those pages, they seem to imply that the solution would be something like this:

That would be correct if this were an Objective-C method, but what you’re dealing with here is a C function.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

"That would be correct if this were an Objective-C method, but what you’re dealing with here is a C function."


Here's where I nod in agreement, smile blankly and walk away. However, from the Apple API documentation, it looks like there are some new methods (or possibly functions) in Beta that are easier to use for this. I'll just have to wait until they are incorporated into the Python bridge.


Thanks.