虽然我不了解所有含义,但以下似乎可行:
$.CFStringGetCStringPtr($.kUTTypeHTML, 0) // -> 'public.html'
# Alternative, with explicit UTF-8 encoding specification
$.CFStringGetCStringPtr($.kUTTypeHTML, $.kCFStringEncodingUTF8) // ditto
kUTType*
常量定义为CFStringRef
,并以指定的编码CFStringGetCStringPtr
返回CFString
对象的内部 C 字符串,前提是它可以“在恒定时间内没有内存分配和复制”或NULL
其他方式提取。
使用内置常量,似乎NULL
总是返回一个 C 字符串(而不是 ),这 - 凭借 C 数据类型映射到 JXA 数据类型 - 可以直接在 JavaScript 中使用:
$.CFStringGetCStringPtr($.kUTTypeHTML, 0) === 'public.html' // true
有关背景信息(从 OSX 10.11.1 开始),请继续阅读。
JXA 本身并不识别CFString
对象,即使它们可以“免费桥接”到NSString
JXA确实识别的类型。
您可以通过执行来验证 JXA 不知道CFString
和的等价性,这应该返回输入字符串的副本,但会失败并返回。NSString
$.NSString.stringWithString($.kUTTypeHTML).js
-[__NSDictionaryM length]: unrecognized selector sent to instance
不识别CFString
是我们的出发点:$.kUTTypeHTML
is 的类型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)
这是houthakker在他的解决方案尝试中所经历的。
对于给定的CF*
函数,您可以通过使用重新定义感兴趣的函数来绕过默认行为:ObjC.bindFunction()
// Redefine CFGetTypeID() to accept any type as-is:
ObjC.bindFunction('CFGetTypeID', ['unsigned long', [ 'void *']])
现在,$.CFGetTypeID($.kUTTypeHTML)
正确返回7
( CFString
)。
注意:重新定义的$.CFGetTypeID()
返回一个 JSNumber
实例,而原来的返回底层数字(值)的字符串表示。CFTypeID
通常,如果您想非正式地了解给定实例的特定类型CF*
CFShow()
,请使用,例如:
$.CFShow($.kUTTypeHTML) // -> '{\n type = "{__CFString=}";\n}'
注意:CFShow()
不返回任何内容,而是直接打印到stderr,因此您无法在 JS 中捕获输出。
您可以重新定义CFShow
withObjC.bindFunction('CFShow', ['void', [ 'void *' ]])
以免显示包装器字典。
对于本机识别的 CF* 类型——那些映射到 JS 原语的类型——你会直接看到特定的类型(例如,CFBoolean
for 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)
类似地,使用已知的 JXA 等价NSDictionary
和CFDictionary
,
ObjC.deepUnwrap($.NSDictionary.dictionaryWithDictionary( $.kUTTypeHTML ))
返回{"type":"{__CFString=}"}
,即,具有属性的 JS 对象,type
其值在这一点上 - 在 ObjC 桥调用往返之后- 仅仅是可能是原始实例的字符串表示。CFString
houthakker 的解决方案尝试还包含一个方便的代码片段,用于将实例的类型名称作为CF*
字符串获取。
如果我们将它重构为一个函数并应用必要的重新定义CFGetTypeID()
,我们会得到以下结果:
- 需要一个hack来使它返回一个可预测的值(见注释和源代码)
- 即使这样,随机字符有时也会作为返回字符串的结尾出现,例如
CFString,
而不是CFString
.
如果有人解释为什么需要破解以及随机字符来自哪里,请告诉我。这些问题可能与内存管理相关,因为两者都CFCopyTypeIDDescription()
返回CFStringCreateExternalRepresentation()
调用者必须释放的对象,我不知道 JXA 是否/如何/何时这样做。
/*
Returns the type name of the specified CF* (CoreFoundation) type instance.
CAVEAT:
* A HACK IS EMPLOYED to ensure that a value is consistently returned f
those CF* types that correspond to JS primitives, such as CFNumber,
CFBoolean, and CFString:
THE CODE IS CALLED IN A TIGHT LOOP UNTIL A STRING IS RETURNED.
THIS SEEMS TO WORK WELL IN PRACTICE, BUT CAVEAT EMPTOR.
Also, ON OCCASION A RANDOM CHARACTER APPEARS AT THE END OF THE STRING.
* Only pass in true CF* instances, as obtained from CF*() function
calls or constants such as $.kUTTypeHTML. Any other type will CRASH the
function.
Example:
getCFTypeName($.kUTTypeHTML) // -> 'CFString'
*/
function getCFTypeName(cfObj) {
// Redefine CFGetTypeID() so that it accepts unkown types as-is
// Caution:
// * ObjC.bindFunction() always takes effect *globally*.
// * Be sure to pass only true CF* instances from then on, otherwise
// the function will crash.
ObjC.bindFunction('CFGetTypeID', [ 'unsigned long', [ 'void *' ]])
// Note: Ideally, we'd redefine CFCopyDescription() analogously and pass
// the object *directly* to get a description, but this is not an option:
// ObjC.bindFunction('CFCopyDescription', ['void *', [ 'void *' ]])
// doesn't work, because, since we're limited to *C* types, we can't describe
// the *return* type in a way that CFStringGetCStringPtr() - which expects
// a CFStringRef - would then recognize ('Ref has incompatible type').
// Thus, we must first get a type's numerical ID with CFGetTypeID() and then
// get that *type*'s description with CFCopyTypeIDDescription().
// Unfortunately, passing the resulting CFString to $.CFStringGetCStringPtr()
// does NOT work: it yields NULL - no idea why.
//
// Using $.CFStringCreateExternalRepresentation(), which yields a CFData
// instance, from which a C string pointer can be extracted from with
// CFDataGetBytePtr(), works:
// - reliably with non-primitive types such as CFDictionary
// - only INTERMITTENTLY with the equivalent types of JS primitive types
// (such as CFBoolean, CFString, and CFNumber) - why??
// Frequently, and unpredictably, `undefined` is returned.
// !! THUS, THE FOLLOWING HACK IS EMPLOYED: THE CODE IS CALLED IN A TIGHT
// !! LOOP UNTIL A STRING IS RETURNED. THIS SEEMS TO WORK WELL IN PRACTICE,
// !! BUT CAVEAT EMPTOR.
// Also, sometimes, WHEN A STRING IS RETURNED, IT MAY CONTAIN A RANDOM
// EXTRA CHAR. AT THE END.
do {
var data = $.CFStringCreateExternalRepresentation(
null, // use default allocator
$.CFCopyTypeIDDescription($.CFGetTypeID(cfObj)),
0x08000100, // kCFStringEncodingUTF8
0 // loss byte: n/a here
); // returns a CFData instance
s = $.CFDataGetBytePtr(data)
} while (s === undefined)
return s
}