The default DLL surrogate provided by the system is quite flexible; it even allows multiple in-process components to be loaded into a single surrogate process. Generally speaking, the default surrogate is more than adequate for well-behaved in-process components. However, misbehaving components might not run properly in the default surrogate. Any code executed by the component under the assumption that it is running in the process space of its client will fail. Such assumptions include access to global variables and callback functions implemented using function pointers. Running this type of component in a surrogate requires a special surrogate that is trained to deal with the unique needs of that component. In other words, you must write a custom surrogate process. Here are some reasons you might decide to do so:
If the features provided by the default DLL surrogate don't meet your needs, you can build your own custom surrogate by implementing the ISurrogate interface in a local component. The definition of the ISurrogate interface is shown below in Interface Definition Language (IDL) notation:
interface ISurrogate : IUnknown { // Load the specified in-process component. HRESULT LoadDllServer([in] REFCLSID clsid); // Exiting... HRESULT FreeSurrogate(); } |
To further illustrate the concept of surrogates, let's look at some code fragments from a simple DLL surrogate named DllNanny. Surrogate processes are launched automatically by the SCM when a client instantiates an in-process object registered for execution within the surrogate. The SCM provides the surrogate with the CLSID of the in-process object as a command-line argument. On start-up, a surrogate process registers its threading model by calling CoInitializeEx. Next, it calls CoRegisterSurrogate to provide the system with a pointer to its ISurrogate interface.
In the following code fragment from dllnanny.cpp, the surrogate follows this start-up procedure with a call to the ISurrogate::LoadDllServer method to indicate the presence of a client activation request. The first (and only) command-line argument provided to the surrogate process on start-up specifies the CLSID for the desired in-process object. This CLSID is converted from a string to a binary CLSID using the CLSIDFromString function, and then it is passed as a parameter to the LoadDllServer method.
void main(int argc, char** argv) { // Initialize COM and create the multithreaded // apartment (MTA). CoInitializeEx(NULL, COINIT_MULTITHREADED); // Instantiate and register the surrogate. CSurrogate surrogate; CoRegisterSurrogate(&surrogate); // Convert the ASCII string-form CLSID in // argv[1] to Unicode. OLECHAR wszCLSID[39]; mbstowcs(wszCLSID, argv[1], 39); // Convert the Unicode CLSID to a binary CLSID. CLSID clsid; CLSIDFromString(wszCLSID, &clsid); surrogate.LoadDllServer(clsid); // Code omitted... |
Upon receiving the client request via the ISurrogate::LoadDllServer method, the surrogate instantiates and registers a class object. This class object is not the actual class object implemented by the in-process component. It is a generic class object implemented by the surrogate process that must support the IUnknown and IClassFactory interfaces.
Next, the implementation of the ISurrogate::LoadDllServer method should call the CoRegisterClassObject function to register the surrogate class object for the specified CLSID. All surrogate class objects should be registered using the REGCLS_SURROGATE flag when the CoRegisterClassObject function is called; surrogate processes should not use REGCLS_SINGLEUSE and REGCLS_MULTIPLEUSE. Here is DllNanny's implementation of the ISurrogate::LoadDllServer method:
HRESULT CSurrogate::LoadDllServer(REFCLSID rclsid) { // Instantiate the surrogate class factory object. m_pcf = new CGenericFactory(rclsid); // Register the surrogate class factory object; // note the use of the REGCLS_SURROGATE flag. return CoRegisterClassObject(rclsid, (IClassFactory*)m_pcf, CLSCTX_LOCAL_SERVER, REGCLS_SURROGATE, &m_pcf->m_dwRegister); } |
When the client calls CoCreateInstance to instantiate the desired in-process object as a local server, the surrogate's IClassFactory::CreateInstance method is called. The surrogate class factory must use the real class factory to create an instance of the object. Calling CoCreateInstance on the in-process object itself does the trick. Thus, DllNanny's implementation of the IClassFactory::CreateInstance method looks like this:
HRESULT CGenericFactory::CreateInstance(IUnknown* pUnknownOuter, REFIID riid, void** ppv) { return CoCreateInstance(m_clsid, pUnknownOuter, CLSCTX_INPROC_SERVER, riid, ppv); } |
During the lifetime of the surrogate process, it is important that CoFreeUnusedLibraries be called periodically. This function, normally called repeatedly by a low-priority thread, unloads any DLLs that are no longer in use. Later, after all clients have exited and all the in-process components running in the surrogate process have terminated, the system calls the ISurrogate::FreeSurrogate method. At that point, the surrogate should revoke all registered class factories using the CoRevokeClassObject function and then cause the surrogate process to exit, as shown here:
HRESULT CSurrogate::FreeSurrogate() { // Revoke the surrogate class factory. HRESULT hr = CoRevokeClassObject(m_pcf->m_dwRegister); // Set an event that causes the surrogate to exit. SetEvent(g_hEvent); return hr; } |
The SetEvent function signals a global event object created in the surrogate's main function, where the WaitForSingleObject function has been called, as shown in the following code fragment. This call causes the surrogate to wait until it is time to exit. Once the event is signaled, WaitForSingleObject returns and the surrogate terminates cleanly.
// The main function... // Create the event. g_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); // Wait for the event to be signaled // by ISurrogate::FreeSurrogate. WaitForSingleObject(g_hEvent, INFINITE); CloseHandle(g_hEvent); CoUninitialize(); } |