Freezing Interface Contracts

[Previous] [Next]

If you're following best practices, once you've published an interface it should remain static because an interface should be considered a binding contract between the client that references it and the class that implements it. Without conscious care, run-time error 429, "ActiveX component can't create object," will occur. This error is raised when a client attempts to reference an interface that doesn't exist. Three main problems generate this error: COM can't find the ActiveX component; the type library of interfaces and classes isn't registered; or an interface contract has been broken. The first two reasons can be chalked up to easily resolvable configuration errors. The third, however, requires our utmost attention.

Problems involving broken interface contracts become significantly amplified in two common scenarios: naive ActiveX component developers deploy their components, which are then used by clients built elsewhere; and component team development environments. In the first scenario, the inexperienced component developer will typically deploy subsequent releases of a component that contains broken interface contracts to computers running the client application, thereby breaking clients that functioned properly with the previous version.

In the second scenario, incompatibilities occur in the development environments of the team members when each member does independent builds on his or her own machine based on the most current source files checked into the source control system. When a team works with a source control system, it's reasonable to adhere to a premise based on best efforts, which states that only proven, successfully built and tested source code should be checked into the source control database. You might think adhering to this premise would prevent any problems, but when the team is working with COM components an additional stipulation must be followed: before a team can co-develop an ActiveX component, interfaces must be defined and frozen.

COM identifies unique interfaces and classes not by the name you give them in Visual Basic but rather by globally unique identifiers (GUIDs). A GUID is 128-bit value that is guaranteed to be unique across time and space. GUIDs can be generated via the COM API function CoCreateGuid. A simple alternative is to generate GUIDs using Guidgen.exe, a utility that ships with Microsoft Visual Studio and with the Microsoft Platform SDK. By means of its simple user interface, as illustrated in Figure B-1, you can generate new GUIDs in four different formats and cut and paste them into your application. GUIDs that identify interfaces and classes are often referred to as IIDs (interface identifiers) and CLSIDs (class identifiers), respectively. GUIDs predate COM, where they're used in remote procedure calls (RPCs) and referred to as UUIDs (universally unique identifiers). All these types of IDs are synonymous. COM users prefer the term GUID.

NOTE
Guidgen.exe is installed as part of the Visual Studio product suite and is by default located in the \Program Files\Microsoft Visual Studio\Common\Tools directory.

During the ActiveX component project Make process, Visual Basic internally calls CoCreateGuid to generate GUIDs for all interfaces and classes you defined within the project. To enable you to control GUID generation, I'll first explain how Visual Basic handles it, and then I'll describe the ideal solution for handling it by using a combination of Visual Basic features and COM IDL.

Figure B-1. Guidgen.exe is a simple and easy way to obtain new GUIDs.

Controlling Interface Compatibility with Visual Basic

Visual Basic allows you to select one of three compatibility options. Select Project Properties from the Project menu, select the Component tab, and then select an option in the Version Compatibility frame. As you can see in Figure B-2, the options are No Compatibility, Project Compatibility, and Binary Compatibility.

click to view at full size.

Figure B-2. Set the compatibility option in the Project Properties dialog box.

Selecting the No Compatibility option will cause Visual Basic to generate new GUIDs for all interfaces, classes, and the embedded type library every time you build the component. Both the Project Compatibility and Binary Compatibility options use a previously registered type library to determine interface compatibility. The results generated by selecting the Project Compatibility option differ depending on the version of Visual Basic you're running. Visual Basic 5 will retain the GUID that identifies the type library but will generate new IIDs and CLSIDs. Visual Basic 6 will only generate new IIDs. It retains CLSIDs across builds with the intention of supporting scripting clients that reference objects via CLSIDs. In other words, every time you build the project clients that bind to interfaces using the IIDs will fail, but scripting clients will continue to work. Project Compatibility mode also allows you to extend an interface by adding new properties and methods. To support this feature, Visual Basic generates a new IID that identifies the interface with the additions, and it adds an IID substitution entry that maps the old IID to the new IID in the Registry. Clients compiled with the older version therefore continue to work by binding to the new interface. New clients bind directly to the new interface.

Binary Compatibility provides the same features as Project Compatibility, with one addition. If you build your project with binary compatibility and then change an existing method, Visual Basic will alert you of a binary incompatibility prior to building the component. (See Figure B-3.) Clicking OK will result in breaking interface compatibility on older clients. New clients will obviously work. GUIDs that identify user-defined types (UDTs) and enumerations are also regenerated.

click to view at full size.

Figure B-3. Interface incompatibility alert dialog box.

Controlling Interface Compatibility with Visual Basic and COM IDL

