Components deliver services by providing classes from which clients can create objects. Clients use services by creating objects and calling their properties and methods.
Information about the classes provided by your component is contained in a type library. In Visual Basic, the type library is included as a resource in the compiled component. Clients access the type library by setting references to it.
Project Name, on the General tab of the Project Properties dialog box, sets the name of your component’s type library, and is used to qualify the names of classes. For example, the following code fragment declares a variable that will hold a reference to an object of the Widget class, provided by a component whose project name is SmallMechanicals:
Public gwdgDriveLink As SmallMechanicals.Widget
Some applications can manipulate objects, but cannot declare variables of a specific object type. Such applications declare generic object variables As Object, and use the project name in the projectname argument of the CreateObject function to get a new object reference, as shown in the following syntax:
Set objectvariable = CreateObject("projectname.class")
The combination of project name and class name is sometimes referred to as a fully qualified class name, or as a programmatic ID. The fully qualified class name may be required to correctly identify an object as belonging to your component. For example, you might implement a Window class in your component. Microsoft Excel also provides a Window object, which could lead to the following confusion for client applications:
' A variable of the Microsoft Excel Window class.
Dim xlWindow As Excel.Window
' A variable of the ProgramX component's Window class.
Dim pxWindow As ProgramX.Window
' A variable of the Window class that belongs to the
' component - Microsoft Excel or ProgramX - that
' appears first in the client application's
' References dialog box!
Dim xWindow As Window
An interface is a set of properties and methods, or events. Every class provided by your component has at least one interface, called the default interface, which is composed of all the properties and methods you declare in the class module.
The default interface is usually referred to by the same name as the class, though its actual name is the class name preceded by an underscore. The underscore prefix is a convention, signifying that the name is hidden in the type library.
If the class raises events, it also has an IConnectionPointContainer interface that enumerates those events. Events are outgoing interfaces, as opposed to the incoming interfaces composed of properties and methods. In other words, clients make requests by calling into your class’s properties and methods, while the events raised by your class call out to event handlers in clients.
Incoming and outgoing interfaces are symbolized differently in interface diagrams, as shown in Figure 6.1.
Figure 6.1 Incoming and outgoing interfaces
Important You should consider interfaces as contracts between you and the user of your component, because changing an interface may cause applications compiled against your component to fail.
Visual Basic provides two mechanisms for enhancing components without affecting compiled applications: version compatibility for interfaces, and multiple interfaces. In order to discuss these mechanisms, however, you have to learn to dig GUIDs.
GUID (pronounced goo-id) stands for Globally Unique IDentifier, a 128-bit (16-byte) number generated by an algorithm designed to ensure its uniqueness. This algorithm is part of the Open Software Foundation (OSF) Distributed Computing Environment (DCE), a set of standards for distributed computing.
GUIDs are used to uniquely identify entries in the Windows registry. For example, Visual Basic automatically generates a GUID that identifies your type library in the Windows registry.
Visual Basic also automatically generates a GUID for each public class and interface in your component. These are usually referred to as class IDs (CLSID) and interface IDs (IID). Class IDs and interface IDs are the keys to version compatibility for components authored using Visual Basic.
Note You may also see GUIDs referred to as UUIDs, or Universally Unique IDentifiers.
This is not a problem we need to worry about in our lifetimes. The algorithm that generates GUIDs would allow you to compile several new versions of your component every second for centuries — without repeating or colliding with GUIDs generated by other developers.
When a developer compiles a program that uses your component, the class IDs and interface IDs of any objects the program creates are included in the executable.
The program uses the class ID to request that your component create an object, and then queries the object for the interface ID. An error occurs if the interface ID no longer exists.
During development of a new component Visual Basic generates new CLSIDs and IIDs every time you compile, as long as either Project Compatibility or No Compatibility is selected on the Component tab of the Project Properties dialog box. Once you’ve released a component, however, and begin working on an enhanced version of it, you can use the Binary Version Compatibility feature of Visual Basic to change this behavior.
As described in detail in "Version Compatibility" in "Debugging, Testing, and Deploying Components," binary version compatibility preserves the class IDs and interface IDs from previous versions of your component. This allows applications compiled using previous versions to work with the new version.
To ensure compatibility, Visual Basic places certain restrictions on changes you make to default interfaces. Visual Basic allows you to add new classes, and to enhance the default interface of any existing class by adding properties and methods. Removing classes, properties, or methods, or changing the arguments of existing properties or methods, will cause Visual Basic to issue incompatibility warnings.
If you decide to ship an incompatible interface, Visual Basic changes the major version number of the type library and suggests that you change the executable name and Project Name, so that the new version of your component will not over-write the old on your users’ hard disks.
The Implements statement, discussed in "Polymorphism," in "Programming with Objects" in the Visual Basic Programmer’s Guide, allows your classes to implement additional interfaces.
When two classes implement the same secondary interface, they are said to be polymorphic with respect to that interface. That is, a client can make early-bound calls to the properties and methods of the interface without having to know the class of the object it’s using.
By creating standard interfaces and implementing them in multiple classes, provided by one or more components, you can take advantage of polymorphism in your applications, or across your entire organization.
For More Information "Creating Standard Interfaces with Visual Basic," later in this chapter, explains how to create interfaces by defining class modules that have no implementation code.
Multiple interfaces provide an alternate means of enhancing and evolving your components while maintaining compatibility with older applications compiled against earlier versions.
The ActiveX rule you must follow to ensure compatibility with multiple interfaces is simple: once an interface is in use, it can never be changed. The interface ID of a standard interface is fixed by the type library that defines the interface.
The way to enhance standard interfaces is to create a new standard interface, embodying the enhancements. Future components, or future versions of existing components, can implement the old interface, the new interface, or both, as shown in Figure 6.2.
Figure 6.2 Providing compatibility with multiple interfaces
The IFinance and IFinance2 interfaces are defined in separate type libraries, which are referenced by the components that implement the interfaces (in this case, versions 1.0 and 2.0 of Finance.dll), and by the applications that use the interfaces (Payroll and GeneralLedger).
System evolution is possible because future applications can take advantage of new interfaces. Existing applications will continue to work with new versions of components, as long as the components continue to implement the old interfaces as well as the new.
You can write applications that can use any of several versions of a component. For example, version 2.0 of GeneralLedger.exe could be written to use IFinance2 if that interface was available, and to use IFinance otherwise.
Obviously, GeneralLedger would be able to provide only a limited set of features in the latter case. This ability to provide limited functionality in the absence of the preferred interface is often referred to as degrading gracefully.
To work with either interface, GeneralLedger might contain code like the following:
Dim fnr As FinanceRules
Dim ifin As IFinance
Dim ifin2 As IFinance2
On Error Resume Next
Set fnr = New FinanceRules
' (Error handling code omitted.)
' Attempt to access the preferred interface.
Set ifin2 = fnr
If Err.Number <> 0 Then
' Access the more limited interface.
Set ifin = fnr
' (Code to provide limited functionality,
' using the object variable ifin.)
Else
' (Code to provide full functionality,
' using the object variable ifin2.)
End If
For More Information "Providing Polymorphism by Implementing Interfaces," later in this chapter, discusses the use and naming of standard interfaces.