Type Libraries and Language Independence

[Previous] [Next]

In COM, clients and objects communicate through vTable binding. This implies that clients and objects must be savvy in their use of function pointers. Luckily, the compiler helps C++ programmers by doing most of the work behind the scenes. vTables are automatically created and populated by the C++ compiler on the object side. The C++ compiler can also generate the required client-side binding code that invokes methods through the vTable function pointers. All client-side programmers have to do is compile their work against a header file that contains the definition of the abstract base class.

But what about programmers who use Visual Basic, Java, and scripting languages? Many developer tools and languages such as these have no built-in support for dealing with function pointers. To create or use COM objects, a development tool or language must follow the COM Specification, which states that vTable binding must be used to conduct all client-object communication. Many higher-level tools and languages need assistance to be able to participate in COM programming.

Visual Basic provides this assistance by adding support to its compiler and adding a Visual Basic-to-COM mapping layer in its runtime. After all, the COM Specification defines the rules clearly. The Visual Basic team knew exactly what it would take to make objects vTable-compliant. On the client side, the Visual Basic compiler automatically creates the vTable binding code required to access a COM object. Fortunately, when a language or tool such as Visual Basic uses a mapping layer, it can hide many of the underlying details from its programmers. This is why Visual Basic is so much easier to use than C++ for COM programming.

The Visual Basic compiler can't make sense of a C++ header file. Therefore, it can't use an abstract base class at compile time to build the client-side binding code. Instead, Visual Basic uses interface definitions that are published inside a special type of file called a type library. The type library tells the Visual Basic compiler what the vTable for each interface looks like.

A type library is a binary repository that contains type information about such things as interfaces and coclasses. It's essentially a catalog that defines a collection of COM data types. Inside a type library, each interface is defined as a set of methods; each coclass is defined as a set of one or more implemented interfaces. When you compile an ActiveX DLL or an ActiveX EXE, Visual Basic automatically creates a type library for you and bundles it into the server's executable image. This type library contains definitions for the coclasses provided by the server and a definition for each interface defined in the server's project.

When compiling a client application, Visual Basic needs to see the server's type library in order to build the client-side binding code. As long as Visual Basic's compiler can read the interface definitions from a type library, it can build the code required for direct vTable binding. You can import a type library into a Visual Basic project by opening the References dialog box from the Project menu. This dialog box presents a list of all the type libraries registered on your development workstation.

So how do you build a type library? It turns out that different programmers build them in different ways. With Visual Basic, it couldn't be easier. You simply compile your server project, and Visual Basic transparently creates the type library behind the scenes by examining your source code. However, many programmers build type libraries using a special language called Interface Definition Language (IDL).

Working with IDL

While type libraries define interfaces and coclasses in a binary format, IDL does the same thing in a readable text-based format. C++ developers who need to create type libraries start by using IDL. Once a developer has defined interface and coclass definitions with IDL, the resulting IDL source file can be fed to the Microsoft IDL (MIDL) compiler. The MIDL compiler is capable of emitting many different things including a type library.

If Visual Basic can build type libraries automatically, why can't a C++ compiler do the same? When the COM team began formalizing the COM Specification, it became obvious that C++ and C couldn't be used to define COM interfaces. C and C++ weren't designed to define functions that can be called across process boundaries, so they allow parameter definitions that are extremely vague. For instance, if a C++ method defines a parameter as a pointer, what does the pointer actually point to? What data must actually move between the client process and the object process? A mere pointer can't define what needs to be moved between the two processes. IDL solves this problem by using syntax that describes method parameters without ambiguity.

IDL looks a lot like C, but it adds a few object-oriented extensions. It also allows the specification of attributes for entities such as type libraries, coclasses, interfaces, methods, and parameters. Here's a watered-down example of what IDL looks like:

 library DogServerLib { interface IDog { HRESULT Bark(); HRESULT RollOver([in] int Rolls); }; interface IwonderDog { HRESULT FetchSlippers(); }; coclass CBeagle { interface IDog; interface IWonderDog; }; }; 

This example shows how type libraries, interfaces, and coclasses are defined in IDL. Note that each method definition has a return value of type HRESULT. COM requires that methods return HRESULT to establish a standardized infrastructure for error reporting. This infrastructure is particularly valuable in a distributed environment in which remote objects can crash or become unreachable for any number of reasons. Chapter 4 discusses error propagation and error handling in more detail. We'll revisit the topic of how HRESULTs are used in that chapter.

When C++ programmers want to create components and interfaces, they must first create an IDL source file containing the appropriate definitions and feed it to the MIDL compiler. Visual Basic programmers, on the other hand, don't go through this process because the Visual Basic IDE creates type libraries directly from Visual Basic code. As you can see, Visual Basic programmers never have to work with IDL.

