ActiveX/COM Q & A

Don Box

Don Box is cofounder of DevelopMentor, a training firm for COM and Windows NT-based development. He also wrote the upcoming book Creating Components using DCOM and C++ for Addison-Wesley. Don can be reached at dbox@develop.com.

QI am building a suite of COM objects that have little or no user interface and will be called from a variety of client languages, perhaps over a network. Should I build inproc servers or outofproc servers?

AAs you might expect, there is no universally true answer. There are some basic guidelines that may help you decide which way to go, but both types of servers would work and each has its own relative strengths and weaknesses. This is why both approaches are available.

The basic trade-off between inproc and outofproc servers has been one of balancing performance with robustness. Inproc servers favor performance while outofproc servers favor robustness. A properly written inproc server imposes no COM-related overhead on each method call. The object's data members and method implementations all reside in the address space of the client and, assuming the server supports the threading model of the client's apartment, can use the client's thread and stack to execute the method call. This yields the best possible performance.

Figure 1 shows the relative performance costs of making a null method call (no parameters, no actual work done in method) for a variety of client/object scenarios. Note that, to achieve good performance, the threading model of the DLL needs to be compatible with the threading model of the client apartment. If this is not the case, then the object will be created in a different apartment (although still in the client's process) and the resulting pointer will be marshaled back to the apartment that calls CoCreateInstance, creating a proxy/stub connection. In some cases, accessing out-of-process objects is actually faster than accessing an inproc object in a different apartment. The implication of the results in Figure 1 is that, if you do not support the threading model of the client, you are losing one of the primary advantages of being inproc: performance.

Figure 1 Lies, Confounded Lies, and Benchmarks

Client

Object

Operations/Sec

Either

Inproc (compatible Threading Model)

28,409,091

MTA

Inproc (ThreadingModel=Apartment)

10,359

MTA

Outofproc (MTA) - Same Machine

10,350

STA

Inproc (ThreadingModel=Free)

9,011

MTA

Outofproc (STA) - Same Machine

6,504

STA

Outofproc (MTA) - Same Machine

5,255

STA

Outofproc (STA) - Same Machine

4,631

MTA

Outofproc (MTA) - Remote Machine

1,191

STA

Outofproc (MTA) - Remote Machine

1,068

MTA

Outofproc (STA) - Remote Machine

1,025

STA

Outofproc (STA) - Remote Machine

953

· Roundtrip (ping) costs—no parameters, no work done in method

· Running Windows NT 4.0 SP2 with a 200Mhz Pentium Pro with 64MB RAM, 10Mbps Idle Ethernet network

· MTA (Multithreaded Apartment) = CoInitializeEx (0, COINIT_MULTITHREADED)

· STA (Single-threaded Apartment) = CoInitializeEx (0, COINIT_APARTMENTTHREADED)


Performance issues aside, when you deploy your object as an inproc server, your clients will be able to create instances of your class in their address spaces alongside all of the other client-side data structures used to build the client program. However, since your object runs in the execution context of the client, it is subject to all of the hazards that can result from raw client access. For example, if the client decides to pass one of your object's interface pointers to the C runtime library's memcpy routine, your object is in trouble:

void NaughtyClient(IFoo *pFooYou) {
   memcpy(pFooYou, "Hello Toast!", 20);
}

Your object is equally susceptible to array boundary overruns, dangling pointers, uninitialized pointers, or just about any other random pointer bug that the client may choose to throw your way.

If your anticipated client is Visual Basic® or Java™, you may think this isn't much of an issue, as these languages tend to protect programmers from themselves more than C or C++. However, it is possible for a Java-based client to load other inproc objects that were written by C++ programmers less skilled than you. If these objects cause a fault, they will bring the client (and by association, your object) down with them.

In contrast, objects that run in outofproc servers are insulated from faults that occur in their clients or in other objects used by their clients. Conversely, clients are insulated from faults that happen in the outofproc servers they communicate with. Roughly speaking, outofproc servers inherit the robustness of the process model of the underlying operating system. Dead servers can be detected by checking the HRESULT returned by all remotable methods. If the facility code of the HRESULT is FACILTY_RPC or the HRESULT is an RPC-related FACILITY_WIN32 error, then the result is from the underlying communications infrastructure, not from the object.

Dead clients are detected automatically by the OXID Resolver (OR). The OR is a system component in COM and runs in the RPCSS service with the RPC endpoint mapper and the COM Service Control Manager. The OR is responsible for translating apartment identifiers (OXIDs) to network/IPC addresses. It is also responsible for managing the ping conversation between any host machines that have active COM-related connections. The client-side OR periodically sends a ping packet to the server-side OR, indicating which object references are still valid. If the server-side OR does not receive a periodic ping notification for some preestablished period of time, it assumes that the client machine has died and runs down all of its outstanding references in the appropriate server processes.

A somewhat more subtle problem related to inproc servers is leaked references. Consider a client that calls your AddRef method one too many timesćor calls your Release method one too few times. Without a balanced AddRef/Release pattern in the client, your reference count will never decrement to zero and your object will never be properly destroyed. Any system-wide or network-wide resources your object had planned on releasing in its destructor may potentially be leaked. For example, consider the following code fragment:

class CMyClass : public IMyInterface {
  HANDLE m_hmutex;  BOOL m_bLocked;
  STDMETHODIMP StartWork(void) {
    m_bLocked = WaitForSingleObject(m_hmutex, 
                                    INFINITE) 
                                   == WAIT_OBJECT_0;
    return S_OK;
  }
  virtual ~CMyClass(void) {
    if (m_bLocked)
      ReleaseMutex(m_hmutex);
  }
    :
};

Despite the fact that the mutex handle will be closed when the client process exits or is terminated, any threads waiting on the mutex in other processes will awaken with an abandoned mutex and probably need to reinitialize any shared resources protected by the mutex. This is not the sign of a well-crafted system.

Had the server been implemented as an out-of-process server, the leaked client reference would only leak the client-side proxy, not the object itself. While the leaked proxy would in fact keep your object alive, the proxy would be run down by COM when the client process exits, in effect implicitly releasing the reference on your object. When the final reference is run down, either explicitly via a client's call to Release on the proxy or implicitly via client process termination, your object will be released by COM, triggering its destructor. Note that your protection from client-side leaks kicks in only when the client process exits or when the client host machine dies. If the client process and host machine keep running indefinitely, then any leaked object references in the client will seem active to the OR. This means that the Stub will never release your object.

A classic solution to this problem is to build an idle timer into the object to detect extended periods of inactivity. After some predetermined idle period, the object could call CoDisconnectObject, terminating all outstanding client connections to the object. While somewhat brutish, this technique would allow the server to reclaim any resources held on behalf of its clients. For this technique to work, the object requires the level of indirection afforded by the proxy/stub connection. Had your object loaded in the apartment of the client, calling CoDisconnectObject would have no effect because the client would still have a raw pointer into your object state.

The problem of leaked references is related to the more general problem of process versus object lifetime. By their nature, all objects live in a process. For an out-of-process server, this process is created dynamically by the Service Control Manager (SCM), based on the server's implementation of main/WinMain. For outofproc servers, the server implementor is in complete control of when the process shuts down. The standard implementation of a server's WinMain is to have the main thread of the process wait around until no objects have outstanding clients to service. This guarantees that the object's "home" will remain alive as long as it is needed.

For inproc servers, the object lives in the client's process. Unfortunately, the client's process lifetime is not tied to the lifetime of the inproc objects it creates. At first glance, this may not seem like a problem. A properly implemented client would never dream of shutting down until all interface pointers it had acquired have been released. However, assume the following client-side code:

void f(IMoniker *pmk, IBindCtx *pbc) {
  IFoo *pfoo = 0;
  pmk->BindToObject(pbc, 0, IID_IFoo, (void**)&pfoo);
// pfoo points to an existing outofproc object
  IBar *pbar = 0;
  CoCreateInstance(CLSID_Bar,CLSCTX_INPROC_SERVER, 
                   0, IID_IBar, (void**)&pbar);
// pbar points to a new inproc object
  pfoo->UseThisObject(pbar);
// pbar is marshaled to pfoo's apartment, creating
// stub in client's apartment to receive incoming 
// calls
  pfoo->Release(); // release client's ref
  pbar->Release(); // release client's ref
}

As shown in Figure 2, the client passes a pointer to an inproc object (pbar) as a method parameter to a remote object (pfoo). This causes the COM remoting layer to build a stub in the client's process to receive packets from the proxy created in the remote object's process for the duration of the UseThisObject method call. This is required to allow the inproc object to receive calls from the remote object. If the remote object does not AddRef the proxy it receives as the [in] parameter to UseThisObject, all is well because the stub routine for UseThisObject will automatically release the proxy to pbar, tearing down the Bar stub once the method call has completed. If, on the other hand, the remote object were to AddRef the Bar proxy it received in UseThisObject, the client process should remain running as long as the proxy is outstanding. Unfortunately, there is no generic mechanism for the inproc object to communicate its lifetime to the client. Because of this, the client is apt to shut down prematurely, leaving the remote object with a dangling proxy. While the remote object will not crash, it will receive an HRESULT from the next method call indicating that the client-side object has gone away.

Figure 2 Sharing an In-process Object

If the Bar object in the previous example was implemented as an outofproc server, the client could safely pass the reference to the remote Foo object, because Bar has its own process, distinct from the client. As shown in Figure 3, when the Bar object is implemented as an outofproc server, the lifetime of the client process is irrelevant to the Bar object's lifetime.

Figure 3 Sharing an Out-of-process Object

You might infer from this example that clients should not pass inproc object references as parameters to methods. While this seems reasonable in theory, in practice it is impossible to know which object references refer to inproc objects. Additionally, many methods accept interface pointers as [in] parameters but do not AddRef them, which is perfectly safe. In practice, it is the job of the client, the object implementor, and the interface designer to ensure that this problem does not arise. One common solution is for the interface designer to document which method calls are likely to AddRef their [in] parameters. The Advise method on IConnectionPoint is a great example of this strategy.

One additional robustness-related problem is that inproc servers do not have their own security context. Like all DLLs, inproc servers load into the client's address space. The code for the inproc object's methods executes using the client's access token. This means that if a trusted user (such as Administrator) loads an inproc object, any methods that the object executes will have access to whatever resources the trusted user has access to. Even worse, the inproc object could use the COSERVERINFO/COAUTHINFO structures used by CoCreateInstanceEx to create objects on other machines, granting impersonation rights to remote objects that the client has never even dreamed of. While most programmers dislike the whole notion of security and may think that this seems like a neat feature to exploit, many users would be bothered by a control that launched arbitrarily dangerous objects on an unlimited number of machines with the user's credentials.

This security problem is often addressed by using code signing and authentication, which works great when the code you want to use is signed by an authority that you trust. However, there may be unsigned code that you really want to experiment with and yet keep it from accessing critical resources without detection. As is shown in Figure 4, if the server is implemented as an outofproc server, you can easily use DCOMCNFG.EXE to configure the server to run as a distinguished security principal. For untrusted code, you could create a new user account (such as UntrustedGuest) and give it virtually no permissions on the system. Additionally, you could configure the system to create audit entries in the security event log whenever the object accesses system resources. For security-sensitive users, this is a critical requirement.

Figure 4 Sandboxing with DCOMCNFG

Given the robustness issues related to inproc servers, you might wonder if there are any compelling reasons to implement inproc servers beyond an unbridled desire for raw speed and performance; there are several. Occasionally, you'll need to design a COM interface that cannot remote. IViewObject is one such interface. IViewObject is often implemented by rendering handlers and by controls. It is a critical interface for the ActiveX Control and ActiveX Document architectures, and for efficiency it needs to run in-process. While it is usually better to design remotable interfaces to allow greater flexibility in terms of object location, you'll sometimes need to break down and design for in-process access.

In addition, when porting large amounts of legacy C++ software to COM, you may need to avoid IDL for expediency and instead define interfaces in header files. This allows you to use intrinsic C++ elements such as classes, templates, and so on. Most of these C++ features prohibit remoting and require the object to run in-process. This strategy breaks down in the long run and should be avoided if at all possible. Perhaps the most compelling reason to implement inproc servers is to integrate with Microsoft Transaction Server (MTS). MTS allows inproc COM objects to be assimilated into a scalable, robust, transaction-based runtime environment. When running inside MTS, your inproc objects run in an MTS-provided surrogate process that affords them the same robust features as an outofproc server. However, for MTS to perform its magic, your object must be implemented as an inproc server.

As of Windows NT® 4.0 Service Pack 2, COM provides built-in support for surrogates outside the scope of MTS. The idea behind COM surrogates is to decouple packaging from activation. Surrogate-based objects are packaged as DLLs, but can be activated as outofproc servers. Enabling a DLL to be activated inside a surrogate is a simple matter of adding some additional registry entries that the SCM consults when servicing an activation request. These entries can be added by client programmers to sandbox untrusted code or to activate legacy DLLs from other hosts.

To let your inproc CLSID be activated in a surrogate process, the CLSID must have an AppID named value. AppIDs are used in COM to group server-level security and remoting configuration information in the registry. AppIDs typically identify a single server process, which can contain one or more CLSIDs. CLSIDs note their AppID by using the AppID named value:

[HKCR\CLSID\{XXX}]
  @=Circle Class
  AppID={ZZZ}
[HKCR\CLSID\{XXX}\InprocServer32]
  @="shape.dll"
  ThreadingModel=Both
[HKCR\CLSID\{YYY}]
  @=Square Class
  AppID={ZZZ}
[HKCR\CLSID\{YYY}\InprocServer32]
  @="shape.dll"
  ThreadingModel=Both

These registry entries indicate that Square and Circle share the same AppID. This implies that they will share a common set of security and remoting settings under HKCR\AppID. To indicate that the AppID corresponds to a DLL surrogate, you need to add the DllSurrogate setting to the registry:

[HKCR\AppID\{ZZZ}]
  @="Shape Server"
  DllSurrogate=

When a non-inproc activation request first occurs for a CLSID, the SCM uses the following precedence rules to decide what to launch:

  1. If the AppID has a LocalService named value, COM uses the OpenSCManager/StartService APIs to start the server.
  2. If the CLSID has a LocalServer32 entry, COM starts the designated process using CreateProcess or CreateProcessAsUser.
  3. If the AppID's DllSurrogate named value is present yet empty, a system-provided surrogate process (dllhost.exe) is started.
  4. If the AppID's DllSurrogate named value is present and contains a file name, the designated custom surrogate is started.
  5. If the AppID's RemoteServerName named value is present, the request is forwarded to the SCM on the designated host.

If the AppID of a surrogate is configured to run as the interactive user or as a distinguished user account, one instance of the process will be started when the AppID's first CLSID is activated. All subsequent activation requests for any CLSIDs that belong to the same AppID will be sent to the already running surrogate. If the AppID is configured to run as the activating user, then a new instance of the dllhost.exe process must be started for each unique client security principal that tries to activate one of the AppID's classes.

Much like MTS packages, surrogate AppIDs form a unit of trust. As shown in Figure 5, all CLSIDs with the same AppID will be loaded into a single surrogate process and will have raw access to each other's objects. CLSIDs that are configured to use the default surrogate but with a different AppID will load in a different instance of dllhost.exe. For many DLLs, the system-provided default surrogate is sufficient. The default surrogate is capable of loading both Multithreaded Apartment (MTA) and Single-threaded Apartment (STA)-compliant DLLs. It also recognizes the Identity and Security AppID settings used by the SCM and DCOMCNFG.EXE.

Figure 5 CLSIDs, AppIDs, and Surrogates

In general, the default surrogate does a more than adequate job for a large class of objects. It is possible, however, that your needs may not fit the behavior of  the default surrogate. These are some possible reasons to implement a custom surrogate:

In general, all of these techniques could be implemented in the context of an outofproc server. However, if you are dealing with legacy DLLs, custom surrogates play a critical role in enabling remote, scaleable, and securable access.

To support user-implemented surrogates, it is possible to designate a custom surrogate for a given AppID. This is accomplished by indicating the file name of the custom surrogate as the value of the DllSurrogate AppID entry:

[HKCR\AppID\{ZZZ}]
  @="Shape Server"
  DllSurrogate=C:\shapeSurrogate.exe

Custom surrogates allow you to write your own WinMain and do more or less whatever you want, provided you properly register your process at runtime as a custom surrogate. As a custom surrogate, you have complete control over the security context and lifetime of the surrogate. Additionally, you are given a chance to wrap any interface pointers that are handed out with your own custom object implementations, which may or may not actually load the designated inproc object. In fact, if a CLSID maps to an AppID with a custom surrogate, the surrogate will start even if no InprocServer32 entry is present for the designated CLSID. Provided that the custom surrogate never tries to access the InprocServer version of the CLSID, this won't be a problem.

The primary requirement for custom surrogates is that they must register an object at startup that correctly implements the ISurrogate interface:

interface ISurrogate : IUnknown {
  HRESULT LoadDllServer([in] REFCLSID rclsid);
  HRESULT FreeSurrogate(void);
}

The LoadDllServer method is called by the SCM to force your process to register a class object for the designated CLSID. The FreeSurrogate is called by OLE32.DLL when the last outstanding connection to your process is terminated, informing you to terminate your process. Your LoadDllServer implementation needs to register a class object with COM to allow the SCM to reach into your process and make class-specific requests.

When implementing a surrogate, you do not want to load and register the inproc class object directly. Instead, the surrogate must create a new surrogate-defined class object wrapper that exposes IUnknown, IClassFactory, and (optionally) IMarshal. This wrapper class must be registered with COM using CoRegisterClassObject:

STDMETHODIMP 
CSurrogate::LoadDllServer(REFCLSID rclsid) {
  CWrapper *pcf = new CWrapper(rclsid);
  if (!pcf) return E_OUTOFMEMORY;
// register with COM using REGCLS_SURROGATE
  HRESULT hr = CoRegisterClassObject(rclsid,            
              (IClassFactory*)pcf, CLSCTX_LOCAL_SERVER,
               REGCLS_SURROGATE, &pcf->m_dwReg);
// keep track of class object to revoke later on
  if (SUCCEEDED(hr))
    m_rgClassObjects.push_back(pcf);
  else
    pcf->Release();
  return hr;
}

By requiring the surrogate to register a wrapper instead of the actual inproc class object, COM allows the surrogate to intercept the initial activation or marshaling request from the client's CoCreateInstance/CoGetClassObject calls. The surrogate is free to either forward the requests directly to the inproc class object or return a custom object that can perform any number of miracles, one of which might include translating the client's wishes into real method calls on an inproc object.

As shown in Figure 6, the client's CoCreateInstance request is forwarded directly to your wrapper's CreateInstance implementation. The expected behavior of a surrogate's wrapper is to use the real inproc class object to implement the method:

STDMETHODIMP 
CWrapper::CreateInstance(IUnknown *pUnkOuter, 
                         REFIID riid,void **ppv){
// forward the call to the "real" inproc factory 
    return CoCreateInstance(m_clsid,pUnkOuter,
                            CLSCTX_INPROC_SERVER,
riid,ppv); }

Figure 6 Custom Surrogate Architecture

If you intend to return a custom wrapper object instead, this is where you do it:

STDMETHODIMP 
CWrapper::CreateInstance(IUnknown *pUnkOuter, 
                         REFIID riid,void **ppv){
// create a new instance wrapper that might
// (or might not) create the real inproc object
    CInstanceWrap *p = new CInstanceWrap(m_clsid);
    p->AddRef();
    HRESULT hr = p->QueryInterface(riid, ppv);
    p->Release();
    return hr;
}

There are relatively few limits on what you can do in this function. Note that if you end up returning a proxy to an outofproc object, your surrogate may not remain running after you return from CreateInstance because your surrogate will not have any outstanding references (the outofproc server will instead). The lack of outstanding references causes COM to call your FreeSurrogate method to shut down your process.

When clients call CoGetClassObject, the SCM reaches into your process and marshals the class object you registered in the CoRegisterClassObject call. Your class object wrapper must implement IMarshal so that your class object wrapper will generically forward the CoGetClassObject request to the inproc class object for arbitrary interfaces. You will then receive the marshaling request via your wrapper's MarshalInterface method. To simply forward the request to the real inproc implementation, the code shown in Figure 7 is sufficient.

Figure 7 Surrogate MarshalInterface

STDMETHODIMP 
CWrapper::MarshalInterface(IStream *pStm, REFIID riid, void *pv, 
                           DWORD dwDstCtx, void *pvDstCtx, DWORD flags)
{
  IUnknown *pUnk = 0;
  HRESULT hr = CoGetClassObject(m_clsid, CLSCTX_INPROC_SERVER, 0, IID_IUnknown, 
                                (void**)&pUnk);
  if (SUCCEEDED(hr))
  {
    hr = CoMarshalInterface(pStm,riid,pUnk, dwDstCtx,pvDstCtx,flags);
    pUnk->Release();
  }
  return hr;                                         
}

If you are implementing a custom wrapper around the DLL's objects, you may want to simply forgo any activation interfaces beyond IClassFactory unless you have a priori knowledge of them and can implement them manually on your class object wrapper. To support wrapping arbitrary interfaces on a class object requires not only an in-depth understanding of type information or MIDL format strings, it also requires that you understand the semantics of the methods, which is not possible in the general case.

To allow COM to call your LoadDLLServer and FreeSurrogate methods, your surrogate process must register its implementation of the ISurrogate interface by calling CoRegisterSurrogate at startup:

HRESULT CoRegisterSurrogate(ISurrogate *ps);

CoRegisterSurrogate must be called after calling CoInitializeEx and (optionally) CoInitializeSecurity. Failure to call CoInitializeSecurity will cause your surrogate to run using the default access permissions for your machine. If you want to get your surrogate's AppID-specific settings from the registry instead, use the new EOAC_APPID capabilities flag, passing your AppID as a GUID to CoInitializeSecurity:

hr = CoInitializeSecurity((void*)&APPID_Custom, 0, 0,
                          0, 0, 0, 0, EOAC_APPID, 0);

Beyond the call to CoRegisterSurrogate, your surrogate process is basically another COM server. You can spawn additional apartments or threads, change process priority, make outbound COM method calls, and so on.

Figure 8 shows a complete implementation of a generic custom surrogate that terminates itself after 15 seconds of inactivity. This surrogate does not attempt to wrap the pointers to the inproc objects, but instead simply provides direct access using the techniques discussed in this article.

Figure 8 TerminateOnIdle Custom Surrogate

////////////////////////////////////////////////////////
//
// TerminateOnIdle.cpp - 1997, Don Box 
//
// A custom surrogate that terminates after 15 seconds of
// no activity. 
//
// Note: Be sure to configure your InprocServer to use this
//       process as a custom surrogate. Also, make sure that 
//       the AppID used in WinMain matches the one you wish to use.
//


#define _WIN32_WINNT  0x0402 
#include <windows.h>
#include <vector>
#include <assert.h>

#ifndef __ISurrogate_INTERFACE_DEFINED__
#error "You need SP2-compliant SDK headers to compile this file."
#endif        

// NOTE! The SP2 SDK patch did not contain an updated UUID.LIB file
// Once this library is patched, this definition should be removed.
const IID IID_ISurrogate = { 0x00000022, 0x0000, 0x0000,
                             {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}};

class CClassObjectWrapper : public IClassFactory,
                            public IMarshal
{
    LONG m_cRef;
    CLSID m_clsid;
    DWORD m_dwReg;
    friend class CSurrogate;
public:
    CClassObjectWrapper(REFCLSID rclsid);
    virtual ~CClassObjectWrapper(void);
    
// IUnknown methods
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
    STDMETHODIMP_(ULONG) AddRef(void);
    STDMETHODIMP_(ULONG) Release(void);
    
// IClassFactory methods
    STDMETHODIMP CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppv);
    STDMETHODIMP LockServer(BOOL fLock);
    
// IMarshal methods
    STDMETHODIMP GetUnmarshalClass(REFIID riid, void *pv, DWORD dwDstCtx,
                                   void *pvDstCtx, DWORD flags, CLSID *pCid);
    STDMETHODIMP GetMarshalSizeMax(REFIID riid, void *pv, DWORD dwDstCtx,
                                   void *pvDstCtx, DWORD flags, DWORD *pSize);
    STDMETHODIMP MarshalInterface(IStream *pStm, REFIID riid, void *pv,
                                  DWORD dwDstCtx, void *pvDstCtx, DWORD flags);
    STDMETHODIMP UnmarshalInterface(IStream *pStm, REFIID riid, void **ppv);
    STDMETHODIMP ReleaseMarshalData(IStream *pStm);
    STDMETHODIMP DisconnectObject(DWORD dwReserved);
};
        
