Managing Interface Design

Before you write any object code or client code for a software project, you must make certain crucial design decisions. Design flaws that you discover midway through the development life cycle can be expensive and can ultimately lead to failure. The most important part of the design phase in a COM application is the creation of the interfaces for client-object communication.

The rules of COM (as defined in the COM Specification) let programmers create extensible objects. If you follow these rules, you can safely refine both object code and client code over time. As you learned in the previous chapter, Visual Basic engages in some trickery behind the scenes to make programmers more productive. Unfortunately, this can cause confusion for those who expect a single set of versioning rules for using Visual Basic by itself or with C++ in an application. There's the original set of rules for extending objects, as defined by the COM purists, and a second set of rules defined by the Visual Basic team.

When you create a large-scale project with Visual Basic, you must make many decisions relating to managing your interface design. In particular, you must decide what role user-defined interfaces will play in your application. If you decide to employ user-defined interfaces in your design, you can take advantage of powerful OOP features such as polymorphism and run-time type information (RTTI). On the other hand, simply using public classes and Visual Basic's transparent mapping of interfaces behind the scenes will result in an application that is far less complex and thus far easier for most Visual Basic programmers to create and maintain.

Complex information systems that will be assembled from a large number of components are good candidates for user-defined interfaces. This is especially true when the components will be owned and versioned by different programming teams. While user-defined interfaces force class authors and client application developers to deal with more complexity, they allow both sets of programmers to revise their work without adversely affecting any other code in the application.

Visual Basic also lets you ignore user-defined interfaces and use MultiUse classes instead. Many programmers create successful COM-based applications with Visual Basic without ever explicitly creating a user-defined interface. This can be a reasonable approach for small projects or projects that involve only automation clients. Before you begin a project, you must weigh the costs and benefits of user-defined interfaces and decide whether to use them in your designs.

If you decide to incorporate user-defined interfaces into your designs, you must address some other critical issues. Here are some questions that you should answer:

  • Should you publish your interfaces in a separate type library?

  • Should you define your interfaces with the Visual Basic IDE or with Interface Definition Language (IDL)?

  • Do you want to adhere to COM's original rules for object extensibility?

Publishing Interfaces in Separate Type Libraries

Interface-based programming requires a formal separation of interface from implementation. This means that all interfaces must be defined independently of coclass definitions. You should see this not only as a requirement in COM but also as an opportunity to define the interfaces for your project in a stand-alone type library file. This allows an application's lead designer to create all the interface definitions ahead of time and to distribute them to other programmers in efficient binary packaging.

In a large project, there are several advantages to defining all the interfaces in a type library file that is independent of any implementation code. This approach makes it easy to assign the ownership of interface definitions to a single team or individual. It makes it harder to add or extend interface definitions, but it builds more discipline into the development process. A developer who is creating a server can't casually add or extend an existing interface. Any change to the application's interface design must be made by the developer who owns the one type library that contains all the interface definitions.

Publishing interfaces in a separate type library also makes it easier to use the same interface definition across multiple servers. For instance, what if you want to create two ActiveX DLLs each of which contains a coclass that implements the same interface? When a designer publishes an interface in a separate type library, two independent DLL authors can easily reference this library and implement the interface. This allows several COM servers to serve up objects that are type compatible with one another. This is valuable when a system is designed around the idea of plug-compatible components.

In a smaller project, it might not make sense to publish interface definitions in a separate type library, especially if the project involves only one small team or a lone developer. It's easier to define the interfaces in the same server project as your coclasses. If your project doesn't benefit from maintaining interfaces and coclasses independently, the administrative overhead of building separate files is more trouble than it's worth.

If you decide to publish a type library of interface definitions, you must decide between using the Visual Basic IDE or IDL. Creating a type library of interface definitions with Visual Basic is fairly easy and a bit awkward. You simply create an ActiveX DLL project and define your interfaces in class modules marked as PublicNotCreatable. On the Component tab of the Project Properties dialog box, you must select the Remote Server Files option to ensure that a stand-alone type library file (with a .tlb extension) is created when you build the DLL. You can throw away the DLL and distribute the .tlb file to other programmers.

The awkward thing about using the Visual Basic IDE is that you must include at least one creatable class in any ActiveX DLL project. The Visual Basic IDE assumes that you want to create servers only, so you have to trick it into building an "interface-only" type library. You must create one dummy class module with the instancing setting MultiUse. It's a little confusing because anyone who uses your type library must ignore this dummy class. This is somewhat distracting, but it's the only way you can build a .tlb file using the Visual Basic IDE.

Defining Interfaces in IDL

Instead of using the Visual Basic IDE, you can publish your interfaces in a type library by writing IDL code. After you write the interface definitions, you feed the IDL source file to the Microsoft IDL (MIDL) compiler that ships with Visual Studio. You can feed the IDL file to the compiler from the command line as follows:

 midl DogServer.idl 

