Friday, March 13, 1998 at 10:59:56 AM Pacific
Reaching for COM NirvanaToday we have a problem that I hope some person or group of people in the Frontier/Win community can help us with. I believe I've asked the question before, but we're still newbies here, so like many newbies we have to ask the question several times, and eventually it sinks in and we get the answer.
The area we're concerned about is the COM server interface for Frontier. At this point, our lead developer in this area, Bob Bierman, has a good handle on the Frontier side of things. But we're still struggling with limits that COM seems to have and we don't want to create an interface that's wrong, that we'll have to support for the life of Frontier/Win.
How it works on the Mac
On the Mac, Frontier has a simple server-side interface that has stood the test of time.
There's a table at system.verbs.traps. Each sub-table of this table is named with a four character ID. In each of these tables is a set of scripts that also have four-character names.
When an Apple Event comes into Frontier, it has a class and a procedure name. Each of these has a four-character name. We look in system.verbs.traps for a table with the same name as the event class. Then we look in that table for a script with the same four-character procedure name.
When we find the script, we pop the parameters off the Apple Event record and build a call to the script with each of the parameters, in order. Then we jump into the call. It calls the script. It does whatever it wants to do. We take the returned result and pass it back out thru the Apple Event channel.
For Windows-only users, I've baked a copy of system.verbs.traps into this as a fat page. If what I explained above doesn't make sense, load the table into your root and browse around. I think any accomplished system developer can easily understand what's going on here.
Why this is so good
It means that we never had to program Frontier itself to handle any specific Apple Event. Frontier, as a server, is completely specified by the contents of system.verbs.traps.
Why this is a bitch on Windows
There doesn't appear to be a way for a COM interface to take a variable number of parameters.
So, we've concocted three ways to work around this, none of which seem very elegant. Maybe it's the best we can do? That's why I wanted to post this now, before we release a version of Frontier that people will point to as an example of bad design, we want to check our assumptions and see if we're missing something.
Here are the three workarounds:
- We define an interface that takes 16 parameters. If you want to call us from Visual Basic or Visual Interdev or whatever, you must supply all 16 parameters. The first one is the address of the object database script you want to call. The remaining 15 are the parameters to the script. What if the script only takes 4 params? Then the remaining 11 parameters must be specified, but they will be ignored by Frontier (we'll have to stand on our head to do this, btw). What if the script takes 20 parameters? You're out of luck.
- We define an interface that allows you to start a procedure call, then you call another interface repeatedly to push each of your parameters onto a stack, then you call another interface to trigger the call. This seems very ugly for the VB programmer. Is there any way to hide the ugliness? We don't know.
- We define a do-script interface, where you send us the text of a script you want us to execute, we'd run it and return a result. There are huge messy problems with this approach, we went down this road on the Mac and nothing interesting could be built on it. Do I remember all the brick walls? No, I just remember that they were there, and remembered not to try to build on this kind of interface.
What we'd like
It would be great if somehow we could define an interface that doesn't care how many parameters it gets. Some people have said this is possible, but since they don't understand Frontier, we don't understand what they're telling us to do.
It would be great if there were a parameter type that was like a list or a collection of values. Then we'd define a single entry point that took two parameters, the first being the address of the script that we're to call, and the second, an ordered collection of the parameters.
It seems at this point that COM wasn't designed with something like Frontier in mind. I just want to be sure that if we release an ugly interface that it's the least ugly interface possible.
If you have ideas please send them to firstname.lastname@example.org, cc'd to email@example.com.
We'd like to get focused on doing the best-possible interface and release it as soon as possible.
March 13, 1998
From Shannon Posniewski, firstname.lastname@example.org:
As far as I know, there is no way to describe a COM interface like you would describe printf (i.e. int printf(const char *pch, ...)). I think the main reason is that COM is unable to properly marshall arguments for which it has no type information.
However, MS found the lack of having variable number of arguments to be a problem as well in Visual Basic. They devised the Automation interface family (IDispatch, ITypeInfo, et al), which handles variable parameter lists, as well as other things (like named parameters).
HRESULT DispInvoke( DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR* pparams, VARIANT FAR* pvarResult, EXCEPINFO pexcepinfo, unsigned int FAR* puArgErr );
That DISPPARAMS FAR* is the parameter list.
The way they handle it is that they pass an array of arguments. Each item in the array has a type and a value. This allows a variable number of arguments (since the array can hold any number of items) and it allows marshalling (since each item in the array has its type right with it). They also provide a whole set of variable coercion functions.
What does this mean for the implementation of your objects? Well, you need to support the IDispatch interface in addition to your other interfaces. This interface is responsible for unpacking the parameters and calling the appropriate internal function. MS does provide an implementation of IDispatch which you can use, assuming that you are using type libraries.
I hope this helps. I'm not sure if you've looked at this and rejected it already, so I'll stop here.
As with many things in COM, it's not easy, but it's consistent and safe.
P.S. If you have MSVC 5.0, try these URLs in the InfoViewer: mk:@ivt:pdobj/native/sdk/ole/auto/src/chap1_4.htm
From Steven Lees, email@example.com:
It sounds like you're describing a couple of problems:
- You want to define a COM interface that takes a variable number of parameters.
- You want to have a dynamic system that doesn't require you to publish all of the callable methods/interfaces ahead of time. (I.e., from your description it sounds like in the Mac version, you can add a new script at runtime, and then immediately invoke it through an Apple Event.)
Some possible solutions:
- COM does allow you to define an interface that takes optional arguments. You'd still have to declare your sixteen parameters, but they'd all be optional, so the caller wouldn't need to specify the ones that it didn't care about. You'd want to make them named parameters--that way when you call from VB, the caller just specifies the parameter values by name. It's not very dynamic since you still have to publish all of the script names up front.
- Define a single method called RunScript that takes two four-char strings and an array of variants. This is almost an exact parallel of what you're doing on the Mac. The strings are the class name and procedure name, and the array is the arglist. This handles the both the varargs and dynamic problems. (Or is this just the same as your do-script workaround #3, with all the same problems?)
Hope that helps--neither one is incredibly elegant but I think they're a little better than the workarounds that you describe. It would help to understand how you are trying to publish the names and param info about the available scripts--i.e. how does the caller know that a script exists, and what params it takes? Are you using a COM typelib?
One other thing worth mentioning--your description of how things work with Apple Events on the Mac sounds almost exactly like the standard IDispatch implementation that's built into COM. If you already have a typelib created that describes the callable scripts, then you can use CreateStdDispatch and it will handle all of the dirty work for you.
More info on the mail site.
This page was last built on 3/13/98; 3:27:09 PM by Dave Winer. firstname.lastname@example.org