Logo Spiria

Partage de données entre processus dans Windows : tube nommé et mémoire partagée

30 mai 2016.
Les données partagées sont un moyen de communication rapide entre les processus parent et enfant. Suivant la taille des données partagées, vous pouvez choisir le tube nommé (named pipe) ou la mémoire partagée nommée (named shared memory). Les exemples suivants illustrent les deux possibilités, et montrent comment utiliser les objets d’événement (event objects) pour synchroniser lecture et écriture de données entre les processus.

Les données partagées sont un moyen de communication rapide entre les processus parent et enfant. Suivant la taille des données partagées, vous pouvez choisir le tube nommé (named pipe) ou la mémoire partagée nommée (named shared memory). Les exemples suivants illustrent les deux possibilités, et montrent comment utiliser les objets d’événement (event objects) pour synchroniser lecture et écriture de données entre les processus.

Supposons le scénario où le processus parent envoie une quantité modeste de données au processus enfant, et que, partant de ces données, le processus enfant produit un grand bloc de données à renvoyer au processus parent.

1. Pour partager une petite quantité de données : le tube nommé

Le tube est une partie de la mémoire partagée que les processus utilisent pour communiquer. Le tube nommé, qui peut être à sens unique ou bidirectionnel (à deux voies), offre aux processus à la fois des services de lecture et d’écriture.

Pour envoyer des données du processus parent au processus enfant, le parent crée le fichier de tube avec un nom, puis il écrit les données dans ce fichier. Pour recevoir ces données, le processus enfant ouvre le fichier tube en utilisant le même nom, et lit les données.

Mais à quel moment le processus enfant lit-il les données ? Nous pouvons utiliser les objets d’événement pour synchroniser la lecture et l’écriture entre les processus. Deux objets d’événement, eventToChild et eventFromChild, sont créés dans le processus parent. Ce dernier utilise eventToChild pour notifier le processus enfant que les données vont être envoyées, tandis que le processus enfant utilise eventFromChild pour informer le processus parent.

Voici le code pour le processus parent :

char* pipeName =” \\\\.\\pipe\\”;
char* eventToChildName = “event_to_child”;
char* eventFromChildName = “event_from_child”;

//Création, dénomination du tube nommé
Handle namedPipe = CreateNamedPipe(pipeName,
			PIPE_ACCESS_DUPLEX +FILE_FLAG_FIRST_PIPE_INSTANCE,
			PIPE_TYPE_MESSAGE + PIPE_WAIT + PIPE_READMODE_MESSAGE, 
			PIPE_UNLIMITED_INSTANCES, 
			100, 
			100, 
			100, 
			nullptr);

//Création de eventToChild, eventSecurity étant le pointeur
//vers la structure SECURITY_ATTRIBUTES. 
Handle eventToChild = CreateEvent(&eventSecurity,
				false, 
				false, 
				eventToChildName );

//Création du eventFromChild utilisé par le processus enfant
//pour notifier le processus parent
Handle eventFromChild = CreateEvent(&eventSecurity,false, false, eventFromChildName );

//Notification du processus enfant
if (!SetEvent(eventToChild))
	return;	

//Écriture des données dans le tube nommé
DWORD writtenSize;
if (!WriteFile(namedPipe, data, sizeof(data), & writtenSize, nullptr) || writtenSize!= sizeof(data))
	return;	

Maintenant, voyons le processus enfant : il crée également un descripteur (handle) de tube nommé, “hPipe”, et ouvre eventToChild et eventFromChild sur la base des mêmes noms d’événements. Après avoir attendu le signal d’un eventToChild du processus parent, le processus enfant lit les données du fichier de tube nommé :

//Attente d'événement en provenance du parent
DWORD wait = WaitForSingleObject( eventToChild, INFINITE );
if(wait != WAIT_OBJECT_0 )
{
	//Traitement de code d'erreur
}

//Lecture continue des données du tube

bool res = false;
while (1)
	{
	res = ReadFile( hPipe, lpBuffer, nNumberOfBytesToRead, & lpNumberOfBytesRead, nullptr) ;
	if( !res )
		break;
	}

2. Pour partager une grande quantité de données : la mémoire partagée nommée

Après la lecture des données du tube nommé, supposons que le processus enfant génère en retour un grand bloc de données à partager avec le processus parent. Le meilleur moyen de gérer cela est de créer un objet de mappage de fichier, mapper l’objet fichier dans l’espace mémoire, informer le processus parent du descripteur de fichier mappé et de la taille des données, puis transférer les données dans la mémoire tampon mappée pour que le parent les lise. Le processus parent localise le secteur de mémoire en utilisant le descripteur de fichier mappé et la taille du tampon, qui sont écrits dans le tube nommé, pour lire directement les données dans la mémoire tampon mappée.

Voici le code pour le processus enfant :

Handle hMapFile = CreateFileMapping(
				INVALID_HANDLE_VALUE,
				NULL,
				PAGE_READWRITE,
				0, 
				bufferSize,
				nullptr);	
 
	if ( hMapFile == nullptr) 
		return;

	//Mappage du fichier en mémoire
	LPSTR mappedBuffer = (LPSTR) MapViewOfFile(
					hMapFile,
					FILE_MAP_ALL_ACCESS,
					0,
					0,
					0 );

//Informer le parent pour qu'il reçoive le descripteur et la taille du tampon
 (!SetEvent( eventFromChild))
	return;

//Après notification, écrire les données dans le tube nommé
DWORD buffer[2];
buffer[0] = (DWORD)hMapFile;
buffer[1] = bufferSize;
if (WriteFile(hPipe, buffer, sizeof buffer, &written, NULL ) || ( written != sizeof buffer ) ))
	return;

//Ici, nous pouvons attendre que le parent retourne un message 
//confirmant qu'il a été notifié et qu'il est prêt à lire les données

//Vider le tampon
memcpy(destination, mappedBuffer, bufferSize);
…

Autrement, après avoir reçu de l’enfant l’indicateur de fichier mappé “childhMapFile” et la taille du tampon, le processus parent peut utiliser la fonction “DuplicateHandle()” pour dupliquer le descripteur “hMapFile”, et alors mapper le fichier dans la mémoire du processus courant. Voici le code :

if ( DuplicateHandle( childProcess, childhMapFile, currentProcess, & hMapFile, 0, false, DUPLICATE_SAME_ACCESS ) )

	// map the file into our process memory
	LPSTR hMappedBuffer = (LPSTR) MapViewOfFile(
				hMapFile,
				FILE_MAP_ALL_ACCESS,
				0,
				0,
				0);
}

3. Conclusion

Les données partagées sont pour les processus l’une des façons de communiquer. Le tube nommé et la mémoire partagée sont utilisés dans différentes circonstances. Les deux processus accèdent au même tube nommé par son nom, et à la mémoire partagée par le descripteur de fichier mappé. L’utilisation appropriée des objets d’événements et de fonctions d’attente pour contrôler les moments de lecture et d’écriture des données, assureront la synchronisation des processus.