JXA, with its built-in ObjC bridge, exposes enumeration and constants from the Foundation framework automatically via the $ object; e.g.:
$.NSUTF8StringEncoding // -> 4
However, there are also useful CFString constants in lower-level APIs that aren't automatically imported, namely the kUTType* constants in CoreServices that define frequently-used UTI values, such as kUTTypeHTML for UTI "public.html".
While you can import them with ObjC.import('CoreServices'), their string value isn't (readily) accessible, presumably because its type is CFString[Ref]:
ObjC.import('CoreServices') // import kUTType* constants; ObjC.import('Cocoa') works too
$.kUTTypeHTML // returns an [object Ref] instance - how do you get its string value?
I have yet to find a way to get at the string at the heart of what's returned:
ObjC.unwrap($.kUTTypeHTML) doesn't work, and neither does ObjC.unwrap($.kUTTypeHTML[0]) (nor .deepUnwrap()).
I wonder:
- if there's a native JXA way to do this that I'm missing.
- otherwise, if there's away to use
ObjC.bindFunction()to define bindings forCFString*()functions that can solve the problem, such as toCFStringGetCString()orCFStringGetCStringPtr(), but it's not obvious to me how to translate the ObjC signatures.
While I don't understand all implications, the following seems to work:
The
kUTType*constants are defined asCFStringRef, andCFStringGetCStringPtrreturns aCFStringobject's internal C string in the specified encoding, if it can be extracted "with no memory allocations and no copying, in constant time" - orNULLotherwise.With the built-in constants, it seems that a C string (rather than
NULL) is always returned, which - by virtue of C data types mapping onto JXA data types - is directly usable in JavaScript:For background information (as of OSX 10.11.1), read on.
JXA doesn't natively recognize
CFStringobjects, even though they can be "toll-free bridged" toNSString, a type that JXA does recognize.You can verify that JXA does not know the equivalence of
CFStringandNSStringby executing$.NSString.stringWithString($.kUTTypeHTML).js, which should return a copy of the input string, but instead fails with-[__NSDictionaryM length]: unrecognized selector sent to instance.Not recognizing
CFStringis our starting point:$.kUTTypeHTMLis of typeCFString[Ref], but JXA doesn't return a JS string representation of it, only[object Ref].Note: The following is in part speculative - do tell me if I'm wrong.
Not recognizing
CFStringhas another side effect, namely when invokingCF*()functions that accept a generic type (or Cocoa methods that accept a toll-free bridgedCF*type that JXA is unaware of):In such cases, if the argument type doesn't exactly match the invoked function's parameter type, JXA apparently implicitly wraps the input object in a
CFDictionaryinstance, whose only entry has keytype, with the associated value containing the original object.[1]Presumably, this is why the above
$.NSString.stringWithString()call fails: it is being passed theCFDictionarywrapper rather than theCFStringinstance.Another case in point is the
CFGetTypeID()function, which expects aCFTypeRefargument: i.e., anyCF*type.Since JXA doesn't know that it's OK to pass a
CFStringRefargument as-is as theCFTypeRefparameter, it mistakenly performs the above-mentioned wrapping, and effectively passes aCFDictionaryinstance instead:This is what houthakker experienced in his solution attempt.
For a given
CF*function you can bypass the default behavior by usingObjC.bindFunction()to redefine the function of interest:Now,
$.CFGetTypeID($.kUTTypeHTML)correctly returns7(CFString).Note: The redefined
$.CFGetTypeID()returns a JSNumberinstance, whereas the original returns a string representation of the underlying number (CFTypeIDvalue).Generally, if you want to know the specific type of a given
CF*instance informally, useCFShow(), e.g.:Note:
CFShow()returns nothing and instead prints directly to stderr, so you can't capture the output in JS.You may redefine
CFShowwithObjC.bindFunction('CFShow', ['void', [ 'void *' ]])so as not to show the wrapper dictionary.For natively recognized CF* types - those that map onto JS primitives - you'll see the specific type directly (e.g.,
CFBooleanforfalse); for unknown - and therefore wrapped - instances, you'll see the wrapper structure as above - read on for more.[1] Running the following gives you an idea of the wrapper object being generated by JXA when passing an unknown type:
Similarly, using the known-to-JXA equivalence of
NSDictionaryandCFDictionary,returns
{"type":"{__CFString=}"}, i.e., a JS object with propertytypewhose value is at this point - after an ObjC-bridge call roundtrip - a mere string representation of what presumably was the originalCFStringinstance.houthakker's solution attempt also contains a handy snippet of code to obtain the type name of a
CF*instance as a string.If we refactor it into a function and apply the necessary redefinition of
CFGetTypeID(), we get the following, HOWEVER:CFString,rather thanCFString.If anyone has an explanation for why the hack is needed and where the random characters come from, please let me know. The issues may be memory-management related, as both
CFCopyTypeIDDescription()andCFStringCreateExternalRepresentation()return an object that the caller must release, and I don't know whether/how/when JXA does that.