Chapter 5. Method Invocation and Marshaling

So far, we've talked about the relationship between interfaces, vtables, and RPCs. Interfaces can be looked at as RPC channels of communication, and you saw how the methods of an interface can be defined in IDL and then compiled by MIDL.EXE into proxy/stub code. Although there are a number of additional details, the COM-based interface implementation can be viewed as a more object-oriented version of the traditional RPC implementation—in either case, you define your interfaces and methods in Interface Definition Language (IDL). MIDL.EXE generates the appropriate proxy/stub code necessary to allow method calls made to your object to traverse the network via RPC.

COM RPC borrows from traditional RPC in a few regards, but one of the most important is how data is marshaled over the wire. Remember that method arguments must be packaged by a proxy function into a form that can be transported over the network. If used as method arguments, pointers must be resolved, arrays must be plucked from the caller's memory and packaged for shipment, and an assortment of other mechanisms come into play to allow method calls to span machine and process boundaries. Clearly, IDL provides the information necessary for MIDL.EXE to generate the appropriate proxy/stub code to do this job. In fact, you can use structures, arrays, strings, and even abstract data structures, such as linked lists and binary trees, as method arguments if you define them correctly in IDL. Of course, more esoteric structures like linked lists or the use of most pointer-based arguments disallow Visual Basic clients from using your object; VB allows only a limited range of types. However, your C++ COM clients can happily use these methods.

Once your component is written and its IDL file is compiled into a type library, COM+ offers two ways for a client to call its methods. In COM terminology, these two methods are referred to asearly andlate binding, and this chapter describes the implementation of both techniques and details the pros and cons of each.

In this chapter, we look at how COM marshaling works in a little more detail. Then, we discuss two different ways of invoking methods on COM objects: early and late binding.

Type Library Marshaling

As discussed in Chapter 1, "COM+: An Evolution," proxy/stub code does not need to be compiled into your client or server binaries; but in traditional RPC, it must be. In traditional RPC IDL, MIDL.EXE generates C files containing complete proxy and stub functions with the expectation that you will compile them into your existing C client and server projects. COM RPC,or Object RPC (ORPC) as it is often called, can still generate complete proxy/stub C files, but they do not need to be compiled in your COM client or server DLL. That is not what they are for. You are probably wondering why MIDL bothers to create them at all, but I am going to delay that discussion for a few paragraphs. For now, imagine that the only MIDL-generated file that really matters is theTLB. The TLB is important because it moves the marshaling responsibility out of the client and server and instead relegates it to COM itself. In other words, the existence of a type library allows COM to perform the marshaling on behalf of the client applications and server objects—relieving them completely of the need to hard-wire or hard-code their marshaling responsibilities in their binaries the way traditional RPC does. Imagine that RPC is the acronym for Real Proxy C files. TLB, then, can be thought of as Totally Liberating Binaries—in other words, type libraries can relieve the binaries from the responsibility of marshaling code.

Because a TLB file seems to be able to assume all marshaling responsibilities for a client and server object, you might imagine that the TLB contains proxy/stub code. It doesn't. Quite the contrary; the TLB file is only a tokenized form of IDL. But, just as traditional RPC IDL is used by MIDL.EXE to generate proxy/stub functions for compile-time support of method remoting, type libraries provide for the creation of run-time proxy/stub functionality. In other words, COM provides marshaling services when those services are required by a client and server object by using the object's type library as a real-time roadmap. It is like the difference between an interpreted language and a compiled one. Dynamic proxy/stub creation keeps the clients and objects lighter, eliminates any kind of language affinity, and provides a binary, language-independent standard of remotability. The process of using a type library to package and remote (marshal) method calls is sometimes called type library marshaling.

If you are using data types that VB cannot understand (C++ pointers, for example), type library marshaling doesn't work. As you see in the section, "Invoke Arguments and Marshaling," type library marshaling uses COM's built-in oleautomation marshaler (OLEAUT32.DLL). But oleaut32.dll can only marshal VB data types (type libraries originated with VB, which is why the two are so closely related). For special C++ types, a type library by itself is no longer enough; you must supply your own marshaler.

Although this sounds complicated, it isn't hard. Take the extra proxy/stub C file that MIDL creates and build a second DLL. This DLL is called aproxy/stub DLL and, because it was created based on your data types defined in IDL, it knows how to marshal your unusual method arguments. This DLL also needs to be registered, just as if it is a COM component, which it is. If you don't like the concept of shipping two DLLs, they can be merged into one. C++ ATL projects give you this option.

It is fair to say that if you have a type library for a specific object registered on a client machine and you are only using VB compatible data types, nothing more is needed for a client application to instantiate and use the remote COM object. However, as useful as type libraries are, there are situations where it is not possible or especially helpful to have a type library present. The following example might be a stretch, but it's one possibility: You work on a locked down NT workstation in a large corporation and are not permitted to modify Registry settings or write to your hard drive (yes, there are such places). However, there is a COM+ object you really want to use in your VB program, but you don't have permissions to install the type library on your machine.

Installation of type libraries is not especially convenient, and certain development environments like Microsoft Internet Information Server cannot access or use a type library when instantiating and calling methods on COM objects. In fact, although IIS'Active Server Pages (ASP) allow you to create and manipulate COM objects to generate HTML, they do not allow the use of a type library to marshal data. Similarly, the scripting engine that executes .VBS (Visual Basic Script or VBScript) files from the command line does not allow the use of type libraries. Why don't these environments facilitate the use of so convenient a mechanism? I suspect it has to do with the complexity of the development environments and the interpretive, non-compilable nature of how they operate. ASP pages, for example, are never compiled; they are interpreted in real-time by IIS. Second, there is no de facto development environment for creating ASP pages. Some developers use Visual InterDev, and some use NOTEPAD.EXE. The use of type libraries, however, demands that the development environment read and interpret them. Because there is no development environment for ASP pages, there is no way to associate the VBScript with a particular type library. It might be that future versions of IIS will, in fact, allow the use of type libraries. At present, however, it does not.

We have established that there are certain situations where you do not have a type library but nonetheless want to use a remote COM object. The method you have employed so far to utilize type libraries is called early binding, and the method that does not use the type library is called late binding. However, as you see later in this chapter where type libraries are used for late binding (see the "Dispinterfaces" section later in this chapter), this is not a hard-and-fast rule. For the moment, however, think of late binding as being required in the absence of a type library. Late binding is easiest to understand in this context.

It is difficult to "show" late binding because on the surface, source code demonstrating early and late binding is almost identical (see Listing 5.1).

Example 5.1.  A VB Client Remotely Controlling Excel Using Early Binding

Dim xlApp as Excel.Application
Set xlApp = New Excel.Application

xlApp.Visible= True
xlApp.Workbooks.Add 'adds a new workbook

