次の方法で共有


Windows と C++

Windows Web サービス

Kenny Kerr

サンプル コードのダウンロード

多くの開発者がこぞって Microsoft .NET Framework を (また、.NET Framework ほどではありませんが Java も) 使用するようになった主な理由の 1 つは、これを使用すると、インターネット用のソフトウェアを作成するのが大幅に容易になるためです。HTTP クライアント アプリケーションと HTTP サーバー アプリケーションのどちらを作成する場合でも、.NET Framework は、HTTP 要求を行い XML を容易に処理するための多くのクラスを提供しました。WSDL ドキュメントを基にして SOAP クライアントを生成し、ASP.NET で SOAP サーバーを実装することさえ可能でした。Web サービス関連の標準が成熟してくると、マイクロソフトは、TCP や UDP などのさまざまなトランスポートを処理するためのますます複雑化する Web 標準をより使いやすくしたり、より汎用性のあるセキュリティ オプションを提供したりするために、Windows Communication Foundation (WCF) を開発しました。これも .NET Framework を基盤として構築されています。

しかし、C++ 開発者は、Web アプリケーションの作成に C++ を使用するのは実用的なのか疑問に思うようになりました。マイクロソフトは ATL クラスや COM ベースのツールキットという形でいくつかの一時的なソリューションを提供しましたが、これらは結局マネージ SOAP スタックの進歩に後れをとり、ほとんど使用されなくなってしまいました。

一見、.NET Framework のみに焦点が絞られたように思われましたが、マイクロソフトの開発者は、C++ 開発者のことを忘れたわけではありませんでした。実際、マイクロソフトの開発者の多くは今でも熱心な C++ 開発者であり、今後も熱心な C++ 開発者であり続けるでしょう。したがって、C++ 開発者を支援するソリューションは、マイクロソフトの Windows プラットフォーム向け戦略の重要な部分を占めます。もちろん、C++ 開発者向けの API の多くは、多くのマネージ フレームワークの基盤にもなっています。

非常にたくさんの API を挙げることができますが、触れておく価値のあるいくつかの API をご紹介します。Windows HTTP サービス (WinHTTP) API は、HTTP クライアントを作成するための強力で柔軟なソリューションを提供します。WinHTTP の詳細については、2008 年 8 月号の私のコラムを参照してください。HTTP サーバー (HTTP.sys) API は、インターネット インフォメーション サービス (IIS) などの本格的な Web サーバーに頼ることなく高パフォーマンスな HTTP サーバーを構築するための基盤を提供します。実際、IIS 自体がこの API を基盤としています。そして、もちろん、XmlLite API は、ネイティブ コード用の小規模で高速な XML パーサーを提供します。XmlLite の詳細については、2007 年 4 月号の私の特集記事を参照してください。

これらすべてを考慮しても、SOAP クライアントや SOAP サーバーを作成するのはやはり骨の折れる作業です。SOAP は当初は単純でしたが (SOAP の "S" は "単純" という意味の "simple" を表します)、その状態は長くは続きませんでした。WinHTTP、HTTP.sys、および XmlLite を使用すると、HTTP トランスポートが処理され XML が解析されるので開発者の負担は大幅に減る場合がありますが、通信層を処理するために記述しなければならないコードがまだ大量に残っています (SOAP ヘッダーの形式設定と変換、他のトランスポート (TCP や UDP など) のサポートなど)。なんとかしてこれらすべてに対処できたとしても、論理的な SOAP 操作を関数呼び出しとして処理できるわけではなく、SOAP エンベロープの解析という作業がまだ残っています。

こうした面倒な問題は過去のものになりました。Windows Web サービス (WWS) API の登場により、C++ 開発者は、Web サービスの世界で自分たちが後れをとっていると考える必要はなくなりました。WWS は、完全にネイティブ コードの SOAP 実装となるように土台から構築されており、多くの WS-* プロトコルをサポートしています。厳密に言うと WWS は C API を通じて公開されます (これにより、他の言語やランタイムとの相互運用が非常に簡単になります) が、最も恩恵を受けるのはおそらく C++ 開発者でしょう。実際、この記事で説明するように、C++ を少し活用すると、WWS は非常に魅力的なものとなります。

