One important aspect of COM+ is its language neutrality. A COM+ component can be written in any language and then seamlessly called from any other language. In the previous section, we defined the ISum interface using C++ code. In Java, the same ISum interface would look like this:
public interface ISum extends com.ms.com.IUnknown { public abstract int Sum(int x, int y); } |
In Visual Basic, ISum would look like this:
VERSION 1.0 CLASS BEGIN MultiUse = -1 'True END Attribute VB_Name = "ISum Attribute VB_Exposed = True Attribute VB_Creatable = True Public Function Sum(x As Long, y As Long) As Long End Function |
These three language-based definitions expose the fallacy of our assumptions. How can we claim to have a language-neutral architecture and then define a single interface differently in every programming language? This would lead to chaos, since a single interface could have multiple correct definitions. The answer to this problem lies in IDL. (We'll introduce IDL in this chapter but provide more details in Chapter 16.)
The Open Software Foundation (OSF) originally developed IDL for the Remote Procedure Call (RPC) package of its Distributed Computing Environment (DCE). IDL helps RPC programmers ensure that both the client and server sides of a project adhere to the same interface. It is important to realize that IDL is not a programming language—it is a language used only to define interfaces.2 For this reason, Microsoft decided to adopt IDL for use in defining COM+ interfaces. Standardizing on one special language for defining interfaces eliminates the confusion generated by having multiple languages define the same interface differently. You can code the implementation of an interface defined in IDL in any language you want. Today, all COM+ programming should begin in IDL. The interface definition for ISum written in IDL is shown in Listing 2-1.
component.idl
import "unknwn.idl"; [ object, uuid(10000001-0000-0000-0000-000000000001) ] interface ISum : IUnknown { HRESULT Sum([in] int x, [in] int y, [out, retval] int* retval); }; |
Listing 2-1. The interface definition for the ISum interface expressed in IDL.
The IDL file quickly betrays its roots in C. A cursory examination reveals a construct rather like a header file, which provides forward declarations for functions. Several aspects of this interface definition immediately attract attention. First, the definition begins with the object attribute, a Microsoft extension to IDL that identifies a COM+ interface. Any interface definition that doesn't begin with the object attribute describes an RPC interface, not a COM+ interface. After the object attribute comes the interface's universally unique identifier (UUID), which distinguishes it from all other interfaces. A UUID is a 128-bit number, usually represented in hexadecimal, that is guaranteed to be unique across space and time. Two different developers might define an interface named ISum, but so long as both interfaces have different UUIDs, no confusion will result. The ISum interface as defined in the IDL file derives from IUnknown, the root of all COM+ objects. The interface definition of IUnknown itself is imported from the unknwn.idl file, where it is defined as follows:
[ local, object, uuid(00000000-0000-0000-C000-000000000046), pointer_default(unique) ] interface IUnknown { typedef [unique] IUnknown *LPUNKNOWN; HRESULT QueryInterface( [in] REFIID riid, [out, iid_is(riid)] void** ppvObject); ULONG AddRef(); ULONG Release(); } |
Notice in the ISum interface definition that each argument of the Sum method is preceded by a directional attribute, [in] or [out]. Since the interface defines the communication between a client and a component, which might end up running on separate machines, the interface definition specifies the direction in which each parameter must travel. Directional attributes in IDL are optimizations that reduce the data transmitted between the client and the component. In this case, the first two parameters of the Sum function must be passed only to the component; they do not need to be passed back to the client because their value will not have changed. The third parameter is flagged with the [out, retval] attributes, indicating that the parameter is a return value and thus needs only to be passed back from the component to the client. Some languages such as Visual Basic and Java that insulate the developer from the returned HRESULT value transparently make the [out, retval] parameter appear to be the value returned by the function. Note that the [in] attribute is not required because it is applied to a parameter by default when no directional parameter attribute is specified.
Once you define an interface in IDL, you can use the Microsoft IDL (midl.exe) compiler to translate the interface into several C/C++ language source files. For now, we will focus on the header file generated by the MIDL compiler, which contains a C++ version of the interfaces defined in IDL. (For more information about the files generated by the MIDL compiler, see the section titled "Interprocess Communication" in Chapter 14.) For example, after compiling the component.idl file containing the ISum interface discussed above, the MIDL compiler generated a header file named component.h that includes the following code:
MIDL_INTERFACE("10000001-0000-0000-0000-000000000001") ISum : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE Sum( /* [in] */ int x, /* [in] */ int y, /* [retval][out] */ int __RPC_FAR *retval) = 0; }; |
This code looks a lot like the C++ interface definition presented earlier; the only difference is the use of the MIDL_INTERFACE rather than the class keyword and the addition of the STDMETHODCALLTYPE macro. (In the basetyps.h system header file, STDMETHODCALLTYPE is defined as __stdcall.) In the rpcndr.h system header file, the MIDL_INTERFACE macro is defined as follows:
#define MIDL_INTERFACE(x) struct __declspec(uuid(x)) __declspec(novtable) |
In C++, a struct is equivalent to a class whose members are public by default. You can use __declspec(uuid("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")) , a Microsoft extension to C++, to attach a GUID directly to a class or structure. This GUID can later be retrieved using another Microsoft extension: __uuidof(x). (The ability to have the compiler be aware of the GUID assigned to a class and be able to retrieve that GUID will prove useful later in this chapter.) The extension __declspec(novtable) tells the compiler not to generate code that initializes the virtual function pointer in the constructor(s) and destructor of the class. This significantly reduces code size. You should apply this option only to abstract base classes such as interface definitions.
We have discussed the problems involved in defining interfaces in a specific programming language, and we have learned how to use IDL to define interfaces in a manner independent of any individual programming language. We have also used MIDL to generate C/C++ code, bringing us back to a language-specific interface definition, the point at which we began. If MIDL could generate Visual Basic and Java code in addition to C++ code, language-dependent interface definitions generated from IDL code would be acceptable. However, you would be wrong to assume that MIDL contains some magic command-line parameter to generate anything other than C/C++ code. To avoid having to update MIDL's code generation engine for every new language that comes along, Microsoft looked for a more extensible mechanism—a way for MIDL to generate interface definitions in a single, universal format that all languages—including Visual Basic, Java, and C++ (arguably a broad spectrum of languages with quite different goals)—can understand. While you ponder the possibilities, let's turn to the client project.