Entwickeln eines nativen C\C++-Moduls für IIS 7.0

von Mike Volodarsky

Einführung

Mit IIS 7.0 und höher ist es möglich, den Server durch Module zu erweitern, die auf zwei Arten entwickelt werden:

  • Verwenden von verwaltetem Code und der ASP.NET Servererweiterungs-APIs
  • Verwenden von systemeigenem Code und den nativen IIS-Servererweiterungs-APIs

Im Gegensatz zu den vorherigen Versionen von IIS ist für die meisten Servererweiterungsszenarien keine systemeigene (C++)-Codeentwicklung erforderlich und kann mit verwaltetem Code und den ASP.NET-APIs berücksichtigt werden. Wenn Sie ASP.NET verwenden, um den Server zu erweitern, können Sie die Entwicklungszeit erheblich reduzieren und die umfangreichen Funktionen von ASP.NET und .NET Framework nutzen. Weitere Informationen zum Erweitern von IIS mit ASP.NET finden Sie unter Entwickeln eines IIS-Moduls mit .NET.

IIS stellt auch eine systemeigene C++-Kernserver-API bereit, die ISAPI-Filter- und Erweiterungs-API aus früheren IIS-Versionen ersetzt. Wenn Sie bestimmte Anforderungen haben, die systemeigene Codeentwicklung erfordern oder Ihre vorhandenen systemeigenen ISAPI-Komponenten konvertieren möchten, nutzen Sie diese API zum Erstellen von Serverkomponenten. Die neue systemeigene Server-API bietet objektorientierte Entwicklung mit einem intuitiven Objektmodell, bietet mehr Kontrolle über die Anforderungsverarbeitung und verwendet einfachere Entwurfsmuster, um robusten Code zu schreiben.

Diese exemplarische Vorgehensweise untersucht die folgenden Aufgaben:

  • Entwickeln eines systemeigenen Moduls mithilfe der systemeigenen Server-API (C++)
  • Bereitstellen eines systemeigenen Moduls auf dem Server

Um das Modul zu kompilieren, müssen Sie das Platform SDK installieren, das die IIS-Headerdateien enthält. Das neueste Windows Vista Platform SDK finden Sie hier.

Um das Platform SDK mit Visual Studio 2005 zu verwenden, müssen Sie das SDK registrieren. Nachdem Sie das SDK installiert haben, tun Sie dies über Start > Programme > Microsoft Windows SDK > Visual Studio-Registrierung > Windows SDK-Verzeichnisse mit Visual Studio registrieren.

Der Quellcode für dieses Modul ist im Beispiel für ein systemeigenes Visual Studio IIS7-Modul verfügbar.

Entwickeln eines systemeigenen Moduls

In dieser Aufgabe untersuchen wir die Entwicklung eines systemeigenen Moduls mithilfe der neuen systemeigenen Server-API (C++). Ein systemeigenes Modul ist eine Windows-DLL, die Folgendes enthält:

  • Die exportierte Funktion RegisterModule. Diese Funktion ist für das Erstellen einer Modulfactory und das Registrieren des Moduls für mindestens ein Serverereignisse verantwortlich.
  • Die Implementierung der Modulklasse, die von der Basisklasse CHttpModule erbt. Diese Klasse stellt die Hauptfunktionalität Ihres Moduls bereit.
  • Die Implementierung der Modulfactoryklasse, die die Schnittstelle IHttpModuleFactory implementiert. Die Klasse ist für die Erstellung von Instanzen Ihres Moduls zuständig.

Hinweis

In einigen Fällen können Sie auch die Schnittstelle IGlobalModule implementieren, um einige der Serverfunktionen zu erweitern, die nicht mit der Anforderungsverarbeitung verbunden sind. Dies ist ein weiterführendes Thema und es wird in dieser exemplarischen Vorgehensweise nicht behandelt.

