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

公開日: 2007 年 11 月 24 日 (作業者: saad (英語))

更新日: 2008 年 3 月 19 日 (作業者: saad (英語))

はじめに

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

  • マネージ コードと ASP.NET サーバー拡張 API を使用する方法。
  • ネイティブ コードと IIS 7 ネイティブ サーバー拡張 API を使用する方法。

以前のバージョンの IIS とは異なり、サーバー拡張シナリオのほとんどでネイティブ (C++) コードの開発が不要になり、マネージ コードと ASP.NET API によって対応できるようになりました。ASP.NET を使用してサーバーの機能を拡張することで、開発時間が大幅に短縮することに加え、ASP.NET と .NET Framework の豊富な機能を活用できるようになります。ASP.NET による IIS 7 の拡張の詳細については、「.NET を使用した IIS 7 モジュールの開発」を参照してください。

IIS 7 では、以前の IIS のリリースの ISAPI フィルターおよび拡張機能 API に代わって、新しい (C++) ネイティブ コア サーバー API も用意されています。ネイティブ コード開発が必要な場合や、既存のネイティブ ISAPI コンポーネントを変換したい場合は、この API を利用してサーバー コンポーネントを構築します。新しいネイティブ サーバー API では、直観的なオブジェクト モデルによるオブジェクト指向開発が可能で、要求処理をより柔軟に制御でき、シンプルなデザイン パターンを用いて堅牢なコードを作成できます。

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

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

モジュールをコンパイルするには、IIS 7 ヘッダー ファイルを含む Windows SDK をインストールする必要があモジュールをコンパイルするには、IIS 7 ヘッダー ファイルを含む Windows SDK をインストールする必要があります。最新の Windows SDK は、こちら(英語)から入手できます。SDK を登録するには、SDK をインストール後、[スタート] ボタン、[プログラム]、[Microsoft Windows SDK]、[Visual Studio Registration]、[Register Windows SDK Directories with Visual Studio] の順にクリックします。

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

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

ここでは、新しいネイティブ (C++) サーバー API を使用したネイティブ モジュールの開発について説明します。ネイティブ モジュールは Windows DLL であり、以下が含まれます 。

  • エクスポートされた RegisterModule 関数。この関数を使って、モジュール ファクトリの作成や、1 つまたは複数のサーバー イベント用のモジュールを登録します。
  • CHttpModule 基本クラスを継承するモジュール クラスの実装。このクラスは、モジュールの主要機能を提供します。
  • IHttpModuleFactory インターフェイスを実装するモジュール ファクトリ クラスの実装。このクラスは、モジュールのインスタンスを作成します。

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

ネイティブ モジュールのライフサイクルは次のようになります。

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

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

    2.  サーバーに要求が届くと、サーバーは次の処理を行います。

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

それでは、このモジュールを構築してみましょう。

このモジュールの全ソース コードは、Visual Studio IIS7 ネイティブ モジュールのサンプル(英語)から入手できます。以下の手順はモジュール開発の最も重要な部分で、サポート コードやエラー処理は含まれていません。

DLL の読み込み時にサーバーが呼び出す RegisterModule 関数を実装します。このシグネチャおよびネイティブ API の残りの部分は、httpserv.h ヘッダー ファイルで定義されます。このヘッダー ファイルは Platform SDK に含まれています (Platform 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 ファイルを使用して、RegisterModule 関数をエクスポートします。

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

mymodulefactory.h:

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

   virtual void Terminate()
    {
    }

};

このモジュール ファクトリは、IHttpModuleFactory インターフェイスを実装し、各要求についてモジュールのインスタンスを作成します。

サーバーは、各要求の最初に、GetHttpModule メソッドを呼び出して、その要求で使用するモジュールのインスタンスを取得します。これにより、次に実装するモジュール クラス CMyHttpModule の新しいインスタンスが返されます。後ほど見ていきますが、サーバーは各要求について常に新しいインスタンスを作成するので、スレッド セーフであるかどうかを心配することなく、簡単に要求の状態を保存できます。

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

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

モジュール クラスの実装

このクラスでは、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 列挙のいずれかの値となります。モジュールが正常にタスクを実行した場合は、RQ_NOTIFICATION_CONTINUE を返します。これにより、パイプラインが実行を続けます。

障害発生時に、エラーを出力して要求処理を中止する場合は、エラー ステータスを設定して、RQ_NOTIFICATION_FINISH_REQUEST を返します。RQ_NOTIFICATION_PENDING を返すことによって、処理を非同期的に実行し、要求の処理スレッドを解放して別の要求用に再利用することができます (非同期実行については、この記事では説明しません)。

このモジュール クラスは、OnAcquireRequestState イベント ハンドラー メソッドをオーバーライドします。パイプライン ステージで機能を提供するために、モジュール クラスで個々のイベント ハンドラー メソッドをオーバーライドする必要があります。RegisterModule でイベント用に登録していても、モジュール クラスで適切なイベント ハンドラー メソッドをオーバーライドしていない場合、モジュールの実行時にエラーが発生します (デバッグ モードでコンパイルしている場合は、デバッグ時のアサーションがトリガーされます)。オーバーライドするメソッドのメソッド シグネチャが、オーバーライドされる CHttpModule クラスの基本クラス メソッドと完全に同じであることを確認してください。

モジュールのコンパイル

コンパイルには Platform SDK が必要です。Platform SDK の入手方法および Visual Studio で SDK を参照できるようにする方法については、はじめにを参照してください。

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

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

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

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

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

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

まとめ

このチュートリアルでは、新しいネイティブ (C++) 拡張 API を使用して、カスタム ネイティブ モジュールを開発および展開する方法について説明しました。ネイティブ (C++) サーバー API の詳細については、IIS 7 SDK ドキュメントを参照してください。

マネージ コードおよび .NET Framework を使用した IIS 7 の拡張の詳細については、「.NET を使用した IIS 7 モジュールの開発」を参照してください。IIS 7 モジュールの管理の詳細については、モジュールの概要に関するホワイト ペーパーを参照してください。