None of the available interface compatibility options provided in Visual Basic alone enforces interface uniqueness and freezes interface contracts in the most appropriate and efficient way. Any action that results in changing an interface, including adding new methods, should be considered the creation of a new interface, not an extension to an existing one. To gain full control of this issue, you should do the following.

  • Avoid defining any properties and methods in the default interface (public method definitions in a public creatable class), with the exception of interface accessor methods for scripting language purposes. This point is explained in detail in the "Defining Automation Support" section.
  • Define interfaces using COM IDL.
  • Compile the IDL file into a type library using the Microsoft IDL (MIDL) compiler (midl.exe), which is included in the Visual C++ install.
  • Register the type library using the Microsoft Type Library Registration Tool (regtlib.exe), which is located in the %SystemRoot% directory (\Windows in Windows 98 and Windows 95, \Winnt in Windows NT and Windows 2000).
  • Servers that define classes that implement the interfaces and clients that bind to the interfaces supported by the classes must reference the same type library. Select References from the Project menu to open the References dialog box and set the appropriate references for a project. (See Figure B-4.)
  • Once you've defined all classes that implement the interfaces packaged in the type library, select Binary Compatibility in the Project Properties dialog box to ensure that scripting clients continue to work across component builds.

click to view at full size.

Figure B-4. Project References dialog box.

To keep code fragments simple and straightforward throughout this book, you'll see in the "Implementation" section of many of the chapters in Part II that I illustrate how to create interfaces using only Visual Basic. However, if you've looked at the sample applications on the companion CD you've notice that in many instances I use a combination of Visual Basic and COM IDL to address the issue of enforcing interface compatibility. So it's only fair that I explain the two undisclosed tasks at this point: defining interfaces in COM IDL and compiling the IDL file into a type library.

Defining Interfaces in COM IDL

IDL, which stands for Interface Definition Language, is a C-like language used mainly for defining COM interfaces. Interface definitions are typically kept in .IDL text files that are eventually compiled into type libraries. The following code fragment of DogLib.idl (taken from the Chapter 12 sample application) illustrates the structure of an IDL file that is compatible with Visual Basic. (I stripped out some of the code to highlight the most important parts that we'll focus on.)

 // File Name: DogLib.idl [ uuid(F6378500-526F-11D3-BD77-00C04F60A0D3), helpstring("BSTAM Dog 1.0 Type Library") ] library DogLib { // TLib : // TLib : OLE Automation :     {00020430-0000-0000-C000-000000000046} importlib("STDOLE2.TLB"); [ uuid(F6378503-526F-11D3-BD77-00C04F60A0D3), dual, nonextensible, oleautomation ] interface IDog : IDispatch {  HRESULT Bark([out, retval] BSTR* ); }; 

The following is a description of some of the elements in the IDL file.

  • uuid(F6378500-526F-11D3-BD77-00C04F60A0D3) This line (in square brackets above the library statement) is the GUID associated with the DogLib type library that will be added to the Registry.
  • library DogLib Defines the DogLib type library. In addition, all interfaces to be exposed in a type library must be defined within the library statement block.
  • importlib("STDOLE2.TLB"); This line is an instruction to import interfaces defined in type library STDOLE2.TLB. The IDispatch interface that is inherited by the IDog interface is obtained from the imported type library. To import interfaces from an IDL file, use the keyword import.
  • uuid(F6378503-526F-11D3-BD77-00C04F60A0D3) This IID identifies the IDog interface.
  • dual Attribute of interface IDog that indicates to the MIDL (Microsoft Interface Definition Language) compiler that IDog is both a custom v-table bound interface attribute and an Automation interface because it derives from interface IDispatch. Scripting language clients can then reference the IDog interface.
  • nonextensible The IDispatch implementation includes only properties and methods defined in the IDog interface and can't be extended at run time with additional methods not defined in the interface.
  • oleautomation Indicates to the MIDL compiler that you'll only use types in interface IDog that are supported by Visual Basic. You'll get a compiler warning when you don't adhere to those types. This attribute can also be used with pure custom interfaces (interfaces that don't derive from IDispatch).
  • interface IDog : IDispatch Specifics an interface IDog that derives from interface IDispatch, thereby making it a dual interface. Methods are defined within the interface.

Compiling IDL into a Type Library

Once you've defined your interfaces in an IDL file, similar to the previous code fragment, run the file through the MIDL compiler to generate a type library (.TLB file). MIDL is a command-line compiler, meaning you must execute it from a command line as follows:

 midl DogLib.idl 

Defining interfaces using IDL might seem a little strange at first, but you eventually get used to it. You're probably thinking it must be difficult to get the IDL code right, especially as the project grows in size. To avoid these worries, you can use the best IDL code generator for Visual Basic available on the market—Visual Basic. To generate an IDL file in Visual Basic, perform the following steps.

  1. Create and save an ActiveX DLL project, suffixing the name of the project with IDLGen. Visual Basic requires you to have at least one public creatable class in your project, but you don't have to put anything in it.
  2. For each interface you want to define in IDL, you must add a class module and change its Instancing property value to PublicNotCreatable and define public properties and methods for the interface.
  3. Make the DLL.
  4. Using the OLE/COM Object Viewer utility that ships with Visual Studio, locate the type library embedded in the DLL. With a little house cleaning, you can reverse engineer the IDL into an .IDL text file. (To start the OLE/COM Object Viewer, select it from the Tools menu in Visual C++ or run the oleview.exe file directly.)
  5. Unregister the IDLGen DLL.
  6. Compile the cleaned up IDL using the MIDL compiler.


Microsoft Visual Basic Design Patterns
Microsoft Visual Basic Design Patterns (Microsoft Professional Series)
ISBN: B00006L567
EAN: N/A
Year: 2000
Pages: 148

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net