Ihr systemeigenes Modul verfügt über den folgenden Lebenszyklus:

  1. Wenn der Serverarbeitsprozess gestartet wird, wird die DLL geladen, die Ihr Modul enthält, und die exportierte Funktion RegisterModule wird aufgerufen. In dieser Funktion:

    a. Erstellen Sie die Modulfactory.
    b. Registrieren Sie die Modulfactory für die Anforderungspipelineereignisse, die Ihr Modul implementiert.

  2. Wenn eine Anforderung eingeht, tut der Server Folgendes:

    a. Er erstellt eine Instanz Ihrer Modulklasse mithilfe der von Ihnen bereitgestellten Factory.
    b. Er ruft für jede Anforderung, für die Sie sich registriert haben, die entsprechende Ereignisbehandlungsmethode in der Modulinstanz auf.
    c. Er entfernt die Instanz des Moduls am Ende der Anforderungsverarbeitung.

Kommen wir nun zum Erstellen.

Der vollständige Quellcode für das Modul ist im Beispiel für ein systemeigenes Visual Studio IIS7-Modul verfügbar. Die folgenden Schritte sind für die Entwicklung des Moduls am wichtigsten und enthalten keine unterstützenden Code und Fehlerbehandlung.

Implementieren Sie die Funktion RegisterModule, die der Server aufruft, wenn die Modul-DLL geladen wird. Die Signatur und der Rest der nativen API werden in der Headerdatei httpserv.h definiert, die Teil des Platform SDK ist (wenn Sie nicht über das Platform SDK verfügen, finden Sie in der Einführung Informationen zum Abrufen):

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 );            
}

Das RegisterModule

Es gibt drei grundlegende Aufgaben, die wir innerhalb des RegisterModule ausführen müssen:

Speichern des globalen Zustands

Wir speichern die globale Serverinstanz und die Modulkontext-ID für die spätere Verwendung in globalen Variablen. In diesem Beispiel werden diese Informationen zwar nicht verwendet, aber für viele Module ist es nützlich, sie zu speichern und später bei der Bearbeitung von Anforderungen zu verwenden. Die Schnittstelle IHttpServer bietet Zugriff auf viele Serverfunktionen, z. B. das Öffnen von Dateien und den Zugriff auf den Cache. Die Modulkontext-ID wird verwendet, um den benutzerdefinierten Modulstatus mehreren Serverobjekten wie Anforderung und Anwendung zuzuordnen.

Erstellen der Modulfactory

Wir implementieren unsere Factoryklasse CMyHttpModuleFactory weiter unten in diesem Beispiel. Diese Factory ist für die Herstellung von Instanzen unseres Moduls für jede Anforderung verantwortlich.

Registrieren der Modulfactory für die gewünschten Anforderungsverarbeitungsereignisse

Die Registrierung erfolgt über die Methode SetRequestNotificatons, die den Server anweist, die Modulinstanz für jede Anforderung mithilfe der angegebenen Factory zu erstellen und die entsprechenden Ereignishandler für jede der angegebenen Anforderungsverarbeitungsphasen aufzurufen.

In diesem Fall interessieren wir uns nur für die Phase RQ_ACQUIRE_REQUEST_STATE. Die vollständige Liste der Phasen, aus denen die Anforderungsverarbeitungspipeline besteht, wird in httpserv.h definiert:

#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

Darüber hinaus können Sie mehrere nicht deterministische Ereignisse abonnieren, die während der Anforderungsverarbeitung auftreten können, aufgrund von Aktionen, die andere Module ausführen, z. B. das Leeren der Antwort auf den Client:

#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

Damit auf unsere Implementierung RegisterModule auf den Server zugegriffen werden kann, müssen wir sie exportieren. Verwenden Sie eine .DEF-Datei, die das Schlüsselwort EXPORTS enthält, um unsere Funktion RegisterModule zu exportieren.

Implementieren Sie als Nächstes die Modulfactoryklasse:

mymodulefactory.h:

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

   virtual void Terminate()
    {
    }

};

