Partager via


Modification des interfaces de manière rétrocompatible

Les méthodes expliquées dans La théorie du contrôle de version pour RPC et COM peuvent être inacceptables pour de nombreuses raisons. La modification d’une version d’interface en fonction des règles exige essentiellement que les nouveaux clients ne communiquent pas avec les anciens serveurs. Cela est souvent impossible avec des logiciels commerciaux déployés sur le terrain. Parfois, Windows a introduit des modifications d’interface en l’absence de GUID ou de versions modifiées. Cela résultait de la nécessité pour les nouveaux clients de communiquer avec les serveurs hérités, et parce que la solution qu’un nouveau client prenait en charge à la fois les anciennes et les nouvelles interfaces était considérée comme indésirable.

Bonne pratique

Il s’agit des méthodes raisonnables pour contourner le problème d’incompatibilité de câble lorsque le GUID d’interface et la version ne peuvent pas être modifiés.

  1. Faites en sorte que l’application connaisse les fonctionnalités de l’autre côté.

    Le client et le serveur ont un protocole qui permet à chacun (ou au moins au nouveau client) d’établir l’identité du partenaire. En règle générale, il suffit que le nouveau client soit informé des fonctionnalités prises en charge par les anciens et les nouveaux serveurs. Cela peut facilement être effectué lorsqu’une application s’accroche à un contexte de connexion et être pris en charge via un appel de fonction de type XxxGetInfo exécuté par le client avant d’effectuer des opérations RPC. Lorsqu’une application gère les fonctionnalités sur une base de mise en production par serveur, un appel avec une incompatibilité avec l’ancien serveur/client ne peut jamais se produire, car l’application contrôle les appels émis pour quel serveur. L’essentiel est que l’application est proactive pour empêcher une incompatibilité de se produire. Cette opération peut être effectuée conjointement avec la deuxième pratique.

  2. Introduction d’une nouvelle API distante.

    Une nouvelle méthode distante n’entre pas en collision avec les méthodes existantes si elle est ajoutée à la toute fin de l’interface. Les anciens clients peuvent appeler de nouveaux serveurs comme ils l’ont toujours fait. Le nouveau client peut appeler la nouvelle méthode sans connaître l’identité du serveur, à condition qu’il surveille les erreurs provenant du serveur appelé. Le temps d’exécution RPC vérifie toujours le numéro de méthode de chaque interface avant une répartition pour s’assurer que la méthode se trouve dans une table v appropriée. Pour une méthode inconnue d’un serveur, l’heure d’exécution RPC déclenche l’exception RPC_S_PROCNUM_OUT_OF_RANGE. Cette exception n’est levée que dans cette situation particulière. Par conséquent, un nouveau client peut watch pour l’exception en tant que signe que l’appel a été passé à un ancien serveur et peut modifier son comportement correctement.

  3. Introduisez de nouveaux paramètres ou de nouveaux types de données uniquement dans les nouvelles méthodes.

    Une raison d’introduire une nouvelle méthode est d’éviter l’incompatibilité des données. Si un nouveau type de données est introduit ou simplement modifié, il doit en principe être utilisé uniquement dans une nouvelle méthode (ou méthodes). Consultez Exemples de modifications incompatibles pour obtenir des exemples de modifications de type de données incompatibles. La seule exception notable à cette règle est décrite à l’élément 4.

  4. Mapper de nouveaux paramètres ou de nouveaux types de données via un wrapper.

    Cette solution s’applique lorsqu’un nouveau paramètre ou un nouveau type de données doit être exposé à un utilisateur, mais n’a pas besoin d’être distant séparément ou peut être mappé aux anciens types de données ou paramètres. Par exemple, de nombreuses API système se retournent et exécutent un appel à distance. Ils peuvent effectuer ou non un mappage des types de données connus de l’utilisateur vers les types de données réellement utilisés dans l’appel RPC sous-jacent. Il est donc toujours utile d’examiner si la modification de l’interface utilisateur doit se propager en tant que modification à une interface distante.

    Une situation similaire peut se produire lorsque l’utilisateur appelle directement une API distante, mais un wrapper peut être introduit pour effectuer un nouveau mappage de type ou d’autres actions supplémentaires qui sont devenues nécessaires. Le langage IDL (Interface Definition Language) offre plusieurs façons de faciliter ce remappage, à savoir [call_as], [transmit_as] et [wire_marshal]. L’attribut [call_as] introduit un wrapper de fonction sur le client et le serveur. Les deux sont placés entre le code utilisateur et le marshaleur. Les autres attributs traitent du mappage de type direct. Pour les problèmes d’extension, [call_as] est le plus fréquemment utilisé et est plus facile à comprendre et à manipuler sans pièges.

  5. Modifiez les types de données via une union sans défaut.

    La modification d’un attribut ou d’un type de données entraîne généralement une incompatibilité de câble. Pour obtenir des exemples, consultez Exemples de modifications incompatibles . Toutefois, dans le cas d’une union sans clause par défaut, l’incompatibilité peut être gérée d’une manière similaire au cas d’une procédure hors plage, comme décrit précédemment. Ce schéma s’applique facilement aux types XxxINFO populaires qui utilisent des unions.

    Par exemple, un appel comme celui-ci

    XxxGetInfo( [in] level, [out] XxxINFO  * pInfo );
    

    pourrait retourner des informations au niveau 1, 2 ou 3, xxxINFO étant une union avec trois branches : 1, 2 et 3.

  6. Utilisez l’attribut [range] pour spécifier la plage.

    Vous pouvez spécifier l’attribut [plage] sur un type d’échelle simple sans rompre la compatibilité descendante. Cet attribut n’affecte pas le format de fil, mais lors de la démarshalling RPC vérifie la valeur sur le câble pour confirmer qu’elle se trouve dans la plage spécifiée dans le fichier .idl. Si ce n’est pas le cas, une exception RPC_X_INVALID_BOUND est levée. Cela est particulièrement utile si le serveur connaît la taille maximale d’un tableau dimensionné.

    Par exemple :

    HRESULT Method1( [in, range(0,100)] ULONG m, [size_is(m)] ULONG *plong); 
    

Le comportement RPC lorsque le niveau indiqué est 4 et que le bras est manquant, dépend de la définition de l’union. Pour une union avec la clause par défaut définie, RPC transmet un type indiqué dans la clause par défaut pour tout ce qui est différent des étiquettes de bras connues (dans ce cas, tout autre type que 1, 2 ou 3). Pour une union sans défaut, l’unmarshaler lève une exception, car par définition, il n’y a pas de valeur par défaut à laquelle revenir. L’exception est RPC_S_INVALID_TAG.

Là encore, un nouveau client peut ajuster son comportement lorsqu’il découvre qu’il a appelé un ancien serveur.

Ces pratiques recommandées permettent de concevoir un type de données pouvant être étendu à l’avenir, en utilisant une union sans défaut dans le fichier IDL. Si vous avez le choix, une union encapsulée est légèrement plus propre.

En raison des bizarreries de représentation interne du protocole de fil NDR64, la recommandation d’ajout d’armes fournie plus haut dans cette section doit être qualifiée comme suit : Le nouveau bras ajouté ne peut pas modifier l’alignement de l’union, et en particulier, l’alignement le plus important des bras ne doit pas changer. Ce n’est généralement pas un problème, car un pointeur dans un bras force l’alignement sur 8. Une conception où chaque bras est un pointeur vers un type de bras est l’un propre moyen de satisfaire à l’exigence.