1
votes

I'm trying to get a Mac Word faux-plugin to work and I'm running into a few weird issues. Sorry - wall of text incoming!

I realize I'm touching on the intersection of several voodoo topics here, so please bear with me.

We've got...

  1. Microsoft Word 2011 Mac
  2. AppleScript
  3. AppleScript-Obj-C

... trying to fake a plug-in. Which I realize is absolutely not supported.

My setup:

  • OS: macOS El Capitan, Xcode 7
  • Word: 2011 (most recent patches)

End of day, the plugin manipulates Word field codes. It creates and edits them. There's a super simple UI to do things like this them and then under the hood the create/update/delete functionality is in various classes.

Theory is:

  1. Export Word AppleScript headers with sdef, then create Obj-C interfaces using sdp (Scripting Bridge)
  2. In some cases where that's just broken (find, for example), bridge to AppleScript with AppleScript-Objective-C
  3. Write Obj-C methods and view controllers to present user with controls

Running through XCode unit tests I've actually got everything working. Hosting through an external dummy app, all works well, too. But what I'd really love to do is have this embedded into Word.

So I looked at two similar, but different options for doing that:

  1. Deploy as framework, call into Framework by referencing a public C interface (via VBA)
  2. Deploy as framework, call through AppleScript (via VBA...)

They're close, but... not quite right.

Option 1 - Framework w/ C Interface

With this approach, you create public C interfaces into your framework and use those to create view controllers. You reference the framework in Word and can call out to the public C methods. Because your framework is more directly hosted by Word, you have to compile it as 32-bit.

Steps:

  1. Create a .m file in your framework and expose public methods for accessing your UI (EX: "MyLibOpenCustomUI").
  2. Reference your framework in VBA
  3. VBA Macro (OpenOurCustomUI) messages your public C interface
Private Declare Sub MyLibOpenCustomUI Lib "/Library/Frameworks/MyFramework.framework/MyFramework" ()
Sub OpenOurCustomUI()
    Call MyLibOpenCustomUI
End Sub

Issues:

This worked well, but for a serious issue with Word field codes. The behavior is incredibly inconsistent.

  1. Attempting to access a field's fieldText (aka "data" from Windows) will frequently return data parameter is nil. The entry_index, range, and other components all seem to be fine. For whatever reason, fieldText appears to be inaccessible.
  NSLog(@"fieldText : %@", [field fieldText]);//data parameter is nil
  1. On those occasions when you can access fieldText (it's still unclear to me how/why this occasionally works), the fieldText is "corrupted" after exactly 256 characters. I can only say "corrupted" as the text appears to go from ascii text to some form of unicode characters and also appears to be truncated after that.

EX: (Then : after "Name" is at position 256)

"Name":䜽芟㘱瑵飯ﶿṼﶿ璒澗瑸瑵ᕻ各

Thinking this could be odd behavior with the AppleScript-Objective-C bits when running via the external framework, I actually tried to call out to AppleScript from Objective-C (embed applescript, write wrapper in Obj-C, then load script at runtime).

Something like this... it just attempts to return the field text for a given field (by numeric index)

  on getFieldDataForFieldAtIndex:theIndex
    set theIndex to theIndex as integer
    tell application "Microsoft Word"
      set theField to field theIndex of active document
      return field text of theField
    end tell
    return missing value
  end getFieldDataForFieldAtIndex:

This gave me a more interesting error: Microsoft Word got an error: Can’t get active document. (error -1728)

So - no dice. When it does work I get "corrupt" string responses. But more times than not I just can't get at the fieldText.

I have absolutely no idea why this is failing and at this point I have to believe it's got to do with how the framework is interacting with Word when launched like this. Which stinks, really. I'd love for this to work.

Option 2 - Framework w/ osascript + AppleScript

With this approach, you use vba to call osascript to call out to an applescript that hosts your UI (I know... I know...). You compile your framework as 64-bit.

In this version, Word fields work fine - strings look great and all field fieldText properties can be accessed and set without issue.

Steps:

  1. Reference popen in VBA
  2. VBA Macro (OpenMyUI)
  3. Calls osascript
  4. Which calls AppleScript (OpenMyUI.scpt) to call public method "show me the interface" in your framework
Private Declare Function popen Lib "libc.dylib" (ByVal command As String, ByVal mode As String) As Long
Sub OpenMyUI()
    'this works from the VBA debugger
    'I _cannot_ get it to work from the VBA when launched from a button in a document... 
    'window closes immediately w/o input    
    Dim scriptToRun As String
    Dim output As String
    Dim command As String
    command = "osascript ~/Library/Application\ Scripts/com.microsoft.Word/OpenMyUI.scpt"
    output = popen(command, "r")
    'If you want this to stick around, you have to do something like...
    'MsgBox "stick around, window"
End Sub
--reference our framework
use framework "/Library/Frameworks/MyFramework.framework"
property theWindowLauncher : class "MyWindowLauncher"

on openMyUI(arg)
    set theReturn to theWindowLauncher's openUpdateOutput
    return theReturn
end openSettings

return openMyUI(1)

Issues:

There are two biggies:

  1. When you invoke the sub via a button click, etc., the UI disappears or never presents. Unless you subsequently call something like "MsgBox 'something'" in VBA. Then it appears. But the behavior is odd - if you don't focus on the custom UI window before closing the dialog box, then the custom UI disappears.
  2. Bigger than that, the window (at least on my machine) became globally modal. Which I've never seen before. That's definitely not good.

What's odd is this works well through the Word VBA debugger. Works perfectly, actually. Just not when you run it by calling the sub directly from a Macro (like on a button click).

Very, very close on both options. Just not close enough. I'm particularly stumped by the fieldText weirdness.

So... I'm wondering. Any creative ideas on how to proceed? I realize what I'm doing here is well past the bounds of "good idea," but I'd love to see this work.

1

1 Answers

1
votes

I still can't answer the issue with option 1 (straight C library reference and broken Word fields), but for option 2 (call out to AppleScript via osascript), I figured out that the window actually did appear - but behind every other window I had up (across all apps) so I never realized it was there.

Solution (option 2): It was then a matter of telling the window to come to the foreground and it "worked" - albeit with the caveat that it was now a globally modal window (not desired).

Something ala...

//wc is our window controller
NSWindow* theWindow = [wc window];
[theWindow setTitle:windowTitle];
[NSApp activateIgnoringOtherApps:YES];
[theWindow setLevel:NSMainMenuWindowLevel];
[theWindow makeKeyAndOrderFront: self];

[NSApp runModalForWindow: theWindow];

For the moment that's "ok enough" to get by. Not what anyone wants, but OK.

I don't really like this process at all (vba -> C popen -> osascript -> AppleScript -> obj-c class method).

  1. It's just ugly. And I know it will break at some point.
  2. Global modal window = super super bad.
  3. Word 2016... I haven't tested it, but I suspect it will be problematic.

Why go through popen -> osascript -> AppleScript? For the life of me I couldn't get MacScript to successfully call the AppleScript file. Also tried using embedded AppleScript. Neither worked. Just got the usual "error 5."

My AppleScript utilizes use framework to make this work, so my guess is MacScript doesn't support this (but that's just a guess). Other test AppleScript (file or embedded) that didn't incorporate use framework ("Hello, World!") worked fine.

Later I'll probably circle back and see if I can make option 1 work by moving the Word interaction to some sort of external setup - that might work to bypass the weird "data is nil" issue and the periodic inability to get ahold of the active document. Wish I understood more of what's going on with that.