CClassObjectWrapper::CClassObjectWrapper(REFCLSID rclsid)
:   m_cRef(0)
{
    m_clsid = rclsid;
}

CClassObjectWrapper::~CClassObjectWrapper(void) { }

// IUnknown methods
STDMETHODIMP 
CClassObjectWrapper::QueryInterface(REFIID riid, void **ppv)
{
    if (riid == IID_IUnknown || riid == IID_IClassFactory)
        *ppv = (IClassFactory*)this;
    else if (riid == IID_IMarshal)
        *ppv = (IMarshal*)this;
    else 
        return (*ppv = 0), E_NOINTERFACE;
    ((IUnknown*)*ppv)->AddRef();
    return S_OK;
}

STDMETHODIMP_(ULONG) 
CClassObjectWrapper::AddRef(void)
{
    return InterlockedIncrement(&m_cRef);
}

STDMETHODIMP_(ULONG) 
CClassObjectWrapper::Release(void)
{
    ULONG result = InterlockedDecrement(&m_cRef);
    if (result == 0)
        delete this;
    return result;
}

// IClassFactory methods
STDMETHODIMP 
CClassObjectWrapper::CreateInstance(IUnknown *pUnkOuter,REFIID riid,void **ppv)
{
// forward the call to the "real" inproc factory 
    return CoCreateInstance(m_clsid,pUnkOuter,CLSCTX_INPROC_SERVER,riid,ppv);
}


