Proper way to execute an AppleScript?

  1. ‹ Older
  2. 6 years ago

    jim m

    2 Apr 2014 Pre-Release Testers, Xojo Pro piDog.com
    Edited 6 years ago

    Looks like just the NSAppleScript object needs to be released…

    Function ExecuteAppleScript(TheScript as String) As String
      #if TargetMacOS
        soft declare function NSClassFromString lib "Foundation" (classname as CFStringRef) as ptr
        soft declare function initWithSource lib "Cocoa" selector "initWithSource:" (obj as ptr,source as CFStringRef) as ptr
        soft declare function executeAndReturnError lib "Cocoa" selector "executeAndReturnError:" (obj as ptr,byref error as ptr) as ptr
        soft declare function alloc lib "Cocoa" selector "alloc" (classRef as Ptr) as Ptr
        soft declare function stringValue lib "Cocoa" selector "stringValue" (classRef as Ptr) as CFStringRef
        soft declare sub CFRelease lib "Cocoa" (obj as ptr)
        
        
        dim nsscript As ptr=initWithSource(alloc(NSClassFromString("NSAppleScript")),TheScript)
        dim err As ptr
        dim descriptor As ptr=executeAndReturnError(nsscript,err)
        if err<> nil then
          Raise New RuntimeException
        else
          CFRelease(nsscript)
          Return stringValue(descriptor)
        end if
      #endif
    End Function
  3. Dave S

    2 Apr 2014 San Diego, California USA

    @Mark W Try this:

    Set the text item delimiters to "/" // Use whatever delimiter you wish here
    Tell application "iCal"
    set theCalendarNames to title of every calendar As String
    return theCalendarNames
    end tell

    Hmmm... AppleScript Editor returns a string...

    but Jims code returns an ERROR

  4. Dave S

    2 Apr 2014 San Diego, California USA

    Works now .... I had to upgrade to the last version he posted.

    Thanks

  5. jim m

    2 Apr 2014 Pre-Release Testers, Xojo Pro piDog.com

    This executes
    Set the text item delimiters to "/" Tell application "iCal" set theCalendarNames to title of every calendar As String return theCalendarNames end tell

    Did you remember to remove the comment? "--" is the comment tag in applescript
    Could also be a line ending issue if using copy/paste in a textarea..

  6. Edited 6 years ago

    @jim m Looks like just the NSAppleScript object needs to be released…

    It'd be better autoreleased. Also, you can't check err to see if there was an error -- you need to check if the return value from executeAndReturnError: was nil.

    One other thing to be aware of is that this won't always return the same results as dragging an AppleScript into the project and calling it. NSAppleEventDescriptor's stringValue method coerces it to typeUnicodeText where as the framework's AppleScript code uses OSACopyDisplayString and passes in kOSAModeDisplayForHumans. Most of the time it won't matter, but there are edge cases where it does.

    Here's my version:

    Function ExecuteAppleScript(scriptSource As String) As String
      #If TargetMacOS
        Declare Function NSClassFromString Lib "Foundation" (classname As CFStringRef) As Ptr
        Declare Function alloc Lib "Foundation" Selector "alloc" (classRef As Ptr) As Ptr
        Declare Function autorelease Lib "Foundation" Selector "autorelease" (obj As Ptr) As Ptr
        Declare Function description Lib "Foundation" Selector "description" (obj As Ptr) As CFStringRef
        // NSAppleScript methods
        Declare Function initWithSource Lib "Foundation" Selector "initWithSource:" (obj As Ptr, source As CFStringRef) As Ptr
        Declare Function executeAndReturnError Lib "Foundation" Selector "executeAndReturnError:" (obj As Ptr, ByRef error As Ptr) As Ptr
        // NSAppleEventDescriptor methods
        Declare Function stringValue Lib "Foundation" Selector "stringValue" (classRef As Ptr) As CFStringRef
        
        Static NSAppleScript As Ptr = NSClassFromString("NSAppleScript")
        Dim script As Ptr = autorelease(initWithSource(alloc(NSAppleScript), scriptSource))
        
        Dim err As Ptr
        Dim descriptor As Ptr = executeAndReturnError(script, err)
        If descriptor = Nil Then
          // Excercise to the reader: create a RuntimeException subclass that extracts
          // information from the error dictionary.
          Dim exc As New RuntimeException
          exc.Message = description(err)
          Raise exc
        End If
        
        Return stringValue(descriptor)
      #EndIf
    End Function
  7. Kem T

    2 Apr 2014 Pre-Release Testers, Xojo Pro, XDC Speakers, MVP Connecticut

    The error has to be released too. Trust me, I had to work hard to convince my MacOSLib partners, and I finally found it documented in CFError.h:

    Usage recommendation for CFErrors is to return them as by-ref parameters in functions. This enables the caller to pass NULL in when they don't actually want information about the error. The presence of an error should be reported by other means, for instance a NULL or false return value from the function call proper:

    CFError *error;
    if (!ReadFromFile(fd, &error)) {
    ... process error ...
    CFRelease(error); // If an error occurs, the returned CFError must be released.
    }

    An error is created for you and retained, then passed as a Ptr. Once you're done with it, you have to release it because, otherwise, the OS has no way of knowing that it needs to be released.

  8. Kem T

    2 Apr 2014 Pre-Release Testers, Xojo Pro, XDC Speakers, MVP Connecticut

    If memory serves, you can pass nil for the error parameter if you don't want that error back. That would work well in Joe's version.

  9. jim m

    2 Apr 2014 Pre-Release Testers, Xojo Pro piDog.com

    Right Kem, I see it now. I didn't see it as a leak because if there is no error there is no object. In the case where an error occurs, the error must be released!

  10. Kem T

    2 Apr 2014 Pre-Release Testers, Xojo Pro, XDC Speakers, MVP Connecticut

    This discussion serves as an unneeded reminder as to why I prefer Xojo to Obj-C.

  11. @Kem T The error has to be released too. Trust me, I had to work hard to convince my MacOSLib partners, and I finally found it documented in CFError.h

    This is only true for CFErrorRef. NSError output parameters are autoreleased and you don't have to release it yourself.

  12. Michel B

    2 Apr 2014 Pre-Release Testers, Xojo Pro

    @Joe R It'd be better autoreleased. Also, you can't check err to see if there was an error -- you need to check if the return value from executeAndReturnError: was nil.

    One other thing to be aware of is that this won't always return the same results as dragging an AppleScript into the project and calling it. NSAppleEventDescriptor's stringValue method coerces it to typeUnicodeText where as the framework's AppleScript code uses OSACopyDisplayString and passes in kOSAModeDisplayForHumans. Most of the time it won't matter, but there are edge cases where it does.

    Here's my version:
    Function ExecuteAppleScript(scriptSource As String) As String #If TargetMacOS Declare Function NSClassFromString Lib "Foundation" (classname As CFStringRef) As Ptr Declare Function alloc Lib "Foundation" Selector "alloc" (classRef As Ptr) As Ptr Declare Function autorelease Lib "Foundation" Selector "autorelease" (obj As Ptr) As Ptr // NSAppleScript methods Declare Function initWithSource Lib "Foundation" Selector "initWithSource:" (obj As Ptr, source As CFStringRef) As Ptr Declare Function executeAndReturnError Lib "Foundation" Selector "executeAndReturnError:" (obj As Ptr, ByRef error As Ptr) As Ptr // NSAppleEventDescriptor methods Declare Function stringValue Lib "Foundation" Selector "stringValue" (classRef As Ptr) As CFStringRef // NSError methods Declare Function localizedDescription Lib "Foundation" Selector "localizedDescription" (obj As Ptr) As CFStringRef Static NSAppleScript As Ptr = NSClassFromString("NSAppleScript") Dim script As Ptr = autorelease(initWithSource(alloc(NSAppleScript), scriptSource)) Dim err As Ptr Dim descriptor As Ptr = executeAndReturnError(script, err) If descriptor = Nil Then // Excercise to the reader: create a RuntimeException subclass that also has a // domain property (as string), extract the NSError domain/code into it, and // throw that instead of a generic RuntimeException. Dim exc As New RuntimeException exc.Message = localizedDescription(err) Raise exc End If Return stringValue(descriptor) #EndIf End Function

    scriptSource : this item does not exist :(

  13. jim m

    2 Apr 2014 Pre-Release Testers, Xojo Pro piDog.com

    @Kem T This discussion serves as an unneeded reminder as to why I prefer Xojo to Obj-C.

    Let's just hope nobody asks how to get the native rtf from a textedit in win32 with declares… :-/

  14. @Michel B scriptSource : this item does not exist :(

    My version used a different parameter name than the previous ones (because I am pedantic and like being different). Did you update the function signature in your project?

  15. Michel B

    2 Apr 2014 Pre-Release Testers, Xojo Pro

    @Joe R My version used a different parameter name than the previous ones (because I am pedantic and like being different). Did you update the function signature in your project?

    That was it. It works now. Thank you.

  16. Kem T

    2 Apr 2014 Pre-Release Testers, Xojo Pro, XDC Speakers, MVP Connecticut

    @Joe R This is only true for CFErrorRef. NSError output parameters are autoreleased and you don't have to release it yourself.

    Good to know. How does it know when to autorelease it?

  17. @Kem T Good to know. How does it know when to autorelease it?

    The NSError is autoreleased as soon as it's created and before it's returned to the caller. This means that the caller doesn't have to worry about its lifetime because it'll be released when the 'nearest' autorelease pool goes away.

  18. Dave S

    2 Apr 2014 San Diego, California USA

    This is all good stuff :)

    Thanks Guys....

  19. Kem T

    2 Apr 2014 Pre-Release Testers, Xojo Pro, XDC Speakers, MVP Connecticut

    @Joe R The NSError is autoreleased as soon as it's created and before it's returned to the caller. This means that the caller doesn't have to worry about its lifetime because it'll be released when the 'nearest' autorelease pool goes away.

    Is it an error to release it yourself?

  20. @Kem T Is it an error to release it yourself?

    Yes.

  21. Kem T

    2 Apr 2014 Pre-Release Testers, Xojo Pro, XDC Speakers, MVP Connecticut

    Good to know, thanks.

  22. Newer ›

or Sign Up to reply!