JXA:从 CoreServices 访问 CFString 常量

mkl*_*nt0 4 objective-c core-services javascript-automation

JXA具有内置的 ObjC 桥接器,Foundation可通过对象自动从框架公开枚举和常量$;例如:

$.NSUTF8StringEncoding  // -> 4
Run Code Online (Sandbox Code Playgroud)

但是,较低级别 API 中也有一些CFString不会自动导入的有用常量,即定义常用UTIkUTType*值的常量,例如UTI 。CoreServiceskUTTypeHTML"public.html"

虽然您可以使用 导入它们ObjC.import('CoreServices'),但它们的字符串值无法(轻松)访问,大概是因为它的类型是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?
Run Code Online (Sandbox Code Playgroud)

我还没有找到一种方法来获取返回内容的核心 字符串ObjC.unwrap($.kUTTypeHTML):不起作用,也不起作用ObjC.unwrap($.kUTTypeHTML[0])(也.deepUnwrap())。

我想知道:

  • 如果我缺少一种本机 JXA 方法来执行此操作。
  • 否则,如果有办法ObjC.bindFunction()定义可以解决问题的函数的绑定CFString*(),例如 toCFStringGetCString()CFStringGetCStringPtr(),但对我来说如何翻译 ObjC 签名并不明显。

mkl*_*nt0 5

虽然我不理解所有含义,但以下似乎有效:

$.CFStringGetCStringPtr($.kUTTypeHTML, 0) // -> 'public.html'

# Alternative, with explicit UTF-8 encoding specification
$.CFStringGetCStringPtr($.kUTTypeHTML, $.kCFStringEncodingUTF8) // ditto
Run Code Online (Sandbox Code Playgroud)

常量kUTType*定义为CFStringRef,并且如果可以“在常量时间内没有内存分配和复制”提取对象的内部 C 字符串,则以指定的编码返回该对象的内部 C 字符串,否则CFStringGetCStringPtr返回。CFStringNULL

使用内置常量,似乎NULL总是返回 C 字符串(而不是 ),它 - 凭借 C 数据类型映射到 JXA 数据类型 - 可直接在 JavaScript 中使用:

 $.CFStringGetCStringPtr($.kUTTypeHTML, 0) === 'public.html' // true
Run Code Online (Sandbox Code Playgroud)

有关背景信息(自 OSX 10.11.1 起),请继续阅读。


JXA 本身并不识别CFString对象,尽管它们可以“免费桥接”到JXA确实NSString识别的类型。

您可以通过执行来验证 JXA 不知道CFString和 的等价性,这应该返回输入字符串的副本,但会失败并显示。NSString$.NSString.stringWithString($.kUTTypeHTML).js-[__NSDictionaryM length]: unrecognized selector sent to instance

不认识CFString是我们的出发点:$.kUTTypeHTML是 类型CFString[Ref],但 JXA 不返回它的JS[object Ref]字符串表示,只返回。

注意:以下内容部分是推测性的 - 如果我错了,请告诉我。

不识别CFString还有另一个副作用,即当调用CF*()接受泛型CF*类型的函数(或接受JXA 不知道的免费桥接类型的 Cocoa 方法)时:
在这种情况下,如果参数类型与调用的参数类型不完全匹配函数的参数类型,JXA 显然隐式地将输入对象包装CFDictionary在一个实例中,该实例的唯一条目具有 key type,关联值包含原始对象。[1]

据推测,这就是上述$.NSString.stringWithString()调用失败的原因:它传递的是CFDictionary包装器而不是CFString实例。

另一个恰当的例子是CFGetTypeID()函数,它需要一个CFTypeRef参数:即任何 CF*类型。

由于 JXA 不知道可以按原样传递CFStringRef参数作为CFTypeRef参数,因此它错误地执行了上述包装,并有效地传递了一个CFDictionary实例:

$.CFGetTypeID($.kUTTypeHTML) // -> !! 18 (CFDictionary), NOT 7 (CFString)
Run Code Online (Sandbox Code Playgroud)

这就是胡撒克他的解决尝试中所经历的。

对于给定的CF*函数,您可以通过使用重新定义感兴趣的函数来绕过默认行为:ObjC.bindFunction()

// Redefine CFGetTypeID() to accept any type as-is:
ObjC.bindFunction('CFGetTypeID', ['unsigned long', [ 'void *']])
Run Code Online (Sandbox Code Playgroud)

现在,$.CFGetTypeID($.kUTTypeHTML)正确返回7( CFString)。

注意:重新定义$.CFGetTypeID()返回一个 JSNumber实例,而原始返回底层数字(值)的字符串CFTypeID表示形式。

一般来说,如果您想非正式地了解给定CF*实例的特定类型,请使用CFShow(),例如:

$.CFShow($.kUTTypeHTML) // -> '{\n    type = "{__CFString=}";\n}'
Run Code Online (Sandbox Code Playgroud)

注意:CFShow()不返回任何内容,而是直接打印到stderr,因此无法捕获 JS 中的输出。
您可以重新定义CFShowwithObjC.bindFunction('CFShow', ['void', [ 'void *' ]])以免显示包装字典。

对于本机识别的 CF* 类型(映射到 JS 原语的类型),您将直接看到特定类型(例如CFBooleanfor false);对于未知的 - 因此被包装的 - 实例,您将看到如上所述的包装器结构 - 继续阅读以了解更多信息。


[1]运行以下命令可以让您了解JXA在传递未知类型时生成的包装器对象

// Note: CFShow() prints a description of the type of its argument
//  directly to stderr.
$.CFShow($.kUTTypeHTML) // -> '{\n    type = "{__CFString=}";\n}'

// Alternative that *returns* the description as a JS string:
$.CFStringGetCStringPtr($.CFCopyDescription($.kUTTypeHTML), 0) // -> (see above)
Run Code Online (Sandbox Code Playgroud)

类似地,使用已知的 JXA 等效项NSDictionaryCFDictionary

ObjC.deepUnwrap($.NSDictionary.dictionaryWithDictionary( $.kUTTypeHTML ))
Run Code Online (Sandbox Code Playgroud)

返回{"type":"{__CFString=}"},即一个具有属性的 JS 对象type,其值在此时 - 在 ObjC 桥调用往返之后 -可能是原始实例的纯粹字符串CFString表示形式。


houthakker 的解决方案尝试还包含一个方便的代码片段,用于以字符串形式获取实例的类型名称。CF*

如果我们将其重构为一个函数并应用必要的重新定义CFGetTypeID(),我们会得到以下结果:

  • 需要进行黑客攻击才能使其返回可预测的值(请参阅注释和源代码)
  • 即使这样,有时也会出现随机字符作为返回字符串的末尾,例如而CFString,不是CFString

如果有人能解释为什么需要破解以及随机字符从何而来,请告诉我。这些问题可能与内存管理相关,因为CFCopyTypeIDDescription()和返回调用者CFStringCreateExternalRepresentation()必须释放的对象,并且我不知道 JXA 是否/如何/何时执行此操作。

$.CFStringGetCStringPtr($.kUTTypeHTML, 0) // -> 'public.html'

# Alternative, with explicit UTF-8 encoding specification
$.CFStringGetCStringPtr($.kUTTypeHTML, $.kCFStringEncodingUTF8) // ditto
Run Code Online (Sandbox Code Playgroud)