The code in Listing 5.1 creates a new instance of Excel, makes the new instance visible, and adds a new workbook. This code assumes that Microsoft Excel has been selected in VB's Project references dialog (this dialog is summoned by selecting the Project, References menu item of the VB IDE). This dialog is responsible for including an object's type library into the current VB project. The inclusion of the type library adds component-specific data types (for example, Excel.Application) to the current project so that they can be implemented in the same way as a VB class. The presence and use of a type library usually means early binding is being used. Late binding looks identical to early binding, but with two small differences (see if you can spot them). Listing 5.2 demonstrates the same functionality as shown in the previous listing, but late binding is being used.

Example 5.2.  A VB Client Remotely Controlling Excel Using Late Binding

Dim xlApp as Object 'note the change in data type to Object
Set xlApp = CreateObject("Excel.Application") 'note we are using the CreateObject
 function

xlApp.Visible= True
xlApp.Workbooks.add 'adds a new workbook

Note that the code in Listing 5.2 does not require that type library of Microsoft Excel be included in the project. However, because the type library is not included, we can no longer declare xlApp to be of type Excel.Application (VB is not, without the inclusion of the type library, familiar with the Excel.Application data-type). Instead, we have to declare xlApp as the generic Object data-type. Similarly, we can't implement a new instance of Excel.Application via the New keyword because VB is not natively familiar with this data-type. Thus, we need to use the CreateObject( ) function, passing in the string "Excel.Application." In the next section, "Late Binding," the use of the Object data-type always means late binding is being used. Let's move on to the next session and explore how late binding works and why it is useful.

Late Binding

If you have ever played Marco Polo in some swimming pool in your youth, you understand the basic premise of late binding. Late binding is a cry in the dark. A client shouts the name of the method it wants to call over the network, and the server object responds with a magical number. This number represents a specific method, and when given, the client can invoke the method by using this number.

If you are incensed at the overhead involving two round trips to call one method (the first to get the magic number and the second to invoke the method) and are wondering why this is necessary, you should keep a couple things in mind. First, COM clients do not know with any certainty what functionality an object may support. An early binding QueryInterface() can be used to ask an object whether a certain interface is supported. In late binding, however, the actual name of the method is used. In either case, the object has the right to say, "I don't support that functionality."

If COM is based on an object supporting one or many interfaces, the purpose of late binding may not seem apparent at first. There are some COM-savvy developers who love the COM paradigm but feel that because of its reliance on the typeless variant and its overall generality, late binding somehow corrupts the strongly-typed, object-oriented nature of COM. However, there are times in development when, philosophy aside, late binding is the simplest way to solve a problem. You will sometimes come across components actually written by Microsoft that support only late binding because of the versatility this mechanism offers. Arguably, late binding simplifies front-development at the expense of complicating the COM paradigm somewhat, but it is probably not too great a price to pay for the versatility late binding gives you.

OLE Automation

Late-binding used to be known as OLE automation. Soon after, early binding was introduced and sometimes called OLE automation. To add to the confusion, there is also a keyword in IDL files ( "oleautomation") which indicates that variables being used in an interface's methods are compatible with OLE automation and implies that only VB-compatible data-types are being used. The word OLE then became passe, and it was simply referred to as automation. Before long, the word automation didn't add anything, so people stopped using it to a large degree. COM implies automation. From now on, I will try to minimize my use of the term automation, because of its vague definition. If, however, you encounter the term in an old MSDN article, it can have any of the preceding definitions, depending on the date of the article and the context in which the term is used.

The Architecture of Late Binding

Late binding is all about asking an object what methods it supports instead of what interfaces. Whereas early binding passes a GUID of the interface it is interested in into QueryInterface(), late binding presents the object with a method name in the form of a wide-character string (wide-character meaning 2 bytes per char). If, after interpreting the string, the object supports a method by that name, it returns with a number representing that method. This number is referred to as a dispatch ID or DISPID. After the client has this number, it can call the method by invoking the number.

You might think that the mechanism used to provide late binding services might be something non-interface and non-RPC based. This might seem strange, but late binding support is provided whenever an object implements an interface called IDispatch. IDispatch is a standard COM interface that, like IUnknown, is a COM system interface that is already registered in the OS and included in the Windows header files. Specifically, IDispatch has four methods, but we are only going to look at the two most important ones for now (see Listing 5.3).

Example 5.3.  Partial Prototype for IDispatch

struct IDispatch: public IUnknown
{
        virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames(
             REFIID riid,
             LPOLESTR *rgszNames,
             UINT cNames,
             LCID lcid,
             DISPID *rgDispId) = 0;

        virtual HRESULT STDMETHODCALLTYPE Invoke(
             DISPID dispIdMember,
             REFIID riid,
             LCID lcid,
             WORD wFlags,
             DISPPARAMS  * pDispParams,
             VARIANT *pVarResult,
             EXCEPINFO *pExcepInfo,
             UINT *puArgErr) = 0;

    //...other functions temporarily ommited for brevity
} ;

GetIDsOfNames is used to get the ID (DISPID) of the method(s) whose name is sent in rgszNames. (You can ask for the DISPID of more than one method at a time.) Invoke is used to call a method by its DISPID. The arguments for this function are stored in pDispParams. Before I get deeper into the specifics of IDispatch's method arguments,

let's summarize. A late binding client instantiates a COM object but asks only for IDispatch. In C++, this looks like the following:

TCHAR progid[255];
IDispatch * idsp;

CLSIDFromProgID(L"comcalc.calc", &pclsid);

HRESULT hr = CoCreateInstance(pclsid , NULL, CLSCTX_ALL,
                              IID_IDispatch, (void **)&idsp);

In VB, it looks like this:

Dim objSomething as Object
Set objSomething = createobject("Comcalc.Calc");

In J++, it looks like the following:

Object idsp;


com.ms.com._Guid IID_IDispatch = new _Guid("{ 00020400-0000-0000-C000-000000000046} ");
com.ms.com._Guid pclsid;



pclsid = com.ms.win32.Ole32.CLSIDFromProgID("comcalc.calc");
idsp   = com.ms.win32.Ole32.CoCreateInstance
         (pclsid, null,    ComContext.INPROC_SERVER |
          com.ms.win32.win.CLSCTX_LOCAL_SERVER,
          IID_IDispatch);

Why does the client bother with IDispatch at all and not ask for the interface it really wants? The late binding client presumably has no type library, so it has no clue about any interfaces other than the established Windows ancestral interfaces, such as IUnknown, IDispatch, and a number of others. These interfaces herald all the way back to the days of Object Linking and Embedding (OLE)—even before there were such things as type libraries. IDispatch is really the lowest common denominator—the last possibility for communicating with a COM object when you have no type library, no header files, and nothing other than the name of the function you want to call and the arguments you believe it will take.

