Objective-C Scripting Bridge and Apple Remote Desk

2019-08-05 21:08发布

问题:

Trying to automatically view a computer in Apple Remote Desktop via Scripting Bridge in Objective-C with this:


    @try {
        SBApplication *RD = [SBApplication applicationWithBundleIdentifier:@"com.apple.RemoteDesktop"];

        // (code to check for ARD running and installed omitted here)
        [RD activate]; // works just fine

        RemoteDesktopComputer *computer = [[[RD classForScriptingClass:@"computer"] alloc]     initWithProperties:
            [NSDictionary dictionaryWithObjectsAndKeys:
                ipAddress,@"InternetAddress", // looked up from header
                nil
             ]
        ];

        // attempt to add it to a container first:
        [(SBElementArray*)[(RemoteDesktopApplication*)RD computers] addObject:computer]; 
        // this is what raises the exception:
        [computer observeZooming:Nil];
    }
    @catch (NSException *e) {
        NSLog(@"Exception: %@", [e description]);
    }

Running this yields the following exception in the log:

    Exception: *** -[SBProxyByClass observeZooming:]: object has not been added to a container yet; selector not recognized [self = 0x6050004819b3]

I've done as much research as there is available on this subject and have learned that SB isn't the easiest to deal with because of how it's wired under the hood, but any experts or veterans of native Scripting Bridge (no third party frameworks or languages other than obj-c, please) is much appreciated.

All prerequisites like linking to the ScriptingBridge.framework and importing Remote Desktop.h are performed - the typecasts are to avoid what appear to be unavoidable link-time errors when building...

Edit 1: Reading the documentation on SBObject (parent of RemoteDesktopComputer) says that it's a reference rather than an actual instance, which you can fetch by calling SBObject's get method (returns id). So I tried running this as well but unfortunately received the same results:

    [[computer get] observeZooming:Nil];

Here's the documentation on SBObject: https://developer.apple.com/library/mac/documentation/cocoa/Reference/SBObject_Class/SBObject/SBObject.html#//apple_ref/occ/instm/SBObject/get

Still trying...

回答1:

(FWIW, I already had the following How To written up, so I'm leaving it here for future reference.)


How to use AppleScript-ObjC in place of Scripting Bridge

Scripting Bridge is, at best, an 80/20/80 "solution" (i.e. 80% of the time it works, 20% of the time it fails, and 80% of the time you've no idea why). There's little point trying to argue with SB when it breaks on stuff that works perfectly well in AppleScript - the Apple engineers responsible designed it that way on purpose and simply refuse to accept they broke spec [1] and screwed up. As a result, the AppleScript language, for all its other deficiencies, remains the only supported solution that is guaranteed to speak Apple events correctly [2].

Fortunately, since OS X 10.6 there has been another option available: use ObjC for all your general programming stuff, and only call into AppleScript via the AppleScript-ObjC bridge for the IPC stuff.

From the POV of your ObjC code, your AppleScript-based ASOC 'classes' are more or less indistinguishable from regular ObjC classes. It requires a bit of fiddling to set up, and you'll pay a bit of a toll when crossing the bridge, but given the crippled, unreliable nature of the alternatives, it's the least horrid of the supported options for anything non-trivial.

Assuming you've already got an existing ObjC-based project, here's how to add an ASOC-based class to it:

  1. In Targets > APPNAME > Build Phases > Link Binary With Libraries, add AppleScriptObjC.framework.

  2. In Supporting Files > main.m, add the import and load lines as shown:

    #import <Cocoa/Cocoa.h>
    #import <AppleScriptObjC/AppleScriptObjC.h>
    
    int main(int argc, const char * argv[]) {
       [[NSBundle mainBundle] loadAppleScriptObjectiveCScripts];
       return NSApplicationMain(argc, argv);
    }
    
  3. To define an ASOC-based class named MyStuff that's callable from ObjC, create a MyStuff.h interface file that declares its public methods:

    // MyStuff.h
    
    #import <Cocoa/Cocoa.h>
    
    @interface MyStuff : NSObject
    
    // (note: C primitives are only automatically bridged when calling from AS into ObjC;
    // AS-based methods with boolean/integer/real parameters or results use NSNumber*)
    
    -(NSNumber *)square:(NSNumber *)aNumber;
    
    @end
    

    along with a MyStuff.applescript file containing its implementation:

    -- MyStuff.applescript
    
    script MyStuff
    
       property parent : class "NSObject"
    
       on square_(aNumber)
           return aNumber ^ 2
       end square_
    
    end script
    
  4. Because the MyStuff class doesn't have an ObjC implementation, the linker can't link your ObjC code to it at build-time. Instead, use NSClassFromString() to look up the class object at run-time:

    #import "MyClass.h"
    
    ...
    
    MyStuff *stuff = [[NSClassFromString(@"MyStuff") alloc] init];
    

    Otherwise it's pretty much indistinguishable from a native ObjC class in normal use:

    NSNumber *result = [stuff square: @3];
    NSLog(@"Result: %@", result);
    

HTH

--

[1] Apple management broke up the original AppleScript team shortly after its initial release, causing its designers to quit in response, so a lot of knowledge of precisely how this stuff should work was lost. In particular, a full, formal specification was never produced for application developers to follow when designing their scripting support, so all they could do was use personal judgement and best guesses, then test against AppleScript to check it worked as hoped. Thus, AppleScript's own Apple event bridge is the de facto specification that every single scriptable app has been implemented against in the last twenty years, so the only way that other AE bridges can ever work correctly is if they mimic AS's own bridge down to every last query and quirk - a lesson, unfortunately, that the current AS team have repeatedly failed to understand [2].

[2] JavaScript for Automation's Apple event supported is equally crappy and busted, incidentally.



回答2:

Scripting Bridge is a defective, obfuscated mess, so when an application command fails to work you've no idea if the problem is SB being defective or the application itself being buggy or simply requiring you to phrase it in a different way.

Therefore, the first step is to write a test script in AS to see it works there. If it does, it's SB that's crap; if not, try fiddling with your AS code (e.g. try phrasing the reference for the at parameter in different ways, or omitting it entirely) till it does.

You should also ask on Apple's AppleScript Users and ARD mailing lists and anywhere else that ARD scripters are likely to hang out, as most apps' scripting documentation is grossly inadequate, so a lot of knowledge of how to do things is word of mouth. (The guy you really want to talk to is John C Welch, aka @bynkii, as he's the guru of ARD scripting.)