Écriture d’un substitut personnalisé

Bien que le substitut fourni par le système soit plus que suffisant pour la plupart des situations, il existe certains cas où l’écriture d’un substitut personnalisé peut être utile. En voici quelques exemples :

  • Un substitut personnalisé peut fournir des optimisations ou des sémantiques qui ne sont pas présentes dans le substitut système.
  • Si une DLL in-process contient du code qui dépend du fait d’être dans le même processus que le client, le serveur DLL ne fonctionnera pas correctement s’il s’exécute dans le substitut système. Un substitut personnalisé peut être adapté à une DLL spécifique pour traiter ce problème.
  • Le substitut système prend en charge un modèle de thread mixte afin qu’il puisse charger des DLL de modèle libre et d’appartement. Un substitut personnalisé peut être adapté pour charger uniquement des DLL d’appartement pour des raisons d’efficacité ou pour accepter un argument de ligne de commande pour le type de DLL qu’il est autorisé à charger.
  • Un substitut personnalisé peut prendre des paramètres de ligne de commande supplémentaires que le substitut système n’a pas.
  • Le substitut système appelle CoInitializeSecurity et lui indique d’utiliser tous les paramètres de sécurité existants trouvés sous la clé AppID dans le Registre. Un substitut personnalisé peut utiliser un autre contexte de sécurité.
  • Les interfaces qui ne sont pas accessibles à distance (comme celles des OCX récents) ne fonctionnent pas avec le substitut système. Un substitut personnalisé peut encapsuler les interfaces de la DLL avec sa propre implémentation et utiliser des DLL proxy/stub avec une définition IDL accessible à distance qui permettrait à l’interface d’être distante.

Le thread de substitution main doit généralement effectuer les étapes d’installation suivantes :

  1. Appelez CoInitializeEx pour initialiser le thread et définir le modèle de thread.
  2. Si vous souhaitez que les serveurs DLL qui doivent s’exécuter sur le serveur puissent utiliser les paramètres de sécurité de la clé de Registre AppID , appelez CoInitializeSecurity avec la fonctionnalité EOAC_APPID. Sinon, les paramètres de sécurité hérités seront utilisés.
  3. Appelez CoRegisterSurrogate pour inscrire l’interface de substitution auprès de COM.
  4. Appelez ISurrogate::LoadDllServer pour le CLSID demandé.
  5. Placez main thread dans une boucle pour appeler Régulièrement CoFreeUnusedLibraries.
  6. Lorsque COM appelle ISurrogate::FreeSurrogate, révoquez toutes les fabriques de classes et quittez.

Un processus de substitution doit implémenter l’interface ISurrogate . Cette interface doit être inscrite lorsqu’un nouveau substitut est démarré et après avoir appelé CoInitializeEx. Comme indiqué dans les étapes précédentes, l’interface ISurrogate a deux méthodes que COM appelle : LoadDllServer, pour charger dynamiquement de nouveaux serveurs DLL dans des substituts existants ; et FreeSurrogate, pour libérer le substitut.

L’implémentation de LoadDllServer, que COM appelle avec une demande de chargement, doit d’abord créer un objet de fabrique de classe qui prend en charge IUnknown, IClassFactory et IMarshal, puis appeler CoRegisterClassObject pour inscrire l’objet en tant que fabrique de classe pour le CLSID demandé.

La fabrique de classes inscrite par le processus de substitution n’est pas la fabrique de classes réelle implémentée par le serveur DLL, mais une fabrique de classes générique implémentée par le processus de substitution qui prend en charge IClassFactory et IMarshal. Étant donné qu’il s’agit de la fabrique de classes du substitut, plutôt que celle du serveur DLL en cours d’inscription, la fabrique de classes du substitut doit utiliser la fabrique de classes réelle pour créer une instance de l’objet pour le CLSID inscrit. IClassFactory::CreateInstance du substitut doit ressembler à l’exemple suivant :

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

La fabrique de classes du substitut doit également prendre en charge IMarshal , car un appel à CoGetClassObject peut demander n’importe quelle interface à partir de la fabrique de classes inscrite, pas seulement IClassFactory. En outre, étant donné que la fabrique de classe générique prend uniquement en charge IUnknown et IClassFactory, les demandes pour d’autres interfaces doivent être dirigées vers l’objet réel. Par conséquent, il doit y avoir une méthode MarshalInterface qui doit être similaire à ce qui suit :

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;
 

Le substitut qui héberge un serveur DLL doit publier le ou les objets de classe du serveur DLL avec un appel à CoRegisterClassObject. Toutes les fabriques de classes pour les substituts DLL doivent être inscrites en tant que REGCLS_SURROGATE. REGCLS_SINGLUSE et REGCLS_MULTIPLEUSE ne doivent pas être utilisés pour les serveurs DLL chargés dans des substituts.

Le respect de ces instructions pour la création d’un processus de substitution lorsqu’il est nécessaire de le faire doit garantir un comportement approprié.

Substitutions DLL