top header
interfacesterm

 

home contents introduction com as a better C++ interfaces classes apartments labs lab setup quizzes
 
previousnext

 
Overview

In this section we look at COM interfaces. We start off by looking at interfaces in general. Then we look at the basics of COM interfaces. After this we look at IUnknown in some detail.

Interface Preamble

For those of you interested in a general discussion of interfaces, go here.This section looks at what defines an interface, what an object-oriented interface looks like, what is so good about interface-based programming and what makes a good interface. This section is optional.

COM Interfaces :  The Basics
 
What is an interface?

Any of the following could accurately describe an interface.

  • A protocol of agreed-upon behavior that allows unrelated entities to interact
  • A well-defined abstraction/encapsulation layer between some client code and some server code
  • Abstract behavior in the form of a collection of logically (semantically) related requests that a client can make of a server component

The totally abstract nature of an interface allows us to separate interface from implementation.

What is a COM interface?

COM interfaces are a collection of logically related virtual functions that are used in the abstract by a client and implemented and exposed by a server. As far as the client is concerned they represent abstract types. The server must make them concrete.

We saw in the "COM as a better C++" chapter how to provide a binary standard interface between a client and a server written in C++. We employed a pure abstract base class to represent the collection of abstract functions, then defined certain rules governing how to use it. Take that idea a step further.

It would be really useful to define an interface mechanism that could bridge the gap between a client and server written in different languages and running in different environments. To define such an interface that matches the disparate type systems of different languages and that supports different platforms requires the following:

  • A binary standard interface mechanism that could be used from all languages and that could span all platforms.
  • A way of uniquely specifying the interface in such a way that it makes sense from all programming languages and on all platforms. One approach would be to define such an interface in a description language that is independent of programming language and platform, but has a syntax to support the interface paradigm and incorporate a set of universally understood types, a standard set of calling conventions, etc. This description language could be translated to a programming-language specific syntax for any given platform. 
  • A set of rules governing how the interface is used, covering runtime type discovery, acquiring an interface, relinquishing an interface, etc. These rules must have meaning in all programming languages.

We end up with a COM interface.

A COM interface is a named collection of abstract methods that define a behavioral protocol between a client and a server. Each COM interface definition comprises a number of parts.

  • The exact binary layout in memory of the interface and the way it is accessed. The mechanism that COM uses is based on a table of virtual functions. Each interface on a given platform adheres to this binary layout and access mechanism.
  • The tightly defined syntax of the interface. This uniquely identifies the interface and unambiguously defines the contents and attributes of the interface, its methods, and their parameters. The special language that COM uses to define this is IDL. The IDL for each interface defines a well-known stack frame per platform that implies a well-known network representation.
  • Well-formed rules about how each interface must be manipulated. These cover dynamic type discovery of interfaces, casting from one interface type to another, acquiring interfaces, relinquishing interfaces, and duplicating interface references.
  • The loosely defined semantics of the interface. This is important because it gives alternate implementations in the same problem domain the chance to provide the same interfaces. It allows for polymorphic implementations.

As we shall see in a moment, there is only one true way to define a COM interface and that is using Interface Definition Language (IDL), the language of COM. However, the exact syntax used to represent a COM interface in any given implementation language may differ.

Defining an interface in IDL

IDL was originally a language for defining DCE RPC interfaces between client and server. We will see later that DCOM is built on RPC. COM IDL is a superset of RPC IDL that deals with object semantics and object reference types. IDL is very C-like. The idea of IDL is that it allows us to define data types and interfaces in a language neutral way. The IDL file is then compiled to create

  • C/C++ language bindings (given that the syntax is so close this makes sense)
  • Language-neutral bindings that can be used from any programming language
  • Proxy/stub code for remoting the interface

We have already seen that a COM interface must be uniquely identified and its methods must all have a well-known stack frame. It is not possible to define this level of detail in C/C++-like syntax. Where it is not possible to provide unambiguous definitions using C-like syntax, attributes can be defined in square brackets to disambiguate.

Here is an example IDL file, foo.idl.

import "unknwn.idl";
[object,uuid(DEFACED1-0229-2552-1D11-ABBADABBAD00)]
interface IFoo : IUnknown
{
    HRESULT SomeMethod([in]short *ps);
};

