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.
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.
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.
Figure B-3. Interface incompatibility alert dialog box.
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.
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.
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.
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.