次の方法で共有


下位互換性のある方法でインターフェイスを変更する

RPC と COM のバージョン管理理論に関するページで説明されている方法は、多くの理由で受け入れられない場合があります。 規則に従ってインターフェイスバージョンを変更するには、基本的に新しいクライアントが古いサーバーと通信しないことが必要です。 これは、多くの場合、この分野に展開された商用ソフトウェアでは不可能です。 Windows では、GUID やバージョンが変更されていないインターフェイスの変更が導入されている場合があります。 これは、新しいクライアントがレガシ サーバーと通信する必要があり、新しいクライアントが古いインターフェイスと新しいインターフェイスの両方をサポートするソリューションが望ましくないと見なされたためです。

ベスト プラクティス

これらは、インターフェイス GUID とバージョンを変更できない場合に、ワイヤ非互換性の問題を回避するための妥当な方法です。

  1. アプリケーションにもう一方の側の機能を認識してもらう。

    クライアントとサーバーには、各 (または少なくとも新しいクライアント) がパートナーの ID を確立できるようにするプロトコルがあります。 通常は、新しいクライアントに古いサーバーと新しいサーバーでサポートされている機能を認識してもらうだけで十分です。 これは、アプリケーションが接続コンテキストを保持している場合に簡単に実行でき、RPC 操作を実行する前にクライアントによって実行される XxxGetInfo 型の関数呼び出しを通じてサポートされます。 アプリケーションがサーバーごとのリリースベースで機能を管理する場合、アプリケーションはどの呼び出しがどのサーバーに発行されているかを制御するため、古いサーバー/クライアントとの互換性のない呼び出しは発生しません。 要するに、アプリケーションは不一致が発生しないようにプロアクティブであるということです。 これは、2 番目のプラクティスと組み合わせて実行できます。

  2. 新しいリモート API を導入します。

    新しいリモート メソッドは、インターフェイスの最後に追加された場合、既存のメソッドと競合しません。 古いクライアントは、常に新しいサーバーを呼び出すことができます。 新しいクライアントは、呼び出されるサーバーからのエラーを監視する場合、サーバーの ID を知らなくても新しいメソッドを呼び出すことができます。 RPC ランタイムは、ディスパッチの前に各インターフェイスのメソッド番号を常にチェックして、メソッドが適切な v テーブル内にあることを確認します。 サーバーに対して不明なメソッドの場合、RPC ランタイムによって例外RPC_S_PROCNUM_OUT_OF_RANGEが発生します。 この例外は、この特定の状況でのみ発生します。 したがって、新しいクライアントは、呼び出しが古いサーバーに送信されたことを示す記号として例外をwatchし、その動作を正常に変更できます。

  3. 新しいメソッドでのみ、新しいパラメーターまたは新しいデータ型を導入します。

    新しい方法を導入する理由の 1 つは、データの非互換性を回避することです。 新しいデータ型が導入された場合、または単に変更される場合は、原則として新しいメソッド (またはメソッド) でのみ使用する必要があります。 互換性のないデータ型の変更の例については、「互換性のない変更の例」を参照してください。 このルールの唯一の注目すべき例外は、項目 4 で説明されています。

  4. ラッパーを使用して新しいパラメーターまたは新しいデータ型をマップします。

    このソリューションは、新しいパラメーターまたはデータ型をユーザーに公開する必要があるが、実際には個別にリモート処理する必要がない場合、または古いデータ型またはパラメーターにマップできる場合に適用されます。 たとえば、多くのシステム API が回り、リモート呼び出しを実行します。 ユーザーの既知のデータ型から、基になる RPC 呼び出しで実際に使用されるデータ型への何らかのマッピングを行っている場合とそうでない場合があります。 したがって、ユーザー インターフェイスの変更をリモート インターフェイスへの変更として伝達する必要があるかどうかを常に調べる価値があります。

    同様の状況は、ユーザーがリモート API を直接呼び出すときに発生する可能性がありますが、新しい型マッピングや必要になったその他のアクションを実行するためにラッパーが導入される可能性があります。 インターフェイス定義言語 (IDL) には、[call_as]、[transmit_as]、[wire_marshal] など、このような再マップを容易にする方法がいくつかあります。 [call_as] 属性は、クライアントとサーバーに関数ラッパーを導入します。 どちらもユーザー コードとマーシャラーの間に配置されます。 その他の属性は、直接型マッピングを扱います。 拡張機能の問題の場合、[call_as] が最も頻繁に使用され、落とし穴なしで理解して操作するのが最も簡単です。

  5. 既定のない共用体を使用してデータ型を変更します。

    通常、属性またはデータ型を変更すると、ワイヤの互換性が低下します。 例については、「 互換性のない変更の 例」を参照してください。 ただし、既定の句のない共用体の場合、前述のように、範囲外のプロシージャの場合と同様の方法で非互換性を管理できます。 このスキームは、共用体を使用する一般的な XxxINFO 型に簡単に適用できます。

    たとえば、次のような呼び出し

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

    は、レベル 1、2、または 3 の情報を返す可能性があり、 XxxINFO は 1、2、3 の 3 つの分岐を持つ共用体です。

  6. [range] 属性を使用して範囲を指定します。

    下位互換性を損なうことなく、単純なスケール型に [range] 属性を指定できます。 この属性はワイヤ形式には影響しませんが、マーシャル解除中に、ワイヤの値が .idl ファイルで指定された範囲内にあることを確認します。 そうでない場合は、RPC_X_INVALID_BOUND例外がスローされます。 これは、サイズが大きい配列の最大サイズをサーバーが認識している場合に特に便利です。

    たとえば、次のように入力します。

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

指定されたレベルが 4 で、アームがない場合の RPC 動作は、共用体の定義によって異なります。 既定の句が定義されている共用体の場合、RPC は、既知のアーム ラベルとは異なる (この場合は 1、2、または 3 以外の) 既定の句で示される型を送信します。 既定のない共用体の場合、定義上、フォールバックする既定値がないため、unmarshaler によって例外が発生します。 例外はRPC_S_INVALID_TAG。

ここでも、新しいクライアントは、古いサーバーを呼び出したことを検出したときに動作を調整できます。

これらの推奨されるプラクティスに従って、リモート可能なデータ型を将来拡張できるように設計する必要がある場合は、IDL ファイルで既定のない共用体を使用します。 選択肢を考えると、カプセル化された共用体は少しクリーンです。

NDR64 ワイヤ プロトコルの内部表現の違いにより、このセクションで前述したアームを追加するための推奨事項は、次のように修飾する必要があります。追加される新しいアームは共用体の配置を変更できません。特に、アームの最大の配置は変更しないでください。 これは通常、8 へのアーム フォースアラインメントのポインターとして問題ではありません。 各アームがアームタイプへのポインタである設計は、要件を満たすクリーン方法の1つです。