アーキテクチャと原理

WWS では、.NET Framework ベースのライブラリでは実現されていないあらゆることが実現されています。WWS は、ネイティブ コード向けに設計されており、依存関係の数が最小限になるように設計されており、使用するメモリの量ができるだけ少なくなるように設計されており、また、非常に高速になるように設計されています。WWS の開発を担当しているチームは、新しいビルドごとにパフォーマンス テストを実行し、WCF や RPC のパフォーマンスと比較します。RPC は一種の基準として使用されます。これは、RPC より高速なものはないためですが、RPC は速度低下を追跡するための信頼できる方法を提供します。このように RPC も基準として役立ちますが、WCF と WWS を比較すると明快な結果が得られます。図 1 は、WCF を使用しているクライアントのワーキング セットと WWS を使用しているクライアントのワーキング セットの比較を示しています。非常に大きな差がありますが、.NET Framework が関係していることを考えるとおそらくそれほど意外ではないでしょう。しかし、WCF を最先端のテクノロジと考えている方 (そのような方は大勢いらっしゃいますが) にとって、図 2 の結果は意外でしょう。この図は、WCF を使用しているサーバーのスループットと WWS を使用しているサーバーのスループットを 1 秒あたりの操作数の形で示したものです。WWS の方が 2 倍以上も高速です。誤解しないでいただきたいのですが、WCF や .NET Framework にはまったく問題はありません。しかし、小規模で高速なものが必要な場合は、C++ とネイティブ コードに勝るものはなかなかありません。皆さんはそんなことは既にご存じですね。


図 1 クライアントのワーキング セットの比較 (値が小さいほど良い)

WWS ランタイムは、Windows 7 および Windows Server 2008 R2 に付属している WebServices.dll に含まれています。また、Windows XP およびそれ以降用のシステム更新プログラムとしても提供されています。WebServices.dll からエクスポートされた関数は WWS API を表しており、WebServices.lib にリンクし Windows SDK に含まれている WebServices.h ヘッダー ファイルをインクルードすることによって、このような関数にアクセスすることができます。ここまでは問題ありませんね。ですが、この API はどのようなものなのでしょうか。XmlLite や Direct2D の API のような COM スタイルの API と違って、この C API について考えるには、背後に存在し、使用されるのを待っている論理的な層とオブジェクトのセットを想像する必要があります。まずは、層に関してこの API を見てみましょう。図 3 は、WWS API によって公開される機能の層を示しています。各層はその真下にある層を基盤として構築されています。各層には複数の関数と構造体が用意されており、各層は抽象化のセットを提供します。ご推測のとおり、アプリケーションではどの層を利用することもできますが、最も単純なプログラミング モデルを提供し詳細の多くを隠ぺいするサービス モデルを主に利用することをお勧めします。トランスポート層は、単に、すべての下になんらかのネットワーク プロトコルがあることを示すものです。WWS では、WinHTTP、HTTP.sys、または Winsock が使用されます。どれが使用されるかは、選択されたトランスポート、およびクライアントとサーバーのどちらを実装するために使用するかによって決まります。


図 2 サーバー スループットの比較 (値が大きいほど良い)


図 3 階層化された API

サービス モデル層は、その名のとおり、SOAP メッセージの交換を完全に抽象化し、論理的な Web サービス操作を関数呼び出しとしてモデル化します。しかし、この層は単独で機能するのではなく、Windows SDK に含まれている Wsutil.exe というコマンドライン ツールを利用します。WSDL ファイルを指定してこのツールを実行すると、ヘッダー ファイル、および C ソース ファイル (提供された説明どおりの Web サービスへの接続と、そのような Web サービスの実装との両方に必要なコードのほとんどが記述されたもの) が生成されます。チャネル バインディングは適切に構成され、メッセージの形式は適切に設定されます。これはずば抜けて単純なアプローチであり、従来の RPC に期待されるようなプログラミング モデルとよく似たプログラミング モデルを提供します。