STDMETHODIMP 
CClassObjectWrapper::LockServer(BOOL fLock)
{
    return S_OK;
}


// IMarshal methods
STDMETHODIMP 
CClassObjectWrapper::GetUnmarshalClass(REFIID riid, void *pv, DWORD dwDstCtx,
                                       void *pvDstCtx, DWORD flags, CLSID *pCid)
{
    *pCid = CLSID_StdMarshal; // we hardwire the standard marshaler here
    return S_OK;
}

STDMETHODIMP 
CClassObjectWrapper::GetMarshalSizeMax(REFIID riid, void *pv, DWORD dwDstCtx,
                                       void *pvDstCtx, DWORD flags, DWORD *pSize)
{
// because we use the standard marshaler, we will never be asked to size a ptr.
    return E_UNEXPECTED;                                         
}

STDMETHODIMP 
CClassObjectWrapper::MarshalInterface(IStream *pStm, REFIID riid, void *pv,
                                      DWORD dwDstCtx, void *pvDstCtx, DWORD flags)
{
// this is called when the SCM needs a pointer to the inproc class object
// we need to return the inproc pointer marshaled into pStm
    IUnknown *pUnk = 0;
    HRESULT hr = CoGetClassObject(m_clsid, CLSCTX_INPROC_SERVER, 0, IID_IUnknown, 
                                  (void**)&pUnk);
    if (SUCCEEDED(hr))
    {
        hr = CoMarshalInterface(pStm,riid,pUnk,dwDstCtx,pvDstCtx,flags);
        pUnk->Release();
    }
    return hr;                                         
}

