On First Encountering OLE
Vector 13:4, page 133
Transcribed from the printed issue; not yet reviewed. Read it in the PDF of the issue, page 133
Background¶
It is much too soon to write a decent appreciation of the work Dyadic have put in since Lancaster to make Dyalog APL 8.1 a robust and effective OLE server. However it seems only fair to record my experiences to date, so that other readers can get a little further a little sooner into this strange new world.
The idea is very simple - you register a namespace as an OLE Server, with a list of the functions and variables that you want made public. Then users of other applications, such as Excel, can call any of the public functions, passing arrays and getting array results. As long as Excel declares the OLE server globally, with statements such as:
Public Const g_a_s = "dyalog.Adrian"
Public g_oobj_AdrianServer As Object
Function AdrianData() As Variant
Set g_oobj_AdrianServer = CreateObject(g_a_s)
AdrianData = g_oobj_AdrianServer.MakeData
End Function
… then Excel loads your server on the first function call and keeps it loaded until you quit Excel or close that workbook. This brings me to the first little warning:
- calls to Dyalog APs such as NFILES or XUTILS are probably a bad idea from an OLE server. The AP is left running after Dyalog is closed, and eventually you will stack enough of them up in memory to prevent it from loading.
The mapping from APL nested array to VBA ‘Variant’ seems very solid, with only the occasional lapse on Excel’s part, such as its inability to store arrays where some (but not all) of the dimensions are zero.
Registering your Server¶
There is a hard connection between the act of saving the workspace and updating the Windows registry with the information about your APL server. This is to ensure that the entries in the registry are always in line with the saved workspace, and in general it works pretty well, but again there a couple of snags to look out for. Having made our OleServer object:
r←Make
⍝ Make me a server please!
⎕WC'OleServer'
r←⎕WG'ClassId'
⎕WS'ExportedVars'(,⊂'Public')
⎕WS'ExportedFns' Public
Make
{B1B355CF-A765-11D0-B01F-00C04FDE82D1}
… and subsequently saved the WS, we can go exploring in REGEDIT, to see what the results were.
… so far so good. Now how does Excel know what to run when we invoke the function on the previous page?
HKEY_CLASSES_ROOT
CLSID
{B1B355CF-A765-11D0-B01F-00C04FDE82D1}=Adrian
LOCAL_SERVER32=C:\APL\DYALOG81.EXE C:\DWS\ACDS.DWS
TYPELIB={B1B355D0-A765-11D0-B01F-00C04FDE82D1}
INTERFACE
{B1B355D1-A765-11D0-B01F-00C04FDE82D1}
TYPELIB
{B1B355D0-A765-11D0-B01F-00C04FDE82D1}
1.0
0
WIN32=C:\DWS\Adrian.tlb
…. note that there are actually three consecutive ids registered (the counter is at the end of the first block) of which Dyalog admits to the CLSID and the TYPELIB. At the moment, I have no idea what the INTERFACE is for, but the rest of it is essential. The LOCAL_SERVER entry starts the interpreter and the CLSID points to the namespace. The .TLB file catalogues the public functions and variables.
Now for the next little snag - if you get carried away and run that Make function again, Dyalog happily generates a new CLSID and duly registers it on saving the workspace - unfortunately it leaves the lower three entries in place (orphaned) so everything will still work, but sooner or later you are going to have to roll up your sleeves and clean up the registry by hand! Now for the real gotcha …
- do not under any circumstances run
⎕EX '.'. This will wipe any OleServer-ness in the workspace, but still leaves all the servers registered. Now when you try starting your server from Excel it will kick off APL and load the WS. And wait … and wait …….
Fortunately there is a Dyalog ‘method’ which you can use to unregister an existing server, but it is essential to run it before you accidentally wipe it out or re-create it, as you have no way of recovering that magic id string otherwise.
Debugging it¶
Off we go to Excel, crack open the VBA "Complete Reference" and write ourselves some toy functions. Everything is going fine until we get back a message like:
… and we want to track down the error. The problem with the Dyalog server is that it bounces the error back to Excel and simply clears the stack. Even if you are running it in a development interpreter, you cannot see what the function arguments were, nor check the values of any local variables at the point of the error. My current solution is to set an error trap (using :Trap in all functions as a global ⎕TRAP currently has no effect in a server) to run a simple error analysis:
SeeError dm;av;nm;log;var
⍝ Simple error analysis
log←''('ERROR ANALYSIS AT ',⍕⎕TS)
:If 2<⍴⎕SI
log,←(⊂'CALLING STACK'),(2↓⎕NSI,¨'.',¨⎕SI,¨'[',¨(⍕¨⎕LC),¨']'),' '
:End
log,←dm,' ' 'WORKING VARIABLES'
⍝ Take the line apart bit by bit and test each name
dm←2⊃⎕DM ⋄ dm←(dm⍳' ')↓dm
av←⎕AV/⍨0=⊃∘⎕NC¨⎕AV
Next:dm↓⍨←¯1+1⍳⍨dm∊av,'#'
nm←dm↑⍨¯1+0⍳⍨dm∊av,⎕D,'#.'
→(0∊⍴nm)↑Done ⋄ dm←(⍴nm)↓dm
:Select ⊃⎕NC nm
:Case 0
log,←⊂nm,' - undefined'
:CaseList 3 4
log,←⊂'Subfunction - ',nm
:Case 2
var←lfmt⍎nm
:If 256<⍴var
var←' ...',¨250↑var
:End
log,←⊂nm,'←',var
:Else
log,←⊂'Invalid name class - ',nm
:End
→(0<⍴dm)↑Next
Done:See↑log
… and then to throw this into an ‘Ontop’ window. Message boxes are not very useful (they tend to appear underneath Excel) so you need to force the log to get drawn with a trick such as:
Installing a Runtime Server¶
This is where the real fun begins. There are two key tricks needed to make it work; one is undocumented and the other is documented incorrectly! The first obvious problem is how to register the OleServer (a namespace in a workspace, remember) as part of a standard install routine. There are two possible attacks:
- Write out a text file with the registry script required to add the correct entries, and call REGEDIT in your installer, passing the script on the command line.
- Run up your workspace as the last step in the installation and get it to register itself. This is the undocumented bit!
It is hard to see how you could do (1) reliably, given that your users are unlikely to take any notice of your suggested directory, so the script file will need to be crafted by the installer on the user’s machine. I was leaning towards the idea of ⎕SAVE, which feels very dangerous, when a chance phone call revealed that there is also a ‘Register’ method on the server. Now we are getting somewhere …
Shell;id
⍝ Start from here
⍝ Called on installation to register the NS as a server.
⍝ Else just falls in to server mode.
:If 0∊⍴2 ⎕NQ'.' 'GetEnvironment' 'Register'
⍎LX
:Else
id←Reg ⍝ See below
Win_msg'OLE Server registered as ...'id
⎕OFF
:End
… where Reg just queues the appropriate (undocumented) method. I really thought that I had cracked it, and even managed a successful install on my own machine under a different user id, in a completely new directory path. OK, make a disk set, walk across to Duncan’s machine and away we go. Everything goes just fine until it attempts the registration, and gets an error saving Adrian.tlb - of course the workspace was saved on my machine, and the namespace thinks it knows where the typelib should be created. On my machine, the path exists, so my ‘test with another user’ trial was a complete fraud. Duncan has a much more rationally structured disk, and I naturally hit a file name error on trying to create the file in a non-existent directory. Bother, said Pooh.
At this point, I made the bad mistake of reading the instructions. If you look up the 'TypelibFile' property in the Dyalog help file, it informs you that this is a read-only property of the server. Fortunately I have had sufficient experience of the Microsoft C++ documentation to know that the software is often ahead of the documentation department, so I gave it a try anyway …
r←Reg
⍝ Register the namespace with the right TypeLib
r←'Adrian'⎕WG'ClassId'
'Adrian'⎕WS'Typelibfile'('Adrian.tlb',⍨⎕WSID↓⍨1-'\'⍳⍨⌽⎕WSID)
2 ⎕NQ'Adrian' 530
… sorry for all the gratuitous commuting - this was written at home with Richard looking over my shoulder, and I do this sort of thing to confuse him. The important thing is that this works with no fuss and is very easy to build into an install script. Assuming your installer has a parameter for the installation directory, you will need to run something like:
… when all the files have been copied.
Conclusion¶
Considering the amount of new stuff, this first release of Dyalog’s OleServer technology is remarkably solid and already feels as if it could be the vehicle for a serious set of runtime applications. They have also removed the requirement for all those pesky DOTs and DINs so you really can ship just a workspace and an interpreter. All we need now is a smaller interpreter!