一方、チャネル層は、特定のトランスポートで送受信されるメッセージを公開しますが、必要に応じて、メッセージの実際の形式設定を開発者自身が行わなくて済むようにすることができます。ここでのメリットは、使用される具体的なトランスポートとエンコードが隠ぺいされることです。チャネル層はバインディング情報の制御を行う場所であり、認証のためであれプライバシーのためであれ、通信をセキュリティで保護できる場所でもあります。

XML 層は、メッセージの形式設定とデータのシリアル化を行うための機能を提供します。開発者はメッセージの内容へのフル アクセスを持ちますが、テキスト、バイナリ、MTOM のいずれと通信する場合でも、使用される具体的なエンコードは隠ぺいされます。驚かれるかもしれませんが、WWS には独自の XML パーサーがあります。WWS ではなぜ単純に XmlLite を使用しないのでしょうか。XmlLite は確かに軽量で非常に高速ですが、いくつかの理由により、WWS に最適とは言えません。最も明らかな理由は、WWS はさまざまなエンコードをサポートする必要があるのに対して、XmlLite はテキストしかサポートしていないことです。また、SOAP メッセージは一般に UTF-8 を使用してエンコードされますが、XmlLite はすべてのプロパティを Unicode 文字列で公開します。これにより、値をコピーする際に余分なコストがかかります。さらに、WWS にはメモリ消費に関する非常に厳しい目標があり (後で説明しますが、そのための API まで用意されています)、XmlLite でこの目標を満たすことはできません。結局、WWS チームは、XmlLite よりも大幅に高速な、SOAP 専用のカスタム パーサーを実装することができました。WWS パーサーは XmlLite に取って代わるようには作られていないことに注意してください。汎用的な XML パーサーとしては XmlLite は非常に優れていますが、WWS の XML 層は、SOAP が要求する XML のサブセットを使用してデータを効率的に SOAP メッセージにシリアル化したり SOAP メッセージからシリアル化したりすることを目的とした非常に用途が限定された機能を開発者に提供します。

WWS API は、この 3 つの層に論理的に属する関数とデータ構造体の他に、すべての層に共通するいくつもの機能 (エラー処理、非同期完了、キャンセル、メモリ管理など) を提供します。この記事で使用できるスペースは限られており、皆さんがすぐに作業を開始できるようにしたいので、この記事の残りの部分では、サービスのクライアントとサーバーを構築するためのサービス モデルの使用についてのみ説明します。WWS の他の部分の詳細については、今後の記事で説明する予定です。

作業を開始する

まずは、図 4 に示す最小限の Web サービス定義を使用します。この WSDL ドキュメントでは、サービスの種類、メッセージ、操作、エンドポイント、およびチャネル バインディングを定義しています。最初にするべきことは、次のように Wsutil.exe を使用してこのドキュメントを処理することです。

Wsutil.exe Calculator.wsdl

これにより、Calculator.wsdl.h というヘッダー ファイルと Calculator.wsdl.c という C ソース ファイルが生成されます。

図 4 Web サービス定義

<wsdl:definitions xmlns:wsdl="https://schemas.xmlsoap.org/wsdl/"
 xmlns:soap="https://schemas.xmlsoap.org/wsdl/soap/"
 xmlns:xsd="ttp://www.w3.org/2001/XMLSchema"
 xmlns:tns="http://calculator.example.org/"
 targetNamespace="http://calculator.example.org/">
    
 <wsdl:types>
   <xsd:schema elementFormDefault="qualified" 
    targetNamespace="http://calculator.example.org/">
    <xsd:element name="AddRequest">
     <xsd:complexType>
      <xsd:sequence>
       <xsd:element name="first" type="xsd:double" />
       <xsd:element name="second" type="xsd:double" />
      </xsd:sequence>
     </xsd:complexType>
    </xsd:element>
    <xsd:element name="AddResponse">
     <xsd:complexType>
      <xsd:sequence>
       <xsd:element name="result" type="xsd:double" />
      </xsd:sequence>
     </xsd:complexType>
    </xsd:element>
   </xsd:schema>
  </wsdl:types>

  <wsdl:message name="AddRequestMessage">
    <wsdl:part name="parameters" element="tns:AddRequest" />
  </wsdl:message>
  <wsdl:message name="AddResponseMessage">
    <wsdl:part name="parameters" element="tns:AddResponse" />
  </wsdl:message>

  <wsdl:portType name="CalculatorPort">
    <wsdl:operation name="Add">
      <wsdl:input message="tns:AddRequestMessage" />
      <wsdl:output message="tns:AddResponseMessage" />
    </wsdl:operation>
  </wsdl:portType>

  <wsdl:binding name="CalculatorBinding" type="tns:CalculatorPort">
    <soap:binding transport="https://schemas.xmlsoap.org/soap/http"/>
    <wsdl:operation name="Add">
      <soap:operation soapAction=
       "http://calculator.example.org/Add" style="document"/>
      <wsdl:input>
       <soap:body use="literal"/>
      </wsdl:input>
      <wsdl:output>
       <soap:body use="literal"/>
      </wsdl:output>
     </wsdl:operation>
   </wsdl:binding>

   <wsdl:service name="CalculatorService">
    <wsdl:port name="CalculatorPort" binding="tns:CalculatorBinding">
     <soap:address location="http://localhost:81/calculator"/>
    </wsdl:port>
   </wsdl:service>

