IIS 7.0 用のネイティブ C\C++ モジュールの開発

作成者: Mike Volodarsky

はじめに

IIS 7.0 以降では、次の 2 つの方法で開発されたモジュールによってサーバーを拡張できます。

  • マネージド コードと ASP.NET サーバー拡張機能 API の使用
  • ネイティブ コードと IIS ネイティブ サーバー拡張機能 API の使用

以前のバージョンの IIS とは異なり、サーバー拡張シナリオの大部分はネイティブ (C++) コード開発を必要とせず、マネージド コードと ASP.NET API を使用して対応できます。 ASP.NET を使用してサーバーを拡張すると、開発時間を大幅に短縮し、ASP.NET と .NET Framework の豊富な機能を利用できます。 ASP.NET を使用した IIS の拡張の詳細については、.NET を使用した IIS モジュールの開発に関する記事を参照してください。

IIS には、以前の IIS リリースの ISAPI フィルターと拡張 API に代わる (C++) ネイティブ コア サーバー API も用意されています。 ネイティブ コード開発を必要とする特定の要件がある場合、または既存のネイティブ ISAPI コンポーネントを変換したい場合は、この API を利用してサーバー コンポーネントを構築します。 新しいネイティブ サーバー API は、直感的なオブジェクト モデルを使用したオブジェクト指向の開発を備え、要求処理をより細かく制御でき、よりシンプルな設計パターンを使用して堅牢なコードを記述できます。

このチュートリアルでは、次のタスクについて説明します。

  • ネイティブ (C++) サーバー API を使用したネイティブ モジュールの開発
  • サーバーへのネイティブ モジュールのデプロイ

モジュールをコンパイルするには、IIS ヘッダー ファイルを含む Platform SDK をインストールする必要があります。 最新の Windows Vista Platform SDK がここに用意されています。

Visual Studio 2005 でプラットフォーム SDK を使用するには、SDK を登録する必要があります。 SDK をインストールした後は、[スタート] > [プログラム] > [Microsoft Windows SDK] > [Visual Studio の登録] > [Windows SDK Directories と Visual Studio の登録] を選択してこれを実行します。

このモジュールのソース コードは、Visual Studio IIS7 ネイティブ モジュール サンプルで入手できます。

ネイティブ モジュールの開発

このタスクでは、新しいネイティブ (C++) サーバー API を使用したネイティブ モジュールの開発について説明します。 ネイティブ モジュールは、次を含む Windows DLL です。

  • RegisterModule エクスポートされた関数。 この関数は、モジュール ファクトリを作成し、1 つ以上のサーバー イベントにモジュールを登録する役割を担います。
  • CHttpModule 基底クラスから継承するモジュール クラスの実装。 このクラスは、モジュールの主な機能を提供します。
  • IHttpModuleFactory インターフェイスを実装するモジュール ファクトリ クラスの実装。 このクラスは、モジュールのインスタンスを作成する役割を担います。

Note

場合によっては、要求処理に関連しないサーバー機能の一部を拡張するために、IGlobalModule インターフェイスを実装することもできます。 これは高度なトピックであり、このチュートリアルでは説明しません。

ネイティブ モジュールのライフ サイクルは次のとおりです。

  1. サーバー ワーカー プロセスが開始されると、モジュールを含む DLL が読み込まれ、エクスポートされた RegisterModule 関数が呼び出されます。 この関数では、次の操作を行います。

    a. モジュール ファクトリを作成します。
    b. モジュールが実装する要求パイプライン イベントのモジュール ファクトリを登録します。

  2. 要求が到着すると、サーバーは次の処理を行います。

    a. 指定したファクトリを使用して、モジュール クラスのインスタンスを作成します。
    b. 登録した要求イベントごとに、モジュール インスタンスで適切なイベント ハンドラー メソッドを呼び出します。
    c. 要求処理の最後にモジュールのインスタンスを破棄します。

ここで、ビルドします。

このモジュールのソース コード全体は、Visual Studio IIS7 ネイティブ モジュール サンプルで入手できます。 以下の手順はモジュールの開発に最も重要であり、サポートコードとエラー処理は含まれていません。

モジュール DLL が読み込まれるときにサーバーが呼び出す RegisterModule 関数を実装します。 その署名とネイティブ API の残りの部分は、プラットフォーム SDK の一部である httpserv.h ヘッダー ファイルで定義されています (プラットフォーム SDK がない場合は、取得方法に関する「概要」を参照してください)。

main.cpp:

HRESULT        
__stdcall        
RegisterModule(        
    DWORD                           dwServerVersion,    
    IHttpModuleRegistrationInfo *   pModuleInfo,
    IHttpServer *                   pHttpServer            
)
{
   // step 1: save the IHttpServer and the module context id for future use 
    g_pModuleContext = pModuleInfo->GetId();
    g_pHttpServer = pHttpServer;

    // step 2: create the module factory 
    pFactory = new CMyHttpModuleFactory();

    // step 3: register for server events 
    hr = pModuleInfo->SetRequestNotifications( pFactory, 
                                              RQ_ACQUIRE_REQUEST_STATE,
                                               0 );            
}

RegisterModule

