DCOM : Serveur in-proc (Dll) activé en local ou à distance


La conception et la distribution d'une application distribuée sous forme de composants est parfois compliquée. Cependant, le modèle COM permet en très peu de temps et sans changement majeur du code source, d'invoquer des composants sur la machine locale ou sur une machine distante. 
Prenons un exemple simple: le composant à réaliser possède une seule interface avec une seule méthode, GetComputerName. Cette méthode retourne le nom de la machine courante qui exécute cette méthode.

Créez un projet nommé Server0301 avec Visual C++ 6.0 et l'assistant de projet ATL COM AppWizard. 

Le server est de type DLL et le code du proxy/stub est séparé. 

 

 

Ajoutez un nouvel objet ATL de type simple objet nommé Info. 

 

 

 

 

Ajoutez la méthode GetComputerName à l'interface IInfo.

 

 

 


La méthode GetComputerName fait appel à la fonction ::GetComputerName du Win32 SDK et retourne le résultat dans un VARIANT. Le VARIANT étant le type générique du Visual Basic mais surtout le seul type reconnu par les langages de script.

STDMETHODIMP CInfo::GetComputerName(VARIANT *vName)
{
	// TODO: Add your implementation code here
	DWORD dw = 255;
	char szComputerName[255];
	::GetComputerName(szComputerName, &dw);
	Sleep(3000);
	CComVariant v(szComputerName);
	v.Detach(vName);
	return S_OK;
}
Pour être en mesure d'invoquer le composant de manière locale ou distante, modifiez le fichier RGS et ajoutez les lignes suivantes. AppId va permettre de visualiser le server dans l'outil de configuration DCOMCNFG afin de préciser le nom du server distant sur lequel va s'exécuter le composant. 
HKCR
{
	GetComp.GCInfo.1 = s 'GCInfo Class'
	{
		CLSID = s '{C4F1B2E4-DD84-11D4-8784-0008C7EA647B}'
	}
	GetComp.GCInfo = s 'GCInfo Class'
	{
		CLSID = s '{C4F1B2E4-DD84-11D4-8784-0008C7EA647B}'
		CurVer = s 'GetComp.GCInfo.1'
	}
	NoRemove CLSID
	{
		ForceRemove {C4F1B2E4-DD84-11D4-8784-0008C7EA647B} = s 'GCInfo Class'
		{
			val AppID = s '{AECA5120-E18E-11d4-8784-0008C7EA647B}'
			ProgID = s 'GetComp.GCInfo.1'
			VersionIndependentProgID = s 'GetComp.GCInfo'
			ForceRemove 'Programmable'
			InprocServer32 = s '%MODULE%'
			{
				val ThreadingModel = s 'Apartment'
			}
			'TypeLib' = s '{C4F1B2D7-DD84-11D4-8784-0008C7EA647B}'
		}
	}
	NoRemove AppID
	{
		{AECA5120-E18E-11d4-8784-0008C7EA647B} = s 'GetComp.GCInfo'
		{
		}
		'GetComp.DLL'
		{
			val AppID = s '{AECA5120-E18E-11d4-8784-0008C7EA647B}'
		}
	}
}

La compilation du projet donne naissance au fichier Server0301.dll ; maintenant, procedez à la création du proxy/stub:
nmake -f Server0301ps.mk
Vouz avez donc 2 fichiers: Server0301.dll et Server0301ps.dll.
Enregistrez ces dll dans la base de registres:
regsvr32 Server0301.dll
regsvr32 Server0301ps.dll

Procédons à la création de l'application cliente. Créez un projet console nommé Client0301 et insérez le code suivant:
// Client0301.cpp : Defines the entry point for the console application.
//

#define STRICT
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0400
#endif
#define _ATL_APARTMENT_THREADED

#include 
#define DBINITCONSTANTS
#define INITGUID
  
#include 
#include 
#include 
#include 
#include "..\\Server0301\\Server0301.h"
#include "..\\Server0301\\Server0301_i.c"

int main(int argc, char* argv[])
{
	USES_CONVERSION;

	if( argc != 2 )
	{
		printf("Client0301 [option]\n");
		printf("-I : inproc\n");
		printf("-R : remote\n");
		return 0;
	}

	char * pArg = argv[1];
	char cOption = ' ';
	if( !strcmpi(pArg, "-I") )
	{
		cOption  = 'i';
	}
	if( !strcmpi(pArg, "-R") )
	{
		cOption  = 'r';
	}

    CoInitialize(NULL);
  
	IInfo * ptr;
	HRESULT hr;

	if( cOption == 'i' )
		hr = CoCreateInstance(CLSID_Info, NULL, CLSCTX_INPROC_SERVER, IID_IInfo, (void **) &ptr);
	else if( cOption == 'r' )
		hr = CoCreateInstance(CLSID_Info, NULL, CLSCTX_REMOTE_SERVER, IID_IInfo, (void **) &ptr);

	if(FAILED(hr))
	{
		printf("Failed to get IInfo interface. Hr=0x%08x, GetLastError=%ld\n", hr, GetLastError());
		return 0;
	}

	CComVariant vOut;
	ptr->GetComputerName(&vOut);

	LPSTR lpszOut = W2A(vOut.bstrVal);
	printf("Computer Name = %s\n", lpszOut);

	ptr->Release();
	ptr = NULL;

	return 0;
}

