This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.


MIND

Advanced Basics
Ted Pattison
Scripting Clients and User-Defined Interfaces
H
ow many things can you think of that don't mix together well? Cats and dogs. Oil and water. Democrats and Republicans. Well, here's another pair you can add to the list: scripting clients and user-defined interfaces.
      Many COM programmers have worked hard getting up to speed on the concepts and practice of interface-based programming. User-defined interfaces require extra work while designing and writing an application, yet the benefits they provide are easy to measure in larger projects.
      When you define your interfaces separately from your creatable components, you can achieve higher levels of reuse, maintainability, and extensibility. You can create polymorphic designs based on plug-compatible components. Your client applications can adapt to any version of a component by conducting runtime tests to see whether an object supports a certain type of interface. Furthermore, you can create a robust versioning scheme that makes it possible to safely upgrade a component or client application without disturbing any of the other components or client applications already in production.
      Once you grasp the key concepts of interface-based programming, it's addictive. Many programmers who use Visual Basic® have fallen in love with user-defined interfaces and try to use them wherever possible. However, there's an unfortunate problem: once you've created a component that implements user-defined interfaces, it's hard to use it directly from a scripting client. That's really disheartening news if you're creating components that will be used by scripting clients, such as Web sites built with ASP.

A Quick COM Refresher
      In COM, there are two fundamentally different interface styles. There are custom interfaces that derive from IUnknown. Clients who use this style of interface use direct vtable binding to access objects. There are also automation interfaces that are based on IDispatch. Scripting clients access objects through late binding and, therefore, require objects that expose IDispatch-style interfaces.

Figure 1: Dual Interface Derived from IDispatch
      Figure 1: Dual Interface Derived from IDispatch

      When Visual Basic defines a COM interface for you behind the scenes, it creates what's known as a dual interface. This is an interface that derives from IDispatch as shown in Figure 1. It can be used by both clients that prefer custom interfaces and clients that require IDispatch- style interfaces. In this sense, a dual interface caters to both client types.

The Typing Problem
      As I mentioned before, there's a problem when you have a component that implements user-defined interfaces. Scripting clients can have trouble using the component. The problem is that scripting clients are written in typeless languages. For example, when you're writing VBScript in an ASP page, your variables and parameters are always defined as variants. When you establish a connection to a COM object, you cannot specify which interface you want to use. Instead, a VBScript client is always connected to a COM object through the interface that's marked as the default for the component being instantiated. The default interface must also be an IDispatch-style interface or a dual interface. This makes it possible for a scripting client to access the object through late binding.
      In COM, clients navigate from one interface to another by calling a method named QueryInterface. C++-based clients can call QueryInterface directly. Visual Basic-based clients can call QueryInterface indirectly by assigning object references to variables and parameters that are typed to the desired interface. However, scripting clients cannot call QueryInterface either directly or indirectly; they cannot navigate to a secondary interface. In essence, they are stuck with the default interface to which they were originally connected. This means that scripting clients simply don't provide the required support to take advantage of components that implement more than one interface.
      Another problem related to Visual Basic makes things even worse: you cannot configure Visual Basic to employ a user-defined interface as the default interface behind a MultiUse class. This is true of user-defined interfaces defined in PublicNotCreatable class modules as well as interfaces defined in IDL.
      The only way you can define the default interface for a MultiUse class is by adding public methods to the class module itself. Let's look at an example. Say you've defined a dual interface named IMyInterface in IDL that looks like this:


 Interface IMyInterface : IDispatch
 {
   HRESULT MyMethod1();
   HRESULT MyMethod2();
 }
Once you've compiled your IDL into a type library, you can register it on your development workstation. (See the article "Visual Basic Design Time Techniques to Prevent Runtime Version Conflicts," by Brian A. Randall and Ted Pattison in the January 2000 issue of MSJ for details on how to do this.) Once you've registered the type library, you can reference it in an ActiveX® DLL project and implement your interface in a MultiUse class named CMyClass like this:

 Option Explicit
 Implements IMyInterface
 
 Private Sub IMyInterface_Method1()
   ' your implementation
 End Sub
 
 Private Sub IMyInterface_Method2()
   ' your implementation
 End Sub
      Even though IMyInterface is the only interface you want your class to support, it is not marked as the default interface. In fact, there's nothing you can do in Visual Basic to make IMyInterface the default interface. If you reverse-engineer the IDL behind the type library that Visual Basic builds into the DLL, you can see the following definitions that illustrate the problem:

 interface _CMyClass : IDispatch {
   // empty due to no public methods in CMyClass
 };
 
 coclass CMyClass {
   [default] interface _CMyClass;
   interface IMyInterface;
 };
      Visual Basic always defines a dual interface from the public members of a MultiUse class and marks it as the default. This is true even when the class doesn't have any public methods. Any user-defined interface you implement by using the Implements keyword will always be a secondary, nondefault interface. This means that a scripting client can't get to the methods in your interface.
      Therefore, when you're creating components with Visual Basic, your scripting clients can only get to the public methods in your creatable classes. It means you cannot define your interfaces separately from the classes that hold your implementations. It means you can't really benefit from the principles of interface-based programming when you're dealing with scripting clients. This is tragic, to say the least.
      Many programmers have worked hard to understand why separating the interface from the implementation is important. So, after all this hard work through the design and coding phase, you end up with a sticky problem. Clients that can call QueryInterface can access the user-defined interfaces behind your components, but scripting clients cannot. How do you deal with this? This month I'm going to look at a few possible solutions.