RegisterModule: 内では、次の 3 つの基本的なタスクを実行する必要があります。

グローバル状態の保存

グローバル サーバー インスタンスと、後でグローバル変数で使用するためのモジュール コンテキスト ID を格納します。 この例ではこの情報は使用されませんが、多くのモジュールでは、後で要求の処理中に保存して使用すると便利です。 IHttpServer インターフェイスは、ファイルの開き方やキャッシュへのアクセスなど、多くのサーバー関数へのアクセスを提供します。 モジュール コンテキスト ID は、カスタム モジュールの状態を、要求やアプリケーションなどの複数のサーバー オブジェクトに関連付けるために使用されます。

モジュール ファクトリの作成

このチュートリアルの後半で、ファクトリ クラス CMyHttpModuleFactory を実装します。 このファクトリは、要求ごとにモジュールのインスタンスを製造する役割を担います。

必要な要求処理イベントのモジュール ファクトリの登録

登録は SetRequestNotificatons メソッドを使用して行われます。このメソッドは、サーバーに指示します。指定したファクトリを使用して要求ごとにモジュール インスタンスを作成し、指定された要求処理ステージごとに適切なイベント ハンドラーを呼び出します。

この場合、RQ_ACQUIRE_REQUEST_STATE ステージのみを扱います。 要求処理パイプラインを構成するステージの一覧全体は、httpserv.h で定義されています。

#define RQ_BEGIN_REQUEST               0x00000001 // request is beginning 
#define RQ_AUTHENTICATE_REQUEST        0x00000002 // request is being authenticated             
#define RQ_AUTHORIZE_REQUEST           0x00000004 // request is being authorized 
#define RQ_RESOLVE_REQUEST_CACHE       0x00000008 // satisfy request from cache 
#define RQ_MAP_REQUEST_HANDLER         0x00000010 // map handler for request 
#define RQ_ACQUIRE_REQUEST_STATE       0x00000020 // acquire request state 
#define RQ_PRE_EXECUTE_REQUEST_HANDLER 0x00000040 // pre-execute handler 
#define RQ_EXECUTE_REQUEST_HANDLER     0x00000080 // execute handler 
#define RQ_RELEASE_REQUEST_STATE       0x00000100 // release request state 
#define RQ_UPDATE_REQUEST_CACHE        0x00000200 // update cache 
#define RQ_LOG_REQUEST                 0x00000400 // log request 
#define RQ_END_REQUEST                 0x00000800 // end request

さらに、クライアントへの応答のフラッシュなど、他のモジュールが実行するアクションのために要求処理中に発生する可能性があるいくつかの確定的でないイベントにサブスクライブできます。

#define RQ_CUSTOM_NOTIFICATION         0x10000000 // custom notification 
#define RQ_SEND_RESPONSE               0x20000000 // send response 
#define RQ_READ_ENTITY                 0x40000000 // read entity 
#define RQ_MAP_PATH                    0x80000000 // map a url to a physical path

RegisterModule 実装にサーバーからアクセスできるようにするには、エクスポートする必要があります。 RegisterModule 関数をエクスポートするための EXPORTS キーワードを含む .DEF ファイルを使用します。

次に、モジュール ファクトリ クラスを実装します。

mymodulefactory.h:

class CMyHttpModuleFactory : public IHttpModuleFactory
{
public:
    virtual HRESULT GetHttpModule(
        OUT CHttpModule            **ppModule, 
        IN IModuleAllocator        *
    )
            
    {
    }

   virtual void Terminate()
    {
    }

};

モジュール ファクトリは IHttpModuleFactory インターフェイスを実装し、各要求でモジュールのインスタンスを作成する役割を果たします。

サーバーは、すべての要求の開始時に GetHttpModule メソッドを呼び出して、この要求に使用するモジュールのインスタンスを取得します。 この実装は、次に実装するモジュール クラス CMyHttpModule の新しいインスタンスを単に返します。 このすぐあとに説明しますが、これにより、サーバーは常に要求ごとにモジュールの新しいインスタンスを作成して使用するため、スレッド セーフを気にせずに要求の状態を簡単に格納できます。

より高度なファクトリ実装では、毎回新しいインスタンスを作成する代わりにシングルトン パターンを使用するか、指定された IModuleAllocator インターフェイスを使用して要求プールにモジュール メモリを割り当てることもできます。 このような高度なパターンについては、このチュートリアルでは説明しません。

Terminate メソッドは、ワーカー プロセスがシャットダウンしてモジュールの最終的なクリーンアップを実行するときに、サーバーによって呼び出されます。 RegisterModule でグローバル状態を初期化する場合は、このメソッドでそのクリーンアップを実装します。

Module クラスの実装

このクラスは、1 つ以上のサーバー イベント中にモジュールの主な機能を提供します。

myhttpmodule.h:

class CMyHttpModule : public CHttpModule
{
public:
    REQUEST_NOTIFICATION_STATUS
    OnAcquireRequestState(
        IN IHttpContext *                       pHttpContext,
        IN OUT IHttpEventProvider *             pProvider
    );
};