Le client propose plusieurs options pour activer le composant. Chaque option est un contexte d'exécution pour CoCreateInstance.
-I pour CLSCTX_INPROC_SERVER : Server0301.dll est directement chargée dans le processus client comme une Dll standard.
-R pour CLS_REMOTE_SERVER : Server0301.dll est chargée sur une machine distante.
Pour pouvoir tirer parti de la dernière option, on va utiliser l'outil de configuration DCOMCNFG:

 

Choisissez l'entrée Server0301.Info, appuyez sur le bouton Propriétés puis choisissez l'onglet Emplacement. Sélectionnnez la case à cocher et précisez le nom du serveur distant:

 

 

 

Sur le serveur distant, enregistrez les dll dans la base de registres:
regsvr32 Server0301.dll
regsvr32 Server0301ps.dll
Sur le serveur distant, ajouter l'entrée DllSurrogate avec la valeur vide sous la clé: HKCR\AppID\{C4F1B30B-DD84-11D4-8784-0008C7EA647B}. En activation à distance, la dll doit être chargée dans un processus d'ou la possibilité de renseigner cette valeur avec votre propre surrogate ou bien de laisser celui du système par défaut qu'est dllhost.exe.
Toujours sur le serveur distant, lancez DCOMCNFG et sélectionnez l'onglet Sécurité par défaut. Dans les permissions d'accès par défaut et dans les permissions d'exécution par défaut, ajoutez le compte du domaine sous lequel la machine client va exécuter l'application cliente.

 


Placez vous sur la machine cliente.
Lancez le test avec l'option -R. Le client active le composant sur la machine distante. Le protocol DCOM rentre en scène.

Remarque: Sur la machine locale, il est possible de voir le composant évoluer dans le surrogate dllhost.exe ; pour cela il faut ajouter DllSurrogate dans la base de registre du client sous HKCR\AppId\xxx et invoquer CoCreateInstance avec CLS_LOCAL_SERVER comme option de contexte d'exécution dans l'application cliente. Modifiez le code client:

// Client0301.cpp : Defines the entry point for the console application.
//

#define STRICT
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0400
#endif
#define _ATL_APARTMENT_THREADED

#include 
#define DBINITCONSTANTS
#define INITGUID
  
#include 
#include 
#include 
#include 
#include "..\\Server0301\\Server0301.h"
#include "..\\Server0301\\Server0301_i.c"

int main(int argc, char* argv[])
{
	USES_CONVERSION;

	if( argc != 2 )
	{
		printf("Client0301 [option]\n");
		printf("-I : inproc\n");
		printf("-L : local\n");
		printf("-R : remote\n");
		return 0;
	}

	char * pArg = argv[1];
	char cOption = ' ';
	if( !strcmpi(pArg, "-I") )
	{
		cOption  = 'i';
	}
	if( !strcmpi(pArg, "-L") )
	{
		cOption = 'l';
	}
	if( !strcmpi(pArg, "-R") )
	{
		cOption  = 'r';
	}

    CoInitialize(NULL);
  
	IInfo * ptr;
	HRESULT hr;

	if( cOption == 'i' )
		hr = CoCreateInstance(CLSID_Info, NULL, CLSCTX_INPROC_SERVER, IID_IInfo, (void **) &ptr);
	else if( cOption == 'l' )
		hr = CoCreateInstance(CLSID_Info, NULL, CLSCTX_LOCAL_SERVER, IID_IInfo, (void **) &ptr);
	else if( cOption == 'r' )
		hr = CoCreateInstance(CLSID_Info, NULL, CLSCTX_REMOTE_SERVER, IID_IInfo, (void **) &ptr);

	if(FAILED(hr))
	{
		printf("Failed to get IInfo interface. Hr=0x%08x, GetLastError=%ld\n", hr, GetLastError());
		return 0;
	}

	CComVariant vOut;
	ptr->GetComputerName(&vOut);

	LPSTR lpszOut = W2A(vOut.bstrVal);
	printf("Computer Name = %s\n", lpszOut);

	ptr->Release();
	ptr = NULL;

	return 0;
}
Le composant fait une attente de 3 secondes au sein de la méthode GetComputerName, ce qui laisse le temps d'observer que le système lance le surrogate. On le constante dans la listes des processus du Gestionnaires de tâches de Windows NT. Attention, le fait de préciser sous HKCR\AppId\xxx les valeurs RemoteServerName et DllSurrogate fait que l'outil DCOMCNFG ne propose plus l'onglet Emplacement ou est précisé le nom de la machine distante. Pour résoudre ce problème, il est possible d'utiliser en lieu et place de DCOMCNFG, l'outil OleView qui est fourni par Visual C++ 6.0. Sélectionnez le mode Expert et déroulez tous les objets. La sélection ne se fait pas sur le AppID comme avec DCOMCNFG mais sur le nom de l'interface. Choisissez Info Class:

 

 

Sélectionnez l'onglet Implementation:

 

 

Sélectionnez l'onglet Activation :

 

 

On peut ainsi préciser l'utilisation du surrogate et aussi du nom de la machine distante. 

 

On a donc 3 modes d'utilisation de notre composant:
- le mode en dll
- le mode local avec dllhost.exe
- le mode distant avec dllhost.exe

Il est aussi possible d'invoquer le composant dans un script VBS:

'client0301.vbs
Main

Sub Main
	Dim Obj
	Dim strOut
	Set Obj = CreateObject("Server0301.Info")
	Obj.GetComputerName strOut
	MsgBox strOut
	Set Obj = Nothing
End Sub
Lancez la commande: wscript client0301.vbs
On constate que c'est l'objet en local qui est instancé et non celui qui est distant.

 

© 2001 Christophe Pichaud. All rights reserved.