Subscribe Bookmark RSS Feed

JMP 9 automation bug

I'm using JMP 9.0.3 64-bit under Windows 7 and automating it from Python using the win32com module. My automation code is based on the JMP 9 Automation Guide, the PDF of which seems now to have disappeared from the website: http://www.jmp.com/support/downloads/pdf/

Here's the bug: the Document automation object has properties Name, FullName, and Path which are supposed to reflect the table name or file name contained therein. However, in many cases these properties turn out to be blank, despite the fact that the table has a non-blank name within JMP, and despite the fact that the automation object for this table can in fact be retrieved using this name.

Here's some Python code that demonstrates the bug. It creates a table using JSL, saves the name of this table, and looks up the table's automation object by name. It then checks whether table.Document.Name in fact matches the known name of the table, and reports the cases where this doesn't hold. It does this 100 times and typically the name starts coming back blank after the first 2-4 iterations:

from win32com.client import gencache
mod = gencache.GetModuleForProgID("JMP.Application")
app = mod.Application()

okay_table = [False]*100

for ii in range(len(okay_table)):
    # Create a table in JMP
    app.RunCommand("::dt=New Table(); ::retval=dt<<Get Name()")

    # Retrieve the name of that just-created table from the JSL variable
    retval = app.GetJSLValue("retval")

    # Retrieve the automation object for that table, by name
    table = app.GetTableHandleFromName(retval)

    # Now, table.Document.Name **SHOULD** match retval, but
    # it may be blank due to the bug.

    if table.Document.Name=="" and table.Document.Path=="" and table.Document.FullName=="":
        print "table %d: got blank table.Document.Name; Path=%s, FullName=%s" % (ii,
            table.Document.Path, table.Document.FullName)
        app.RunCommand("close(DataTable(::retval), nosave)")
        okay_table[ii]=False
    else:
        print "table %d: looks okay; Name=%s, FullName=%s, Path=%s" % (ii,
            table.Document.Name, table.Document.FullName, table.Document.Path)
        app.RunCommand('close(DataTable("%s"), nosave)' % table.Document.Name)
        okay_table[ii]=True


print "Number of bad tables: %d" % okay_table.count(False)

Typical output:

$ python JMP_automation_bug.py
table 0: looks okay; Name=Untitled 304, FullName=Untitled 304.jmp, Path=Untitled 304.jmp
table 1: looks okay; Name=Untitled 305, FullName=Untitled 305.jmp, Path=Untitled 305.jmp
table 2: got blank table.Document.Name; Path=, FullName=
table 3: got blank table.Document.Name; Path=, FullName=
table 4: got blank table.Document.Name; Path=, FullName=
...
table 98: got blank table.Document.Name; Path=, FullName=
table 99: got blank table.Document.Name; Path=, FullName=
Number of bad tables: 98

This bug is becoming quite a show-stopper for me. I quite frequently need to manipulate tables in automation code and then exchange the table names with JSL code, and this bug makes it impossible to do so reliably.

Has anyone else encountered it? Any known fixes or workarounds?

10 REPLIES
briancorcoran

Joined:

Jun 23, 2011

The documentation should also be installed at c:\program files\sas\jmp\9\Support Files English\Documentation\Automation Reference.pdf.



You might have a timing issue.  You might try a different approach, using the NewDataTable method on the Application object:

myJMP = CreateObject("JMP.Application")

   Call Sleep(1000)

  myJMP.Visible = True

   Dim names As String

  names = ""

 

   For i As Integer = 1 To 30

   Dim dt As JMP.DataTable

   Dim tableName As String

  dt = myJMP.NewDataTable("")

  Sleep(100)

  tableName = dt.Document.Name

  names = names + tableName

  names = names & vbCrLf

   Next

  MessageBox.Show(names, "Tables Created", MessageBoxButtons.OK)

Brian,

Yes, the Automation Reference is installed locally. I was just pointing out that it seems no longer to be available online.


This is not, as far as I can tell, a timing issue. I tried an approach similar to yours of introducing a delay prior to checking dt.Document.Name, but this makes no difference. Using automation code (app.NewDataTable) to create tables, instead of creating them with JSL, does not fix the problem in all cases, nor can I rely on it in general.

I've discovered a workaround, but it's an excruciatingly awkward one. In order to get the name of an automation table, I now do this:

  1. Get names of all JMP tables in a JSL list
  2. Use app.GetJSLValue to get this list into the automating application.
  3. Loop through the list of names one-by-one, looking up automation table objects by name using app.GetTableHandleFromName
  4. I compare the OLE object identity of each table to the OLE object identity of my target table. If they match, I return the name which I used to look it up.