STDMETHODIMP 
CClassObjectWrapper::UnmarshalInterface(IStream *pStm,REFIID riid,void **ppv)
{
    return E_UNEXPECTED;
}

STDMETHODIMP 
CClassObjectWrapper::ReleaseMarshalData(IStream *pStm)
{
    return CoReleaseMarshalData(pStm);                                         
}

STDMETHODIMP 
CClassObjectWrapper::DisconnectObject(DWORD dwReserved)
{
    return S_OK;
}

class CSurrogate : public ISurrogate
{
// we need to track all of our class objects and CoRevoke them at teardown time
    std::vector<CClassObjectWrapper *> m_rgClassObjects;
public:
    CSurrogate(void);
    virtual ~CSurrogate(void);
    
    // IUnknown methods
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
    STDMETHODIMP_(ULONG) AddRef(void);
    STDMETHODIMP_(ULONG) Release(void);
    
    // ISurrogate methods
    STDMETHODIMP LoadDllServer(REFCLSID rclsid);
    STDMETHODIMP FreeSurrogate();
};

CSurrogate::CSurrogate(void){}
CSurrogate::~CSurrogate(void){}

// IUnknown methods
STDMETHODIMP 
CSurrogate::QueryInterface(REFIID riid, void **ppv)
{
    if (riid == IID_IUnknown || riid == IID_ISurrogate)
        *ppv = (ISurrogate*)this;
    else 
        return (*ppv = 0), E_NOINTERFACE;
    ((IUnknown*)*ppv)->AddRef();
    return S_OK;
}

