ScriptingBridge - как это работает за кулисами

Контекст: я работаю над мостом Pharo / Smalltalk -> Objective-C

Сценарий: в следующем фрагменте Objective-C ScriptingBridge:

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, чтобы определить, понимает ли объект сообщение / селектор, но он возвращает NULL для сообщений сценария, таких как playpause

Вопрос № 1 Почему class_getInstanceMethod возвращает NULL для скриптовых сообщений типа playpause? Тот же вопрос для class_copyMethodList? Что особенного в сообщениях сценариев, что они не действуют как другие сообщения Obj-C (кроме случаев, когда они действуют!)?

Вопрос № 2 [РЕШЕН - см. ответ @ Мэтта]

Где, согласно документам, в «динамически определяемом подклассе для приложение iTunes "размещает ли SB" специфичные для приложения методы, которые автоматически обрабатывают отправку событий Apple "? И, учитывая, что class_getInstanceMethod не может найти такое поведение (см. Ниже), какой надежный способ для моста проверить это (т.е. существует ли такой метод / сообщение)?

Objective-C Runtime API сообщает о смешанных результатах. С одной стороны, у класса iTunesApplication, похоже, нет никаких методов (или свойств, если на то пошло):

  • class_copyMethodList([iTunes class]... возвращает нулевые методы
  • class_getInstanceMethod, который мост использует для поиска и выполнения методов, не работает.

С другой стороны, #playpause можно запросить и отправить через другие части API:

  • respondsToSelector: -> ИСТИНА
  • methodSignatureForSelector: возвращает подпись
  • и performSelector: фактически отправляет сообщение

Как ни странно, methodForSelector:@"playpause" успешно возвращает IMP в Obj-C, но вылетает, если отправлено с другой стороны моста.

Вопрос №3 [решен]

Как можно смоделировать / воспроизвести [3]?

Ответил @Willeke в комментариях: [iTunes sendEvent:'hook' id:'PlPs' parameters:0]


person Sean DeNigris    schedule 25.05.2020    source источник
comment
Я не уверен, что понимаю вопрос. Низкоуровневый путь [iTunes playpause] - [iTunes sendEvent:'hook' id:'PlPs' parameters:0]. [iTunes propertyWithCode: 'pTrk'] возвращает SBObject*, [iTunes propertyWithClass:[self.iTunes classForScriptingClass:@"track"] code:'pTrk'] возвращает iTunesTrack*.   -  person Willeke    schedule 26.05.2020
comment
@Willeke, это уже полезно, если я не могу найти ответ. Мой вопрос сводится к следующему: как мне получить доступ, например, метод currentTrack динамически определяемого подкласса для приложения iTunes с другой стороны моста, если API отражения времени выполнения Obj-C не может видеть этот метод? Где SB скрывает эти динамически реализуемые методы / свойства? Я бы предпочел как-нибудь вызвать playpause из Smalltalk, чем заново реализовывать его кодом, подобным тому, который вы предоставили   -  person Sean DeNigris    schedule 27.05.2020
comment
Специфичные для приложения методы относятся к терминам сценариев, которые используются в вашем приложении. Эти термины - это не те методы, которые вы ищете, это команды (через Apple Events), на которые приложение реагирует. Вы можете попробовать скомпилировать / запустить скрипт и выявить ошибку (синтаксическую или другую) или посмотреть словарь скриптов приложения (хотя это все равно не скажет, правильно ли используется термин).   -  person red_menace    schedule 29.05.2020
comment
@red_menace Вы говорите, что реальных методов не существует, но что SBApplication стимулирует методы, динамически перехватывая `responsedsToSelector:` и друзей и конвертируя отправленные сообщения в отправленные Apple Event?   -  person Sean DeNigris    schedule 30.05.2020
comment
Почему бы не изучить альтернативный мост с открытым исходным кодом, чтобы увидеть, как это работает? Посмотрите appscript и т. Д. Внимание, вот драконы. Если бы это было легко, это сделали бы другие.   -  person matt    schedule 30.05.2020
comment
Очевидно, что существуют методы для реализации различных событий Apple, но было бы просто совпадением, если бы существовал метод с тем же именем, что и термин сценария. Нет однозначного соответствия терминологии и методов сценариев приложения в целевом приложении, но вы все равно не можете просто вызвать какой-либо случайный метод в другом приложении - см. Руководство по созданию сценариев какао для получения дополнительной информации. .   -  person red_menace    schedule 30.05.2020
comment
@matt, я, должно быть, плохо объясняю! Использовал Applescript в течение 12 лет; многому научился из ур книги :) Люблю писать сценарии, но ненавижу AS. appscript был мечтой, пока не умер. Теперь SB может работать в моих сценариях использования. Войдите в мост Pharo / Smalltalk Obj-C выше. Я хочу использовать SB через этот мост для связи со скриптовыми приложениями из Pharo. Однако мост Pharo не может напрямую находить / отправлять сообщения, созданные SB, которые отражают команды / свойства сценария, потому что он внутренне использует class_getInstanceMethod, что не работает для этих методов SB. Таким образом, я ищу обходной путь ...   -  person Sean DeNigris    schedule 01.06.2020
comment
Ах, ну извини, я ничего не знаю ни о Фаро, ни о class_getInstanceMethod. Но appscript имеет открытый исходный код, поэтому, как я уже сказал, если вы хотите знать, как это работает, вы можете убедиться в этом сами. Это все, что я могу предложить, извините.   -  person matt    schedule 01.06.2020
comment
@red_menace Разве ScriptingBridge не так уж и хорош в том, что он предоставляет сообщения Obj-C, которые однозначно соответствуют условиям сценария ?! Пример отправки playhouse был из документа SB, а не из того, что я придумал (и работает в Obj-C, только не в Pharo без некоторой магии времени выполнения Obj-C).   -  person Sean DeNigris    schedule 01.06.2020
comment
@matt Спасибо. Я посмотрю, может ли appscript вдохновить.   -  person Sean DeNigris    schedule 01.06.2020
comment
ScriptingBridge работает иначе - он связывает объекты Какао и типы данных, но используется механизм Apple Events, а не сообщения Objective-C. Целевое приложение получает и отправляет эти события Apple Events, реализуя их через свой скриптовый интерфейс, что и определяет термины.   -  person red_menace    schedule 01.06.2020
comment
@red_menace Я понимаю, что в конечном итоге события Apple отправляются в целевое приложение. Мне не хватает того, что происходит между [iTunes playpause] и событием Apple. Если SB не использует сообщения Objective-C, что означают документы, когда подклассы SBApplication реализуют методы, специфичные для приложения, которые автоматически обрабатывают отправку событий Apple? Почему iTunes respondsToSelector: @"playpause" работает, т.е. возвращает истину? А как [iTunes playpause] работает? И т. Д. И т. Д.   -  person Sean DeNigris    schedule 03.06.2020
comment
Потому что вы начинаете с создания физического класса iTunes на основе словаря iTunes (sdef), в котором объявлен playpause. И это подкласс SBApplication. Гоша, мне очень жаль, что я удалил свой предыдущий комментарий.   -  person matt    schedule 03.06.2020


