These notes present a comparison between OLE and CORBA in regards to the set of features they provide for developing distributed systems. It focuses on the technical aspects of these frameworks only. The comparison is provided from a developer perspective, and is categorized by the provisions in the two systems for expressiveness in the object models, and support for the development of servers and clients. A few other sundry areas are covered as well.
OLE is an object technology coming out of Microsoft. OLE was once an acronym for Object Linking and Embedding since it was originally meant to support compound documents. The framework is based on COM (Component Object Model), and will eventually support distribution.
CORBA stands for the Common Object Request Broker Architecture - a specification for distributed software systems published by the OMG (Object Management Group) consortium.
This comparison breaks up the developers' view of distributed object systems into the following categories:
A bunch of miscellaneous criteria are also bunched together in the last section.
The Object Models Writing Servers : defining a distributable object Defining the interface of an object and identifying such interfaces Defining an operation on an object Implementing an object in a given implementation language Providing means for creating an object Making an object uniquely identifiable and testing for object equality Making a local object distributable Controlling the concurrent behavior of objects Controlling the lifetime behavior of objects and object implementations Making objects persistent Providing support for implementations with no static type information Reusing code (implementation inheritance) Reporting abnormal behavior (support for exceptions etc.) Allowing callback style interaction from servers to clients Memory management Object server evolution Cross-platform support Writing Clients : using a distributable object Locating a distributable object Using a distributable object remotely vs. using it locally Writing a client in the same language as the server vs. in a different language Making operation invocations without static type information Miscellaneous
An implementation of an interface is an array of pointers to functions. Users always access COM objects through such interfaces, and not through some pointer to the entire object. This structure is at the heart of the binary standard of COM. In principle, this avoids the need for a separate interface definition language. This is also the reason that COM does not support multiple inheritance of interfaces.
Each interface is assigned a GUID (globally unique identifier) - these can be created using some provided software, or through Microsoft. The software version uses the timestamp, a random number and the network address of the machine to generate the GUID.
Each object supports the IUnknown interface at least - the methods in this interface allow:
Each COM object also has a class. Objects of the same class implement the same interfaces. But two COM objects implementing the same interfaces needn't be of the same class.Objects are implemented in servers, and these can be of three kinds -- in-process, local or remote (the last is not yet supported). An implementation registers itself by providing a unique class id, the type of server, and the location of the server's binary.
Objects can be given persistent names called monikers which can also be made smart about initializing or restoring persistent information in the object. There is a notion of connectable objects which permits objects to establish a two-way dialogue with their users.
Objects are denoted by object references (or objrefs for short). An object may be denoted by multiple distinct objrefs. Objrefs can be "stringified" transformed into strings). Given such a string form, one can get back the original objref and make requests to it. Objrefs have duplicate and release operations that can be used to implement reference counting, as well as a is_a method that can be used for maintaining runtime type safety if desired. There is also an is_equivalent operation that takes another objref and returns true for cases where it can be determined that the two objrefs denote the same object.
Clients access services by issuing requests which contain information about the target object (the object reference), the operation, the parameters for the operation (if any) and an optional request context. These requests can be made through pre-compiled stubs or through a dynamic invocation mechanism. Requests cause a service to be performed. If an abnormal condition occurs, an exception can be returned. A request made by a client using an objref is conveyed to the corresponding object implementation by the ORB (Object Request Broker).
The set of possible operations that a client may request of an object is determined by its interface. Interfaces are defined in OMG IDL (Interface Definition Language). IDL permits the declaration of modules, interfaces, exceptions, constants and typedefs. Modules are a scoping mechanism. Interfaces can contain declarations for attributes and operations in addition to typedefs, constants and exceptions. Interfaces can also inherit from one or more other interfaces (name conflicts in such cases are not permitted). An ORB doesn't necessarily need the IDL definitions for its operation, or any type information for that matter. In practice though, type information is made available either through IDL interfaces, or stub routines or entries in the interface repository.
Language mappings define how to access objects through the ORB using a particular language. A mapping includes a definition of the IDL declarations into data types and procedures of that language, the structure of stubs and skeletons, the dynamic invocation interface, the object adapters and the interface to the ORB. A language mapping can also define the interaction between object invocations and thread of control in the client or the implementation.
An ORB provides its services through one or more object adaptors. An object adaptor provides for the creation and interpretation of objrefs, implementation activation/deactivation, registration of implementations, etc. Different object adaptors can qbe used to target the specific needs of sets of implementations (database adaptor, high security adaptor, etc.).
CORBA specifies an interface repository as a way of presenting type information at runtime, and an implementation repository as a way for an ORB to locate and activate object implementations.
The CORBAServices specification defines a whole set of interfaces and objects that support some basic services crucial to the building of distributed applications. These include services such as naming, relationships, properties, life cycle, transactions, security, etc. Some of these services are still in the process of being standardized. The CORBAFacilities specification will define higher-level non-critical functionalities such as an electronic mail service.
OLE's interfaces support inheritance but not multiple inheritance. The restriction has to do with the binary standard for object implementations that the object model defines. However, objects can implement multiple interfaces, and an interface pointer to any particular interface can be obtained through the QueryInterface operation all objects must implement.
The MIDL (Microsoft IDL) compiler can take IDL definitions and generate the header files that are needed to permit use of the objects being defined. However, it is not necessary to use IDL - there are other ways of conveying the type information contained in IDL definitions to the framework, such as type libraries.
Interfaces are identified by interface identifiers, which are created by running a function called CoCreateGuid that generates a 128-bit GUID (globally unique identifier). To create this globally unique identifier, the function uses the current date and time, an incremental counter, a random number generated using a clock sequence, and a machine identifier that is either obtained from the network card or created.
Interfaces, from a user perspective, are identified by their fully scoped names (the scoping being provided by the names of modules, interfaces, structures, unions, operations and exceptions). However, since this isn't good enough to maintain global uniqueness, CORBA defines the notion of a repository identifier which uniquely identifies a type definition. The repository identifier is usually computed using the fully scoped name as well as some localization prefix, but can be user specified as well -- DCE UUID format ids are also permitted.
Attributes are a convenient way of defining a pair of get and set operations in an interface.
OLE defines the IClassFactory interface with the CreateInstance operation. If an object implements this interface, it can be used to create instances of the objects for which it is a factory. (There also exists an IClassFactory2 interface that includes operations to permit licensed creation of objects).
If one has the class identifier, one can use the CoGetClassObject function to get its IClassFactory interface and then use that to create objects.
If one needs to create only one instance of an object, one can use the CoCreateInstance function that takes a class identifier and generates an instance.
In OLE, object identity can be checked by using the QueryInterface operation to get the pointer to each objects IUnknown interface and comparing for equality on the pointer values returned.
An ORB provides the is_equivalent operation on object references which takes an objref as an argument and returns true if the ORB can determine equality of the object references. However, the failure of this test does not necessarily indicate that the object references refer to different objects.
For C applications, the effort involved is significantly larger since, amongst other things, the user has to provide the vtable style data structures for their implementations.
Object implementations are provided either as local executables (.EXE files) or as dynamically loaded libraries (.DLL files). Local servers terminate themselves when no longer in use. However, dlls have to be explicitly freed through the CoFreeUnusedLibraries function that in turn calls DllCanUnloadNow on all the dlls not in use.
In the containment model, one object uses an instance of another in its implementation and provides a its own version of the contained object's interface to users. It can control which calls go through to the contained objects and which calls it wishes to handle on its own. This is useful to override some aspects of an object's behavior.
In the aggregation model, the outer object directly exposes the interface of the inner object. In this case, the inner object must know that it is part of a larger object, since, for instance, the operations in the IUnknown interface must now behave as though they belong to the outer object.
The CORBA specification also defines a large set of system exceptions that can typically occur.
The events service itself is much more elaborate, and permits blocking and non-blocking style event pulling, fan-in and fan-out of event flow, typed event communication (where suppliers can call operations on consumers through some mutually-agreed interface), etc.
For parameters in operations, in parameters must be allocated and freed by the caller, out parameters must be allocated by the function (even in the case of failure) and freed by the caller, and inout parameters must be allocated and freed by the caller but might be freed and reallocated by the function in between.
COM provides a task memory allocation service that is available to objects and clients, and which is what makes it possible to hand memory across processes.
The memory allocation for parameters to operations is a function of the language mapping.
OLE currently doesn't support remote objects, and hence it is difficult to comment on all the overheads involved. However, OLE does define and provide standard marshalling for objects. Objects can also choose to implement their own marshalling by supporting the IMarshal interface. The advantages of customized marshalling are that users need only compile in marshalling code if its to be used, and can also marshall just the relevant information (which could be something as trivial as a pointer into shared memory instead of a full blown state description).
Objects that wish to be able to provide type information to clients must implement the IProvideClassInfo interface. Clients can also use the class id to get type information from the registry entries, in which case they don't need an instance of the class to get type information about it.
Clients can invoke operations through the IDispatch interface. Using the available type information, clients can query the object to find out the dispatch identifiers for various operations, and once it has these, it can call the IDispatch::invoke operation, giving it the dispatch id for whichever operation it wishes to invoke. Arguments are passed as an array of VARIANTARG structures. A VARIANTARG is essentially a union type that can hold any of the basic types in OLE. To pass non-standard data structures, they have to be mapped to these simpler types.
CORBA also defines the dynamic invocation interface (DII) which allows clients to invoke operations on objrefs without having access to compiled stubs for the interface of that object.This is done through the create_request operation on objrefs which creates a CORBA::Request object. This is filled with information about the operation and the arguments (arguments are wrapped in Anys which contain a type and a value of that type). Clients can then make an invocation by calling the invoke operation on Requests. Operations can also be invoked asynchronously using the DII, so that clients can carry on until they need to access the response from the invocation.
Using the IFR and the DII, clients can make typesafe calls on objects without having access to compiled stubs for the object.