Hacking away at the Problem
      The first technique I'll look at is for programmers who love a challenge. Scripting clients cannot navigate to a secondary interface on their own. However, you can get them there if you give them a little help. Look at the following code in a MultiUse class and notice the new method, GetMyInterface:


 Option Explicit
 Implements IMyInterface
 
 Private Sub IMyInterface_Method1()
   ' your implementation
 End Sub
 
 Private Sub IMyInterface_Method2()
   ' your implementation
 End Sub
 
 '*** entry point for a scripting client
 Public Function GetMyInterface() As IMyInterface
   Set GetMyInterface = Me
 End Sub
      GetMyInterface is a public method in the default interface and it is, therefore, accessible to a scripting client. This method has a return type of IMyInterface, which results in an implicit call to QueryInterface. By calling GetMyInterface, a scripting client can navigate from the default interface to a secondary interface. Here's an example of some VBScript code that connects to IMyInterface and calls Method1:

 Dim ProgID, ref1, ref2
 ProgID = "MyDll.CMyClass"
 Set ref1 = Server.CreateObject(ProgID)
 Set ref2 = ref1.GetMyInterface()
 ref2.Method1
 Set ref1 = Nothing
 Set ref2 = Nothing
      As you can see, this technique allows a scripting client to access a user-defined interface like IMyInterface. You should note that IMyInterface should be defined as a dual interface that derives from IDispatch as opposed to a custom interface that derives from IUnknown (see Figure 1). This makes it possible for a scripting client to use late binding when calling methods such as Method1 and Method2.
      On the surface, this approach seems like it provides a really nice solution. It allows you to design an application with scripting clients that benefits from the principles of interface-based programming. However, this approach has a significant problem: it doesn't work when the client and object are running in different processes. In fact, it doesn't even work when the client and object are running in different apartments in the same process.
      The problem with this approach is that it breaks the COM remoting layer. Whenever a client establishes a connection with a COM object in a different apartment, the COM runtime inserts a proxy/stub pair between them. There will be a separate proxy/stub pair for each interface. For instance, there should be one proxy/stub pair for the default interface behind CMyClass, and there should be another proxy/stub pair for IMyInterface. However, the second required proxy/stub pair for IMyInterface never gets created.
      When a scripting client calls the GetMyInterface method on a remote object, things don't work correctly. This is because the COM remoting layer attempts an optimization. It sees that there's already one IDispatch connection, so it doesn't attempt to create a second IDispatch connection. Even though each dual interface has a different IID, the typeless scripting client is always asking for IDispatch.
      The resulting problem is that a call to GetMyInterface returns a redundant reference to the default interface, and the scripting client doesn't navigate from one interface to another in the intended manner. When the scripting client attempts to call Method1, the call will fail. The default interface behind CMyClass doesn't support Method1.
      So maybe this technique isn't so great after all. It works when the client and object run in the same apartment, but it breaks in all other cases. When will this get you in trouble? Let's say you create a component and an ASP script client that use this technique. Next, you install the component in a COM+ library application—or a Microsoft® Transaction Service (MTS) library package—on the same machine as the ASP code. Things work just fine at first. However, what happens if the system administrator decides to reconfigure your component to run in a COM+ server application (or an MTS server package) for reasons related to security or fault tolerance? Your code doesn't work anymore because you've created a dependency that's easy to break. This is a dependency you probably want to avoid.