Die Modulfactory implementiert die Schnittstelle IHttpModuleFactory und dient zum Erstellen von Modulinstanzen nach jeder Anforderung.

Der Server ruft die Methode GetHttpModule am Anfang jeder Anforderung auf, um die Instanz des Moduls abzurufen, die für diese Anforderung verwendet werden soll. Die Implementierung gibt einfach eine neue Instanz unserer Modulklasse CMyHttpModule zurück, die wir als Nächstes implementieren. Wie wir gleich sehen werden, können wir den Anforderungsstatus problemlos speichern, ohne uns Gedanken über Threadsicherheit zu machen, da der Server immer eine neue Instanz des Moduls für jede Anforderung erstellt und verwendet.

Fortschrittlichere Fabrikimplementierungen können ein Singleton-Muster verwenden, anstatt jedes Mal eine neue Instanz zu erstellen, oder die bereitgestellte Schnittstelle IModuleAllocator verwenden, um Modulspeicher im Anforderungspool zuzuweisen. Diese erweiterten Muster werden in diesem Beispiel nicht erläutert.

Die Methode Terminate wird vom Server aufgerufen, wenn der Arbeitsprozess heruntergefahren wird, um die endgültige Bereinigung des Moduls durchzuführen. Wenn Sie einen globalen Zustand in RegisterModule initialisieren, implementieren Sie die Bereinigung in dieser Methode.

Implementieren der Modulklasse

Diese Klasse ist für die Bereitstellung der Hauptfunktionalität des Moduls während eines oder mehrerer Serverereignisse verantwortlich:

myhttpmodule.h:

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

Die Modulklasse erbt von der Basisklasse CHttpModule, die eine Ereignishandlermethode für jedes der zuvor erläuterten Serverereignisse definiert. Wenn die Anforderungsverarbeitungspipeline jedes Ereignis ausführt, ruft sie die zugeordnete Ereignishandlermethode für jede der Modulinstanzen auf, die für dieses Ereignis registriert wurden.

Jede Ereignishandlermethode weist die folgende Signatur auf:

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

Die Schnittstelle IHttpContext bietet Zugriff auf das Anforderungskontextobjekt, das verwendet werden kann, um Anforderungsverarbeitungsaufgaben auszuführen, z. B. das Überprüfen der Anforderung und das Bearbeiten der Antwort.

Die Schnittstelle IHttpEventProvider wird durch eine spezifischere Schnittstelle für jedes Ereignis ersetzt, das bestimmte Funktionen für das Modul bereitstellt. Beispielsweise empfängt der Ereignishandler OnAuthenticateRequest die Schnittstelle IAuthenticationProvider, mit der das Modul den authentifizierten Benutzer bzw. die authentifizierte Benutzerin festlegen kann.

Die Rückgabe jeder Ereignishandlermethode ist einer der Werte der Enumeration REQUEST_NOTIFICATION_STATUS. Sie müssen RQ_NOTIFICATION_CONTINUE zurückgeben, wenn das Modul die Aufgabe erfolgreich ausgeführt hat. Die Pipeline sollte die Ausführung fortsetzen.

Wenn ein Fehler aufgetreten ist und Sie die Anforderungsverarbeitung mit einem Fehler abbrechen möchten, müssen Sie den Fehlerstatus festlegen und RQ_NOTIFICATION_FINISH_REQUEST zurückgeben. Mit der Rückgabe RQ_NOTIFICATION_PENDING können Sie asynchron Arbeiten ausführen und die Threadverarbeitung der Anforderung zulassen, damit sie für eine andere Anforderung wiederverwendet werden kann. Die asynchrone Ausführung wird in diesem Artikel nicht behandelt.