[uuid(DEFACED2-0229-2552-1D11-ABBADABBAD00)]
library FooLib {
    importlib(”°stdole32.tlb”±);
    interface IFoo; // cause TLB inclusion
}

Notice that the line

importlib(”°stdole32.tlb”±);

is required in order to use the standard COM types without including their definition in the type library.

If an interface is defined at global scope, MIDL will emit code to marshal the interface so it can be used remotely. If exported types are referred to inside a library block, MIDL will emit a type library definition that can be used to generate language-specific bindings.

Notice the following

  • Square brackets that define the attributes of the interface. The uuid() attribute defines the unique identifier for the interface. The in attribute specifies that the method parameter is an input only parameter.
  • The interface is derived from IUnknown, the mother of all COM interfaces.
  • The method returns an HRESULT.

All of these points are explained later.

The file imports the file unknwn.idl. This contains the IDL definition for IUnknown. There are a bunch of system supplied IDL files that describe system-defined interfaces in IDL. You will need to import these whenever you make reference to any system-defined interfaces. Here is a list of the most commonly used IDL files.

  • WTYPES.IDL - basic data types
  • UNKNWN.IDL - core type interfaces
  • OBJIDL.IDL - core infrastructure interfaces
  • OLEIDL.IDL - OLE interfaces
  • OAIDL.IDL - Automation interfaces
  • OCIDL.IDL - ActiveX Control interfaces
Building the IDL

We run this IDL through a compiler called the Microsoft IDL (MIDL) compiler. This will spit out a number of things.

  • C/C++ language specific bindings in foo_i.c, and foo.h
  • Type library language independent bindings usable from any language
  • Marshalling code for remoting this interface

Here is a diagram that illustrates.

interface_idl

GUIDs

Remember back to the "COM as a better C++" chapter. We had the problem of uniquely identifying interfaces. We used strings. It would be quite easy to imagine a situation where two developers independently define completely different interfaces and identify them with the string "IFoo". When they put them in the public domain trouble ensues.

COM interfaces are uniquely identified by 128-bit numbers called GUIDs. These 128 bit numbers are allocated by the system using an algorithm that is guaranteed not to provide duplicates (at least until the year 3034!). In fact, GUIDs are language-neutral type identifiers used to identify  COM Interfaces (IIDs), COM Classes (CLSIDs), COM Type Libraries (TLBIDs), and so on. Anything that needs to be uniquely identified in COM is given a GUID. This guaranteed uniqueness solves namespace collisions but is quite unfriendly to users and programmers alike. The GUIDGEN tool can be used to generate GUIDs. It is just a wrapper around the COM API CoCreateGuid() that can be called by any COM code. Generally you don't need to allocate GUIDs programmatically, since they are allocated once by some tool (like GUIDGEN) at design time.

Because C/C++ doesn't have 128-bit numbers GUIDs are represented as a structure named GUID in C++. Likewise they are represented as a class (com.ms.com._Guid) in Java. Look at the IID structure that follows. This is just a typedef for the GUID structure.

An IDL file requires string-based GUIDs. The COM runtime system also requires that certain configuration information be in the registry, identified by a string-based GUID. When GUIDGEN generates a new GUID it provides structure-based definitions and string-based definitions. There are also COM APIs such as StringFromGUID2() and CLSIDFromString() that will convert.

How are COM interfaces represented in a particular language?

In C++ interfaces are not defined as part of the language. However, with some attention to care and detail we can simulate them with a pure abstract base class. Here is some of the output from running foo.idl through the MIDL compiler to get foo.h and foo_i.c that describes C/C++ language mappings.

extern const IID IID_IFoo;
struct IFoo : public IUnknown {
    virtual HRESULT _stdcall SomeMethod(short *ps) = 0;
};
const IID IID_IFoo = {0xDEFACED1, 0x0229, 0x2552,
{ 0x1D, 0x11, 0xAB, 0xBA, 0xDA, 0xBB, 0xAD, 0x00 } };

Java and VB mappings are generated from the type library, foo.tlb.

VB has the confusingly named "non-creatable" class as its expression of an interface.

Java has its own interfaces defined as part of the language. So the IFoo interface would look like this in Java:

public interface IFoo {
    public void SomeMethod(short[] ps);
}

