4

I'm invoking a small AppleScript handler from within a larger Cocoa/Objective-C app (using the AppleScript-ObjC framework to call the AppleScript methods directly from the Objective-C code). I originally tried using Scripting Bridge, but that didn't work for me due to compatibility problems with the external app. The only purpose of the AppleScript is essentially to send a small string to the external app. This is the first time I've attempted to do something like this in order to control an external application, so please bear with me if I'm making an obvious mistake.

Occasionally, the AppleScript may encounter errors depending on the state of the external application, and I would like to handle those errors appropriately in my Objective-C code.

The way I'm doing this right now is that I have a try block in the AppleScript:

try
    -- Do stuff
    return the number 0
on error the error_message number the error_number
    return the error_number
end try

I return zero if the script finishes normally, and return the error number if it doesn't. Then in the Objective-C code, I test the return value and throw a "normal" exception in cases where there is an error, so that I can handle that in my regular exception-handling code.

This works, but is there a way to trap the AppleScript exception directly from the Objective-C code? When I run the code without the AppleScript try-error code in XCode, it seems that it hits my exception breakpoint before it returns from the AppleScript, but it never invokes the @catch block for NSException. Is there some way to catch that exception that is being hit somehow? I'm guessing the answer is no, but I wanted to check that there isn't some better way to do this than what I'm doing. Apple's documentation on AppleScript-ObjC is rather sparse, and mostly just discusses handling errors within AppleScript.

Update: So, I just tried playing around with the breakpoint setup a little bit, and it turns out that it's a C++ exception being sent from the AppleScript code, not an Objective-C one. That's why it wasn't being caught by the @catch block I was using. It seems that the AppleScript-ObjC mechanism uses exceptions for control flow/recoverable errors, and I had my breakpoint set up to break when they are thrown. So, I think it's probably best to just catch the errors in the AppleScript application, and return an error code as a string or integer to the Objective-C code.

mnemia
  • 1,549
  • 2
  • 12
  • 14
  • 4
    I can't answer your question specifically, but I'll point out that in Objective-C/Cocoa, exceptions are meant to indicate truly exceptional conditions, typically due to programmer error rather than errors that can be safely recovered from. From the [documentation](http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/Exceptions/Articles/ExceptionsAndCocoaFrameworks.html): "The Cocoa frameworks are generally not exception-safe. The general pattern is that exceptions are reserved for programmer error only, and the program catching such an exception should quit soon afterwards." – Andrew Madsen Aug 01 '13 at 17:30
  • That's a good point, although some of the errors that could be coming from the AppleScript could well be unrecoverable errors. The only reason I ask that is that invoking the AppleScript code DOES seem to raise an exception that hits my exception breakpoint even for minor, recoverable errors...but it's not a normal Cocoa NSException, from what I can tell. So I just want to know if I can catch that from my Objective-C code (as opposed to AppleScript) somehow. – mnemia Aug 01 '13 at 18:14
  • Interesting. How have you setup your exception breakpoint? What does the stack trace look like when the breakpoint is tripped? – Andrew Madsen Aug 01 '13 at 19:28
  • How are you're running the AppleScript in the code. And sending the string. I ask this because NSApplescript has the executeAndReturnError: method. – markhunte Aug 03 '13 at 08:06
  • I'm using the AppleScript-ObjC (ASOC) framework to run it. (Apple has about 4 methods of running AppleScript events from Objective-C code). With ASOC, you can embed AppleScript code directly into your Objective-C program, and call methods implemented in AppleScript from Objective-C (although the process for setting this up leaves a little bit to be desired: you have to implement an NSObject category to tell Objective-C about the signature of the AppleScript methods, since there are no header files for them). I used this method because it seems better than using NSAppleScript with strings. – mnemia Aug 03 '13 at 17:40
  • Don't mess with categories; just declare a protocol, `@protocol YOURClass [method signatures go here] @end`, and use `NSClassFromString(@"YOURClass")` to retrieve your AppleScript 'class' so you can instantiate it. As for NSAppleScript, don't mess with code munging to pass arguments: use -executeAppleEvent:error: to invoke a handler in your script. The downside is you have to pack and unpack the NSAppleEventDescriptors yourself (or find a 3rd-party wrapper that does it for you); so yeah, ASOC is usually the much better choice as it does [most] ObjC<->AS mappings automatically. – foo Aug 06 '13 at 12:06
  • BTW, the main hassle with using ASOC from ObjC seems to be in passing non-object values (C ints, floats, pointers) as arguments/results to AS-based methods. ASOC seems to know the right signatures for existing Cocoa methods so maps their args/results correctly, but for methods you define yourself it always seems to treat all args and results as `id`, and crashes on anything else. (Very annoying, and makes me wonder if the AS devs cheated by hardcoding mappings for existing Cocoa methods instead of using -[NSObject methodSignatureForSelector:], which AS 'classes' could then override if needed.) – foo Aug 06 '13 at 12:12
  • Thanks for the extra info. Yeah, I actually came to the same conclusion about using categories later myself, and had already switched over (I had seen a few tutorials and such that said the obvious, but found that a simple protocol worked fine when I tried it.) Luckily, I so far haven't had much need to pass any non-object values as parameters to ASOC code (I'm only using NSString), so I've avoided that mess. But overall, I agree that it seems a lot easier and cleaner than using NSAppleScript. – mnemia Aug 06 '13 at 16:35
  • FWIW, ObjC now provides a concise boxing syntax for C literals and expressions (`@5.5`, `@YES`, `@(myVar)`) etc. so passing primitive values to ASOC handlers is much less of a chore than it used to be: just use the `@(...)` syntax to box your C primitive as its Cocoa equivalent when writing your method calls in ObjC. (Just remember to declare the argument type on your protocol as `NSNumber*`, not `int`/`NSInteger`/`BOOL`/etc.) – foo Aug 06 '13 at 20:35