You can live a productive life as a Visual Basic programmer without ever seeing or understanding IDL. However, if you learn the basics of IDL, you'll be a better COM programmer. This is especially true if you're concerned with what's really going on behind the scenes or with interoperability among components written in other languages such as C++. With an understanding of IDL, you can see exactly what Visual Basic is doing under the covers to provide support for COM.

A COM utility named OLEVIEW.EXE can help you reverse-engineer a type library into readable text-based IDL code. Figure 3-2 shows how you can use this utility to examine the coclasses and interfaces built into your ActiveX DLLs and ActiveX EXEs. Be sure to set OLEVIEW.EXE to expert mode (the Expert Mode command is on the View menu) when you attempt to read a type library. If you don't do this, you won't see the type libraries in the left-side tree view control of OLEVIEW.EXE.

click to view at full size.

Figure 3-2 OLEVIEW.EXE lets you examine and modify many aspects of your COM servers. This example shows how OLEVIEW.EXE lets you reverse-engineer a type library.

How Visual Basic Maps to COM

COM requires that every object implement at least one interface. Visual Basic makes things easy for you by creating a default interface in each creatable class. All of the public methods and properties from a class are placed in a default interface. For instance, assume that you have (in an ActiveX DLL) a class CCollie with the following public interface:

 Public Name As String Public Sub Bark() ' implementation End Sub 

Visual Basic creates a hidden interface named _CCollie from the public properties and methods of the class module. The fact that this interface is marked hidden in the type library means that other Visual Basic programmers can't see it in the Object Browser or through Microsoft IntelliSense. (Visual Basic also hides any type name that begins with an underscore.) Visual Basic then creates a coclass named CCollie that implements _CCollie as the default interface. The basic IDL looks like this:

 [hidden] interface _CCollie { [propget] HRESULT Name([out, retval] BSTR* Name); [propput] HRESULT Name([in] BSTR Name); HRESULT Bark(); }; coclass CCollie { [default] interface _CCollie; }; 

This transparent one-to-one mapping allows Visual Basic classes to be COM-compliant without any assistance from the programmer. Naive Visual Basic programmers have no idea what's really going on. Any Visual Basic client can contain the following code to use this class:

 Dim Dog As CCollie Set Dog = New CCollie Dog.Name = "Lassie" Dog.Bark 

In the code above, the variable declared with the type CCollie is transparently cast to a _CCollie reference. This makes sense because a client must communicate with an object through an interface reference. This also makes COM programming easy in Visual Basic. As long as there is a one-to-one mapping between your classes and the interfaces that you want to export, you don't have to create user-defined interfaces. However, if you don't employ user-defined interfaces, you can't really tap into the power of interface-based designs.

Chapter 2 showed how to implement user-defined interfaces in a Visual Basic application. When you define an interface in Visual Basic 5 with a PublicNotCreatable class and implement it in a class, the resulting IDL code looks something like this:

 interface IDog { HRESULT Name([out, retval] BSTR* Name); HRESULT Name([in] BSTR Name); HRESULT Bark(); }; interface _CBeagle { }; coclass CBeagle { [default] interface _CBeagle; interface IDog; }; 

Notice that IDog is defined as an implemented interface in the CBeagle class, yet it's not marked as the default interface. Even when your class contains no public members, Visual Basic automatically creates a default interface of the same name preceded by an underscore. You can't change this to make another interface the default. In the next chapter, you'll see that sometimes you must put functionality in the default interface, which means that you must add public members in your class. This is always the case when you create Visual Basic components for scripting clients.

Visual Basic 6 works differently than Visual Basic 5. When you mark a class module as PublicNotCreatable, Visual Basic 6 creates a coclass and a default interface. For instance, when you create the interface IDog with a property and a method in a class module marked as PublicNotCreatable, the resulting IDL looks like this:

 [hidden] interface _IDog : IDispatch { HRESULT Name([out, retval] BSTR* Name); HRESULT Name([in] BSTR Name); HRESULT Bark(); }; [noncreatable] coclass IDog { [default] interface _IDog; }; 

This means that in Visual Basic 6 you can't create a COM interface without an associated coclass inside your server's type library as you could in Visual Basic 5. PublicNotCreatable class modules always produce an interface and a noncreatable coclass. The noncreatable attribute means that the class can't be instantiated from a COM client. However, any code that lives inside the same server project can create objects from a PublicNotCreatable class. In Visual Basic 6, you should think of these as "public-not-externally-creatable" classes.