</wsdl:definitions>

生成されたファイルの内容を見る前に、クライアントとサーバーのどちらを実装するかにかかわらず、少し下地作りをしておく必要があります。最初に必要となるのは、エラー情報を表現する手段です。WWS API は、エラー オブジェクトを通じて、WWS API 関数用および SOAP エラー用の豊富なエラー情報を公開します。C API であるこのオブジェクトには、非透過ハンドルと一連の関数が用意されています。この場合、WS_ERROR* はエラー オブジェクトへのハンドルを表し、WsCreateError はエラー オブジェクを作成する関数です。WsFreeError 関数を呼び出すと、このオブジェクトは解放されます。エラー オブジェクトには、エラーに関する異なるレベルの情報を表す複数の文字列を格納することができます。こうした文字列を取得するには、まず、文字列がいくつあるかを突き止める必要があります。これを行うには、WsGetErrorProperty 関数を呼び出してエラー オブジェクトへのハンドルを渡し、WS_ERROR_PROPERTY_STRING_COUNT 定数を指定します。この情報を得たら、WsGetErrorString 関数を呼び出し、エラー オブジェクトへのハンドル、および取得する文字列の 0 から始まるインデックスを渡します。API 関数を使用して独自のエラー オブジェクトにデータを設定することもできます。もちろん、C++ を少し使用すると、これをより単純で信頼性の高いものにするのに大いに役立ちます。図 5 は、単純なエラー オブジェクト ラッパーを示しています。図 6 に示すように、エラー オブジェクトに格納されている文字列を容易に列挙することができます。

図 5 エラー オブジェクト ラッパー

class WsError
{
    WS_ERROR* m_h;
public:
    WsError* m_h(0)
    {}
    ~WsError()
    {
        if (0 != m_h)
    }
    HRESULT Create(const WS_ERROR_PROPERTY* properties,
                        ULONG propertyCount)
    {
        return WsCreateError(properties, propertyCount, &m_h);
    }
    HRESULT GetProperty(WS_ERROR_PROPERTY_ID id, void* buffer,
                            ULONG bufferSize)
    {
        return WsGetErrorProperty(m_h, id, buffer, bufferSize);
    }
    template <typename T>
    HRESULT GetProperty(WS_ERROR_PROPERTY_ID id, out T* buffer)
    {
        return GetProperty(id, buffer, sizeof(T));
    }
    HRESULT GetString(ULONG index, WS_STRING* string)
    {
        return WsGetErrorString(m_h, index, string);
    }
    operator WS_ERROR*() const
    {
        return m_h;
    }
};

図 6 エラー オブジェクトに格納されている文字列の列挙

WsError error;
HR(error.Create(0, 0));
// Something goes wrong . . .
ULONG stringCount = 0;
HR(error.GetProperty(WS_ERROR_PROPERTY_STRING_COUNT,&stringCount));

for (ULONG i = 0; i < stringCount; ++i)
{
    WS_STRING string;
    HR(error.GetString(i, &string));
    wprintf(L"Error %d: %.*s\n", i, string.length, string.chars);
}