VB's Object Data Type

The Object data type in VB always implies late binding. In VB, Object is another word for IDispatch, and every method call on an object data type is always a late binding call. The intricacies of this call are hidden, however.

Invoke Arguments and Marshaling

It stands to reason that if IDispatch is a system interface which ships with Windows and allows clients to invoke methods on remote objects, the system must also have some way to marshal arguments over the wire from the client to the server object. After all, in late binding you don't have a type library to generate run-time proxy/stubs. So, some other marshaling mechanism must already be in place—the oleautomation marshaler. It lives in a DLL called oleaut32.dll. Prior to Windows 2000 (and Service Pack 4 on NT 4) oleaut32.dll could be accidentally unregistered by overzealous unregistration routines. Automation stopped working entirely at that point, and you had to re-register oleaut32.dll to get things working again.

In short, oleaut32 knows how to handle all the networking details of IDispatch (late binding) method invocations. It is the IDispatch system proxy/stub that replaces the MIDL generated type library you're used to and allows the IDispatch interface to invoke method calls over the wire. As far as marshaling the method arguments goes, even though you have no type library, all possible data types are pre-defined by COM. This is possible because oleautomation marshaling is being used, and this form of marshaling only allows the use of a limited number of well-defined data types to be used as method arguments in late binding calls. And does it surprise you that the only data types IDispatch allows you to use are those available in Visual Basic? Because IDispatch is so closely tied to Visual Basic and its data-types, C++ developers have to deal with slightly more complexities. You see how when we discuss dispinterfaces and dual interfaces a little later in this chapter.

The Variant

In the previous section, I mention that the number of data types that can be used in a late binding call are limited to those data types available to Visual Basic. Visual Basic has a nice array of data types: the unremarkable long, integer, double, string, and some interesting ones like currency, date, and others. COM knows how to marshal all of these over the wire.

There is, however, one small problem: If you look at the structure of IDispatch, you might notice that all remote method calls must happen through the Invoke() method. How can you write one function that can accept any number of arguments where each can be a different data type? In C, you might look towardunions. For non-C programmers, unions are similar to structures with one critical difference—where a structure is as large as the sum of its data members, a union is only as large as its biggest data member. Of course, there's a trade-off for this reduced size—only a single data member of a union can be valid at any given time. Take a look at Listing 5.4 for an example of a C-style union.

Example 5.4.  An Example of a C Union

//remember, unlike a structure, a union expects to be only one of these
//type and will only be as big as its biggest type.

union AnyofThese
{
int iVal;
float fVal;
char cVal;
//and every other "Automation Type" that late binding supports
}

Conceivably, you can send method arguments into the Invoke() function as an array of unions. This allows you to send any number of arguments, each one of which can be of a different type. In other words, although you are sending arguments as an array of homogenous types (AnyofThese), each element in the array can represent a different data type by having a different data member with valid data in it.

Unions have most of what we need to represent multi-type method arguments, but they're not quite enough. One of the interesting features of late binding is that it automatically converts your arguments to the type appropriate to your method. In other words, if you call the late-bound method Add with two string arguments instead of two integers, the object performs the string-to-integer conversion automatically, if possible.

Because of this, the implementor of IDispatch cannot be sure which member of the union has valid data. The solution to this problem is to add adiscriminator that tells the remote object what member of the union is valid (see Listing 5.5).

Example 5.5.  A Discriminated Union

enum types  {VT_INTEGER,VT_FLOAT,VT_CHAR};

struct Variant
{
enum types discriminator; //this tells implementor IDispatch what member is
//valid
union AnyofThese
{
int iVal;
float fVal;
char cVal;
…
}
};

Now you have a special kind of discriminated union called a variant. Variants are used by IDispatch to represent method arguments. If you are calling a method via IDispatch's Invoke() method, which takes two arguments, you need to give Invoke the DISPID of the method you want to call and an array of two variants—each with the appropriate discriminator set (see Listing 5.6).

Example 5.6.  A Pseudo-Late Binding Client Demonstrating How IDispatch Uses Variants as Method Arguments

Variant args[2];
Variant  retval;
int id;

args[0].discriminator=VT_INT;
args[0].ival=2;

args[1].discriminator=VT_INT;
args[1].ival=3;

IPsudoDispatch-&>GetIDofFunction("Add", &id);
IPsudoDispatch-&>RemoteInvoke(id, args, &retval);
cout<<"The sum is  "«retval«endl;

What Can a Variant Hold?

Variants allow you to get around the problem of using heterogeneous data types as arguments by throwing type safety to the wind. Not surprisingly, there is a data type called Variant in VB. A VB variant can hold any other type of value. So, the following VB code is perfectly legal:

Dim v as Variant:
v=33
v="hi there"
v=CreateObject("comcalc.calc")
v=form1

J++ also has the concept of the Variant, as in the following:

Object v;
v=new Integer(33)
v="hi there"

Notice that the v can hold object types, as well as data types. Don't confuse Java's Object data type with VB's Object data type, however. In Java, an Object can hold anything (just like a Variant), but VB's Object can only hold an IDispatch pointer.

InC andC++, the variant is implemented as a discriminatedunion. Its definition can be found in OAIDL.H, and it is very revealing to look at. Basically, itsdiscriminator is a structure that has a union as a data member, as shown in Listing 5.7.

Example 5.7.  Partial Prototype for a VARIANT

