Writing a Custom Surrogate

While the system-supplied surrogate will be more than adequate for most situations, there are some cases where writing a custom surrogate could be worthwhile. Following are some examples:

  • A custom surrogate could provide some optimizations or semantics not present in the system surrogate.
  • If an in-process DLL contains code that depends on being in the same process as the client, the DLL server will not function correctly if it is running in the system surrogate. A custom surrogate could be tailored to a specific DLL to deal with this.
  • The system surrogate supports a mixed-threading model so that it can load both free and apartment model DLLs. A custom surrogate might be tailored to load only apartment DLLs for reasons of efficiency or to accept a command-line argument for the type of DLL it is allowed to load.
  • A custom surrogate could take extra command-line parameters that the system surrogate does not.
  • The system surrogate calls CoInitializeSecurity and tells it to use any existing security settings found under the AppID key in the registry. A custom surrogate could use another security context.
  • Interfaces that aren't remotable (such as those for recent OCXs) will not work with the system surrogate. A custom surrogate could wrap the DLL's interfaces with its own implementation and use proxy/stub DLLs with a remotable IDL definition that would allow the interface to be remoted.

The main surrogate thread should typically perform the following setup steps:

  1. Call CoInitializeEx to initialize the thread and set the threading model.
  2. If you want the DLL servers that are to run in the server to be able to use the security settings in the AppID registry key, call CoInitializeSecurity with the EOAC_APPID capability. Otherwise, legacy security settings will be used.
  3. Call CoRegisterSurrogate to register the surrogate interface to COM.
  4. Call ISurrogate::LoadDllServer for the requested CLSID.
  5. Put main thread in a loop to call CoFreeUnusedLibraries periodically.
  6. When COM calls ISurrogate::FreeSurrogate, revoke all class factories and exit.

A surrogate process must implement the ISurrogate interface. This interface should be registered when a new surrogate is started and after calling CoInitializeEx. As indicated in the preceding steps, the ISurrogate interface has two methods that COM calls: LoadDllServer, to dynamically load new DLL servers into existing surrogates; and FreeSurrogate, to free the surrogate.

The implementation of LoadDllServer, which COM calls with a load request, must first create a class factory object that supports IUnknown, IClassFactory, and IMarshal, and then call CoRegisterClassObject to register the object as the class factory for the requested CLSID.

The class factory registered by the surrogate process is not the actual class factory implemented by the DLL server but is a generic class factory implemented by the surrogate process that supports IClassFactory and IMarshal. Because it is the surrogate's class factory, rather than that of the DLL server that is being registered, the surrogate's class factory will need to use the real class factory to create an instance of the object for the registered CLSID. The surrogate's IClassFactory::CreateInstance should look something like the following example:

STDMETHODIMP CSurrogateFactory::CreateInstance(
  IUnknown* pUnkOuter, 
  REFIID iid, 
  void** ppv)
{
    void* pcf;
    HRESULT hr;
 
    hr = CoGetClassObject(clsid, CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory, &pcf);
    if ( FAILED(hr) )
        return hr;
    hr = ((IClassFactory*)pcf)->CreateInstance(pUnkOuter, iid, ppv);
    ((IClassFactory*)pcf)->Release();
    return hr;
}
 

The surrogate's class factory must also support IMarshal because a call to CoGetClassObject can request any interface from the registered class factory, not just IClassFactory. Further, since the generic class factory supports only IUnknown and IClassFactory, requests for other interfaces must be directed to the real object. Thus, there should be a MarshalInterface method which should be similar to the following:

STDMETHODIMP CSurrogateFactory::MarshalInterface(
  IStream *pStm,  
  REFIID riid, void *pv, 
  WORD dwDestContext, 
  void *pvDestContext, 
  DWORD mshlflags )
{   
    void * pCF = NULL;
    HRESULT hr;
 
    hr = CoGetClassObject(clsid, CLSCTX_INPROC_SERVER, NULL, riid, &pCF);
    if ( FAILED(hr) )
        return hr;   
    hr = CoMarshalInterface(pStm, riid, (IUnknown*)pCF, dwDestContext, pvDestContext,  mshlflags);
    ((IUnknown*)pCF)->Release();
    return S_OK;
 

The surrogate that houses a DLL server must publish the DLL server's class object(s) with a call to CoRegisterClassObject. All class factories for DLL surrogates should be registered as REGCLS_SURROGATE. REGCLS_SINGLUSE and REGCLS_MULTIPLEUSE should not be used for DLL servers loaded into surrogates.

Following these guidelines for creating a surrogate process when it is necessary to do so should ensure proper behavior.

DLL Surrogates