It turns out that the differences between Visual Basic 5 and Visual Basic 6 are hidden inside the type library. The code you write in Visual Basic 6 is the same as the code you write in Visual Basic 5. In the example above, IDog is a coclass and _IDog is a hidden interface. Whenever you use the type IDog with the Implements keyword or use it to create object references, Visual Basic silently casts it to _IDog.

Note that your classes can also implement interfaces that are defined in type libraries referenced in your project. To implement them, you must import the type library using the References dialog box (accessed from the Project menu). These type libraries can be built with either Visual Basic or by using IDL and the MIDL compiler. Once your project can see the interface definition, you can use the Implements keyword in a class module. In large projects whose designs depend on user-defined interfaces, it might make sense to distribute the interface definitions in a type library that's independent of any servers that implement them. In Chapter 5, we'll look at how to build and distribute type libraries using IDL and the MIDL compiler. More important, we'll look at some reasons why you might or might not want to do this.

Globally Unique Identifiers (GUIDs)

COM offers a scheme for identifying data types such as interfaces and coclasses that is much better than relying on their human-readable names such as IDog and CBeagle. COM uses something called a globally unique identifier (GUID). A GUID is a 128-bit integer. Approximately 3.4 x 1038 values are possible for a 128-bit integer, so it's safe to assume that an unlimited supply of these identifiers is available for use in COM. Most compilers and databases don't support 128-bit integers, so GUIDs are usually stored in other formats.

C++ programmers use a structure (equivalent to a user-defined type in Visual Basic) that represents a 128-bit value with a set of smaller integer values. A GUID can also be expressed in 32-character hexadecimal form, which makes it somewhat readable. This is known as the Registry format because that's how GUIDs are stored in the Windows Registry. Here's an example of what a GUID looks like in the Registry:

 {C46C1BE0-3C52-11D0-9200-848C1D000000} 

COM supplies a system-level function named CoCreateGUID, which is used to generate new GUIDs. The function relies on an algorithm that uses information such as the unique identifier from the computer's network card and the system clock to create a GUID that is guaranteed to be unique across time and space. Programmers creating interface or coclass definitions in IDL can use a utility named GUIDGEN.EXE to generate GUIDs. This allows them to copy and paste GUIDs into IDL source files as well as C++ source code. Visual Basic programmers never have to worry about this because the Visual Basic IDE generates GUIDs behind the scenes when they're needed.

GUIDs are used in many places in COM, but you should start by examining their use with interfaces and coclasses. Each COM interface has an associated GUID called an interface ID (IID). Each coclass has an associated GUID called a class ID (CLSID). When you examine IDL, you'll notice that each interface and coclass has a uuid attribute (where uuid stands for universally unique identifier). Don't let the uuid attribute confuse you. A uuid is the same thing as a GUID. Here's an example of an IID and a CLSID in IDL:

 [ uuid(3B46B8A8-CA17-11D1-920B-709024000000) ] interface _IDog { // methods }; [ uuid(3B46B8AB-CA17-11D1-920B-709024000000) ] coclass CBeagle { // interfaces }; 

When you build an ActiveX DLL or an ActiveX EXE, all required CLSIDs and IIDs are transparently generated and compiled into the server's type library. The GUID then becomes the physical name for an interface or a coclass. When a client application is compiled against the type library, these GUIDs are also compiled into the client's binary image. This enables the client application to ask for a specific coclass and interface whenever it needs to create and bind to an object at runtime.

Visual Basic does a pretty good job of hiding GUIDs from programmers. When you reference a type library in a Visual Basic project, you simply use the friendly names of interfaces and coclasses in your code. Visual Basic reads the required GUIDs out of the type library at compile time and builds them into the EXE or DLL when you choose the Make command from the File menu.

When you create a COM server, Visual Basic also hides the GUIDs from you. It automatically generates GUIDs for your interfaces and coclasses on the fly whenever you build a server using the Make command. This is convenient, but sometimes it would be nice if Visual Basic offered a little more flexibility. It doesn't allow you to take a specific GUID and associate it with an interface or a coclass. For instance, you might have a cool designer GUID like this:

 "{DEADBEEF-BADD-BADD-BADD-2BE2DEF4BEDD}" 

Unfortunately, the Visual Basic IDE won't let you assign this GUID to one of the coclasses in your Visual Basic server. Visual Basic also requires that you build your server projects with an appropriate compatibility mode setting. If you don't, your GUIDs will change from build to build. Chapter 5 covers compatibility in greater depth.



Programming Distributed Applications with COM+ and Microsoft Visual Basic 6.0
Programming Distributed Applications with Com and Microsoft Visual Basic 6.0 (Programming/Visual Basic)
ISBN: 1572319615
EAN: 2147483647
Year: 2000
Pages: 70
Authors: Ted Pattison

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