用VS2008开发ISAPI程序

因为ISAPI扩展和调用它的进程(IIS)在同一的进程地址空间中,这样它们就可以互相直接联系。这种方式一个最大的隐患就是会导致整个IIS当机,而且在有些时候是整个网站当掉。看一下下面的图:

你看到,如果ISAPI扩展程序遇到问题且处理不当,将会影响整个网站的服务进程。就像上图所示,ISAPI扩展和IIS的通讯是通过一个ECBExtension Control Block)结构的指针,其结构然如下:

typedef struct _EXTENSION_CONTROL_BLOCK

{

   DWORD     cbSize;                 // size of this struct.

   DWORD     dwVersion;              // version info of this spec

   HCONN     ConnID;                 // Context number not to be modified!

   DWORD     dwHttpStatusCode;       // HTTP Status code

   CHAR      lpszLogData[HSE_LOG_BUFFER_LEN];// null terminated log info

 

   LPSTR     lpszMethod;             // REQUEST_METHOD

   LPSTR     lpszQueryString;        // QUERY_STRING

   LPSTR     lpszPathInfo;           // PATH_INFO

   LPSTR     lpszPathTranslated;     // PATH_TRANSLATED

 

   DWORD     cbTotalBytes;           // Total bytes indicated from client

   DWORD     cbAvailable;            // Available number of bytes

   LPBYTE    lpbData;                // pointer to cbAvailable bytes

 

   LPSTR     lpszContentType;        // Content type of client data

 

   BOOL (WINAPI * GetServerVariable) (HCONN hConn,

   LPSTR      lpszVariableName,

   LPVOID     lpvBuffer,

   LPDWORD    lpdwSize );

 

   BOOL (WINAPI * WriteClient)  (HCONN ConnID,

   LPVOID     Buffer,

   LPDWORD    lpdwBytes,

   DWORD      dwReserved );

 

   BOOL (WINAPI * ReadClient)  (HCONN ConnID,

   LPVOID     lpvBuffer,

   LPDWORD    lpdwSize );

 

   BOOL (WINAPI * ServerSupportFunction)( HCONN hConn,

   DWORD      dwHSERequest,

   LPVOID     lpvBuffer,

   LPDWORD    lpdwSize,

   LPDWORD    lpdwDataType );

 

}EXTENSION_CONTROL_BLOCK, *LPEXTENSION_CONTROL_BLOCK;

无论是调用进程还是ISAPI扩展,它们之间的任何信息都是通过EBC来要传递给对方的。我们已简单的看了一下ECB结构。现在,我们来看看IIS是如何通过与ISAPI扩展进行通讯,来为网站访问者服务的。

当一个ISAPI扩展被访问(比如:http://www.mydomain.com/script/example.dll? ID=p05874 & Tx=870250AZT6)时,IIS会检查example.dll是否已经被加载进内存中。如果没有加载,IIS会加载。一旦DLL被加载进内存,一个工作线程便开始运行我们的ISAPI扩展程序(example.dll)。先是DLL的入口函数DllMain被调用。调用完成后,IIS开始调用GetExtensionVersion函数,这个函数主要实现了下面两个功能:

  1. 报告ISAPI可以实现的扩展服务
  2. 取得一个简短的描述扩展的字符串。

然后IIS开始调用HttpExtensionProc函数。这个函数会传递一个ECB指针给ISAPI扩展以开始真正的调用。通过这个函数ISAPI可以向客户端(如浏览器)回写数据。过会儿我们会检查。

第三个也是最后一个ISAPI扩展的入口函数是TerminateExtension函数。它在当ISAPI扩展从内存中卸载的时候调用。所有的清除代码可以在这个函数中实现。

简言之,一个ISAPI扩展是一个导出三个函数的非常规范的DLL

  1. GetExtensionVersion
  2. HttpExtensionProc
  3. TerminateExtension (可选)

手头有了这些资料,我们开始DllMain的代码编写。它是所有的DLL的入口点函数。

DLLMain——入口点函数

就像微软说的,DllMain是每个DLL的入口函数,它是可选的。如DLL中定义了DllMain,在进程或线程初始化/中止时,或者当使用LoadLibrary加载和FreeLibrary卸载时调用这个入口点函数。这个如何函数原型如下:

BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwCallReason, LPVOID lpReserved);

如果你的ISAPI扩展程序中提供这个过程,那么它将在初始化和结束时被被调用。dwCallReason参数可以是下面预定义值之一:

  • DLL_PROCESS_ATTACHED
  • DLL_THREAD_ATTACH
  • DLL_THREAD_DETACH
  • DLL_PROCESS_DETACH

对每个参数的详细描述超出了本文讨论范围,有兴趣的读者可以到MSDN上了解更多关于本函数的信息。

再有,我们可以保存hMoudle参数以便以后的使用。最后只是简单的返回一个TURE值。我们在开发扩展程序时,通常在这个函数中不做什么事。

GetExtensionVersion——真正的入口函数

这个函数是IIS调用的第一个入口函数,是ISAPI扩展用来向IIS提供信息用的。为了进一步的了解这个函数,我们来看一下这个函数的原型:

BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO *pVer);

在这个函数的调用时,我们假定使用pver参数来填写扩展信息,HSE_VERSION_INFO结构如下:

typedef struct _HSE_VERSION_INFO

{

   DWORD dwExtensionVersion;

   CHAR lpszExtensionDesc[HSE_MAX_EXT_DLL_NAME_LEN];

}HSE_VERSION_INFO, *LPHSE_VERSION_INFO;

dwExtensionVersion是扩展的版本号,lpszExtensionDescription是扩展的描述。如果返回TRUE便表示我们告诉IIS我们的扩展程序准备好了,可以使用了;而返回FALSE则表示IIS不可以使用本扩展。

