Présumons l'interface MS-IDL suivante.
import "oaidl.idl";
import "ocidl.idl";
[object, uuid (abcdabcd-abcd-abcd-abcd-abcdabcdabcd)]
interface IManipSymboles : IUnknown
{
// Méthode InverserSequence()
// Prend deux séquences de n bytes (src et dest), inverse le
// contenu de la séquence src et dépose le résultat dans dest
HRESULT InverserSequence ([in, size_is(n)] char *src,
[in] int n,
[out, size_is(n)] char dest);
};
Une implémentation naïve de la méthode InverserSequence() de cette interface par un composant C++ nommé Manipulateur pourrait être comme suit.
#include <algorithm>
using namespace std;
// ...
//
// Méthode InverserSequence()
// Prend deux séquences de n bytes (src et dest), inverse le
// contenu de la séquence src et dépose le résultat dans dest
//
HRESULT __stdcall Manipulateur::InverserSequence
(unsigned char *src, int n, unsigned char *dest)
{
//
// a) inverser les éléments de la séquence src (note: nous
// modifions ici un paramètre [in] passé par adresse... est-
// ce une pratique sage?)
//
reverse(src, src+n);
//
// b) copier les éléments (inversés) de src dans dest (ok
// car les deux sont de même taille; re: spécification IDL)
//
copy(src, src+n, dest);
return S_OK;
}
// ...
C'est un algorithme qui semble correct, du fait que le paramètre src est qualifié [in], donc intrant au sens strict, dans la spécification IDL. Cela signifie qu'en présence de marshalling entre le client et le serveur, les données pointées par src voyageront du client vers le serveur et ne referont pas le chemin inverse. Ainsi, bien que src soit un pointeur et bien que, dans un processus monolithique, cela signifierait que modifier les données pointées par src dans la méthode modifie du même coup les données pointées par l'adresse correspondante dans le sous-programme appelant, on pourrait croire qu'une approche RPC dans un SCS entraînerait une plus grande isolation des homologues et ferait disparaître cet effet de bord.
Aurions-nous eu raison de penser ainsi? La réponse est ça dépend... ce qui est assurément l'une des pires formes de réponse pour un problème informatique. Et ce ça dépend est une conséquence directe de certaines optimisations du modèle COM, qui entraînent en contrepartie une espèce de perte de pureté du modèle CS.
En effet, lorsque le client et le serveur d'un SCS partagent un même espace adressable (client indigène, serveur indigène, serveur à contexte interne, types ne nécessitant pas de marshalling particulier), COM fait en sorte qu'il n'y ait aucun marshalling d'impliqué entre les homologues. Cela signifie un niveau de performance à l'invocation d'une méthode d'un composant qui soit comparable à celui d'un appel de méthode virtuelle, ce sur quoi personne ne crachera, mais cela signifie aussi que toute modification faite par un serveur sur un paramètre passé par adresse, même si ce paramètre est qualifié [in], peut entraîner un effet de bord pervers sur le code du client.
Comment se prévaloir contre ces effets de bord qui peuvent survenir grâce à des considérations hors du code en tant que tel? En adoptant de bonnes pratiques de programmation, en particulier en ne modifiant jamais un paramètre [in] qui soit passé par adresse (en réalité, il n'y a pratiquement jamais de bonne raison d'agir ainsi). Dans le cas qui nous intéresse, le code aurait pu être écrit ainsi et devenir sécuritaire en tout temps:
#include <algorithm>
using namespace std;
// ...
//
// Méthode InverserSequence()
// Prend deux séquences de n bytes (src et dest), inverse le
// contenu de la séquence src et dépose le résultat dans dest
//
HRESULT __stdcall Manipulateur::InverserSequence
(unsigned char *src, int n, unsigned char *dest)
{
//
// a) inverser les éléments de la séquence src (note: nous
// modifions ici un paramètre [in] passé par adresse... est-
// ce une pratique sage?)
//
reverse(src, src+n);
//
// b) copier les éléments (inversés) de src dans dest (ok
// car les deux sont de même taille; re: spécification IDL)
//
copy(src, src+n, dest);
return S_OK;
}
// ...
// ...
// Méthode InverserSequence()
// Prend deux séquences de n bytes (src et dest), inverse le
// contenu de la séquence src et dépose le résultat dans dest
HRESULT __stdcall Manipulateur::InverserSequence
(unsigned char *src, int n, unsigned char *dest)
{
//
// a) copier les éléments de src dans dest (ok car les deux
// sont de même taille; re: spécification IDL). Notez que nous
// n'avons en rien modifié ce vers quoi pointe src
//
copy(src, src+n, dest);
//
// b) inverser les éléments de la séquence dest
//
reverse(dest, dest+n);
return S_OK;
}
// ...
On obtient le même niveau de performance qu'auparavant: une copie de n éléments et une inversion de l'ordre de n éléments d'une séquence. La différence entre la version précédente et celle-ci est que désormais, seul le paramètre qualifié [out] est modifié... et c'est là sa raison d'être!