Don't Implement IDispatch more than Once
      Scripting clients only know about one interface, IDispatch. When you implement two dual interfaces in one component, you have created an ambiguity—you are implementing IDispatch more than once. As you have seen, this can be problematic. From a design perspective, a component should only expose one IDispatch implementation. This was an assumption that COM's designers used when they architected their remoting layer.
      Recall that Visual Basic-based components always expose the default interface as a dual interface. This is the interface that's built from the class's public methods. This means that a Visual Basic MultiUse class already has an IDispatch implementation before you implement any secondary interfaces. This implies that any secondary, user-defined interfaces you're going to implement should derive from IUnknown instead of IDispatch. This will ensure that your components do not provide multiple implementations of IDispatch. It also means that scripting clients will not be able to access these user-defined interfaces.
      So, I still haven't solved the problem at hand. If you have a component that implements user-defined interfaces, how do you access it from a scripting client? The solution is to flatten out all these user-defined interfaces into a single IDispatch-style interface. This interface will be a superset of all methods in all other interfaces. You're going to implement this interface by using public methods in a MultiUse class.
      To extend the example I've used so far, assume you have a MultiUse class that implements two different user-defined interfaces, IMyInterface and IMyOtherInterface. Also assume that these two interfaces derive from IUnknown instead of IDispatch. The class definition is shown in Figure 2. The public methods in the MultiUse class serve as the entry point for a scripting client. There is one public method to map a scripting client to every method in each user-defined interface. While adding scripting client support like this can be somewhat tedious, it does solve the problem—a scripting client can call any method that the component implements.

Creating a Wrapper Component
      While the previous solution gets the job done, at times you might need another technique that doesn't require modifications to the original component. This might be the case if you're dealing with a component that implements user-defined interfaces and you're not able to modify its source code. Instead of adding public methods to the original component, you can create a complimentary wrapper component that provides access to scripting clients (see Figure 3). The wrapper component creates an instance of the original component and maps public methods to the methods in the user-defined interfaces.

Figure 3: Access Through Wrapper Object
      Figure 3: Access Through Wrapper Object

       Figure 4 shows an example of what the original component CMyClass looks like. The component implements two different user-defined interfaces. Figure 5 shows an example of the wrapper component, CMyScriptWrapper. When the wrapper component is instantiated, it uses the Class_Initialize procedure to create an instance of CMyClass. During initialization the wrapper component acquires a separate connection for each user-defined interface. The wrapper component uses these connections to forward public methods to method implementations.
      One you flatten out all the user-defined interfaces into one big default interface, a scripting client can access every method. The scripting client simply instantiates the wrapper component, then calls methods directly. Here's an example of some VBScript code in an ASP page:

 Dim ProgID, obj
 ProgID = "MyWrapperDll.CMyScriptWrapper"
 Set obj = Server.CreateObject(ProgID)
 obj.Method1
 obj.Method2
 obj.Method3
 Set obj = Nothing
      When you're defining and implementing the methods in the wrapper component, you'll need to forward parameters passed from the scripting client to the component you're wrapping. In many cases, the parameters can be forwarded without any casting or conversion. In some cases, however, you may need to redefine the parameters into types that are compatible with whatever scripting clients you're using. You may also need to convert these parameters back and forth as appropriate.
      Let's look at an example. VBScript cannot call a method that has output parameters typed to anything other than a variant. If a VBScript client attempts to call a method from a Visual Basic-based component with a ByRef parameter that's defined as Long or String, you'll experience a runtime error. You must redefine all ByRef parameters as variants and perform conversions whenever they're necessary. For instance, if you're wrapping a component with a method that looks like this

 Sub GetData(ByRef x As Long, ByRef y As String)
you must write your wrapper component with a method that looks like this:

 Sub GetData(ByRef x As Variant, ByRef y As Variant)
   Dim tempX As Long, tempY As String
   Ref1.GetData tempX, tempY
   x = tempX
   y = tempY
 End Sub
      Note that you'll be responsible for modifying your wrapper component whenever anyone extends the original component to support another interface. You should also realize (and be thankful) that your version concerns aren't as complicated as they could be since the wrapper component only supports scripting clients. This means that the version compatibility setting for the project that holds your wrapper component doesn't matter. If you had to worry about clients that use direct vtable binding, you'd have to compile all later versions of the wrapper component using binary compatibility.

A little Review
      So scripting clients take something as elegant as interface-based programming and make it less than elegant. When you decide to create a component with user-defined interfaces, it's best if you can make the assumption that the component will be accessed exclusively by clients that can call QueryInterface. This also means that your component will not support scripting clients directly. However, the good news is that you can still program in terms of abstract and concrete classes.
      If you're creating a component that's going to support scripting clients, you're usually better off avoiding user-defined interfaces. Just create your components by using public methods in MultiUse classes. Scripting clients will be able to call any method directly.
      In an imperfect world, however, there are times when you cannot plan your fate so carefully. If you're creating a Web site in which an ASP client must access a vendor-provided component that implements user-defined interfaces, you'll have to do a little extra work. The best approach is usually to create a wrapper component that flattens out all the user-defined interfaces into a single interface accessible to scripting clients. This makes it possible to get your scripting clients up and running in no time.

From the January 2000 issue of Microsoft Internet Developer.