Since the advent of the general-purpose microprocessor, microprocessors could be programmed to perform arbitrary tasks that the hardware was never specifically designed for. Sequences of instructions written in pure binary or some form of assembly language made programming much easier than manually flipping switches to customize the computer's purpose.
As code needed to execute on a multitude of perpetually evolving and competing hardware platforms, assembly’s platform dependence rendered it unusable. Gradually, structured languages like FORTRAN and COBOL enabled programmers to more efficiently use their time by not having to write every new program from scratch. The code’s reuse potential gave way to libraries that were large collections of routines and subroutines. These libraries as well as the programs that used them grew exponentially in size until they became difficult to maintain.
Eventually object oriented programming solved many of the problems associated with structured programming. Rather then having a large interconnected network of functions, OOP allowed for software systems composed of discrete software modules. Ideally, these objects had rational and well-planned relationships between them. Most of the functionality in an object is hidden and only public parts are accessible to the outside world. A well designed system could now be easily understood since many of the details concerning how objects worked were hidden. Additionally, aggregation and inheritance allowed the creation of a new object by simply using and modifying existing objects. OOP still did not provide as much reusability as expected because most objects where designed to work as part of their initial system. Since software systems evolved so rapidly, objects often did things that the original software designers did not anticipate. Objects where seldom simply plugged into new systems.
The next step in the evolution of software development is Component based development. Component-based development breaks a set of functionality into a client, an interface and an implementation. The client is a piece of software that requires a set of features. That set of features is defined as an interface. The implementation simply acts as a server that supports this interface. The real difference here between interface-based development and OOP is that an interface is more like a contract. Once an interface is established, the client expects it and the server must support it exactly.
Distributed computing has extended the object oriented and component paradigms and carried them to new heights. Now it is possible for objects or components that exist on separate machines or platforms to communicate with each other across a heterogeneous network. The primary distributed computing paradigms are Microsoft's Distributed Component Object Model (DCOM), OMG's Common Object Request Broker Architecture (CORBA) and Sun’s Java/Remote Method Invocation (Java/RMI).
There are several commonalties amongst the aforementioned technologies. For instance whenever a client needs to communicate with a remote object, it invokes a method implemented by the remote object. The methods that can be invoked on the remote object are described in its interface, which can be described in an Interface Definition Language (IDL). The interfaces specified in the IDL file serve as a contract between a remote object server and its clients. Clients can thus interact with these remote object servers by invoking methods defined in the IDL. Another attribute that is shared in all three paradigms is that methods are invoked on a remote object by sending method parameters in a network message via a predefined protocol (Internet Inter-ORB Protocol (IIOP) for CORBA, Object Remote Procedure Call (ORPC) for DCOM, and Java Remote Method Protocol (JRMP) for Java/RMI) to the server, which then deciphers the network message and invokes the methods on itself based on the parameters sent. Finally all three paradigms use some form of central repository that maps names to object implementations that is used to locate remote objects, also there exists a mechanism for assigning unique identifiers to objects so that they can be located.
The above technologies merely provide the ability to invoke methods on remote objects but do not handle the growing complexity of modern software development. The modularity of applications, the manner in how methods are invoked, how objects are configured, security, concurrency, synchronization, replication, exception handling and error recovery is either not handled or handled only to a small degree by the aforementioned technologies. Sophisticated functionality for the aforementioned technologies is handled by, Microsoft Transaction Server for DCOM, Enterprise Java Beans for Java/RMI and the CORBA Component Model for CORBA. These middleware component technologies allow the application developer to concentrate on programming only the business logic, while removing the need to write all the "plumbing" code that is required in any enterprise application development scenario.
A CORBA application usually consists of an Object Request Broker (ORB), a client and a server. An ORB is responsible for matching a requesting client to the server that will perform the request, using an object reference to locate the target object. When the ORB examines the object reference and discovers that the target object is remote, it marshals the arguments and routes the invocation out over the network to the remote object's ORB. The remote ORB then invokes the method locally and sends the results back to the client via the network. There are many optional features that ORBs can implement besides merely sending and receiving remote method invocations including looking up objects by name, maintaining persistent objects, and supporting transaction processing. A primary feature of CORBA is its interoperability between various platforms and programming languages.
The first step in creating a CORBA application is to define the interface for the remote object using the OMG's interface definition language (IDL). Compiling the IDL file will yield two forms of stub files; one that implements the client side of the application and another that implements the server. The following Java IDL file called
module CORBAQuoteApp {
interface CORBAQuote
{
string getPrice(in string stock_ticker); // obtain a stock’s share price from ticker
};
};
CORBAQuote.idl, causes the following files to be created when compiled:
| _CORBAQuoteImplBase.java | This abstract class is the server skeleton, providing basic CORBA functionality for the server. It implements the CORBAQuote.java interface. The server class CORBAQuoteServant extends _CORBAQuoteImplBase. |
| _CORBAQuoteStub.java | This class is the client stub, providing CORBA functionality for the client. It implements the CORBAQuote.java interface. |
| CORBAQuote.java | This interface contains the Java version of our IDL interface. It contains the single method sayCORBAQuote. The CORBAQuote.java interface extends org.omg.CORBA.Object, providing standard CORBA object functionality as well. |
| CORBAQuoteHelper.java | This final class provides auxiliary functionality, notably the narrow method required to cast CORBA object references to their proper types. |
| CORBAQuoteHolder.java | This final class holds a public instance member of type CORBAQuote. It provides operations for out and inout arguments, which CORBA has but which do not map easily to Java's semantics. |
Below is the CORBA client code with detailed comments explaining every step.
import CORBAQuoteApp.*; // The package containing our stubs.
import org.omg.CosNaming.*; // CORBAQuoteClient will use the naming service.
import org.omg.CORBA.*; // All CORBA applications need these classes.
/**
* CORBAQuoteClient.java
*
*
* Created: Fri Sep 15 05:37:00 2000
*
* @author Dare Obasanjo
* @version 1.0
*/
public class CORBAQuoteClient {
public CORBAQuoteClient() { ; }
public static void main(String args[])
{
try{
// Create and initialize the ORB which will perform the local marshalling
// and IIOP work
ORB orb = ORB.init(args, null);
// Get the root naming context
org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService");
NamingContext ncRef = NamingContextHelper.narrow(objRef);
// Resolve the object reference in naming
NameComponent nc = new NameComponent("GTNum", "");
NameComponent path[] = {nc};
CORBAQuote CORBAQuoteRef = CORBAQUOTEHelper.narrow(ncRef.resolve(path));
// Call the CORBAQuoteServer object and print results
String ticker = "ORCL";
String price = quoteRef.getPrice(ticker);
System.out.println(ticker + " = $" + price);
ticker = "MSFT";
price = quoteRef.getPrice(ticker);
System.out.println(ticker + " = $" + price);
ticker = "SUNW";
price = quoteRef.getPrice(ticker);
System.out.println(ticker + " = $" + price);
ticker = "APPL";
price = quoteRef.getPrice(ticker);
System.out.println(ticker + " = $" + price);
} catch(Exception e) {
System.out.println("CORBA Error: " + e);
e.printStackTrace(System.out);
}
} /* main(String[]) */
} // CORBAQuoteClient
Note that in the above code fragment the CORBA invocations look like a method call on a local object. The complications of marshaling parameters to the wire, routing them to the server-side ORB, unmarshaling, and placing the upcall to the server method are completely transparent to the client programmer. Also note that a naming service is one of the features that is declared optional by the CORBA specification and thus may not be supported by some ORBs. In that situation, CORBA clients use a stringified object reference to find their first object.
Here is the server code for our application with detailed comments explaining what is going on.
// The package containing our stubs.
import CORBAQuoteApp.*;
// CORBAQuoteServer will use the naming service.
import org.omg.CosNaming.*;
// The package containing special exceptions thrown by the name service.
import org.omg.CosNaming.NamingContextPackage.*;
// All CORBA applications need these classes.
import org.omg.CORBA.*;
/**
* CORBAQuoteServer.java
*
*
* Created: Fri Sep 15 05:56:58 2000
*
* @author Dare Obasanjo
* @version 1.0
*/
/*
*A server is a process that instantiates one or more servant objects.
*/
public class CORBAQuoteServer {
public CORBAQuoteServer() { ; }
public static void main(String args[])
{
try{
// Create and initialize the ORB
ORB orb = ORB.init(args, null);
//Create the servant and register it with the ORB. Every server instantiates an ORB
//and registers its servant objects so that the ORB can find the server when it
//receives an invocation for it.
CORBAQuoteServant QuoteRef = new CORBAQuoteServant();
orb.connect(QuoteRef);
// Get the root naming context
org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService");
NamingContext ncRef = NamingContextHelper.narrow(objRef);
//Bind the object reference in naming so that when the client calls resolve("CORBAQuote") on
//the initial naming context, the naming service returns an object reference to the
//CORBAQuote servant.
NameComponent nc = new NameComponent("CORBAQuote", "");
NameComponent path[] = {nc};
ncRef.rebind(path, CORBAQuoteRef);
// Wait for invocations from clients
java.lang.Object sync = new java.lang.Object();
synchronized(sync){
sync.wait();
}
} catch(Exception e) {
System.err.println("CORBA ERROR: " + e);
e.printStackTrace(System.out);
}
}
}//CORBAQuoteServer
/**
* The servant implements the interface generated by idltojava and actually performs
* the work of the operations on that interface.
*/
class CORBAQuoteServant extends _CORBAQuoteImplBase
{
public String getPrice(String stock_ticker)
{
if(stock_ticker.equalsIgnoreCase("MSFT"))
return "64 3/16";
else if(stock_ticker.equalsIgnoreCase("DELL"))
return "35 3/4";
else if(stock_ticker.equalsIgnoreCase("YHOO"))
return "105 7/8";
else if(stock_ticker.equalsIgnoreCase("AOL"))
return "55 1/4";
else if(stock_ticker.equalsIgnoreCase("RHAT"))
return "21 3/16";
else if(stock_ticker.equalsIgnoreCase("RADS"))
return "22 43/64";
else if(stock_ticker.equalsIgnoreCase("SUNW"))
return "113 1/16";
else if(stock_ticker.equalsIgnoreCase("LNUX"))
return "49 1/2";
else if(stock_ticker.equalsIgnoreCase("ORCL"))
return "78 5/16";
else if(stock_ticker.equalsIgnoreCase("CSCO"))
return "62 3/4";
else if(stock_ticker.equalsIgnoreCase("ITWO"))
return "172 3/16";
else
return "Unknown Stock Ticker";
}/* getPRICE(String) */
}//CORBAQuoteServant
CORBA is often considered a superficial specification because it concerns itself more with syntax than with semantics. CORBA specifies a large number of services that can be provided but only to the extent of describing what interfaces should be used by application developers. Unfortunately, the bare minimum that CORBA requires from service providers lacks mention of security, high availability, failure recovery, or guaranteed behavior of objects outside the basic functionality provided and instead CORBA deems these features as optional. The end result of the lowest common denominator approach is that ORBs vary so wildly from vendor to vendor that it is extremely difficult to write portable CORBA code due to the fact that important features such as transactional support and error recovery are inconsistent across ORBs. Fortunately a lot of this has changed with the development of the CORBA Component Model, which is a superset of Enterprise Java Beans.
RMI is a technology that allows the sharing of Java objects between Java Virtual Machines (JVM) across a network. An RMI application consists of a server that creates remote objects that conform to a specified interface, which are available for method invocation to client applications that obtain a remote reference to the object. RMI treats a remote object differently from a local object when the object is passed from one virtual machine to another. Rather than making a copy of the implementation object in the receiving virtual machine, RMI passes a remote stub for a remote object. The stub acts as the local representative, or proxy, for the remote object and basically is, to the caller, the remote reference. The caller invokes a method on the local stub, which is responsible for carrying out the method call on the remote object. A stub for a remote object implements the same set of remote interfaces that the remote object implements. This allows a stub to be cast to any of the interfaces that the remote object implements. However, this also means that only those methods defined in a remote interface are available to be called in the receiving virtual machine.
RMI provides the unique ability to dynamically load classes via their byte codes from one JVM to the other even if the class is not defined on the receiver's JVM. This means that new object types can be added to an application simply by upgrading the classes on the server with no other work being done on the part of the receiver. This transparent loading of new classes via their byte codes is a unique feature of RMI that greatly simplifies modifying and updating a program.
The first step in creating an RMI application is creating a remote interface. A remote interface is a subclass of java.rmi.Remote, which indicates that it is a remote object whose methods can be invoked across virtual machines. Any object that implements this interface becomes a remote object.
package calculator;
import java.rmi.Remote;
import java.rmi.RemoteException
/**
* StockPriceCalculator.java
*
*
* Created: Fri Sep 15 22:53:46 2000
*
* @author Dare Obasanjo
* @version 1.0
*/
public interface StockPriceCalculator extends Remote {
String obtainStockPrice(StockTicker StockTicker) throws RemoteException;
} // StockPriceCalculator
To show dynamic class loading at work, an interface describing an object that can be serialized and passed from JVM to JVM shall also be created. The interface is a subclass of the java.io.Serializable interface. RMI uses the object serialization mechanism to transport objects by value between Java virtual machines. Implementing Serializable marks the class as being capable of conversion into a self-describing byte stream that can be used to reconstruct an exact copy of the serialized object when the object is read back from the stream. . Any entity of any type can be passed to or from a remote method as long as the entity is an instance of a type that is a primitive data type, a remote object, or an object that implements the interface java.io.Serializable. Remote objects are essentially passed by reference. A remote object reference is a stub, which is a client-side proxy that implements the complete set of remote interfaces that the remote object implements. Local objects are passed by copy, using object serialization. By default all fields are copied, except those that are marked static or transient. Default serialization behavior can be overridden on a class-by-class basis. Below is the code for the StockTicker interface which describes an interface for classes that will be passed to or returned from remote methods.
package calculator;
import java.io.Serializable;
/**
* StockTicker.java
*
*
* Created: Fri Sep 15 22:53:46 2000
*
* @author Dare Obasanjo
* @version 1.0
*/
public interface StockTicker extends Serializable {
String getTicker();
}
Clients of the distributed application can dynamically load objects that implement the StockTicker interface even if they are not defined in the local virtual machine. The next step is to implement the remote interface, the implementation must define a constructor for the remote object as well as define all the methods declared in the interface Once the class is created, the server must be able to create and install remote objects. The process for initializing the server includes; creating and installing a security manager, creating one or more instances of a remote object, and registering at least one of the remote objects with the RMI remote object registry (or another naming service such as one that uses JNDI), for bootstrapping purposes. Below is an example server:
package stockquoteserver;
import java.rmi.*;
import java.rmi.server.*;
import calculator.*;
/**
* RMIQuoteServer.java
*
*
* Created: Sat Sep 16 10:51:08 2000
*
* @author Dare Obasanjo
* @version 1.0
*/
/*
* UnicastRemoteObject is a convenience class, that can be used as a superclass for
* remote object implementations
*/
public class RMIQuoteServer extends UnicastRemoteObject
implements StockPriceCalculator {
//constructor Parent class’ constructor
public RMIQuoteServer() throws RemoteException{ super();}
// remote method implementation. The Clients provide the RMIQuoteServer with a
// StockTicker object, which has an implementation of the getticker method.
public String obtainStockPrice(StockTicker Ticker){
String stock_ticker = ticker.getTicker();
if(stock_ticker.equalsIgnoreCase("MSFT"))
return "64 3/16";
else if(stock_ticker.equalsIgnoreCase("DELL"))
return "35 3/4";
else if(stock_ticker.equalsIgnoreCase("YHOO"))
return "105 7/8";
else if(stock_ticker.equalsIgnoreCase("AOL"))
return "55 1/4";
else if(stock_ticker.equalsIgnoreCase("RHAT"))
return "21 3/16";
else if(stock_ticker.equalsIgnoreCase("RADS"))
return "22 43/64";
else if(stock_ticker.equalsIgnoreCase("SUNW"))
return "113 1/16";
else if(stock_ticker.equalsIgnoreCase("LNUX"))
return "49 1/2";
else if(stock_ticker.equalsIgnoreCase("ORCL"))
return "78 5/16";
else if(stock_ticker.equalsIgnoreCase("CSCO"))
return "62 3/4";
else if(stock_ticker.equalsIgnoreCase("ITWO"))
return "172 3/16";
else
return "Unknown Stock Ticker";
}/* obtainStockPrice(String) */
public static void main(String[] args) {
//The security manager determines whether downloaded code
//has access to the local file system or can perform any other
//privileged operations. All rmi programs must use a security manager.
if (System.getSecurityManager() == null) {
System.setSecurityManager(new RMISecurityManager());
}
/*
here is where name resolution occurs. Here the rmi registry is used to allow clients
obtain a reference to the remote object
*/
//create a remote name for the object, consisting of the host name where the registry //exists and the name of the object.
String name = "//localhost/StockPriceCalculator";
try {
StockPriceCalculator server = new RMIQuoteServer();
//here the object name is added to the rmi registry
Naming.rebind(name, server);
System.out.println("RMIQuoteServer bound");
} catch (Exception e) {
System.err.println("RMIQuoteServer exception: " + e.getMessage());
e.printStackTrace();
}
}/* main(String[]) */
} // RMIQuoteServer
It is not necessary to have a thread wait to keep the server alive. As long as there is a reference to the RMIQuoteServer object in another virtual machine, local or remote, the RMIQuoteServer object will not be shut down, or garbage collected. Because the program binds a reference to the RMIQuoteServer in the registry, it is reachable from a remote client, and the registry itself. The RMI system takes care of keeping the RMIQuoteServer process up. The RMIQuoteServer is available to accept calls and won't be reclaimed until its binding is removed from the registry, and no remote clients hold a remote reference to the RMIQuoteServer object.
An RMI client behaves similarly to a server; after installing a security manager, the client constructs a name used to look up a remote object. The client uses the Naming.lookup method to look up the remote object by name in the remote host's registry. When doing the name lookup, the code creates a URL that specifies the host where the server is running. The name passed in the Naming.lookup call has the same URL syntax as the name passed in the Naming.rebind call, on the server. Below is the code for an RMI client.
package client;
import java.rmi.*;
import calculator.*;
/**
* RMIQuoteClient.java
*
* Created: Sat Sep 16 12:00:01 2000
*
* @author Dare Obasanjo
* @version 1.0
*/
public class RMIQuoteClient {
public static void main(String args[]) {
if (System.getSecurityManager() == null) {
System.setSecurityManager(new RMISecurityManager());
}
try {
String name = "//localhost/StockPriceCalculator";
StockPriceCalculator calc = (StockPriceCalculator) Naming.lookup(name);
StockTicker ticker = new NASDAQStock("MSFT");
String price = calc.obtainPrice(ticker);
System.out.println(ticker + " = $" + price);
StockTicker ticker = new NASDAQStock("CNET");
String price = calc.obtainPrice(ticker);
System.out.println(ticker + " = $" + price);
StockTicker ticker = new NASDAQStock("YHOO");
String price = calc.obtainPrice(ticker);
System.out.println(ticker + " = $" + price);
} catch (Exception e) {
System.err.println("RMIQuoteClient Exception: " + e.getMessage());
e.printStackTrace();
}
}
} // RMIQuoteClient
for reasons of completeness here is the code for a class that implements the StockTicker interface.
package client;
import java.rmi.*;
import calculator.*;
/**
* NASDAQStock.java
*
* Created: Sat Sep 16 12:05:30 2000
*
* @author Dare Obasanjo
* @version 1.0
*/
public class NASDAQStock implements StockTicker {
String name;
public NASDAQStock(String name) { setTicker(name);}
/**
* Get the value of name.
* @return Value of name.
*/
public String getTicker() {return name;}
/**
* Set the value of name.
* @param v Value to assign to name.
*/
public void setTicker(String v) {this.name = v;}
} // NASDAQStock
Microsoft's Distributed Component Object Model provides a powerful convention with witch binary components can be glued together to form sophisticated applications. DCOM is built with interfaces. An interface is a mechanism through which a client and server communicate without knowing each other's details. An interface is sometimes referred to as an abstract class in object oriented nomenclature. All DCOM components and interfaces must inherit from IUnknown, the base DCOM interface. IUnknown consists of the methods AddRef(), Release() and QueryInterface(). AddRef() and Release() are used to for reference counting and memory management. Essentially, when an object's reference count becomes zero, it must self-destruct. The Quote component does this by deleting it's this pointer. Normally deleting a this pointer is very dangerous, but reference counting guarantees that the operation is safe. QueryInterface serves to return pointers to the requested interface.
class CQuote : public IQuote
{
public:
// IUnknown
ULONG __stdcall AddRef();
ULONG __stdcall Release();
HRESULT __stdcall QueryInterface(REFIID riid, void** ppv);
// IQuote
HRESULT __stdcall Quote(BSTR bstrStock, float* retval);
CQuote() : m_cRef(1) { g_cLocks++; }
~CQuote()
{
cout << "Component: CQuote::~CQuote()" << endl;
g_cLocks--;
}
private:
ULONG m_cRef;
};
ULONG CQuote::AddRef()
{
cout << "Component: CQuote::AddRef() m_cRef = " << m_cRef + 1 << endl;
return ++m_cRef;
}
ULONG CQuote::Release()
{
cout << "Component: CQuote::Release() m_cRef = " << m_cRef - 1 << endl;
if(--m_cRef != 0)
return m_cRef;
delete this;
return 0;
}
HRESULT CQuote::QueryInterface(REFIID riid, void** ppv)
{
if(riid == IID_IUnknown)
{
cout << "Component: CQuote::QueryInterface() for IUnknown returning " << this << endl;
*ppv = static_cast(this);
}
else if(riid == IID_IQuote)
{
cout << "Component: CQuote::QueryInterface() for IQuote returning " << this << endl;
*ppv = static_cast(this);
}
else
{
*ppv = NULL;
return E_NOINTERFACE;
}
AddRef();
return S_OK;
}
QueryInterface gives the client access to the proper one. QueryInterface helps the client discover what interfaces the server supports. Here, in the Quote example, the client must call QueryInterface and request the IQuote interface. Once the proper pointer is returned, the client can call IQuote::Quote(). Here, the Quote component supports both IUnknown and IQuote. A visual COM component, for instance, may implement dozens of different interfaces. Interfaces in DCOM must be defined in IDL. The following IDL code defines the IQuote interface.
object, uuid(10000001-0000-0000-0000-000000000001) ]
interface IQuote : IUnknown
{
HRESULT Quote([in] BSTR bstrStock, [out, retval] float* retval);
};
Here the IDL code defines the Quote component and the interface it supports. The uuid is Universally Unique Identifier. The uuid, more commonly referred to as a Globally Unique Identifier (GUID), is generated by an algorithm that guarantees that no GUID is generated twice. Therefore, the GUID can be used by clients to name the component during instantiation. The IDL code, however, isn’t available to clients at runtime. Instead, the IDL is compiled into a binary format called a type library. A tool such as Visual Studio reads this type library and becomes aware of the nature of the component. Visual studio then understands the nature of the component and can provide a rich development experience and allow components to be managed and glued together. A more distributed example would be a stock quote component that lives in the New York Stock Exchange. As long as they had the type libraries and proper permissions, any client could work with the NYSE quote component as if it were part of their system. The original author doesn't need to consider the fact that E*TRADE will one day use their component and the E*TRADE developer doesn't have to worry about the fact that the component is actually in New York. A DCOM component's type library opens allows it to be discovered and used by the outside world.
In addition to being able to communicate with an object, a client must be able to instantiate it. In an object-oriented language like C++, instantiation is often really just memory allocation and a call the constructor after optimization. Sometimes the details of construction might be very complex, but are resolved at compile time. In a component system, the client doesn't have the luxury of compile time resolution and does not know any details about the object like it's memory size. Therefore, a class object is necessary. A class object implements a special interface called IClassFactory and takes care of the details of instantiation for the client.
class CFactory : public IClassFactory
{
public:
// IUnknown
ULONG __stdcall AddRef();
ULONG __stdcall Release();
HRESULT __stdcall QueryInterface(REFIID riid, void** ppv);
// IClassFactory
HRESULT __stdcall CreateInstance(IUnknown *pUnknownOuter, REFIID riid, void** ppv);
HRESULT __stdcall LockServer(BOOL bLock);
CFactory() : m_cRef(1) { g_cLocks++; }
~CFactory() { g_cLocks--; }
private:
ULONG m_cRef;
};
ULONG CFactory::AddRef()
{
cout << "Component: CFactory::AddRef() m_cRef = " << m_cRef + 1 << endl;
return ++m_cRef;
}
ULONG CFactory::Release()
{
cout << "Component: CFactory::Release() m_cRef = " << m_cRef - 1 << endl;
if(--m_cRef != 0)
return m_cRef;
delete this;
return 0;
}
HRESULT CFactory::QueryInterface(REFIID riid, void** ppv)
{
if(riid == IID_IUnknown)
{
cout << "Component: CFactory::QueryInterface() for IUnknown returning " << this << endl;
*ppv = (IUnknown*)this;
}
else if(riid == IID_IClassFactory)
{
cout << "Component: CFactory::QueryInteface() for IClassFactory " << this << endl;
*ppv = (IClassFactory*)this;
}
else
{
*ppv = NULL;
return E_NOINTERFACE;
}
AddRef();
return S_OK;
}
HRESULT CFactory::CreateInstance(IUnknown *pUnknownOuter, REFIID riid, void** ppv)
{
if(pUnknownOuter != NULL)
return CLASS_E_NOAGGREGATION;
CQuote *pQuote = new CQuote;
cout << "Component: CFactory::CreateInstance() " << pQuote << endl;
if(pQuote == NULL)
return E_OUTOFMEMORY;
// QueryInterface probably for IID_IUnknown
HRESULT hr = pQuote->QueryInterface(riid, ppv);
pQuote->Release();
return hr;
}
HRESULT CFactory::LockServer(BOOL bLock)
{
if(bLock)
g_cLocks++;
else
g_cLocks--;
return S_OK;
}
The IClassFactory implementation for the Quote component is itself a COM component. When a client wants to instantiate an instance of the Quote component, it calls a Win32 function called CoCreateInstance with the component's GUID. CoCreateInstance then checks the windows registry and figures out where the component's class factory resides. It is often either in a local dll or on a different machine. The class factory is then instantiated and used to create instances of the actual component from then on through the IClassFactory::CreateInstance method.
DCOM works over the network by using proxy's and stubs. When the client instantiates a component whose registry entry suggests that it resides outside the process space, DCOM creates a wrapper for the component and hands the client a pointer to the wrapper. This wrapper, called a proxy, simply marshals methods calls and routes them across the network. On the other end, DCOM creates another wrapper, called a stub, which unmarshals methods calls and routes them to an instance of the component.