Ответы (1)


Если SB не использует сообщения Objective-C, что означают документы, говоря «подклассы SBApplication реализуют специфичные для приложения методы, которые автоматически обрабатывают отправку событий Apple»? Почему iTunes RespondsToSelector: @ "playpause" работает, т.е. возвращает истину? А как работает [iTunes playpause]? И т. Д. И т. Д.

Это работает, потому что самое первое, что вы делаете в приложении-мосте сценариев, - это генерируете заголовок. В 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 ресурс целевого приложения и преобразовать эту информацию в способ для соответствующих команд быть дано на вашем языке, каким бы он ни был.

person matt    schedule 03.06.2020
comment
Я очень ценю ваш постоянный диалог по этому поводу. Мне хочется принять этот ответ, потому что он охватывает большую часть моего вопроса, и я подозреваю, что это лучшее, что я собираюсь получить. Но пока я исследую appscript, я изменил вопрос, чтобы сосредоточиться на основной части, которая все еще отсутствует, т.е. что особенного в сообщениях сценария SB, что они не действуют как другие сообщения Obj-C (кроме случаев, когда они это делают!)? Например, почему функции времени выполнения Obj-C, такие как class_getInstanceMethod и class_copyMethodList, не видят сообщения сценария, такие как playpause? - person Sean DeNigris; 04.06.2020
comment
Я недостаточно знаю о функциях времени выполнения, чтобы ответить на этот вопрос. Я думаю, это связано с тем, что динамически генерируемый класс, такой как iTunesApplication, не связан с приложением. Но я не понимаю, насколько это актуально; такого рода функции времени выполнения - это никогда не то, как вы собирались это делать. Это не так, как работает любой мост AppleScript. Это моя точка зрения. - person matt; 04.06.2020
comment
Хорошо, позвольте мне покопаться в коде приложения и посмотреть, смогу ли я решить эту проблему на основе ваших щедрых указателей ... - person Sean DeNigris; 04.06.2020
comment
Ну, это зависит от вкуса appscript. Есть вариант Swift, который работает точно так, как я только что описал: вы начинаете с явной генерации файла кода Swift на основе sdef целевого приложения, который затем встраивается в проект. Мое приложение SyncMe3 использует это для написания сценария Finder. - person matt; 04.06.2020
comment
С другой стороны, rb-appscript, разновидность ruby, не требует этого, потому что ruby ​​использует утиную печать: компилятор позволит вам сказать что угодно на any объект, и он может быть разрешен во время выполнения, поэтому нет необходимости в предварительном создании файла кода, чтобы его можно было скомпилировать позже. Вместо этого мы начинаем со второго шага, когда наш код обращается к целевому приложению, динамически получает его sdef, интерпретирует его и использует результат для ответа на method_missing. - person matt; 04.06.2020
comment
Другими словами, некоторые языки не могут компилироваться без предварительно сгенерированного связующего кода: Perl, Objective-C, Swift - общие примеры. Другие языки более динамичны и позволяют говорить что угодно: JavaScript, Python, Ruby. - person matt; 04.06.2020