STDMETHODIMP_(ULONG) 
CSurrogate::AddRef(void)
{
    return 2;
}

STDMETHODIMP_(ULONG) 
CSurrogate::Release(void)
{
    return 1;
}

// ISurrogate methods
STDMETHODIMP 
CSurrogate::LoadDllServer(REFCLSID rclsid)
{
// create a wrapper class object
    CClassObjectWrapper *pcf = new CClassObjectWrapper(rclsid);
    if (!pcf)
        return E_OUTOFMEMORY;
// register the wrapper with COM using REGCLS_SURROGATE
    pcf->AddRef();
    HRESULT hr = CoRegisterClassObject(rclsid, (IClassFactory*)pcf,
                                       CLSCTX_LOCAL_SERVER, REGCLS_SURROGATE,
                                       &pcf->m_dwReg);
// keep track of class object to revoke later on
    if (SUCCEEDED(hr))
        m_rgClassObjects.push_back(pcf);
    else
        pcf->Release();
    return hr;
}

STDMETHODIMP 
CSurrogate::FreeSurrogate()
{
// tear down all connections with COM
    for (int i = 0; i < m_rgClassObjects.size(); i++)
    {
        CoRevokeClassObject(m_rgClassObjects[i]->m_dwReg);
        m_rgClassObjects[i]->Release();
    }
// release pointers to class object wrappers
    m_rgClassObjects.erase(m_rgClassObjects.begin(), m_rgClassObjects.end());
// terminate main thread
    PostQuitMessage(0);
    return S_OK;
}
        
        
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR szCmdParam, int)
{
// join a new single-threaded apartment
    HRESULT hr = CoInitializeEx(0, COINIT_APARTMENTTHREADED);
    assert(hr == S_OK);

// pick up access permission settings from our AppID.
    const GUID APPID_Custom = { 0x5ae3AAAA, 0x79A0, 0x11D0,
                             { 0x8B, 0xE7, 0x00, 0x80, 0xC7, 0x8C, 0x6C, 0x8C }};
    hr = CoInitializeSecurity((void*)&APPID_Custom, 0, 0, 0,
                               0, 0, 0, EOAC_APPID, 0);
    assert(hr == S_OK);

// declare and register our implementation of ISurrogate
    CSurrogate sur;
    hr = CoRegisterSurrogate(&sur);
    assert(hr == S_OK);

// explicitly load/register the initial CLSID
// on input, we will receive the CLSID of the class that caused our surrogate 
// to load as a command line argument. We need to explicitly load and register
// this initial class ourselves.

    OLECHAR wszGUID[39];
    mbstowcs(wszGUID, szCmdParam, 39);
    wszGUID[38] = 0;  CLSID clsidInitialClass;
    hr = CLSIDFromString(wszGUID, &clsidInitialClass);
    assert(hr == S_OK);

    hr = sur.LoadDllServer(clsidInitialClass);
    assert(hr == S_OK);

// run a message pump that terminates after 15 seconds of inactivity.
// this is the "custom" behavior of our surrogate
    DWORD dwIdleTime = 15000; 
    MSG msg;
    HANDLE heventDone = CreateEvent(0, FALSE, FALSE, 0);
    while (MsgWaitForMultipleObjects(1, &heventDone, FALSE, dwIdleTime, 
                                     QS_ALLINPUT) == WAIT_OBJECT_0 + 1)
        while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
            if (msg.message != WM_QUIT)
                DispatchMessage(&msg);
            else
                SetEvent(heventDone);

// turn off COM, which will implicitly call our FreeSurrogate if
// it has not happened already
    CoUninitialize();
    return 0;
}

