Breathing fresh life into LISP applications with a modern GUI
This recent entry on Jimmy Bergmark's JTB World Blog brought to my attention the fact that ObjectDCL is about to become an Open Source project. Chad Wanless, the father of ObjectDCL, was a very active ADN member for many years, but - according to this post on the ObjectARX discussion group - is now unable to spend time working on ObjectDCL due to a severe medical condition. In case Chad is reading this... Chad - all of us here at ADN wish you a speedy recovery and all the best for your future endeavours.
Ignoring the background behind the decision to post the technology as Open Source, this is good news for developers with legacy codebases that include user interfaces implemented using DCL (Dialog Control Language).
I did want to talk a little about what other options are available to developers of LISP applications that make use of DCL today. There are a couple of approaches to calling modules implementing new user interfaces for LISP apps, whether through COM or through .NET. I'll talk about both, but I will say that .NET is the most future-proof choice at this stage.
Both techniques work on the principle that you redesign your user interface using VB6 or VB.NET/C#, and call through to these functions from LISP. Reality is often more complex - you may have more complex interactions from (for instance) particular buttons in your dialog - but these examples demonstrate what you can do to replace a fairly simple UI where you pass the initial variables into a function and receive the modified variables at the other end, once the user closes the dialog. You can also extend it to handle more complex situations, but there may be much more work needed - perhaps even use of AutoCAD's managed API from within the dialog code.
COM: Using a VB ActiveX DLL from Visual LISP
For an in-depth description of this technique, ADN members can find the information here. I apologise to those who are not able to access this content, but I don't want to dillute the issue by copying/pasting the whole article into this blog. Especially as this technique is describing the use of VB6, which is no longer at the forefront of Microsoft's development efforts.
The approach is to create an ActiveX DLL project in VB6, which is simply a COM module implementing code that can be referenced and called using a ProgID. AutoCAD's COM Automation interface exposes a method called GetInterfaceObject from the Application object that simply calls the equivalent of CreateObject on the ProgID passed in, but within AutoCAD's memory space. Once you've loaded a module using GetInterfaceObject, not only can you then call code displaying fancy VB-generated UIs from LISP, but because the code is in the same memory space as AutoCAD, it executes very quickly - on a par with VBA, ObjectARX or the managed API in terms of the speed with which it can access AutoCAD's object model.
.NET: Defining LISP-callable functions from a .NET application
The following technique is really the more future-proof approach, and has become possible since the implementation of "LISP callable wrappers" in AutoCAD 2007's managed API. Essentially it comes down to the ability to declare specific .NET functions as being LISP-callable. If you look back at one of my early posts about creating a .NET application, you'll notice the use of an attribute to declare a command, such as <CommandMethod("MyCommand")>. With AutoCAD 2007 you can simply use <LispFunction("MyLispFunction")> to denote a function that can be called directly from LISP.
From there it's simply a matter of unpackaging the arguments passed in and packaging up the results (the bit in-between is where you get to have fun, using .NET capabilities to create beautiful user interfaces or to integrate with other systems etc., etc.). Here's some code to show the handling of arguments and packaging of the results:
<LispFunction("LISPfunction")> _
Public Function VBfunction(ByVal rbfArgs As ResultBuffer) As ResultBuffer
'Get the arguments passed in...
Dim arInputArgs As Array
Dim realArg1 As Double
Dim intArg2 As Integer
Dim strArg3 As String
arInputArgs = rbfArgs.AsArray
realArg1 = CType(arInputArgs.GetValue(0), TypedValue).Value
intArg2 = CType(arInputArgs.GetValue(1), TypedValue).Value
strArg3 = CType(arInputArgs.GetValue(2), TypedValue).Value
'Do something interesting here...
'...
'...
'Package the results...
'Use RTREAL (5001) for doubles
'Use RTSTR (5003) for strings
'Use RTSHORT (5005) for integers
Dim rbfResult As ResultBuffer
rbfResult = New ResultBuffer( _
New TypedValue(CInt(5001), 3.14159), _
New TypedValue(CInt(5003), 42), _
New TypedValue(CInt(5005), "Goodbye!"))
Return rbfResult
End Function
The code assumes the first argument passed in will be a real, followed by an integer and then finally a string. Here's what happens if you call the code like this:
Command: (LISPfunction 1.234 9876 "Hello!")
(3.14159 42 "Goodbye!")
Here are a couple of useful articles on the ADN site regarding this:
LispFunction examples for AutoLISP to .NET
.NET ResultBuffer returns dotted pairs to Visual LISP instead of normal list