1 Answers1

0

AppleScript uses C++ exceptions to implement its own exceptions, but nothing else; you shouldn’t see them in code that completes successfully. If an AppleScript exception escapes to point of returning to Objective-C, the bridge actually swallows it -- you should get a log message, and a nil/zero/NO return value from the bridged method. The upshot of all this is that you’re already doing the right thing: trap the error in AppleScript and return something that your Objective-C code can detect.

As a side note, AppleScriptObjC is mainly intended for developers writing applications primarily or entirely in AppleScript. If you’re writing a primarily Objective-C application with a few bits that control other applications via scripting, consider using ScriptingBridge instead.

Chris N
  • 905
  • 5
  • 10
  • Except ScriptingBridge doesn't work properly and often breaks on stuff that works perfectly in AS. The only supported way to speak Apple events correclty is AS, so being able to use ASOC instead of messing with NSAppleScript crap is a big benefit for ObjC users too. FWIW, I've been trying to figure out how ASOC's ObjC->AS argument mapping works, but haven't had much luck. For now, I think the least awful option might be to return an NSArray containing 2 values: the result (return value or 'missing value') and error info ('missing value' or NSError); but if I find a better way I'll post it. – foo Aug 06 '13 at 11:45
  • Yep. As I said above, I initially tried to use Scripting Bridge and found it to be a nightmare for some applications. I like the idea of Scripting Bridge, but it doesn't work correctly in practice because the (good) idea is so poorly implemented. Just to get it to autogenerate headers that would compile took manual editing of the dictionary files, and some AppleScript events simply didn't work (while others did), for the exact same actions as normal AS code that worked fine. ASOC worked much better, although it's also not without its warts. – mnemia Aug 06 '13 at 16:41
  • Yeah, Apple really screwed up SB. ObjC-Appscript worked (works?) an absolute treat and it's a huge shame Apple didn't learn from/copy/steal it, but it's no longer maintained or supported as a result of Apple legacying/deprecating the Carbon APIs it relies on without providing any Cocoa alternatives. For doing anything non-trivial from ObjC nowadays, AppleScript-ObjC is often the best option: AppleScript may be a PITA as a language but it knows how to speak Apple events properly, and the ASOC bridge takes most of the pain out of connecting the two. – foo Aug 06 '13 at 20:48
  • 1) If ScriptingBridge doesn’t work correctly on some applications, file bugs. If they don’t know about them, they won’t fix them. 2) ObjC-Appscript isn’t really as doomed as the author seems to think it is. The Apple Event Manager documentation moved to the “retired” library, but all the calls are still flagged as available and not deprecated. Trust the headers. – Chris N Aug 07 '13 at 22:32
  • 1) I'm willing to file bugs with Apple and occasionally do, although I've found that most bugs I file on radar get ignored completely (not even marked as duplicates), even if I give detailed test cases and sample code. So I've come to see the exercise as a bit pointless. 2) SB DOES have some serious bugs/problems in that it fails for applications that work fine with plain AppleScript. 3) That's good to hear, although I'd prefer an Apple solution if it exists (where Apple is going with scriptability seems to be a question, given the plethora of semi-competing ideas they have put out.) – mnemia Aug 07 '13 at 23:08
  • Chris N: why should anyone bother though? 10.5 was a few days out the door when Matt Neuburg raised the first compatibility bug affecting BBEdit, to which the response was "we designed it to work with Cocoa Scripting-based apps". (Ironic attitude since Finder, iTunes, FileMaker, Office, Adobe CS and most major AppleScriptable productivity apps are Carbon-based.) In any case, SB's design is fundamentally flawed; the only way to fix it properly would be to throw it out and start over. (Appscript had already solved the whole AE bridge design problem, btw; it was SB that unsolved it again.) – foo Aug 08 '13 at 00:00
  • As for "trust the headers", I wouldn't trust Apple not to yank any Carbon API with zero warning, regardless of how essential or irreplaceable it is to third-party developers. They weren't afraid to upset heavyweights like Adobe and MS when they pulled the Carbon 64 GUI APIs; QuickTime-based developers are still crying over what's been done to that; and 10.8 moved a large part of the remaining Carbon APIs straight to deprecated status with zero warning, including Component Manager, Alias Manager and a bunch of others upon which AppleScript itself is built. Carbon is a bad risk, period. – foo Aug 08 '13 at 00:06
  • Accepting this answer, as it basically answers my question, although I don't really agree that SB is a good alternative. – mnemia Aug 12 '13 at 17:28