JXA:从CoreServices访问CFString常量 [英] JXA: Accessing CFString constants from CoreServices
问题描述
虽然您可以使用ObjC.import('CoreServices')
导入 ,但它们的 string值是(容易)访问的,大概是因为其类型为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*()
函数定义绑定,例如CFStringGetCStringPtr()
,但这对我来说并不明显如何翻译ObjC签名.
虽然我不理解所有含义,但以下内容似乎可行:
$.CFStringGetCStringPtr($.kUTTypeHTML, 0) // -> 'public.html'
# Alternative, with explicit UTF-8 encoding specification
$.CFStringGetCStringPtr($.kUTTypeHTML, $.kCFStringEncodingUTF8) // ditto
kUTType*
常量定义为CFStringRef
,CFStringGetCStringPtr
以指定的编码返回CFString
对象的内部C字符串, if 可以将其提取-否则为NULL
.
使用内置常量,似乎总是返回一个C字符串(而不是NULL
),由于C数据类型映射到JXA数据类型,它可以直接在JavaScript中使用:
$.CFStringGetCStringPtr($.kUTTypeHTML, 0) === 'public.html' // true
有关背景信息(自OSX 10.11.1起),请继续阅读.
JXA无法本地识别CFString
对象,即使它们可以免费桥接"到NSString
,JXA 可以识别这种类型.
您可以通过执行$.NSString.stringWithString($.kUTTypeHTML).js
来验证JXA是否不知道CFString
和NSString
的等效性,应该返回输入字符串的副本,但会失败,并显示-[__NSDictionaryM length]: unrecognized selector sent to instance
.
不认识CFString
是我们的出发点:$.kUTTypeHTML
是CFString[Ref]
类型,但是JXA不会返回它的 JS 字符串表示形式,只会返回[object Ref]
.>
注意:以下内容部分是推测性的-如果我错了,请告诉我.
不识别CFString
有另一个副作用,即调用接受泛型类型的CF*()
函数(或接受JXA是免费的桥接CF*
类型的Cocoa方法时)不知道):
在这种情况下,如果参数类型与调用的函数的参数类型不完全匹配,则JXA显然会在CFDictionary
实例中隐式地包装输入对象,该实例的唯一条目具有键
大概是这就是上面的$.NSString.stringWithString()
调用失败的原因:它是通过CFDictionary
包装而不是CFString
实例传递的.
另一个恰当的例子是CFGetTypeID()
函数,该函数需要一个CFTypeRef
自变量:即 any CF*
类型.
由于JXA不知道可以按原样传递CFStringRef
参数作为CFTypeRef
参数,因此它错误地执行了上述包装,并有效地传递了CFDictionary
实例:>
$.CFGetTypeID($.kUTTypeHTML) // -> !! 18 (CFDictionary), NOT 7 (CFString)
这是 houthakker 在 注意: 对于本机识别的CF *类型-映射到JS原语的那些*,您将直接看到特定的类型(例如, [1] 运行以下命令会为您提供由JXA生成的包装对象的想法 ,当传递未知类型时:>
类似地,使用 返回 houthakker的解决方案尝试还包含一个方便的代码段,用于获取类型为 name 的 如果我们将其重构为函数并应用 如果有人对为什么需要破解以及随机字符来自何处有解释,请告诉我.这些问题可能与内存管理有关,因为 JXA, with its built-in ObjC bridge, exposes enumeration and constants from the However, there are also useful While you can import them with I have yet to find a way to get at the string at the heart of what's returned:
I wonder:CFShow()
不返回任何内容,而是直接打印到 stderr ,因此您无法在JS中捕获输出.
您可以用ObjC.bindFunction('CFShow', ['void', [ 'void *' ]])
重新定义CFShow
,以免显示包装字典. CFBoolean
表示false
);对于未知的实例(因此也包含包装实例),您将看到上面的包装器结构-继续阅读
// 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)
NSDictionary
和CFDictionary
的JXA已知等效项,ObjC.deepUnwrap($.NSDictionary.dictionaryWithDictionary( $.kUTTypeHTML ))
{"type":"{__CFString=}"}
,即具有属性type
的JS对象,其值在这一点上-在ObjC桥调用往返之后-仅 string 可能是原始CFString
实例的表示形式.
CF*
实例作为字符串.CFGetTypeID()
的必要重新定义,则会得到以下信息:
CFString,
而不是CFString
.CFCopyTypeIDDescription()
和CFStringCreateExternalRepresentation()
都返回了调用者必须释放的对象,而我不知道JXA是否/何时/何时这样做./sup>
/*
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
}
Foundation
framework automatically via the $
object; e.g.:$.NSUTF8StringEncoding // -> 4
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"
.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?
ObjC.unwrap($.kUTTypeHTML)
doesn't work, and neither does ObjC.unwrap($.kUTTypeHTML[0])
(nor .deepUnwrap()
).
ObjC.bindFunction()
to define bindings for CFString*()
functions that can solve the problem, such as to CFStringGetCString()
or CFStringGetCStringPtr()
, 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:
$.CFStringGetCStringPtr($.kUTTypeHTML, 0) // -> 'public.html'
# Alternative, with explicit UTF-8 encoding specification
$.CFStringGetCStringPtr($.kUTTypeHTML, $.kCFStringEncodingUTF8) // ditto
The kUTType*
constants are defined as CFStringRef
, and CFStringGetCStringPtr
returns a CFString
object's internal C string in the specified encoding, if it can be extracted "with no memory allocations and no copying, in constant time" - or NULL
otherwise.
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:
$.CFStringGetCStringPtr($.kUTTypeHTML, 0) === 'public.html' // true
For background information (as of OSX 10.11.1), read on.
JXA doesn't natively recognize CFString
objects, even though they can be "toll-free bridged" to NSString
, a type that JXA does recognize.
You can verify that JXA does not know the equivalence of CFString
and NSString
by 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 CFString
is our starting point: $.kUTTypeHTML
is of type CFString[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 CFString
has another side effect, namely when invoking CF*()
functions that accept a generic type (or Cocoa methods that accept a toll-free bridged CF*
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 CFDictionary
instance, whose only entry has key type
, with the associated value containing the original object.[1]
Presumably, this is why the above $.NSString.stringWithString()
call fails: it is being passed the CFDictionary
wrapper rather than the CFString
instance.
Another case in point is the CFGetTypeID()
function, which expects a CFTypeRef
argument: i.e., any CF*
type.
Since JXA doesn't know that it's OK to pass a CFStringRef
argument as-is as the CFTypeRef
parameter, it mistakenly performs the above-mentioned wrapping, and effectively passes a CFDictionary
instance instead:
$.CFGetTypeID($.kUTTypeHTML) // -> !! 18 (CFDictionary), NOT 7 (CFString)
This is what houthakker experienced in his solution attempt.
For a given CF*
function you can bypass the default behavior by using ObjC.bindFunction()
to redefine the function of interest:
// Redefine CFGetTypeID() to accept any type as-is:
ObjC.bindFunction('CFGetTypeID', ['unsigned long', [ 'void *']])
Now, $.CFGetTypeID($.kUTTypeHTML)
correctly returns 7
(CFString
).
Note: The redefined $.CFGetTypeID()
returns a JS Number
instance, whereas the original returns a string representation of the underlying number (CFTypeID
value).
Generally, if you want to know the specific type of a given CF*
instance informally, use CFShow()
, e.g.:
$.CFShow($.kUTTypeHTML) // -> '{\n type = "{__CFString=}";\n}'
Note: CFShow()
returns nothing and instead prints directly to stderr, so you can't capture the output in JS.
You may redefine CFShow
with ObjC.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., CFBoolean
for false
); 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:
// 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)
Similarly, using the known-to-JXA equivalence of NSDictionary
and CFDictionary
,
ObjC.deepUnwrap($.NSDictionary.dictionaryWithDictionary( $.kUTTypeHTML ))
returns {"type":"{__CFString=}"}
, i.e., a JS object with property type
whose value is at this point - after an ObjC-bridge call roundtrip - a mere string representation of what presumably was the original CFString
instance.
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:
- A hack is needed to make it return a value predictably (see comments and source code)
- Even then a random character sometimes appears as the end of the string returned, such as
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()
and CFStringCreateExternalRepresentation()
return an object that the caller must release, and I don't know whether/how/when JXA does that.
/*
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
}
这篇关于JXA:从CoreServices访问CFString常量的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!