So, given all of the issues discussed here, how do you decide which type of server to provide? Here are some situations that may motivate you to implement an outofproc server:

In the following situations, you might want to implement an inproc server:

The availability of surrogates blurs the distinction somewhat. If you implement an inproc server and designate either a custom or system-provided surrogate, you are really delegating the choice to the client. Since many clients (including Java and Visual Basic) use the CLSCTX_ALL flag when calling CoCreateInstance, most clients on the local host will get the inproc version, and remote-host clients will get the server loaded in a surrogate. This means that local-host clients get the robustness semantics of an inproc server, while remote-host clients get the superior robustness semantics of an outofproc server. Because of this, surrogates are really best suited for allowing legacy inproc servers to be accessed across host boundaries or for sandboxing untrusted DLLs.

If you are building distributed objects for arbitrary clients, you are probably better off implementing outofproc servers, or perhaps hosting your inproc server in MTS. Fortunately, provided you first define all interfaces in IDL to ensure that all of your methods are remote-capable, it is fairly painless to port from one type of server to another because most of the changes happen in the registration and server-lifetime portions of the code.

Have a question about programming with ActiveX or COM? Send your questions via email to Don Box at dbox@develop.com or http://www.develop.com/dbox

This article is reproduced from Microsoft Systems Journal. Copyright © 1997 by Miller Freeman, Inc. All rights are reserved. No part of this article may be reproduced in any fashion (except in brief quotations used in critical articles and reviews) without the prior consent of Miller Freeman.

To contact Miller Freeman regarding subscription information, call (800) 666-1084 in the U.S. and Canada, or (303) 678-0439 in all other countries. For other inquiries, call (415) 905-2200.