モジュール クラスは CHttpModule 基底クラスを継承します。これは、前述した各サーバー イベントのイベント ハンドラー メソッドを定義します。 要求処理パイプラインは、各イベントを実行すると、そのイベントに登録されている各モジュール インスタンスで、関連付けられたイベント ハンドラー メソッドを呼び出します。

各イベント ハンドラー メソッドには、次のシグネチャがあります。

REQUEST_NOTIFICATION_STATUS
    OnEvent(
        IN IHttpContext *                       pHttpContext,
        IN OUT IHttpEventProvider *             pProvider
    );

IHttpContext インターフェイスは、要求の検査や応答の操作などの要求処理タスクを実行するために使用できる要求コンテキスト オブジェクトへのアクセスを提供します。

IHttpEventProvider インターフェイスは、モジュールに特定の機能を提供する各イベントのより具体的なインターフェイスに置き換えられます。 たとえば、OnAuthenticateRequest イベント ハンドラーは、モジュールが認証されたユーザーを設定できるようにする IAuthenticationProvider インターフェイスを受け取ります。

各イベント ハンドラー メソッドの戻り値は、REQUEST_NOTIFICATION_STATUS 列挙の値の 1 つです。 モジュールがタスクを正常に実行した場合は、RQ_NOTIFICATION_CONTINUE を返す必要があります。パイプラインは実行を続行する必要があります。

エラーが発生し、エラーで要求処理を中止する場合は、エラーの状態を設定し、RQ_NOTIFICATION_FINISH_REQUEST を返す必要があります。 RQ_NOTIFICATION_PENDING 戻り値を使用すると、非同期的に作業を実行し、要求を処理するスレッドを手放して、別の要求に再利用できます。 非同期実行については、この記事では説明しません。

このモジュール クラスは、OnAcquireRequestState イベント ハンドラー メソッドをオーバーライドします。 いずれかのパイプライン ステージで機能を提供するには、モジュール クラスがそれぞれのイベント ハンドラー メソッドをオーバーライドする必要があります。 RegisterModule でイベントに登録しても、モジュール クラスの適切なイベント ハンドラー メソッドをオーバーライドしない場合、モジュールは実行時に失敗します (デバッグ モードでコンパイルされた場合はデバッグ時アサーションをトリガーします)。 オーバーライドするメソッドのメソッド シグネチャが、オーバーライド対象の CHttpModule クラスの基底クラス メソッドと正確に等しいことを注意深く確認してください。

モジュールのコンパイル

コンパイルするには Platform SDK が必要です。 これを取得して Visual Studio で参照できるようにする方法の詳細については、「概要」を参照してください。

ネイティブ モジュールのデプロイ

モジュールをコンパイルしたら、それをサーバーにデプロイする必要があります。 モジュールをコンパイルし、IIS7NativeModule.dll (および必要に応じて IIS7NativeModule.pdb デバッグ シンボル ファイル) を IIS を実行しているコンピューター上の任意の場所にコピーします。

ネイティブ モジュールは、アプリケーションに直接追加できるマネージド モジュールとは異なり、まずサーバーにインストールする必要があります。 これには管理特権が必要です。

ネイティブ モジュールをインストールするには、いくつかのオプションがあります。

  • APPCMD.EXE コマンド ライン ツールの使用
    APPCMD を使用すると、モジュールのインストールが簡単になります。 [スタート]>[プログラム]>[アクセサリ] に移動し、コマンド ライン プロンプトを右クリックして、[管理者として実行] を選択します。 コマンド ライン ウィンドウで、次のコマンドを実行します。
    %systemroot%\system32\inetsrv\appcmd.exe install module /name:MyModule /image:[FULL\_PATH\_TO\_DLL]
    ここで、[FULL_PATH_TO_DLL] は、ビルドしたモジュールを含むコンパイル済み DLL への完全なパスです。
  • IIS 管理ツールの使用
    これにより、GUI を使用してモジュールを追加できます。 [スタート] > [ファイル名を指定して実行] に移動し、「inetmgr」と入力して Enter キーを押します。 localhost に接続し、モジュール タスクを見つけてダブルクリックして開きます。 次に、右側のウィンドウの [ネイティブ モジュールの追加] タスクを選択します。
  • モジュールの手動インストール
    モジュールを applicationHost.config 構成ファイルの <system.webServer>/<globalModules> 構成セクションに追加して手動でインストールし、同じファイルの <system.webServer>/<modules> 構成セクションに参照を追加して有効にします。 構成を直接編集するのではなく、前の 2 つのオプションのいずれかを使用してモジュールをインストールすることをお勧めします。

このタスクは完了です。新しいネイティブ モジュールの構成が完了しました。

まとめ

このチュートリアルでは、新しいネイティブ (C++) 拡張 API を使用して、カスタム ネイティブ モジュールを開発してデプロイする方法について説明しました。 ネイティブ (C++) サーバー API の詳細については、「ネイティブ コード開発の概要」を参照してください。

マネージド コードと .NET フレームワークを使用して IIS を拡張する方法については、.NET を使用した IIS モジュールの開発に関する記事を参照してください。 IIS モジュールの管理の詳細については、モジュールの概要に関するホワイト ペーパーを参照してください。