次に必要となるのは、ヒープ オブジェクトです。ヒープ オブジェクトを使用すると、メッセージを生成または使用する際、および他のさまざまな API 構造体を割り当てる必要がある際に、メモリの割り当てを正確に制御することができます。また、ヒープ オブジェクトを使用すると、特定の関数を正常に実行するために必要なメモリの量を正確に知る必要がないので、プログラミング モデルが単純になります。Windows SDK に含まれている多くの古い関数を使用する場合、たとえば、必要な記憶域の量を推測したり、まず特定の方法で関数を呼び出して、関数を正常に実行するためにはどのくらいのメモリを割り当てる必要があるかを突き止めたりする必要がありました。WWS のヒープ オブジェクトを使用すると、この余分なコーディングがすべて不要になり、API のメモリ使用量を制御する優れた方法が提供されます。これは、セキュリティの観点からも役に立ちます (API が割り当てることができるメモリの量の上限を指定する必要がある場合に役立ちます)。WS_HEAP* はヒープ オブジェクトへのハンドルを表し、WsCreateHeap はヒープ オブジェクトを作成する関数です。WsFreeHeap 関数を呼び出すと、このオブジェクトは解放されます。ヒープ オブジェクトを作成したら、WsAlloc 関数を使用してヒープからメモリを割り当てることができますが、この記事では、単に、他の API 関数が必要に応じて使用できるように、ヒープ オブジェクトへのハンドルを他の API 関数に渡します。

図 7 は、単純なヒープ オブジェクト ラッパーを示しています。これを利用すると、次のように、サイズの上限が 250 バイトのヒープ オブジェクトを作成することができます。

WsHeap heap;

HR(heap.Create(250, // max size
               0, // trim size
               0, // properties
               0, // property count
               error));

エラー オブジェクトをヒープ オブジェクトの Create メソッドにどのように渡しているかに注目してください。ヒープ オブジェクトの作成中に問題が発生した場合、原因を解明するためにエラー オブジェクトから情報を得ることができます。

図 7 ヒープ オブジェクト ラッパー

class WsError
{
    WS_ERROR* m_h;
public:
    WsError* m_h(0)
    {}
    ~WsError()
    {
       if (0 != m_h)
    }
    HRESULT Create(const WS_ERROR_PROPERTY* properties, 
            ULONG propertyCount)
    {
       return WsCreateError(properties, propertyCount, &m_h);
    }
    HRESULT GetProperty(WS_ERROR_PROPERTY_ID id, void* buffer, 
               ULONG bufferSize)
    {
        return WsGetErrorProperty(m_h, id, buffer, bufferSize);
    }
    template <typename T>
    HRESULT GetProperty(WS_ERROR_PROPERTY_ID id, out T* buffer)
    {
        return GetProperty(id, buffer, sizeof(T));
    }
    HRESULT GetString(ULONG index, WS_STRING* string)
    {
        return WsGetErrorString(m_h, index, string);
    }
    operator WS_ERROR*() const
    {
      return m_h;
    }
};
class WsHeap
{
    WS_HEAP* m_h;
public:
    WsHeap() : m_h(0)
    {}
    ~WsHeap()
    {
        if (0 != m_h) WsFreeHeap(m_h);
    }
    HRESULT Create(SIZE_T maxSize, SIZE_T trimSize, 
            const WS_HEAP_PROPERTY* properties, 
            ULONG propertyCount, 
            in_opt WS_ERROR* error)
    {
        return WsCreateHeap(maxSize, trimSize, properties, propertyCount,
                      &m_h, error);
    }
    operator WS_HEAP*() const
    {
        return m_h;
    }
};

クライアント

