Escrevendo um substituto personalizado

Embora o substituto fornecido pelo sistema seja mais do que adequado para a maioria das situações, há alguns casos em que escrever um substituto personalizado pode valer a pena. Estes são alguns exemplos:

  • Um substituto personalizado pode fornecer algumas otimizações ou semânticas não presentes no substituto do sistema.
  • Se uma DLL em processo contiver código que depende de estar no mesmo processo que o cliente, o servidor DLL não funcionará corretamente se estiver sendo executado no substituto do sistema. Um substituto personalizado pode ser adaptado a uma DLL específica para lidar com isso.
  • O substituto do sistema suporta um modelo de threading misto para que ele possa carregar DLLs de modelo livre e de apartamento. Um substituto personalizado pode ser adaptado para carregar apenas DLLs de apartamento por razões de eficiência ou para aceitar um argumento de linha de comando para o tipo de DLL que ele tem permissão para carregar.
  • Um substituto personalizado pode usar parâmetros de linha de comando extras que o substituto do sistema não tem.
  • O substituto do sistema chama CoInitializeSecurity e diz a ele para usar quaisquer configurações de segurança existentes encontradas na chave AppID no registro. Um substituto personalizado pode usar outro contexto de segurança.
  • Interfaces que não são remotas (como as de OCXs recentes) não funcionarão com o substituto do sistema. Um substituto personalizado poderia encapsular as interfaces da DLL com sua própria implementação e usar DLLs proxy/stub com uma definição IDL remotamente que permitiria que a interface fosse remota.

O thread substituto principal normalmente deve executar as seguintes etapas de configuração:

  1. Chame CoInitializeEx para inicializar o thread e definir o modelo de threading.
  2. Se você quiser que os servidores DLL que serão executados no servidor possam usar as configurações de segurança na chave do Registro AppID, chame CoInitializeSecurity com o recurso EOAC_APPID. Caso contrário, as configurações de segurança herdadas serão usadas.
  3. Chame CoRegisterSurrogate para registrar a interface substituta para COM.
  4. Chame ISurrogate::LoadDllServer para o CLSID solicitado.
  5. Coloque o thread principal em um loop para chamar CoFreeUnusedLibraries periodicamente.
  6. Quando COM chama ISurrogate::FreeSurrogate, revogue todas as fábricas de classe e saia.

Um processo substituto deve implementar a interface ISurrogate. Essa interface deve ser registrada quando um novo substituto é iniciado e depois de chamar CoInitializeEx. Conforme indicado nas etapas anteriores, a interface ISurrogate tem dois métodos que o COM chama: LoadDllServer, para carregar dinamicamente novos servidores DLL em substitutos existentes, e FreeSurrogate, para liberar o substituto.

A implementação de LoadDllServer, que COM chama com uma solicitação de carga, deve primeiro criar um objeto de fábrica de classe que ofereça suporte a IUnknown, IClassFactory e IMarshal e, em seguida, chamar CoRegisterClassObject para registrar o objeto como a fábrica de classe para o CLSID solicitado.

A fábrica de classes registrada pelo processo substituto não é a fábrica de classe real implementada pelo servidor DLL, mas é uma fábrica de classe genérica implementada pelo processo substituto que suporta IClassFactory e IMarshal. Como é a fábrica de classe do substituto, em vez da do servidor DLL que está sendo registrado, a fábrica de classe do substituto precisará usar a fábrica de classe real para criar uma instância do objeto para o CLSID registrado. O substituto IClassFactory::CreateInstance deve se parecer com o exemplo a seguir:

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;
}
 

A fábrica de classes do substituto também deve oferecer suporte a IMarshal porque uma chamada para CoGetClassObject pode solicitar qualquer interface da fábrica de classes registrada, não apenas IClassFactory. Além disso, como a fábrica de classes genéricas suporta apenas IUnknown e IClassFactory, as solicitações para outras interfaces devem ser direcionadas para o objeto real. Assim, deve haver um método MarshalInterface que deve ser semelhante ao seguinte:

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;
 

O substituto que hospeda um servidor DLL deve publicar o(s) objeto(s) de classe do(s) servidor(es) DLL com uma chamada para CoRegisterClassObject. Todas as fábricas de classe para substitutos de DLL devem ser registradas como REGCLS_SURROGATE. REGCLS_SINGLUSE e REGCLS_MULTIPLEUSE não devem ser usados para servidores DLL carregados em substitutos.

Seguir essas diretrizes para criar um processo substituto quando for necessário fazê-lo deve garantir o comportamento adequado.

Substitutos da DLL