However, it would not be a COM interface but a Java interface. If we were to use the JACTIVEX.EXE tool that comes with Visual J++ 6, we could parse the type information in foo.tlb and get a Java definition of a COM interface that looks like this.

/** @com.interface(iid=com.ms.com.IUnknown, thread=AUTO) */
public interface IFoo extends com.ms.com.IUnknown
{
    /** @com.method(vtoffset=0, addFlagsVtable=4) @com.parameters([in,size=1,type=ARRAY] ps) */
    public void SomeMethod(short[] ps);
    public static final com.ms.com._Guid iid = new com.ms.com._Guid((int)0xDEFACED1, (short)0x0229, (short)0x2552,
                                                                    (byte)0x1D, (byte)0x11, (byte)0xAB, (byte)0xBA,
                                                                    (byte)0xDA, (byte)0xBB, (byte)0xAD, (byte)0x00);
}

Notice the javadoc comments, /** */. They define information meaningful to the Visual J++ 6 compiler. It will use this information to build a .class file annotated with information that the VM understands in order for it to deal with this COM interface. Notice also that IFoo derives from the interface com.ms.com.IUnknown. This is a Microsoft Java interface that defines the behavior of IUnknown.

At the moment, you don't need to understand the details of how IDL syntax maps to specific language syntax. You just need to appreciate that, given the right tools, IDL can be used to generate language mappings for a COM interface in potentially any language/environment. How that language/environment provides the support that allows a client to call the interface or a server to implement the COM interface is not interesting here.

Let's take a moment to do a lab that allows us to define some interfaces.

HRESULTS and Error handling

Did you notice that our method returned an HRESULT? Let's investigate this.

Remoting

Where is the server code that implements the interface IFoo going to be in relation to the client? In the same process, in a different process, perhaps, on a different machine? Well, it could be any of these. Certainly for the last two cases, then the client and server code are in different processes.  The semantics for calling remote (out-of-process) methods are slightly different to those for calling local (in-process) methods. Mainly, the client and server do not share the same stack!

This means there has to be some glue code that sits between the client and server. It allows the client to think it is calling the IFoo interface on an in-process object and allows the server object to think that its IFoo interface is being called by an in-process client. If this is to be transparent to both client and server, the interface must be remoted in the following way.

  • There has to be an object in the client's address space that exposes IFoo and looks, feels, tastes, and smells like the real object. When the client calls a method on IFoo all it does is forwards a request message to the server that fully describes the method call, waits for a response, then passes any results back to the client. Let's call this a proxy.
  • There has to be an object in the server's address space that accepts the message and calls the real object. It harvests any results from the real object and returns a response message describing them. Let's call this a stub.

apartments

The remoting protocol used by the proxy and stub to exchange messages is called ORPC, an extension of DCE RPC. The process of serializing the data to/from the method call into messages to be exchanged is called marshalling. The format of marshalled data is very tightly defined and is defined by an extension to DCE NDR.

The code for the proxy and stub is generated from the MIDL compiler, in foo_p.c and dlldata.c. So do you see how the IDL must unambiguously define the interface, and the stack frame for every method in it, for transmission across the network? That is what IDL attributes are mostly about. They provide MIDL with unambiguous information to generate marshalling code for when this interface is used out-of-process. We will look at some IDL attributes in a moment.

HRESULTs

When you call  a method, it can report an error by either throwing an exception or returning an error code. Today, we have no way in IDL of describing typed exceptions such as the ones you may have seen in C++ or Java. However, there is an exception throwing/catching mechanism in COM that can be used as the basis for mapping COM exceptions to language-specific exceptions. There is just no way of describing it in IDL. This is beyond the scope of this discussion right now. If you would like more information, go here.

So at the moment, when we define interface methods in IDL we are stuck with error codes.

When we call a remote interface implementation, we have the possibility of errors being generated by the server object or by the remoting/marshalling layer. The remoting/marshalling layer needs a guaranteed way of being able to do the following.

  • Specify that a server-defined error has occurred.
  • Specify that a remoting/marshalling error has occurred.
  • Make sure the two do not get confused.
  • Allow the client to be oblivious of where errors are generated, yet aware that there has been an error.

In order to achieve this, the ORPC messaging protocol contains a DWORD field in the response message that describes errors from the server or from the marshalling layer. Whatever the server returns from its interface method calls will be returned in this field. This field has to be formatted in such a way that we can tell whether a remoting/marshalling error occurred or a server error occurred. We must settle on a standard error field format. This is what an HRESULT is.