サービス モデルのクライアント側の中心となるのは、サービス プロキシ オブジェクトです。生成されたソース コードには、CalculatorBinding_CreateServiceProxy という関数が含まれています。この関数の名前は、エンドポイントの名前、または、WSDL ドキュメント内で定義されているバインディング名に由来しています。この関数では、サービス プロキシ オブジェクトを作成し、このオブジェクトへの非透過ハンドルを表す WS_SERVICE_PROXY* を返します。WsFreeServiceProxy 関数を呼び出すと、このオブジェクトは解放されます。サービス プロキシ オブジェクトを作成したら、アプリケーションでは、WsOpenServiceProxy 関数を使用してサービス エンドポイントを開き、サービス プロキシを通じて Web サービスの呼び出しを行うことができます。WsOpenServiceProxy が厳密にどのような処理を行うかは、使用されるトランスポートによって異なります。また、サービス プロキシ オブジェクトは、解放する前に WsCloseServiceProxy 関数を使用して閉じるよう注意する必要があります。もちろん、こうしたハウスキーピング処理はすべて、図 8 に示す単純なクラス内にうまくラップすることができます。これを利用すると、図 9 に示すように、サービス プロキシを作成し、開くことができます。

図 8 サービス プロキシ ラッパー

class WsServiceProxy
{
    WS_SERVICE_PROXY* m_h;
public:
    WsServiceProxy() : m_h(0)
    {}
    ~WsServiceProxy()
    {
        if (0 != m_h)
        {
            Close(0, 0); // error
            WsFreeServiceProxy(m_h);
        }
    }
    HRESULT Open(const WS_ENDPOINT_ADDRESS* address, 
            const WS_ASYNC_CONTEXT* asyncContext, 
            WS_ERROR* error)
    {
        return WsOpenServiceProxy(m_h, address, asyncContext, error);
    }
    HRESULT Close(const WS_ASYNC_CONTEXT* asyncContext, 
            WS_ERROR* error)
    {
        return WsCloseServiceProxy(m_h, asyncContext, error);
    }
    WS_SERVICE_PROXY** operator&()
    {
        return &m_h;
    }
    operator WS_SERVICE_PROXY*() const
    {
        return m_h;
    }
};

図 9 サービス プロキシを作成し開く

WsServiceProxy serviceProxy;

HR(CalculatorBinding_CreateServiceProxy(0, // template value
                                        0, // properties
                                        0, // property count
                                        &serviceProxy,
                                        error));

WS_ENDPOINT_ADDRESS address =
{
    {
        static_cast<ULONG>(wcslen(url)),
        const_cast<PWSTR>(url)
    }
};

HR(serviceProxy.Open(&address,
                     0, // async context
                     error));

送信されるメッセージのアドレスを指定するために、WS_ENDPOINT_ADDRESS 構造体が使用されています。一般に、この構造体は、チャネル層でのプログラミングに使用します。今回は、URL 部分にのみデータを設定しており、残りの部分はサービス プロキシによって処理されます。

ここで、もう 1 つの生成された関数である CalculatorBinding_Add を使用することができます。ご想像のとおり、この関数は、Web サービスの Add 操作を表します。使い方は次のように非常に簡単です。

const double first = 1.23;
const double second = 2.34;
double result = 0.0;

HR(CalculatorBinding_Add(serviceProxy,
                         first,
                         second,
                         &result,
                         heap,
                         0, // properties
                         0, // property count
                         0, // async context
                         error));

ASSERT(3.57 == result);

Web サービスとの通信が終わったら、次のようにサービス プロキシの通信チャネルを閉じる必要があります。

HR(serviceProxy.Close(0, // async context
                      error));

サーバー

クライアント側のプログラミング モデルの中心となるのがサービス プロキシであるのに対して、サーバーは、サービス ホストを作成し管理します。サービス ホストは、提供されたチャネル情報に基づいてさまざまなエンドポイントでリッスンするために必要なランタイムを提供します。ここでも、サービス モデルを使用しているため、詳細のほとんどは抽象化されており、必要な作業はサービスのエンドポイントとホストを作成することだけです。その他の作業は WWS によって行われます。

まずは、サービス エンドポイントを作成します。図 10 に示すように、サービス モデルでは、もう 1 つの生成された関数である CalculatorBinding_CreateServiceEndpoint という形でこれを行います。エンドポイントを作成するには、エンドポイントがどこでリッスンするかを示すアドレスを指定する必要があります。これは WS_STRING 構造体によって提供されています。これは長さが前に付いた Unicode 文字列で、null で終了する必要はありません。エンドポイントは要求を実行する必要があるため、サービスによって公開される操作にマップされる関数ポインターのテーブルを指定する必要があります。生成された CalculatorBindingFunctionTable 構造体がこのために使用されます。最後に、エンドポイント自体は WS_SERVICE_ENDPOINT 構造体で表され、指定されたヒープ内に割り当てられます。