You can also create a workspace in the C++ environment that automates the build process. After you build the .tlb file, you can distribute it to other programmers. A Visual Basic programmer can register the .tlb file by opening the References dialog box from the Project menu and selecting the Browse option. This will open another dialog box that lets you to locate the .tlb file on your hard disk and include it in the current project. This also registers the type library on your development workstation.

Developers who use tools and languages other than Visual Basic like to define interfaces in IDL. For instance, COM-based projects using C++ require the use of IDL, so many developers use this language for publishing their type libraries. IDL gives you much more control over what gets published in your type library than does Visual Basic. For example, IDL lets you create type libraries without a dummy coclass. It also lets you select a specific GUID for an IID in a type library. (Visual Basic doesn't.) As you can see, Visual Basic automatically builds the type library behind the scenes, but it provides little flexibility.

If you use IDL to define your interfaces, you should be aware of Visual Basic's limitations when it comes to implementing COM interfaces. Many interfaces that can be expressed in IDL are usable from C++ but incompatible with Visual Basic. Programmers who are familiar with IDL from COM-based C++ projects must know what works in Visual Basic and what doesn't. If you want your interfaces to be Visual Basic compatible, you must follow these rules:

  • Method names can't start with an underscore (_).

  • Don't use COM [out] parameters unless you also use [retval].

  • All methods must return HRESULTs.

  • Don't use unsigned long or short integers.

  • Don't use parameters that contain pointers.

  • Don't use typedefs (UDTs in Visual Basic terms) in earlier versions of COM (prior to Microsoft Windows NT 4 with Service Pack 4).

  • Don't derive one user-defined COM interface from another user-defined interface. Visual Basic-compatible interfaces must derive directly from IUnknown or IDispatch. (And you should seriously consider deriving interfaces only from IUnknown.)

The first six rules above pose a slight inconvenience and can affect your interface's efficiency. For example, designers who are used to C++ must give up such things as unsigned integers and [out] parameters. But you must follow these rules if you want to implement your interfaces in Visual Basic classes.

You can quickly create IDL that is compatible with Visual Basic by simply building a server with the Visual Basic IDE that contains the interface definitions you want. You can then examine the server's type library with Ole View. The type library viewer of Ole View reverse-engineers the equivalent IDL from your Visual Basic type library. This lets you copy and paste the resulting interface definitions into a new IDL source file. Anything in a Visual Basic-generated type library is always Visual Basic compatible. Creating IDL in this manner is a great way to learn about the language.

The final rule in the list turns out to be a significant limitation when it comes to designing and refining an application's interface design. This rule states that a user-defined interface can't be derived from another user-defined interface. A Visual Basic class can inherit from an interface, but one interface can't inherit from another. This means that Visual Basic allows only one level of interface inheritance. As you'll see later in this chapter, this limitation changes the way you extend the functionality of objects in an application. An important thing to consider when you're defining interfaces with IDL is whether you should derive your user-defined interfaces from IUnknown or IDispatch. If you want to create a dual interface, you should derive from IDispatch and use the [dual] attribute, like this:

 [     uuid(079046B1-072F-11d2-B6F2-0080C72D2182)     object, dual, nonextensible ] interface IMyDual : IDispatch {     HRESULT Bark() } 

If you want to create a pure vTable interface, you should derive from IUnknown and use the [oleautomation] attribute to enable the universal marshaler, like this:

 [     uuid(079046B2-072F-11d2-B6F2-0080C72D2182)     object, oleautomation ] interface IMyCustomInterface : IUnknown {     HRESULT Bark() } 

Here's why you should derive from IUnknown instead of IDispatch. The only clients who really need dual interfaces are automation clients such as VBScript. These clients can't call QueryInterface either explicitly or implicitly. The only interface they can use is the default interface for a coclass. The only way to create a default interface in Visual Basic is to use public members in a creatable class. This means that any user-defined interface you define in IDL and implement in a Visual Basic class is unreachable by scripting clients.

It's safe to assume that any client that can navigate to a user-defined interface is capable of pure vTable binding. You should therefore derive the user-defined interface you create in IDL from IUnknown instead of IDispatch. When you use the Implements keyword in a creatable class in an ActiveX DLL or an ActiveX EXE, Visual Basic can implement either a pure vTable interface or a dual interface. However, pure vTable interfaces are cleaner than dual interfaces.

Factoring Interfaces with Inheritance

In the world of COM, the term interface implies an all-or-nothing contract. When coclass authors implement an interface, they take on the obligation to supply a reasonable implementation for each method in the interface. But what if you want to simply supply an implementation for a subset of the methods in an interface? It seems reasonable to assume that this would violate the rules of COM, but in fact it doesn't need to. COM has a workaround for this. You must supply a response for each method in an interface, but the object can indicate that it has no meaningful implementation for a method by returning a well-known HRESULT, E_NOTIMPL.

When several class authors elect to implement the same subset of methods for a specific IID, this is usually a sign of bad interface design. The interface designer should factor out this subset of methods into a smaller interface. Splitting out the subset into its own interface is known as interface factoring. If you use this technique properly, an object should never return E_NOTIMPL. There should always be a match in the set of methods that a class author wants to implement and in the set of methods you've defined in a user-defined interface.

Most C++ programmers who use IDL factor out interfaces by using interface inheritance. For example, suppose you created an interface named IWonderDog that includes both the Bark method and the FetchSlippers method. After several class authors have implemented your interface, you find that some of them have implemented the Bark method, but they raise an E_NOTIMPL exception when FetchSlippers is called. You can factor out the Bark method into the IDog interface in IDL using interface inheritance. The IWonderDog interface can simply be derived from IDog, like this:

 interface IDog : IUnknown {     HRESULT Bark(); } interface IWonderDog : IDog {     HRESULT FetchSlippers(); } 

Some class authors will implement IWonderDog and will therefore supply an implementation of both Bark and FetchSlippers. Other authors will want to supply only an implementation of Bark, so they can implement IDog instead. The use of inheritance in interface factoring makes things easy for both the interface designer and coclass authors. An author who implements the IWonderDog interface also implicitly implements the IDog interface. An object can hand out an IWonderDog reference to any client that requests the IDog interface because an IWonderDog reference "is an" IDog reference.

Factoring Interfaces with Visual Basic

Visual Basic can't implement a COM interface that's derived from another user-defined interface; it can only implement interfaces that are derived directly from IUnknown or IDispatch. If you define the IWonderDog interface in IDL by deriving from IDog, it will be incompatible with Visual Basic. You can still factor interfaces, but you have to do it in a much more awkward fashion. For instance, you can define the IWonderDog interface like this:

 interface IWonderDog : IUnknown {     HRESULT FetchSlippers(); } 

In this example, the IWonderDog interface is unrelated to the IDog interface. A client with an IWonderDog reference can't invoke the Bark method. When you factor interfaces, you typically want one interface to contain a superset of the methods from another. If you want to make the IWonderDog interface a superset of IDog, you must define it like this:

 interface IWonderDog : IUnknown {     HRESULT Bark();     HRESULT FetchSlippers(); } 

If you're creating a class that needs to be compatible with both IDog and IWonderDog, you must define two sets of entry points for each duplicated method in your class, like this:

 Implements IDog Implements IWonderDog Private Sub IDog_Bark()     ' Implementation End Sub Private Sub IWonderDog_Bark()     ' Forward to implementation     IDog_Bark End Sub Private Sub IWonderDog_FetchSlippers()     ' Implementation End Sub 

You can see that interface factoring is possible but awkward with Visual Basic. Visual Basic's inability to make use of interface inheritance is unfortunate. When you create a set of interfaces that you want to implement with both Visual Basic and C++, you can't use interface inheritance; that means C++ class authors suffer along with Visual Basic class authors.

Choosing an Approach to Extending Your Objects

COM purists and the Visual Basic team have starkly contrasting views on how to extend an object. Chapter 5 described the technique promoted by the Visual Basic team. This technique uses the concept of version-compatible interfaces, and the Visual Basic IDE cleverly builds all the code behind the scenes to hook up everything at run time.

You need to remember that Visual Basic creates a new IID each time it makes a new version-compatible interface. This means that Visual Basic is consistent with COM's concept of interface immutability. However, Visual Basic doesn't create a new logical name for the new interface. For example, assume that you have a class named CDog in a project marked for binary compatibility. Visual Basic creates a transparent interface for CDog named _CDog and assigns it an IID in the first build. If you add a few methods to the class and rebuild it, Visual Basic creates a different IID but the interface name remains _CDog. What's happening here? Visual Basic automatically changes the physical name of the interface from version to version, but the logical name remains the same.

C++ programmers using IDL, on the other hand, create a new logical name as well as a new physical IID for each new interface. These programmers will look at you strangely when you say that you're going to extend an existing interface (or Visual Basic class) by adding a few new methods. Extending interfaces isn't a part of their culture. When you create an application using both Visual Basic and C++, you should consider taking the concept of interface immutability one step further: When you create a new interface, you should also give it a new logical name (such as IDog2). This is difficult or impossible to do without using IDL. If you're working on a large project that involves Visual Basic, C++, and Java developers, it might be best to work strictly with user-defined interfaces defined within IDL source files.

In many situations, using IDL in a Visual Basic project is overkill. With small projects or projects that use only automation clients, IDL is too costly. With projects that use only Visual Basic components, you can trust the versioning and object extensibility model that you get when you build your server in binary-compatibility mode. The Visual Basic team provides you with a scheme that works well. As you can see, you must weigh many factors before you decide how to manage the interfaces for a project.

Programming Distributed Applications With Com & Microsoft Visual Basic 6.0
Programming Distributed Applications with Com and Microsoft Visual Basic 6.0 (Programming/Visual Basic)
ISBN: 1572319615
EAN: 2147483647
Year: 1998
Pages: 72
Authors: Ted Pattison © 2008-2017.
If you may any questions please contact us: