JXA:从 CoreServices 访问 CFString 常量
JXA: Accessing CFString constants from CoreServices
JXA 及其内置的 ObjC 桥,通过 $
对象自动公开 Foundation
框架中的枚举和常量;例如:
$.NSUTF8StringEncoding // -> 4
但是,在较低级别的 API 中也有一些有用的 CFString
常量不会自动导入,即 CoreServices
中的 kUTType*
常量定义了常用的 UTI values, such as kUTTypeHTML
对于 UTI "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?
我还没有找到一种方法来获取返回内容的核心 string:
ObjC.unwrap($.kUTTypeHTML)
不起作用,ObjC.unwrap($.kUTTypeHTML[0])
也不起作用(.deepUnwrap()
)。
我想知道:
- 如果有我缺少的本机 JXA 方法来执行此操作。
- 否则,如果有办法使用
ObjC.bindFunction()
为CFString*()
函数定义绑定可以解决问题,例如CFStringGetCString()
or CFStringGetCStringPtr()
,但我不清楚如何翻译 ObjC 签名。
$.kUTTypeHTML 出现在 return CFDictionary(见下文)中,因此您应该在以下位置找到可用的方法:
编辑:事实证明,JXA-ObjC-CF 交互中的一些类型复杂性意味着下面的代码片段不是学习 CF 对象引用类型的可靠或普遍适用的方法。 (请参阅下面的讨论)。
https://developer.apple.com/library/mac/documentation/CoreFoundation/Reference/CFDictionaryRef/
ObjC.import('CoreServices')
var data = $.CFStringCreateExternalRepresentation(
null,
$.CFCopyTypeIDDescription(
$.CFGetTypeID($.kUTTypeHTML)
),
'UTF-8',
0
); // CFDataRef
cPtr = $.CFDataGetBytePtr(data);
// --> "CFDictionary"
虽然我不明白所有含义,但以下似乎有效:
$.CFStringGetCStringPtr($.kUTTypeHTML, 0) // -> 'public.html'
# Alternative, with explicit UTF-8 encoding specification
$.CFStringGetCStringPtr($.kUTTypeHTML, $.kCFStringEncodingUTF8) // ditto
kUTType*
常量定义为CFStringRef
,CFStringGetCStringPtr
return是CFString
对象的指定编码的内部C字符串,if 可以提取 "with no memory allocations and no copying, in constant time" - 或者 NULL
否则。
使用内置常量,C 字符串(而不是 NULL
)似乎总是 returned,这 - 由于 C 数据类型映射到 JXA 数据类型 -可直接用于 JavaScript:
$.CFStringGetCStringPtr($.kUTTypeHTML, 0) === 'public.html' // true
关于背景信息(截至OSX 10.11.1),继续阅读。
JXA 本身不能识别 CFString
对象,即使它们可以是 "toll-free bridged" 到 NSString
,JXA 可以识别的类型 认识。
您可以通过执行 $.NSString.stringWithString($.kUTTypeHTML).js
来验证 JXA 不 知道 CFString
和 NSString
的等价性,应该 return 输入字符串的副本,但失败并显示 -[__NSDictionaryM length]: unrecognized selector sent to instance
.
不识别 CFString
是我们的出发点:$.kUTTypeHTML
是 CFString[Ref]
类型,但 JXA 不 return 一个 JS 的字符串表示,只有 [object Ref]
.
注意:以下部分是推测 - 如果我错了请告诉我。
不识别 CFString
有另一个副作用,即当调用 CF*()
函数接受 generic 类型(或 Cocoa 方法时接受 JXA 不知道的免费桥接 CF*
类型):
在这种情况下,如果参数类型与被调用函数的参数类型不完全匹配,JXA 显然隐式地包装 CFDictionary
实例中的输入对象,其唯一条目具有键 type
,关联值包含原始对象。[1]
据推测,这就是上述 $.NSString.stringWithString()
调用失败的原因:它被传递给 CFDictionary
包装器而不是 CFString
实例。
另一个例子是 CFGetTypeID()
函数,它需要一个 CFTypeRef
参数:即 any CF*
类型。
由于 JXA 不知道可以按原样传递 CFStringRef
参数作为 CFTypeRef
参数,因此它错误地执行了上述包装,并有效地传递了 CFDictionary
实例改为:
$.CFGetTypeID($.kUTTypeHTML) // -> !! 18 (CFDictionary), NOT 7 (CFString)
这就是houthakker experienced in .
对于给定的 CF*
函数,您可以 绕过 默认行为,方法是使用 ObjC.bindFunction()
重新定义 感兴趣的函数:
// Redefine CFGetTypeID() to accept any type as-is:
ObjC.bindFunction('CFGetTypeID', ['unsigned long', [ 'void *']])
现在,$.CFGetTypeID($.kUTTypeHTML)
正确 returns 7
(CFString
).
注意:重新定义的$.CFGetTypeID()
return是一个JSNumber
实例,而原来的return是一个字符串 表示基础数字(CFTypeID
值)。
通常,如果您想非正式地了解给定 CF*
实例的具体类型,请使用 CFShow()
,例如:
$.CFShow($.kUTTypeHTML) // -> '{\n type = "{__CFString=}";\n}'
注意:CFShow()
return什么都没有,而是直接打印到 stderr,所以你无法捕获输出JS.
您可以将 CFShow
重新定义为 ObjC.bindFunction('CFShow', ['void', [ 'void *' ]])
以便不显示包装器字典。
对于本地识别的 CF* 类型——那些映射到 JS 基元的类型——你会直接看到特定类型(例如,CFBoolean
代表 false
);对于未知的 - 因此被包装的 - 实例,您将看到如上所述的包装器结构 - 请继续阅读以了解更多信息。
[1] 运行 以下为您提供 idea 正在生成的包装器对象通过 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 ))
returns {"type":"{__CFString=}"}
,即具有属性 type
的 JS 对象,其值在此时 - 在 ObjC-bridge 调用之后 roundtrip - 仅仅是 string 表示可能是原始 CFString
实例。
还包含一个方便的代码片段,用于获取 CF*
实例的类型 name 作为字符串。
如果我们将其重构为一个函数并应用 CFGetTypeID()
的必要重新定义,我们将得到以下内容,但是:
- 需要 hack 才能使其 return 成为可预测的值(参见注释和源代码)
- 即便如此,随机字符有时也会出现在 returned 字符串的末尾,例如
CFString,
而不是 CFString
。
如果有人能解释为什么需要破解以及随机字符的来源,请告诉我。这些问题可能与内存管理有关,因为 CFCopyTypeIDDescription()
和 CFStringCreateExternalRepresentation()
return 都是 caller 必须释放的对象,而我没有知道 whether/how/when 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
}
您可以通过首先 re-binding CFMakeCollectable 函数将 CF 类型强制转换为 NS 类型,这样它需要 'void *' 和 returns 'id',然后使用它执行强制转换的函数:
ObjC.bindFunction('CFMakeCollectable', [ 'id', [ 'void *' ] ]);
var cfString = $.CFStringCreateWithCString(0, "foo", 0); // => [object Ref]
var nsString = $.CFMakeCollectable(cfString); // => $("foo")
为了使其更易于在您的代码中使用,您可以在 Ref 原型上定义一个 .toNS() 函数:
Ref.prototype.toNS = function () { return $.CFMakeCollectable(this); }
下面是如何使用这个带有 CFString 常量的新函数:
ObjC.import('CoreServices')
$.kUTTypeHTML.toNS() // => $("public.html")
JXA 及其内置的 ObjC 桥,通过 $
对象自动公开 Foundation
框架中的枚举和常量;例如:
$.NSUTF8StringEncoding // -> 4
但是,在较低级别的 API 中也有一些有用的 CFString
常量不会自动导入,即 CoreServices
中的 kUTType*
常量定义了常用的 UTI values, such as kUTTypeHTML
对于 UTI "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?
我还没有找到一种方法来获取返回内容的核心 string:
ObjC.unwrap($.kUTTypeHTML)
不起作用,ObjC.unwrap($.kUTTypeHTML[0])
也不起作用(.deepUnwrap()
)。
我想知道:
- 如果有我缺少的本机 JXA 方法来执行此操作。
- 否则,如果有办法使用
ObjC.bindFunction()
为CFString*()
函数定义绑定可以解决问题,例如CFStringGetCString()
orCFStringGetCStringPtr()
,但我不清楚如何翻译 ObjC 签名。
$.kUTTypeHTML 出现在 return CFDictionary(见下文)中,因此您应该在以下位置找到可用的方法:
编辑:事实证明,JXA-ObjC-CF 交互中的一些类型复杂性意味着下面的代码片段不是学习 CF 对象引用类型的可靠或普遍适用的方法。 (请参阅下面的讨论)。
https://developer.apple.com/library/mac/documentation/CoreFoundation/Reference/CFDictionaryRef/
ObjC.import('CoreServices')
var data = $.CFStringCreateExternalRepresentation(
null,
$.CFCopyTypeIDDescription(
$.CFGetTypeID($.kUTTypeHTML)
),
'UTF-8',
0
); // CFDataRef
cPtr = $.CFDataGetBytePtr(data);
// --> "CFDictionary"
虽然我不明白所有含义,但以下似乎有效:
$.CFStringGetCStringPtr($.kUTTypeHTML, 0) // -> 'public.html'
# Alternative, with explicit UTF-8 encoding specification
$.CFStringGetCStringPtr($.kUTTypeHTML, $.kCFStringEncodingUTF8) // ditto
kUTType*
常量定义为CFStringRef
,CFStringGetCStringPtr
return是CFString
对象的指定编码的内部C字符串,if 可以提取 "with no memory allocations and no copying, in constant time" - 或者 NULL
否则。
使用内置常量,C 字符串(而不是 NULL
)似乎总是 returned,这 - 由于 C 数据类型映射到 JXA 数据类型 -可直接用于 JavaScript:
$.CFStringGetCStringPtr($.kUTTypeHTML, 0) === 'public.html' // true
关于背景信息(截至OSX 10.11.1),继续阅读。
JXA 本身不能识别 CFString
对象,即使它们可以是 "toll-free bridged" 到 NSString
,JXA 可以识别的类型 认识。
您可以通过执行 $.NSString.stringWithString($.kUTTypeHTML).js
来验证 JXA 不 知道 CFString
和 NSString
的等价性,应该 return 输入字符串的副本,但失败并显示 -[__NSDictionaryM length]: unrecognized selector sent to instance
.
不识别 CFString
是我们的出发点:$.kUTTypeHTML
是 CFString[Ref]
类型,但 JXA 不 return 一个 JS 的字符串表示,只有 [object Ref]
.
注意:以下部分是推测 - 如果我错了请告诉我。
不识别 CFString
有另一个副作用,即当调用 CF*()
函数接受 generic 类型(或 Cocoa 方法时接受 JXA 不知道的免费桥接 CF*
类型):
在这种情况下,如果参数类型与被调用函数的参数类型不完全匹配,JXA 显然隐式地包装 CFDictionary
实例中的输入对象,其唯一条目具有键 type
,关联值包含原始对象。[1]
据推测,这就是上述 $.NSString.stringWithString()
调用失败的原因:它被传递给 CFDictionary
包装器而不是 CFString
实例。
另一个例子是 CFGetTypeID()
函数,它需要一个 CFTypeRef
参数:即 any CF*
类型。
由于 JXA 不知道可以按原样传递 CFStringRef
参数作为 CFTypeRef
参数,因此它错误地执行了上述包装,并有效地传递了 CFDictionary
实例改为:
$.CFGetTypeID($.kUTTypeHTML) // -> !! 18 (CFDictionary), NOT 7 (CFString)
这就是houthakker experienced in
对于给定的 CF*
函数,您可以 绕过 默认行为,方法是使用 ObjC.bindFunction()
重新定义 感兴趣的函数:
// Redefine CFGetTypeID() to accept any type as-is:
ObjC.bindFunction('CFGetTypeID', ['unsigned long', [ 'void *']])
现在,$.CFGetTypeID($.kUTTypeHTML)
正确 returns 7
(CFString
).
注意:重新定义的$.CFGetTypeID()
return是一个JSNumber
实例,而原来的return是一个字符串 表示基础数字(CFTypeID
值)。
通常,如果您想非正式地了解给定 CF*
实例的具体类型,请使用 CFShow()
,例如:
$.CFShow($.kUTTypeHTML) // -> '{\n type = "{__CFString=}";\n}'
注意:CFShow()
return什么都没有,而是直接打印到 stderr,所以你无法捕获输出JS.
您可以将 CFShow
重新定义为 ObjC.bindFunction('CFShow', ['void', [ 'void *' ]])
以便不显示包装器字典。
对于本地识别的 CF* 类型——那些映射到 JS 基元的类型——你会直接看到特定类型(例如,CFBoolean
代表 false
);对于未知的 - 因此被包装的 - 实例,您将看到如上所述的包装器结构 - 请继续阅读以了解更多信息。
[1] 运行 以下为您提供 idea 正在生成的包装器对象通过 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 ))
returns {"type":"{__CFString=}"}
,即具有属性 type
的 JS 对象,其值在此时 - 在 ObjC-bridge 调用之后 roundtrip - 仅仅是 string 表示可能是原始 CFString
实例。
CF*
实例的类型 name 作为字符串。
如果我们将其重构为一个函数并应用 CFGetTypeID()
的必要重新定义,我们将得到以下内容,但是:
- 需要 hack 才能使其 return 成为可预测的值(参见注释和源代码)
- 即便如此,随机字符有时也会出现在 returned 字符串的末尾,例如
CFString,
而不是CFString
。
如果有人能解释为什么需要破解以及随机字符的来源,请告诉我。这些问题可能与内存管理有关,因为 CFCopyTypeIDDescription()
和 CFStringCreateExternalRepresentation()
return 都是 caller 必须释放的对象,而我没有知道 whether/how/when 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
}
您可以通过首先 re-binding CFMakeCollectable 函数将 CF 类型强制转换为 NS 类型,这样它需要 'void *' 和 returns 'id',然后使用它执行强制转换的函数:
ObjC.bindFunction('CFMakeCollectable', [ 'id', [ 'void *' ] ]);
var cfString = $.CFStringCreateWithCString(0, "foo", 0); // => [object Ref]
var nsString = $.CFMakeCollectable(cfString); // => $("foo")
为了使其更易于在您的代码中使用,您可以在 Ref 原型上定义一个 .toNS() 函数:
Ref.prototype.toNS = function () { return $.CFMakeCollectable(this); }
下面是如何使用这个带有 CFString 常量的新函数:
ObjC.import('CoreServices')
$.kUTTypeHTML.toNS() // => $("public.html")