【问题标题】:ScriptingBridge - How Does It Work "Behind the Scenes"ScriptingBridge - 它是如何“在幕后”工作的
【发布时间】:2020-05-25 17:06:13
【问题描述】:

上下文:我正在处理a Pharo/Smalltalk -> Objective-C bridge

场景:在下面的Objective-C ScriptingBridge sn-p中:

iTunesApplication *iTunes = [SBApplication applicationWithBundleIdentifier:@"com.apple.iTunes"];

iTunesTrack *currentTrack = iTunes.currentTrack; //[1]
// This low level way works too
//iTunesTrack *currentTrack = [iTunes propertyWithCode: 'pTrk']; //[2]

[iTunes playpause]; //[3]

问题:桥使用class_getInstanceMethod 来确定对象是否理解消息/选择器,但对于像playpause 这样的脚本消息,它返回NULL

问题 #1 为什么class_getInstanceMethod 对像playpause 这样的脚本消息返回NULL? class_copyMethodList 有同样的问题吗?脚本消息的作用与其他 Obj-C 消息不同(除非它们这样做!),有什么特别之处?

问题 #2 [已解决 - 请参阅 @Matt 的回答]

根据the docs,在“为 iTunes 应用程序动态定义的子类”中,SB 将“自动处理 Apple 事件发送的特定于应用程序的方法”放在哪里?而且,鉴于class_getInstanceMethod 无法找到这种行为(见下文),桥接测试它的可靠方法是什么(即是否存在这样的方法/消息)?

Objective-C 运行时 API 报告混合结果。一方面,iTunesApplication 类似乎没有任何方法(或属性):

  • class_copyMethodList([iTunes class]... 返回零个方法
  • 网桥用于查找和执行方法的class_getInstanceMethod 失败。

另一方面,#playpause 可以通过 API 的其他部分进行查询和发送:

  • respondsToSelector: -> 是的
  • methodSignatureForSelector: 返回签名
  • 和performSelector:实际发送消息

奇怪的是,methodForSelector:@"playpause" 在 Obj-C 中成功返回了 IMP,但如果从桥的另一端发送则会崩溃。

问题 #3 [已解决]

如何模拟/复制 [3]?

@Willeke 在 cmets 中回答:[iTunes sendEvent:'hook' id:'PlPs' parameters:0]

【问题讨论】:

  • 我不确定我是否理解这个问题。 [iTunes playpause] 的“低级方式”是[iTunes sendEvent:'hook' id:'PlPs' parameters:0]。 [iTunes propertyWithCode: 'pTrk'] 返回 SBObject*,[iTunes propertyWithClass:[self.iTunes classForScriptingClass:@"track"] code:'pTrk'] 返回 iTunesTrack*。
  • @Willeke 如果我找不到答案,这已经很有帮助了。我的问题归结为:我如何访问例如如果 Obj-C 运行时反射 API 似乎看不到该方法,则从桥的另一端“为 iTunes 应用程序动态定义的子类的 currentTrack 方法”? SB 在哪里“隐藏”这些动态实现的方法/属性?我宁愿以某种方式从 Smalltalk 中调用“playpause”,而不是使用类似于您提供的代码重新实现它
  • 为什么不研究一个替代的开源桥来看看它是如何完成的呢?看看 appscript 等。警告,这里是龙。如果这很容易,其他人就会做到。
  • 啊,好吧,对不起,我对 Pharo 或 class_getInstanceMethod 一无所知。但是 appscript 是开源的,所以正如我所说,如果你想知道它是如何工作的,你可以自己看看。抱歉,我只能提供这些了。
  • 因为您首先从物理上生成一个基于 iTunes 字典 (sdef) 的 iTunes 类,其中声明了 playpause。它是一个 SBApplication 子类。天哪,很抱歉我删除了我之前的评论。

标签: objective-c smalltalk pharo objective-c-runtime scripting-bridge


【解决方案1】:

如果 SB 不使用 Objective-C 消息,那么文档中的“SBApplication 的子类实现自动处理 Apple 事件发送的特定于应用程序的方法”是什么意思?为什么 iTunes respondsToSelector: @"playpause" 工作,即返回 true? [iTunes 播放暂停] 是如何工作的?等等等等。

它之所以有效,是因为您在脚本桥应用程序中所做的第一 件事就是生成一个标头。在 Catalina 中,您可以这样做:

sdp -f h --basename iTunes /System/Applications/Music.app/Contents/Resources/com.apple.Music.sdef

这会读取 iTunes 字典 (sdef) 并为一组类似的 Objective-C 类生成标题。现在您有一个 iTunes.h 文件,您将其包含在应用程序项目中并导入到您的代码中。它包含这一行:

- (void) playpause;  // toggle the playing/paused state of the current track

所以现在playpause 被明确声明为可以发送到 iTunesApplication 对象的合法命令。然后,当您实际运行应用程序时,您会说

iTunesApplication* tunes = (iTunesApplication*)[SBApplication applicationWithBundleIdentifier:@"com.apple.music"];

这会导致您的应用程序再次与 iTunes(音乐)对话并获取字典 (sdef),从而为标头中声明的方法生成实现。 playpause 命令的实现正是sdef 所说的那样:即将hookPlPs 事件发送到iTunes。

这就解释了为什么你可以说 playpause 以及当你说它时会发生什么。

这就是 AppleScript ——它是一个应用程序,它提供了一个列表,您可以使用 Apple 事件对它说的话,以及引用这些 Apple 事件的类似英语的术语。

所以如果你想写一个桥,你必须做同样的事情:你需要提供一种方法来搜索目标应用程序的sdef资源并将该信息转换为以您的语言给出相应命令的方式,无论它是什么。

【讨论】:

  • 非常感谢您继续就此展开对话。我很想接受这个答案,因为它涵盖了我的大部分问题,我怀疑这是我能得到的最好的答案。但是,当我调查 appscript 时,我修改了问题以关注仍然缺失的主要部分,即 SB 脚本消息有什么特别之处,它们不像其他 Obj-C 消息(除非它们这样做!)?例如,为什么像 class_getInstanceMethod 和 class_copyMethodList 这样的 Obj-C 运行时函数无法“看到”像 playpause 这样的脚本消息?
  • 我对运行时函数知之甚少,无法回答这个问题。我认为这与动态生成的类(如 iTunesApplication)没有链接到应用程序有关。但我不明白这有什么关系。那些运行时函数从来没有你将如何做到这一点。这不是 any AppleScript 桥的工作方式。这就是我的观点。
  • 好的,让我深入研究一下 appscript 代码,看看能否根据您的慷慨指点解决这个问题...
  • 嗯,这取决于 appscript 的风格。有一种 Swift 风格与我刚才描述的完全一样:您首先根据目标应用程序的 sdef 显式生成一个 Swift 代码文件,然后将其内置到项目中。我的 SyncMe3 应用程序使用它来编写 Finder 脚本。
  • 另一方面,ruby 风格的 rb-appscript 不需要这样做,因为 ruby​​ 使用鸭子类型:编译器会让你说 anything any 对象,并且它可以在运行时解析,因此不需要事先生成代码文件以便以后编译。我们从第二步开始,我们的代码 到达目标应用程序并动态获取其sdef 并对其进行解释,并使用结果响应method_missing。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-09-24
  • 1970-01-01
  • 1970-01-01
  • 2011-06-16
  • 2010-12-28
  • 1970-01-01
  • 2012-05-02
相关资源
最近更新 更多