Code for my horrible ugly workaround:

    def GetTableName(app, table):
        if table.Document.Name:
            return table.Document.Name
        else:
            # Get names of all JMP tables
            app.RunCommand("""
                NamesDefaultToHere(1);
                ::_retval={};
                for(ii=1, ii<=NTable(), ii++,
                    InsertInto(::_retval, DataTable(ii)<<GetName())
                )""")
            tns = app.GetJSLValue("_retval")

            # Compare object identity based on obj address shown in repr(table._oleobj_):
            #    <PyIDispatch at 0x00000000041997B0 with obj at 0x0000000000381458>
            table_ole_obj = int(repr(table._oleobj_).split()[-1][:-1], 0)
            for tn in tns:
                tn_ole_obj = int(repr(app.GetTableHandleFromName(tn)._oleobj_).split()[-1][:-1], 0)
                if table_ole_obj==tn_ole_obj:
                    return tn
            else:
                raise Exception

Brian, I should mention that this issue is definitely not Python-specific. I can easily get the same incorrect behavior from VBScript automation code, even if I include lengthy delays:

JMP_automation_bug.vbs:

Set myJMP = CreateObject("JMP.Application")
myJMP.Visible = True
WScript.Sleep 1000

tns = ""
For i = 1 To 30
   myJMP.RunCommand "::dt=New Table(); ::retval=dt<<Get Name()"
   WScript.Sleep 1000
   retval = myJMP.GetJSLValue("retval")
   Set dt = myJMP.GetTableHandleFromName(retval)
   WScript.Sleep 1000
   tableName = dt.Document.Name
   If tableName="" Then
     WScript.Echo "blank tableName"
   Else
     WScript.Echo "non-blank tableName: ", tableName
   End If
   tns = tns + tableName
   tns = tns & vbCrLf
   myJMP.RunCommand "close(DataTable(::retval), nosave)"
Next

WScript.Echo tns

I've also confirmed that the bug also exists in JMP 11 and can be reproduced with the same JMP_automation_bug.vbs example as with JMP 9.

briancorcoran

Joined:

Jun 23, 2011

I'm curious why you don't want to use NewDataTable off of the application object?  That is the recommended way of creating a new table via automation.

Brian

Brian, I can certainly use app.NewDataTable when I actually want to create a table from automation code. But my real use cases are much more complex than these very simple examples which are designed simply to demonstrate this bug.

In particular, I'm writing a complex application that depends on the ability to reliably pass the names of arbitrary tables between JSL and automation code:

  • Sometimes I create a table with automation and then need to run JSL code that interacts with it.
  • Sometimes I create a table with JSL and then need to run automation code that interacts with it.

My application now does this for hundreds of tables a day, and I started getting tons of mysterious errors which I traced to this bug.

briancorcoran

Joined:

Jun 23, 2011

I think there is a timing issue between the close of the table and the reuse of the dt JSL variable for the next table.  If you were able to use the Document object to do the Close (as shown below) I think it would solve the problem.      

       myJMP = CreateObject("JMP.Application")

        myJMP.Visible = True

        Call Sleep(1000)

        Dim tns As String

        Dim retval As Object

        Dim tableName As String

        tns = ""

        Dim tab As JMP.DataTable

        For i As Integer = 1 To 10

            myJMP.RunCommand("::dt=New Table();")

            myJMP.RunCommand("::retval=dt<<Get Name();")

            retval = myJMP.GetJSLValue("retval")

            tab = myJMP.GetTableHandleFromName(retval)

            Doc = tab.Document

            tableName = Doc.Name()

            If tableName = "" Then

                tableName = "blank"

            End If

            tns = tns + tableName

            tns = tns & vbCrLf

            Doc.Close(False, "")

        Next i

        MessageBox.Show(tns, "Tables Created", MessageBoxButtons.OK)

Brian, the underlying issue may be timing-related but there is no reliable timing-related workaround.

  1. I showed previously that adding 1s timing delays didn't fix the problem.
  2. The bug still appears if I use tab.Document.Close(False, "")
  3. The bug still appears if I don't close the tables at all; though somehow it becomes much more sporadic, where it will work fine for 100 tables and then fail for 50 in a row, and then start working again for another 50 tables.

Most importantly, though, I cannot rely on any kind of workaround that requires me to do any specific operation exclusively in automation code. My application depends on the ability to reliably read and pass the names of tables between automation and JSL code.

I strongly suspect this to be a bug in JMP's COM automation interface, because the table objects seem to function normally except for the blank tab.Document.Name property. Has someone taken a look at that code?

briancorcoran

Joined:

Jun 23, 2011

I am responsible for the code, and have looked at the Name method.  I don't see a problem there.  I see a problem with lifetime issues of the underlying Window in this mode of usage, which is why I was suggesting a timing problem.