An HRESULT is just a 32-bit value that is partitioned up in such a way as to allow the remoting/marshalling layer to overload it with communications errors. Here is what it looks like.

interface

The top bit determines whether this code represents success or failure. This information can be extracted by the client using the SUCCEEDED(hr) or FAILED(hr) macros as follows.

IFoo *pf;

.....

short s=10;
HRESULT hr = pf->SomeMethod(&s);
if (SUCCEEDED(hr)) {
    ...
}

The next few bits are reserved.

The facility code defines the general technology area in which the error occurred. These are system-defined and cannot be added to by a programmer. The FACILITY_ITF facility code  is used to define interface-specific errors. FACILITY_NULL is used for generic errors. The rest are fairly self-explanatory and are defined in the system header file winerror.h.

The error code itself is facility-specific.

There are macros HRESULT_CODE(hr), HRESULT_FACILITY(hr) but you will probably never use them. More likely, you will test for success or failure, then perform a test for a specific error. It is also possible to use the Win32 FormatMessage() API to look up a human-readable description of the error at runtime.

The HRESULT names indicate the severity (S for success, E for Error), the facility, and the code because they are in the following format, FACILITY_SEVERITY_CODE. For example DISP_E_EXCEPTION indicates a failure (E) occurred because an automation (FACILITY_DISPATCH) method reported an exception code (EXCEPTION). STG_S_CONVERTED indicates that a successful (S) conversion (CONVERTED) was performed on a storage file (FACILITY_STORAGE).

FACILITY_NULL are for generic error codes and are implicit in the name and follow the format SEVERITY_CODE. Here are some common ones:
S_OK means a successful yes.
S_FALSE means a successful no.
E_FAIL means "something failed but I can't be bothered to tell you what."
E_NOTIMPL means an error has occurred because "it did not make sense for me to implement this function" (bad interface design) or "I couldn't be bothered to implement this function" (lazy programmer).
E_OUTOFMEMORY means an error occurred because a resource could not be dynamically created.
E_INVALIDARG means an error occurred because "you passed a bad parameter."
E_UNEXPECTED means "What? You weren't supposed to call this method now, go read the spec!"
E_POINTER means "You passed me a pointer parameter that I wasn't expecting to be NULL".

Interface implementations can use the MAKE_HRESULT(severity, facility, code) to construct interface-specific errors.

Just to be clear,  there is no reason that HRESULTs cannot be mapped to language-specific exceptions - we just have no way of describing it in IDL today. Also there is no reason that you cannot return meaningful, typed results as well as a physical HRESULT.

IDL attributes

As mentioned, IDL is a C-like language that allows us to define COM interfaces and data types. It also allows us to define attributes to disambiguate the behavior of the types we are defining. Some of these attributes such as in and out are important when we cannot assume a local context for a method call. They are essential to allow the proxy/stub pair to intelligently deal with parameters. For example, consider the following interface:

[uuid(...),object]
interface IFoo : IUnknown
{
    ...
    HRESULT SomeOtherMethod([out]short *ps1, [in]long len, [in,size_is(len)]short *ps2);
}

It is necessary for us to specify attributes for parameters to tell the marshalling layer how to interpret them. For instance, in the first parameter we are passing a short by reference. But is it being passed from the caller to the object, from the object to the caller, or both? Using the out attribute tells the marshalling layer that the parameter is being passed from the object to the caller. So the first parameter does not need to be sent in the request that represents this method call, but it does need to come back in the response. The third parameter is the address of an array of shorts. The in attribute tells the marshalling layer that we are passing the array from the caller to the object in the request and that it does not need to come back again in the response. The size_is attribute tells the marshalling layer that the size of the array is contained in the second parameter, so it knows how many shorts to transmit in the request. As you can probably see, using these attributes in the correct way to provide the marshalling layer with enough information is crucial.

We have already seen that COM methods that are going to be remoted need to return HRESULTS. It turns out that when we remote a method the request packet contains header information plus the [in] parameters and the response packet contains header information plus the [out] parameters and the HRESULT.

Next we will look at the COM type system in some detail and how the mother of all interfaces, IUnknown, is used to match disparate type systems.


Top of document