struct  tagVARIANT
    {
    union
        {
        struct  __tagVARIANT
            {
            VARTYPE vt;
            WORD wReserved1;
            WORD wReserved2;
            WORD wReserved3;
            union
                {
                LONG lVal;
                BYTE bVal;
                SHORT iVal;
//    …and every other data-type VB supports

You can find every data type that VB (and therefore late binding) supports; and because variants can hold object references as well as flat data values, you can also find IDispatch and IUnknown data members defined in the following variant structure:

…variant code omitted for brevity
                IUnknown __RPC_FAR *__RPC_FAR *ppunkVal;
                IDispatch __RPC_FAR *__RPC_FAR *ppdispVal;

Another less apparent capability of the variant is its capability to hold arrays of information. This makes it possible for blocks or blobs of data to be passed to and from a Visual Basic COM object. Internally,arrays in Visual Basic are represented bysafearrays. Because a variant must be able to hold all VB data types including arrays, it isn't surprising to find safearrays defined in the Variant, as in the following:

…
SAFEARRAY __RPC_FAR *parray;
…

Safearrays are an entire discussion unto themselves. However, like most things in COM, their explanations are ten times more complicated than their reality.

Invoking a Method on IDispatch

In VB, using late binding is as simple as the following:

Dim o as Object
Set  o=createobject("Comcalc.Calc")

MsgBox o.add (1,2)

The details are more or less hidden. However, in C++, and to some extent J++, all the intricacies of IDispatch and variants are exposed. And although you might not think this is the most elegant architecture, this is how late binding is done. Here is the real code that performs (or automates) the Add function on a remote object through the IDispatch interface. For C++, refer to Listing 5.8. For J++, refer to Listing 5.9.

Example 5.8.  C++ and IDispatch

DISPID dispid;
DISPPARAMS dp={ NULL,NULL,0,0} ;
VARIANTARG vargs[2];
VARIANT arg1,arg2,result;
IDispatch * idsp;

TCHAR progid[255];
CLSID pclsid;

CLSIDFromProgID(L"comcalc.calc", &pclsid);


HRESULT hr = CoCreateInstance(pclsid , NULL, CLSCTX_ALL,
                              IID_IDispatch, (void **)&idsp);

arg1.vt= VT_I2;
arg1.iVal = 1;

arg2.vt= VT_I2;
arg2.iVal = 2;

vargs[0]=arg1;
vargs[1]=arg2;

dp.rgvarg=vargs;
dp.cArgs = 2; //number of args
OLECHAR * name=L"add";

//below:  note that is the object did not support this method
//GetIDsOfNames would have returned an error.
idsp->GetIDsOfNames(IID_NULL, &name, 1, GetUserDefaultLCID(), &dispid);

idsp->Invoke(dispid, IID_NULL, GetUserDefaultLCID(), DISPATCH_METHOD,
             &dp, &result,0,0);
//ABOVE:  GetUserDefaultLCID() passed the location identifier to invoke
//so that it can accommodate strings of different spoken languages
//it will be US English for us

cout<<result.iVal<<endl;
idsp->Release();

Example 5.9.  J++ and IDispatch

Object idsp;
Object args[];
int    retval[];
int    MethodId;

com.ms.com._Guid IID_IDispatch = new _Guid("{ 00020400-0000-0000-C000"+
                                           "-000000000046} ");
com.ms.com._Guid pclsid;

args=new Object[2];
args[0]="100";
args[1]="200";
retval=new int[1];


pclsid = com.ms.win32.Ole32.CLSIDFromProgID("comcalc.calc");
idsp   = com.ms.win32.Ole32.CoCreateInstance (pclsid, null,
           ComContext.INPROC_SERVER |
           com.ms.win32.win.CLSCTX_LOCAL_SERVER,IID_IDispatch);

MethodId=com.ms.com.Dispatch.getIDOfName(idsp,"Add");

com.ms.com.Dispatch.invoke(idsp,
                           MethodId,
                           Dispatch.Method,
                           args,
                           retval);

The C++ code is a bit more complex than the J++ code. However, although some structures in the C++ version of the code seem a bit confusing, they are all pretty simple. Take DISPPARAMS, for example; it is nothing but a structure with four elements, declared as shown in Listing 5.10.

Example 5.10.  The DISPPARAMS Structure

typedef struct  tagDISPPARAMS
    {
    VARIANTARG __RPC_FAR *rgvarg;
    DISPID __RPC_FAR *rgdispidNamedArgs;
    UINT cArgs;
    UINT cNamedArgs;
    }

The first element holds a pointer to an array of variants (our method parameters), as emphasized by the following line:

dp.rgvarg=vargs;

Remember that vargs is an array of variants, because this is how you must package your function arguments if you are calling a method using late binding. rgdispidNamedArgs is used primarily for compatibility with Visual Basic style function arguments. Visual Basic allows you to have optional function arguments that can be passed as parameters in any order. For example, in VB, the following is possible:

VBFunc(arg1:=1, arg5:=5,  arg3:=3)

If you are taking advantage of this feature, rgdispidNamedArgs is important so that you can map the variant argument you are passing to Invoke() to the appropriate optional argument on the server-side function. Keep in mind that even if VB considerations off your C++ or J++ sensibilities, it is important to be aware that VB is a strong driving architectural force in COM.

Late binding and many other COM technologies are entirely based on the VB model. In fact, it's fair to say that a large degree of the complexity you see in COM/DCOM results from the necessity to support Visual Basic. Because many VB programmers (and the development environment itself) do not work with threading, explicit memory allocation, pointers, and networking issues, the COM architecture inherent in the product has to pick up the ball.

If you examine the preceding code and ignore all but the most obvious function parameters, it should be pretty straightforward. You might notice IDispatch's member function GetIDsOfNames being called prior to Invoke(). As we discussed, IDispatch does not let you invoke a method by string name in a single step. The Invoke() function takes a DISPID as its first argument (DISPID is simply defined as a long), so you invoke a remote method by number not by name. This is all GetIDsOfNames does—it allows you to send an array of method names as strings and get back their specific DISPID for use in the Invoke() function.

Surprisingly, late binding Visual J++ is much more complicated than you might expect (see Listing 5.11).

Example 5.11.  Using IDispatch Directly from J++

Object idsp;
Object  args[];
int retval[];
int MethodId;

com.ms.com._Guid IID_IDispatch = new  _Guid("{ 00020400-0000-0000-C000-"+
                                            "000000000} ");
com.ms.com._Guid pclsid;

args=new Object[2];
args[0]="100";
args[1]="200";
retval=new int[1];


pclsid = com.ms.win32.Ole32.CLSIDFromProgID("comcalc.calc");
idsp   = com.ms.win32.Ole32.CoCreateInstance (pclsid, null,
              ComContext.INPROC_SERVER |
              com.ms.win32.win.CLSCTX_LOCAL_SERVER,IID_IDispatch);

MethodId=com.ms.com.Dispatch.getIDOfName(idsp,"Add");

com.ms.com.Dispatch.invoke(idsp,
                           MethodId,
                           Dispatch.Method,
                           args,
                           retval);

Keep in mind, however, that in most cases the preceding code is only necessary when a Java client has no prior knowledge of the object it is automating. This might seem like an odd statement, given that is what late binding is all about (asking what methods an object supports at run-time). However, you see in the next section that there are situations where a type library can actually help out late binding clients.

Late Binding, Type Libraries, and the Tale of IDispatch

It seems that late binding should have absolutely nothing to do with type libraries. Late binding is, after all, a way to remote method calls without a type library, right? It probably surprises you, then, to find out that the first type libraries only provided for late binding.

This is what makes COM complicated. It is not that any one facet of the technology is difficult; it is just that COM has evolved such that an appendage useful for one thing ended up serving another purpose. Such is the nature of evolution. When climates change, creatures change, which is what happened to IDispatch.

In the early days of COM, IDispatch was the only mechanism for remoting objects, and there were no type libraries. COM was the underlying technology for something called OLE. The main thrust of OLE was about integrating desktop applications, such as Word, Excel, PowerPoint, and so on, with one another (as in placing an Excel spreadsheet inside a Word document) and with programs that any developer could write to, remote control, or automate these same applications. IDispatch was the interface that all OLE automatable applications had to support. In this way, it was possible to call CoCreateInstance( ) to create an instance of Microsoft Word, ask for IDispatch, and then ask Word to load and print a file, all from an external client application.

Visual Basic was, and arguably still is, the pre-eminent Windows development environment. Much of VB's popularity has to do with its simplicity. No matter how complicated the back-development had to become to support VB's front-simplicity—IDispatch wasn't simple enough. But VB users could easily create and manipulate instances of Word, Excel, and so on with the following code:

Dim objApp as Object
Set objApp=CreateObject("Excel.Application")
objApp.Visible=True
objApp.Workbooks.Add
objApp.ActiveSheet.Cells(1,1) =123

There was no way that the development environment could perform any kind of compile-time checking. Because IDispatch resolves method names at run-time via the function GetIDsofNames(), the methods for a given object are not known at compile-time. The concept of a type library was introduced so that method calls could be resolved at compile-time and to allow nice, GUI-based object browsers to exist.

Object Definition Language (ODL) Files

Type libraries were originally introduced to give development environments and object browsers a way to see what methods an object would support. IDispatch still needed to be used, but its use could be simplified by the presence of a type library. In Microsoft Foundation Classes (MFC), for example, a wizard was introduced that could read a type library for an automatable application (for example, Microsoft Word) and automatically generate a friendly C++ class (derived from ColeDispatchDriver) which had methods corresponding to those listed in the type library. The MFC client could simply instantiate an instance of this class, call its methods, and all the ugly details of IDispatch were completely hidden from view.

COM+ and Microsoft Office

It is important to note that EXE-based applications that support OLE Automation, such as Word and Excel, can not be COM+ components. This is primarily because they are not in-process servers (DLLs), so they can not be run inside COM+'s DLLHOST.EXE surrogate (more on this in the Chapter 6, "The COM+ Catalog"). This is not to say that they can not be used by COM+ objects or clients, or that they themselves do not contain COM classes (EXE servers definitely do); they just can't participate in COM+ services, such as queueing, transactions, and pooling.

Visual Basic users also benefited from these early (and later) type libraries. With the advent of IntelliSense, method suggestions now pop up whenever the developer presses a period following an object in the editor (you can find a screen-shot in Figure 1.4 of Chapter 1). And if this isn't enough help, the developer can simply press F2, and an object browser appears.

Where did these early type libraries come from? Today, type libraries come from IDL files, or they are created by development environments like VB with Win32 calls behind the scenes. Back then, type libraries came from ODL files. ODL was the COM predecessor to IDL.IDL files are compiled using MIDL.EXE; ODL files are compiled with an older utility called MkTypeLib.EXE. ODL is the ancestor of modern IDL; just as you can see evolutionary echoes from millions of years ago in the forms of alligators and crocodiles, you can still see pure ODL alive today in MFC applications. It is possible to generate COM objects using the wizards in MFC, but they only produce IDispatch supporting objects with an ODL-based type library. ATL, on the other hand, favors IDL, and its wizards run MIDL.EXE behind the scenes.

Dispinterfaces

Dispinterfaces are an alternate form of interface declaration. They are not necessary anymore, but they still pop up from time to time. Although ODL files are their normal habitat, it is not unusual to find dispinterfaces in IDL files. Until recently,advisesinks for connectionpoints (VB's event notification scheme as implemented in COM) had to be described in IDL by dispinterfaces as opposed to regular interfaces. What does a dispinterface look like? See Listing 5.12.

Example 5.12.  An Example of a Dispinterface

dispinterface DICalc {
        methods:
            [id(0x00000064), helpstring("Add")]
            int Add([in] long Param1,
                    [in] long Param2);

            [id(0x0000006d), helpstring("Subtract")]
            void Subtract([in] long Param1,
                          [in] long Param2);
};


…

coclass DispCalc
{
    dispinterface DICalc;

} ;

As you can see, dispinterfaces are pretty straightforward. In fact, it is fair to say that they are simpler than ordinary IDL interfaces (orcustom interfaces, as user-created, non-dispinterfaces are commonly called). Dispinterfaces don't inherit from anything; their methods are actually allowed to return real values (unlike ordinary interface methods that must return an HRESULT), and they have the concept of properties, just like classes in Visual Basic.

When you see a dispinterface, think IDispatch. The two are synonymous; a dispinterface's methods may only be accessed through late binding. This may seem odd, but dispinterfaces were introduced early in COM's evolution at a time when OLE automation relied on IDispatch. IDispatch does not require a type library, but a type library was nonetheless desirable to fuel developer tools such as IntelliSense, Object Browsers and MFC's (Microsoft Foundation Class) object wrapper wizards (they generated friendly client-side C++ classes that allowed clients to easily call IDispatch methods on a server). Dispinterfaces were therefore introduced so that type libraries could be created for clients, even though it was not yet possible to use interfaces other than IDispatch for OLE automation.

Thus, the developer of DispCalc must implement IDispatch. A client wishing to call a method of DispCalc would then execute the GetIDsofNames function against this interface, which would return the DISPIDs of all the methods listed by the dispinterface.

Similarly, the client then calls Invoke( ), passing in the DISPID of method it wishes to call, followed by an array of Variants representing arguments to the method. The implementor of IDispatch must therefore implement the Invoke() function. This involves the following:

  • Perform some form of switch/case on the DISPID passed into Invoke().

  • Unpackage the variant array of arguments.

  • If possible, convert arguments to the appropriate data type in the event that the discriminator of a specific variant/argument indicates it is a type different from what the type library promises.

It is tedious, if not especially difficult work, to implement IDispatch. No matter what language you use, you almost never do this by hand. The ATL class IDispatchImpl provides this functionality automatically for the C++ developer, and VB/J++ insists on providing IDispatch support automatically. For the most part, aside from some nuances having to do with the difference between properties and methods, the implementation of a coclass that supports a dispinterface does not look much different from one that provides optional late binding support for a custom interface. Remember, some clients (ASP pages, command-line VBScript) can only use late binding, and it is not unusual for a COM object to support both early and late binding. In other words, the object supports one or many custom interfaces like ICalc and IFinancial, but also supports IDispatch. The implementor of IDispatch then responds positively if the string names of any of the methods of either ICalc or IFinancial come in through GetIDsOfNames(). You might imagine that the IDL for such an object would have an ordinary interface declaration, as shown in Listing 5.13.

Example 5.13.  An Ordinary IDL Interface Declaration

[
    odl,
    uuid(1051376B-A601-11D3-8090-00E0811008ED),
    version(1.0),
    hidden,
    dual,
    nonextensible,
    oleautomation
]
interface ICalc : IUnknown
{
    [id(1), helpstring("method Add")]
    HRESULT Add([in] int x, [in] int y,
                [out,retval] int * r );
    [id(2), helpstring("method Divide")]
    HRESULT Divide([in] int x, [in] int y,
                   [out,retval] int * r);
};
  • It would also have a dispinterface declaration as shown in Listing 5.12. The coclass, then, might look like the following:

coclass CalcSDK
{  
    [default] interface ICalc; 
    dispinterface DICalc; 
};

But this is not how it's done. It is, after all, pretty redundant—you are declaring what is essentially the same interface in two different ways. It would be nice if there were some way to consolidate the two interfaces into one declaration that indicates that the interface supports both early binding and late binding. It would be nice if the interface were dual.

Dual Interfaces

Dual interfaces are ordinary custom interfaces that indicate that late binding (IDispatch) support will also be implemented by the object. Syntactically, dual interfaces are identical to custom interfaces with one small difference. A custom interface declaration in IDL looks like this:

interface ICalc:  IUnknown

Dual interfaces look like this:

interface ICalc:  IDispatch

In an IDL file, a dual interface inherits from IDispatch instead of IUnknown. Other than that, there is absolutely no difference in the IDL file. However, the implementation is affected. When MIDL.EXE is run on an IDL file containing a dual interface, MIDL does what it always does and generates an H file containing a C++ class prototype that mirrors the interface declaration. This prototype also inherits from IDispatch instead of IUnknown (see Listing 5.14).

Example 5.14.  C++ Prototype of ICalc Now Inheriting from IDispatch

//this is in the MIDL  generated .h file
MIDL_INTERFACE("638094E5-758F-11d1-8366-0000E83B6EF3")
ICalc  : public IDispatch //as opposed to IUnknown
    {
    public:

I said that in C++, the implementation of a coclass is usually a simple class that multiplies inherits from each interface it wants to support. Interfaces are always implemented as abstract base classes in C++, and this is exactly what MIDL puts in its generated header file. Remember, MIDL reads the IDL file and creates one C++ interface prototype for each interface it sees declared. It translates each interface into an abstract base class complete with pure virtual prototypes for each method listed in the IDL file. The C++ developer who wants to implement a coclass supporting this interface must inherit from this MIDL-generated C++ prototype. And because the MIDL generated prototype inherits from IDispatch, the implementor of the coclass has no choice but to support it.

You're not off the hook in terms of implementing the methods of IUnknown, either. Your derived class is required to implement IUnknown as well because, like all interfaces in COM, IDispatch inherits from IUnknown.

Now that there are dual interfaces, there isn't much call for dispinterfaces. It seems that dual interfaces give the best of both worlds. You have the convenience, power, and descriptive IDL of custom interfaces, and you automatically get the late binding support for free—simply by inheriting from IDispatch instead of IUnknown in the IDL file. But there is a price.

The price is small. The methods of your dual interface can only use those data types that IDispatch can accept in its Invoke() function. After all, the purpose of a dual interface is to allow the client to access the methods either directly via the vtables of your custom interfaces (early binding) or via IDispatch (late binding). However, if the same method can be called either way by a client, the method cannot do anything with early binding that can't also be done with late binding. For instance, you cannot have a dual interface with a method that takes a linked list or a pointer to an array as a parameter. Although this is allowed in an early-bound interface, there is no way to pass these kind of non-VB data types through IDispatch's Invoke() function.

Invoke() expects method arguments to be passed in as an array of variants. You might, therefore, assume that only variants can be used as the parameters of methods in dual interfaces. Close. Although you can use variants as method parameters, you are limited to those data types that can be represented by a variant. In other words, you can use any data type that a variant can hold. This gives you integers, doubles, longs, strings, and every other VB data type you know and love. Variants can also hold Visual Basic arrays (safearrays).

If you are content with using Visual Basic data types, you should probably make your interfaces dual. Of course in VB, you have no choice; but in C++ and J++, it is up to you.

Late Binding, Marshaling, and the oleautomation Tag

Summary and review never hurts. So far, we have discussed the following three scenarios involving late binding:

  • No type library late binding

  • Type library late binding using dispinterfaces

  • Type library late binding using Dual Interfaces

In the first scenario, COM knows how to marshal method calls made through IDispatch. Therefore, a COM object that supports IDispatch can (to use old terminology), be automated over a network without a type library being present. Just for fun, the VB code in Listing 5.15 instantiates and uses an instance of Microsoft Excel on the remote machine Darjeeling.

Example 5.15.  VB Late Binding with No Type Library Used

Dim o as object
Set o=createobject("excel.application", "Darjeeling")
o.visible=true
o.workbooks.add
o.cells.range("A1")="hi there"

Permissions on Darjeeling must allow this, so an administrator on Darjeeling must run DCOMCNFG.EXE, a utility used to adjust launch and access permissions for DCOM servers. That done, however, the client can do what it wants with someone else's Excel—all from a remote machine without a type library. This is the miracle of IDispatch and late binding.

For the second scenario, atype library can exist for an object and contain one or manydispinterfaces that describe what late binding methods the object(s) supports. In this case, compile-time validation can occur because the development environment can check the developer's function invocation against the type library's definition to make sure he is calling the function correctly. Likewise, DISPID s can be gleaned from the type library possibly saving a call to GetIDsofNames(). In the end, however, IDispatch is still used.

A third late binding possibility is that a type library exists but has dual interfaces as opposed to dispinterfaces (there can be a mix of the two, but this is not common). A client can still choose to automate an object using IDispatch even though early binding would also have been possible. Indeed, clients like ASP pages have no other mechanism available.

In each and every scenario described here, something called the oleautomation marshaler is used to remote the late-bound method calls. The marshaler lives in oleaut32.dll and is responsible for packing and moving across the wire anything that comes through IDispatch. In fact, although it's not necessary, you will often find the oleautomation tag present in an interface's attribute block, as shown in Listing 5.16.

Example 5.16.  Common IDL Tags for an Interface

[
      odl,
      uuid(1051376B-A601-11D3-8090-00E0811008ED),
      version(1.0),
      hidden,
      dual,
      oleautomation
]

This tag makes it clear that the oleautomation marshaler will be used. It is not, however, necessary. The oleautomation marshaler can still be used even if this tag is not present.

One interesting, sometimes confusing, aspect of the oleautomation marshaler is that it can also be helpful to customize interfaces (those that inherit from IUnknown in IDL) that do not support late binding. How? Very simply, the oleautomation marshaler can be used to marshal arguments for methods of custom interfaces as long as those methods only use oleautomation (VB) data types. If you don't use oleautomation compatible data types (only C++ gives you this latitude), you need to supply your own,proxy/stub DLL. This DLL is used by COM to marshal your arguments instead of the oleautomation marshaler. Again, you only need to have your own proxy/stub DLL if you want to use data types that are not available in Visual Basic. For more information on creating your own proxy/stub DLL (you can only do this with C++), see the book's companion source code http://www.newriders.com/complus.

J++ and Dual Interfaces

If you use the standard J++ mechanisms to build a COM object (that is, you allow the environment to produce a type library from your Java source), the COM object will be composed entirely of dispinterfaces. And if that isn't enough, your Java source must be peppered with little non-standard directives that inform the compiler of COM-specific details. Granted, the J++ environment can put the appropriate directives in for you, but you can see why Sun is very angry with Microsoft. In J++, non-Java, platform-specific directives are placed in comment blocks—specifically after a /**. In this sneaky way, non-Java/pro-Microsoft features are included in J++ without affecting the Java language.

Consider the block of J++ code used to build a COM DLL shown in Listing 5.17.

Example 5.17.  A Simple Implementation of a Class Supporting ICalc and IFinancial in VJ++

interface ICalc {
    int Add(int a, int b);
    int Subtract(int a, int b);
}

interface IFinancial {
    public void GetMortgagePayment();
    public void GetPrimeRate();
}

/**
 * This class is designed to be packaged with a COM DLL output format.
 * The class has no standard entry points, other than the constructor.
 * Public methods will be exposed as methods on the default COM interface.
 * @com.register ( clsid=7087BCCA-3571-477C-85C5-B192C1A2C94C, typelib=D1676CBE-0B58-44E3-BC0F-83F64A021E39 )
 */

public class Calc implements ICalc, IFinancial
{
    public Calc() { } ;

    // Implementation for ICalc:
    public int Add(int a,int b) { return a+b;} ;
    public int Subtract(int a, int b){ return a-b;} ;

    // Implementation for IFinancial:
    public void GetMortgagePayment() { } ;
    public void GetPrimeRate() { } ;
}

If you look at the type library of the COM object produced by J++, you get the code shown in Listing 5.18.

Example 5.18.  Type Library Automatically Generated from the Code in Listing 5.17

// Generated .IDL file (by the OLE/COM Object Viewer)
//
// typelib filename: MyCalc.dll

[
  uuid(D1676CBE-0B58-44E3-BC0F-83F64A021E39),
  version(1.0),
  helpstring("MyCalc")
]
library MyCalc
{
    TLib : OLE Automation : { 00020430-0000-0000-C000-000000000046}
    importlib("stdole2.tlb");

    // Forward declare all types defined in this typelib
    dispinterface Calc_Dispatch;



    [
      uuid(B5582180-6C48-4F2B-87E4-E6B26F3C92A6)
    ]
    dispinterface Calc_Dispatch {
        properties:
        methods:
            [id(0x00000064), helpstring("Add")]
            long Add([in] long Parameter0,
                     [in] long Parameter1);
            [id(0x00000065)]
            VARIANT wait([in, out] VARIANT* Parameter0,
                         [in, out] VARIANT* Parameter1);
            [id(0x00000066), helpstring("hashCode")]
            long hashCode();
            [id(0x00000067), helpstring("GetPrimeRate")]
            void GetPrimeRate();
            [id(0x00000068), helpstring("toString")]
            BSTR toString();
            [id(0x00000069), helpstring("equals")]
            VARIANT_BOOL equals([in] IDispatch* Parameter0);
            [id(0x0000006a), helpstring("GetMortgagePayment")]
            void GetMortgagePayment();
            [id(0x0000006b), helpstring("notify")]
            void notify();
            [id(0x0000006c), helpstring("getClass")]
            IDispatch* getClass();
            [id(0x0000006d), helpstring("Subtract")]
            long Subtract([in] long Parameter0,
                          [in] long Parameter1);
            [id(0x0000006e), helpstring("notifyAll")]

            void notifyAll();
    } ;

    [
      uuid(7087BCCA-3571-477C-85C5-B192C1A2C94C),
      helpstring("Calc")
    ]
    coclass Calc {
        [default] dispinterface Calc_Dispatch;
    } ;
} ;

There are a couple of things worth noting about this code:

  • Unlike the Java source code, our generated coclass is not composed of two interfaces (ICalc and IFinancial), but is an amalgamation of the two interfaces called Calc_Dispatch. This is because we did not add any COM compiler directives to our Java interfaces.

  • J++ added seven new member functions to the Calc_Dispatch interface. These additional methods have nothing to do with COM. These exist because J++ generates the COM interface by scanning the Java object's public methods, including those inherited from java.lang.Object. More information about these methods can be found in the Java API documentation. More information about these methods can be found in the JDK (Java Development Kit). For now, refer to Table 5.1.

    Table 5.1.  New Member Functions to the Calc Dispatch Interface

    Java MethodMeaning
    equalsReturns true if the object passed to this method is the same as the current object. Note that this is only meant to compare two Java objects in the same virtual machine, unless the method is overidden
    getClassThis returns an instance of java.lang.Class describing the underlying Java class. A COM client could potentially use and manipulate this through IDispatch, though such a case would be very rare.
    hashCodeThis method returns the hashcode of the object. A hashcode is basically a numeric identifier. In Java, all objects have distinct hashcodes (so they can be stored in hashtables). For two objects to be equal (see the equal entry), their hashcodes must be the same.
    notifyWakes up a single thread that is Waiting on this object's monitor. A thread waits on an object's monitor by calling one of the wait methods. This is for multithreading within a single virtual machine, and probably should not be used by COM clients.
    notifyAllWakes up all threads that are waiting on this.object's monitor. A thread waits on an object's monitor by calling one of the wait methods.This is for multithreading within a single virtual machine, and probably should not be used by COM clients.
    toStringReturns a string that is represented by the object. This is a combination of the Java class name and the object's hashcode. In J++ 6.0, ToString returns coclassname@hex (hashtable). For example, if your object has a hashcode of 131, ToString returns Calc@83 (131 = hex 83)
    waitPlaces the current Java VM thread on the object's monitor until signaled by notify or notifyAll. Probably should not be used by COM clients.
  • Most importantly, the Calc_Dispatch interface is declared to be a dispinterface. This means that early binding (vtable binding) is not possible.

Supporting Dual or Custom Interfaces in J++

There is a way, however, to produce a COM object in J++ with interfaces that are dual (or even a standard vtable interface). It involves forcing the interface on J++ by importing the type library you want your component to have. The procedure is as follows:

  1. Create an IDL file that implements your interfaces as dual—for example, MyObject.IDL (see Listing 5.19).

    Example 5.19.  IDL File Describing Dual Interfaces to be Used by J++

     
          [
             uuid(D544BFC0-BC81-11d0-A982-00AA00C0177B),
             helpstring("javaLib Type Library 1.2a"),
             version(1.2)
          ]
          library javaLib
    
    {
            importlib("stdole32.tlb");
    
            [
              object,
              dual,
              uuid(D544BFC1-BC81-11d0-A982-00AA00C0177B),
              helpstring("Itest Interface")
            ]
            interface ICalc : IDispatch
            {
                [id(1), helpstring("method TestLong")]
                HRESULT add([in] long parm1, [in] long parm2, [out,retval] long *ret);
                [id(2), helpstring("method TestString")]
                HRESULT TestString([in] long parm1, [in] long parm2,
                                   [out,retval] long *ret);
            }
    
            [
              uuid(D544BFC2-BC81-11d0-A982-00AA00C0177B),
              helpstring("test Object")
            ]
            coclass MyObject
            {
              [default] interface ICalc;
            } ;
          } ;
  2. Run MIDL.EXE (this comes with Visual C++ or the Platform SDK) on the IDL to produce a type library (in this case, MyObject.TLB).

  3. Assuming that the type library can be found in the D: MyCalc directory, start Visual J++, click New Project, select the Existing tab, highlight the MyCalc folder, and click Import Project Folder.

  4. Note that the MyObject.TLB file appears in the MyCalc Project Folder. Right-click the MyCalc Project Folder and go to MyCalc Project Properties.

  5. In the dialog box, select the COM Classes tab, click Use Existing Type Library, and click Select.

  6. In the COM Templates dialog box, click Browse. Go to the MyCalc directory and select the MyObject.TLB file.

  7. Click Open and then OK. Click OK again.

  8. Visual J++ now creates wrappers for all the COM interfaces and coclasses in the type library. J++ provides an implementation for all methods of all wrapper classes in the form shown in Listing 5.20.

    Example 5.20.  J++ coclass Wrapper for IDL File Shown in Listing 5.20

    /** @com.register(clsid=D544BFC2-BC81-11D0-A982-00AA00C0177B,
    typelib=D544BFC0-BC81-11D0-A982-00AA00C0177B, version="1.2", description="test Object")*/
    
    public class MyObject implements IUnknown,com.ms.com.NoAutoScripting,
                 myobject.ICalcDefault
    {
      public int add(int parm1, int parm2) {
          throw new com.ms.com.ComFailException(0x80004001); // E_NOTIMPL
      }
    
      public int TestString(int parm1, int parm2) {
          throw new com.ms.com.ComFailException(0x80004001); // E_NOTIMPL
      }
    }
  9. Next, remove the E_NOTIMPL lines and provide your own implementation.

  10. Indicate to J++ that this project will be packaged as a COM DLL by doing all of the following:

    • Right-click the MyCalc project folder.

    • Select MyCalc Properties.

    • Click the Output Format tab.

    • Select the Enable Packaging check box.

    • Select the COM DLL as the package type.

  11. The COM DLL is now built based on the type library you provided. Your interfaces are thus dual and can be accessed through early and late binding.

Examining the Wrapper

Finally, it is worthwhile to examine the wrapper interface that J++ produced for you, as shown in Listing 5.21.

Example 5.21.  J++ Generated Interface Prototype for IDL Declaration of ICalc

// Dual interface ICalc
/** @com.interface(iid=D544BFC1-BC81-11D0-A982-00AA00C0177B, thread=AUTO,
type=DUAL) */
public interface ICalc extends IUnknown
{
  /** @com.method(vtoffset=4, dispid=1, type=METHOD, name="add", addFlagsVtable=4)
      @com.parameters([in,type=I4] parm1, [in,type=I4] parm2, [type=I4] return) */
  public int add(int parm1, int parm2);

  /** @com.method(vtoffset=5, dispid=2, type=METHOD, name="TestString",
      addFlagsVtable=4)
      @com.parameters([in,type=I4] parm1, [in,type=I4] parm2, [type=I4] return) */
  public int TestString(int parm1, int parm2);


  public static final com.ms.com._Guid iid = new com.ms.com._Guid((int)0xd544bfc1,
      (short)0xbc81, (short)0x11d0, (byte)0xa9, (byte)0x82, (byte)0x0, (byte)0xaa,
      (byte)0x0, (byte)0xc0, (byte)0x17, (byte)0x7b);
}

J++ determines the properties of the ICalc interface (being dual, for example) by utilizing highlighted @com directives. You can, in fact, use these directives directly to produce a dual interface (see http://www.microsoft.com/java/resource/java_com2.htm for examples of various @com directives and how to use them), or simply write the IDL and let J++ do the work for you.

Is there a downside to creating dual interfaces this way? Not really. In fact, creating COM interfaces through IDL is the preferred approach to preserve COM's philosophy of language independence. Automatically generating a type library with J++ exposes language-specific methods, therefore should probably only be used for prototyping or experimentation.

J++ COM clients, however, do have access to these seven methods because of the approach Microsoft has taken with its Java/COM integration. Because a Java wrapper class is created to represent an underlying coclass, this class is a regular Java object and may be treated as one. Note that under this approach, the seven methods are not exposed as COM methods. They are merely Java object methods, and calls to them will not be remoted or marshaled to the server.

Summary

COM provides two mechanisms for clients to invoke method calls on the interfaces of COM objects—early binding and late binding. Early binding implies the existence and inclusion of a type library. It is generally true that a client using a type library to invoke methods on a server object is using early binding. However, there are type libraries that include dispinterfaces that always require late binding.

Late binding requires two complete round trips to invoke a method. The absence of a type library (or a type library containing only dispinterfaces) mandates that only late binding can be used by a client. The first round trip in a late binding call involves sending the method call as a string to the server object and requesting a special number that represents the function, called a DISPID. The second round trip involves invoking the method using the DISPID and sending arguments as arrays of variants. Variants are discriminated unions that can hold any Visual Basic data type.

Early binding is more efficient than late binding. Early binding calls require only one round trip (late binding calls take two), and in the event of a client EXE using a compatibly threaded in-process server, the calls can actually be very fast, direct invocations on the vtable of the object. Additionally, a development environment can perform compile-time type checking using the type libraries present.

Although the benefits of early binding are many and it should be used wherever possible, late binding is essential in certain circumstances. If a type library is not available or a development environment cannot read a type library (as in the case of ASP pages), late binding might be the only option. An interface might support both early and late binding to be of maximum use in all scenarios. Such an interface inherits from IDispatch in its IDL declaration and is referred to as dual.

References

Portions of this chapter are excerpted from the following:

Gregory Brill, "Writing COM Clients with Late and Early Binding," C++ Users Journal, Volume 16, No. 10(1988): 37-51.

Reprinted by permission of C++ Users Journal.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.152.241