HttpExtensionProc——主要入口函数

一个ISAPI扩展的令人着迷部分是HttpExtensionProc。目前你所能想到的是这个函数可以用来向客户端回写数据。为了看明白这是怎么发生的,我们来一下这个函数原型:

DWORD WINAPI HttpExtensionProc(EXTENSION_CONTROL_BLOCK *pECB);

这里pECB是一个扩展控制块(ECB)的指针。这个结构用来解决IISISAPI扩展之间的通讯问题。在这个函数中,你可以决定你的网页中包含什么内容,如何返回给用户,怎样做?还想起来这个结构的成员了?ECB成员中包含了一个方法,原型如下:

BOOL WriteClient(HCONN ConnID, LPVOID Buffer, LPDWORD lpdwBytes, DWORD dwSync);

使用这个函数,你可以将数据放入缓冲区中,呈现给用ConnId标识的客户端。比如说,要想发送 “A BIG RED ROSE”这几个字给客户,你可以简单地进行下面的调用:

char szBigRedRos[] =

  "<font color='#FF0000' size='3'><b>A BIG RED ROSE</b></font>";

DWORD dwSize = strlen(szBigRedRos);

pECB->WriteClient(pECB->ConnID, szBigRedRose, dwSize, 0);

哈哈,到此为止。我想你已经拥有最基础的知识来开发你自己的第一个ISAPI扩展程序了。让我们开始吧。。。

项目需求

1.              一点点耐性

2.              一个MSVC++6编译器

3.              一个装有IISMS WINDOWS2000的操作系统

4.              一个网页浏览器

目标

我们决定不用MFC而用WIN32 API来开发开发一个ISAPI扩展程序。这个程序的功能是检查Master Card (万事达)卡号是否有效。我们给这个ISAPI扩展DLL命名为Validate.dll。在这个扩展程序中将简单地往客户端写一串字符以告诉客户:你所提供的卡号是否有效。当然,我们一定会检查,如果用户使用下面的URL地址:

http://mydomain/script/validate.dll?some%20string

http://mydomain/script/validate.dll?

或者相似的无效URL地址的情况(当然,所谓“无效”是从我们程序员的角度来看的,使用者可不这么想)。

当上面这种无效URL地址的情况发生时,我们将简单地回复下面一段文字到客户端:What you have entered is an invalid Master Card #.

上面就是我们的ISAPI扩展所能做的一切。

 

1、打开Visual Studio 2008

 

2、打开解决方案资源管理器视图->选择项目->添加新建项->C++文件(.cpp)

3、根据第二步,再添加一个模块定义文件(.def)

 

6、打开解决方案资源管理器视图->选择项目->属性->配置->所有配置->平台->所有平台

  • 常规->输出目录:$(SolutionDir)$(PlatformName)\$(ConfigurationName)
  • 常规->中间目录:$(PlatformName)\$(ConfigurationName)
  • 常规->配置类型:动态库(.dll)
  • 常规->MFC使用:使用标准 Windows 库
  • 常规->字符集:未设置
  • 常规->全程序优化:使用链接时间代码生成
  • C/C++->常规->调试信息格式:程序数据库(/Zi)
  • (如果需要编译64位的ISAPI)C/C++->常规->检测64位可移植性问题:是(/Wp64)

 

 

 

代码:

 1 #include <windows.h>
 2 #include <stdio.h>
 3 #include <stdlib.h>
 4 //#include <httpfilt.h>
 5 #include <httpext.h> //ISAPI扩展的头文件
 6 
 7 void WriteContext(EXTENSION_CONTROL_BLOCK *pECB, char *pszFormat, );
 8 void StartContext(EXTENSION_CONTROL_BLOCK *pECB);
 9 void EndContext(EXTENSION_CONTROL_BLOCK *pECB);
10 
11 BOOL APIENTRY DLLMain(HANDLE hModule, DWORD dwCallReason, LPVOID lpReserved)
12 {
13     return TRUE;
14 }
15 
16 BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO *pVer)
17 {
18     pVer->dwExtensionVersion = HSE_VERSION;
19     strncpy(pVer->lpszExtensionDesc, "My first ISAPI program", HSE_MAX_EXT_DLL_NAME_LEN);
20     return TRUE;
21 }
22 
23 DWORD WINAPI HttpExtensionProc(EXTENSION_CONTROL_BLOCK *pECB)
24 {
25     StartContext(pECB);
26     WriteContext(pECB, "<p>this is my first ISAPI program!!hello money!!</p>");
27     EndContext(pECB);
28     return HSE_STATUS_SUCCESS;
29 }
30 
31 BOOL WINAPI TerminateExtension(DWORD dwFlags)
32 {
33     return TRUE;
34 }
35 
36 void WriteContext(EXTENSION_CONTROL_BLOCK *pECB, char *pszFormat, )
37 {
38     char szBuffer[1024];
39     va_list arg_ptr;
40     va_start(arg_ptr, pszFormat);
41     vsprintf(szBuffer, pszFormat, arg_ptr);
42     va_end(arg_ptr);
43 
44     DWORD dwSize = strlen(szBuffer);
45     pECB->WriteClient(pECB->ConnID, szBuffer, &dwSize, 0);
46 }
47 
48 void StartContext(EXTENSION_CONTROL_BLOCK *pECB)
49 {
50    WriteContext(pECB, "<html>\r\n<body>\r\n");
51 }
52  
53 void EndContext(EXTENSION_CONTROL_BLOCK *pECB)
54 {
55    WriteContext(pECB, "</body>\r\n</html>");

56 } 

 

其中EXTENSION_CONTROL_BLOCK结构用来和IIS通信

 

 

 

posted @ 2009-11-11 10:34  firefly_liu  阅读(4177)  评论(1编辑  收藏  举报