Unsere Modulklasse setzt die Ereignishandlermethode OnAcquireRequestState außer Kraft. Um Funktionen in einer der Pipelinephasen bereitzustellen, muss die Modulklasse die entsprechende Ereignishandlermethode überschreiben. Wenn Sie sich in RegisterModule für ein Ereignis registrieren, aber die entsprechende Ereignishandlermethode in Ihrer Modulklasse nicht überschreiben, wird Ihr Modul zur Runtime fehlschlagen (und eine Debugzeit Assertion auslösen, wenn im Debugmodus kompiliert wurde). Achten Sie darauf, dass die Methodensignatur der überschreibenden Methode genau der Basisklassenmethode der Klasse CHttpModule entspricht, die Sie überschreiben.

Kompilieren des Moduls

Denken Sie daran, dass Sie zum Kompilieren das Platform SDK benötigen. Weitere Informationen zum Abrufen und Aktivieren von Visual Studio zum Verweisen darauf finden Sie in der Einführung.

Bereitstellen eines systemeigenen Moduls

Nachdem Sie das Modul kompiliert haben, müssen Sie es auf dem Server bereitstellen. Kompilieren Sie das Modul, und kopieren Sie dann IIS7NativeModule.dll (und bei Bedarf die Debugsymboldatei IIS7NativeModule.pdb) an einen beliebigen Speicherort auf dem Computer, auf dem IIS ausgeführt wird.

Systemeigene Module müssen im Gegensatz zu verwalteten Modulen, die direkt zur Anwendung hinzugefügt werden können, zuerst auf dem Server installiert werden. Dies erfordert Administratorberechtigungen.

Ein systemeigenes Modul können Sie auf verschiedene Arten installieren:

  • Verwenden des Befehlszeilentools APPCMD.EXE
    APPCMD vereinfacht die Modulinstallation. Wechseln Sie zu Start>Programme>Zubehör, klicken Sie mit der rechten Maustaste auf die Eingabeaufforderung der Befehlszeile, und wählen Sie „Als Administrator ausführen“ aus. Führen Sie im Befehlszeilenfenster Folgendes aus:
    %systemroot%\system32\inetsrv\appcmd.exe install module /name:MyModule /image:[FULL\_PATH\_TO\_DLL]
    Dabei ist [FULL_PATH_TO_DLL] der vollständige Pfad zur kompilierten DLL, die das soeben erstellte Modul enthält.
  • Verwenden des IIS-Verwaltungstools
    Auf diese Weise können Sie ein Modul mithilfe einer GUI hinzufügen. Wechseln Sie zu Start>Ausführen, geben Sie „inetmgr“ ein, und drücken Sie die Eingabetaste. Stellen Sie eine Verbindung mit localhost her, suchen Sie die Aufgabe „Module“, und doppelklicken Sie darauf, um sie zu öffnen. Klicken Sie dann im rechten Bereich auf die Aufgabe Systemeigenes Modul hinzufügen.
  • Manuelles Installieren des Moduls
    Installieren Sie das Modul manuell, indem Sie es zum Konfigurationsabschnitt <system.webServer>/<globalModules> in der Konfigurationsdatei applicationHost.config hinzufügen, und fügen Sie einen Verweis auf das Modul im Konfigurationsabschnitt <system.webServer>/<modules> in derselben Datei hinzu, um es zu aktivieren. Sie sollten eine der beiden vorherigen Optionen verwenden, um das Modul zu installieren, und die Konfiguration nicht direkt bearbeiten.

Die Aufgabe ist abgeschlossen – wir haben die Konfiguration des neuen systemeigenen Moduls abgeschlossen.

Zusammenfassung

In diesem Beispiel haben Sie erfahren, wie Sie ein benutzerdefiniertes systemeigenes Modul mithilfe der neuen nativen Erweiterbarkeits-APIs (C++) entwickeln und bereitstellen. Weitere Informationen zu den systemeigenen (C++)-Server-APIs finden Sie in der Übersicht über die Entwicklung von systemeigenem Code.

Informationen zum Erweitern von IIS mithilfe von verwaltetem Code und .NET Framework finden Sie unter Entwickeln eines IIS-Moduls mit .NET. Weitere Informationen zum Verwalten von IIS-Modulen finden Sie im Whitepaper zur Modulübersicht.