図 10 CalculatorBinding_CreateServiceEndpoint

class WsServiceHost
{
    WS_SERVICE_HOST* m_h;
public:
  WsServiceHost() : m_h(0)
  {}
  ~WsServiceHost()
  {
    if (0 != m_h)
    {
        Close(0, 0); 
        WsFreeServiceHost(m_h);
    }
  }
  HRESULT Create(const WS_SERVICE_ENDPOINT** endpoints, 
          const USHORT endpointCount, 
          const WS_SERVICE_PROPERTY* properties, 
          ULONG propertyCount, WS_ERROR* error)
  {
     return WsCreateServiceHost(endpoints, endpointCount, properties,
                      propertyCount, &m_h, error);
  }
  HRESULT Open(const WS_ASYNC_CONTEXT* asyncContext, WS_ERROR* error)
  {
    return WsOpenServiceHost(m_h, asyncContext, error);
  }
  HRESULT Close(const WS_ASYNC_CONTEXT* asyncContext, WS_ERROR* error)
  {
    return WsCloseServiceHost(m_h, asyncContext, error);
  }
  operator WS_SERVICE_HOST*() const
  {
    return m_h;
  }
};

WsServiceProxy serviceProxy;
const WS_STRING address =
{
    static_cast<ULONG>(wcslen(url)),
    const_cast<PWSTR>(url)
};

CalculatorBindingFunctionTable functions =
{
    AddCallback
};

WS_SERVICE_ENDPOINT* endpoint = 0;

HR(CalculatorBinding_CreateServiceEndpoint(0, // template value
                                           &address,
                                           &functions,
                                           0, // authz callback
                                           0, // properties
                                           0, // property count
                                           heap,
                                           &endpoint,
                                           error));

次は、サービス ホストを作成します。WS_SERVICE_HOST* はサービス ホスト オブジェクトへのハンドルを表し、WsCreateServiceHost はサービス ホスト オブジェクトを作成する関数です。WsFreeServiceHost 関数を呼び出すと、このオブジェクトは解放されます。WsCreateServiceHost 関数では、エンドポイントのリストを受け取ってサービス ホスト オブジェクトを作成します。ここで、WsOpenServiceHost 関数を使用して、すべてのエンドポイントのリスナーを起動することができます。WsCloseServiceHost 関数を使用して通信を停止します。サービス プロキシの場合と同様に、サービス ホストは解放する前に閉じるようにしてください。ここでも、こうしたハウスキーピング処理はすべて、図 11 に示す単純なクラス内にうまくラップすることができます。これを利用すると、次のように Web サービスを開始することができます。

const WS_SERVICE_ENDPOINT* endpoints[] = { endpoint };

WsServiceHost serviceHost;

HR(serviceHost.Create(endpoints,
                      _countof(endpoints),
                      0, // properties
                      0, // property count
                      error));

HR(serviceHost.Open(0, // async context
                    error));

この場合はエンドポイントは 1 つしかありませんが、サービス ホストにエンドポイントをさらに追加するのは非常に容易であることがおわかりいただけるでしょう。サービスを停止するときが来たら、サービス ホストの通信チャネルを閉じればよいだけです。

HR(serviceHost.Close(0, // async context
                     error));

以下に示すように、Web サービス操作を実際に実装するのは、最も簡単な部分の部類に入ります。

HRESULT CALLBACK AddCallback(__in const WS_OPERATION_CONTEXT*, 
                 __in double first, 
                 __in double second, 
                 __out double* result, 
                 __in_opt const WS_ASYNC_CONTEXT* /*asyncContext*/,
                 __in_opt WS_ERROR* /*error*/)
{
    *result = first + second;
    return S_OK;
}

AddCallback 関数のシグネチャはどのように指定すればよいのかについて疑問を持っていらっしゃる方のためにお伝えしておくと、このシグネチャも、生成されたソース コードによって提供されます。

スペース上、今月の記事はこれで終わりにしなければなりませんが、Windows Web サービス API が提供する機能とメリットについて十分理解していただけたのではないでしょうか。ご覧のとおり、ついに、細かい設定なしで使用できる最新の SOAP スタックが C++ 開発者に提供されました。この SOAP スタックは、可能な範囲で最高のパフォーマンスとメモリ使用量を実現し、C++ を少し活用すると非常に魅力的なものとなります。

図 11 サービス ホスト ラッパー

class WsServiceHost
{
    WS_SERVICE_HOST* m_h;
public:
    WsServiceHost() : m_h(0)
    {}
    ~WsServiceHost()
    {
        if (0 != m_h)
        {
            Close(0, 0);
            WsFreeServiceHost(m_h);
        }
    }
    HRESULT Create(const WS_SERVICE_ENDPOINT** endpoints,
            const USHORT endpointCount,
            const WS_SERVICE_PROPERTY* properties,
            ULONG propertyCount, WS_ERROR* error)
    {
        return WsCreateServiceHost(endpoints, endpointCount, properties,
                                            propertyCount, &m_h, error);
    }
    HRESULT Open(const WS_ASYNC_CONTEXT* asyncContext, WS_ERROR* error)
    {
        return WsOpenServiceHost(m_h, asyncContext, error);
    }
    HRESULT Close(const WS_ASYNC_CONTEXT* asyncContext, WS_ERROR* error)
    {
        return WsCloseServiceHost(m_h, asyncContext, error);
    }
    operator WS_SERVICE_HOST*() const
    {
        return m_h;
    }
};

使用状況

マイクロソフトのさまざまなチームが既に、担当の製品やテクノロジの中で Windows Web サービス (WWS) API を採用し始めています。多くの場合、WWS は、カスタムメイドの SOAP スタックの代わりに使用されるようになっています。また、Windows Communication Foundation (WCF) のような商用の実装を WWS に置き換えることにしたチームまでありました。ほんのいくつかの例をご紹介します。

Microsoft Web Services on Devices (WSD) API を使用すると、開発者は、Devices Profile for Web Services (DPWS) に基づいてクライアントやサーバーを作成することができます。Windows 7 の WSD API では、WSD ランタイムが送信する SOAP メッセージ内の XML の作成と正規化に WWS が使用されるようになりました。WSD チームは、新機能を追加し既存のコードをリファクターする際に WWS の使用を拡大することを計画しています。

Windows CardSpace は、Web サービス標準に基づく、マイクロソフトによる ID システムの実装です。Windows CardSpace は、当初は WCF を使用して実装されていましたが、ダウンロード可能なインストーラーとランタイム ワーキング セットのサイズに関する非常に厳しいビジネス要件を満たすように、ネイティブ コードと WWS を使用して書き換えられています。

Microsoft Forefront Threat Management Gateway (TMG) は、ネットワークのパフォーマンスを保証し向上させるためにファイアウォールとキャッシュの機能を提供するセキュリティ プラットフォームです。TMG の URL フィルター機能では、URL を分類するために Microsoft 評価サービスに接続するのに WWS が使用されます。

Windows 公開キー基盤 (PKI) クライアントは、自動登録、およびユーザー/アプリケーション主導の証明書登録により、証明書の自動ライフサイクル管理を提供します。Windows 7 には、従来のような LDAP や DCOM の制限を受けることなく証明書の登録を完了することを可能にする、新しい Web サービスのセットが導入されています。PKI クライアントは、新しい証明書登録ポリシー (MS-XCEP) プロトコルや証明書登録用の WS-Trust 拡張機能 (MS-WSTEP) を含むすべての操作に、WWS を利用します。WWS クライアントは、WCF を使用して実装されている Windows Server 2008 R2 の新しい Active Directory 証明書サービスとも、公の証明書発行者が提供する Web サービスとも通信します。

Kenny Kerr は、Windows 向けのソフトウェア開発を専門にしているソフトウェア設計者です。彼はプログラミングおよびソフトウェア設計に関して執筆を行い、開発者を指導しています。連絡先は weblogs.asp.net/kennykerr (英語) です。