Developing ISAPI extensions and filters can be frustrating. Many of the debugging tips and tricks you've used in the past don't work when for ISAPI. Even the most basic features of your debugger have to be changed or customized to step through your ISAPI code.
This chapter gives you tips, techniques, and debugging hints to build extensions and filters into your Web server. Even if you have built ISAPI applications in the past, this chapter gives you the insight to make future ISAPI applications solid, safe, and durable.
In this chapter, you learn the following:
Many problems with ISAPI filters and extensions can be traced back to a few common problems. For example, a filter that doesn't load is usually the result of incorrectly declaring the ISAPI functions or forgetting to put a few needed libraries in the search path of the server.
So before you go crazy trying to attach your debugging environment to your Web server, you may want to read this section to find out if you have forgotten something obvious.
Let's say you've just compiled a fancy ISAPI extension and put it into your Scripts directory. You're all ready to try it out so you fire up your browser and enter the URL that should give you the results from your extension. Instead of your results, your browser gives you the message in Figure 17.1.
A server message resulting from bad extension exports.
Maybe you've created an ISAPI filter. You've registered it and now you start your Web server. Everything seems okay since your Web server started, but you suddenly realize that your filter is not really loaded.
You check the Windows NT system event log and see the following message: "The HTTP Filter DLL badfilter.dll failed to load. The data is the error." (By the way, you get a similar message when an ISAPI extension fails to load.)
Your Web server is trying to load your DLL but it can't find a function it is looking for. The function responsible for this error is usually GetExtensionVersion(), in the case of extensions, or GetFilterVersion(), in the case of filters.
This problem is usually in one (or more) of the following three categories:
Using the Wrong Calling Convention
One reason that your ISAPI extension or filter is not working may be your use of the wrong calling convention. Since the designers of ISAPI do not know the language your application will be programmed in, they must make a few assumptions about the way your function appears in your DLL.
The first assumption, the way parameters are expected on the stack, is provided to you in the function declarations in the ISAPI header files. Below are the two declarations for the DLL entry points of an ISAPI extension:
BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO *pVer);
DWORD WINAPI HttpExtensionProc(EXTENSION_CONTROL_BLOCK *pECB);
The keyword WINAPI is a macro expanded as stdcall. As you may have guessed, this is the default calling convention for all Win32 functions. See your handy compiler reference to learn more about how the stdcall calling convention puts parameters on the stack.
The only trick to calling conventions is when you are programming in C++. By default, C++ uses its own name decoration. Name decoration is the way a function is declared in the object file by the compiler before it is linked with the right library files.
Since Win32 functions use C, not C++, name decoration, you need to enclose your ISAPI entry point functions with the extern "C" block. Therefore, the prototypes for an ISAPI extension when programming in C++ should look like this:
extern "C" {
BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO *pVer);
DWORD WINAPI HttpExtensionProc(EXTENSION_CONTROL_BLOCK *pECB);
}
Once you've declared your ISAPI entry points, your Web site should have no problem finding its functions.
Forgetting to Export Your ISAPI Functions
Another common error that results in an extension giving your browser the "procedure not found" message or a filter not loading is when you forget to export your DLL's functions. Your compiler has not found any problems with the way you have declared your ISAPI entry point functions.
But you have not told it that these functions should be visible to processes outside your library. Functions that are expected to be called from outside your library are called exported functions.
The normal way to export a function in C++ is to use the __declspec(dllexport) keyword in front of your function declaration. However, the ISAPI functions are already declared as __stdcall and the compiler won't let you have both.
The way around this is to export your functions using a .DEF file. This is the standard way of exporting functions from a library when programming in C. Listing 17.1 shows a typical .DEF file for an ISAPI extension. The function names after the EXPORTS keyword tell the linker to export these functions, no matter how they are declared.
Listing 17.1 LST17_1.DEF-Sample ISAPI Extension .DEF File
LIBRARY "Sample"
DESCRIPTION "Sample ISAPI Extension"
EXPORTS
GetExtensionVersion
HttpExtensionProc
After compiling and linking your ISAPI DLL, you may want to see if the two ISAPI entry point functions have indeed been exported. Microsoft Visual C++ users can use the DUMPBIN.EXE utility in the \MSDEV\BIN directory.
Using the DUMPBIN myfilter.dll /EXPORTS command lists all the exported functions in the myfilter.dll library. The absence of your functions from the output of this program tell you that you have probably forgotten to include the .DEF file when linking your ISAPI DLL.
Missing Files Needed by Your ISAPI DLL
Another reason that your ISAPI extension or filter does not load may be that your library can't find dependent files. For instance, if you write an ISAPI filter that uses MFC, your filter automatically needs the runtime MFC DLLs.
Put all dependent files in the same directory as your or extension or filter, or put them in the system32 directory. Older versions of Microsoft's Internet Information Server (IIS) used LoadLibrary() to load your extension or filter and would not search your DLL's path for any dependent libraries or files.
Version 3.0 of IIS uses LoadLibraryEx() with the LOAD_WITH_ALTERED_SEARCH_PATH flag. This causes the operating system to first search the directory of the loading library for any dependent files.
Improper file or directory permissions may also cause your DLL's load to fail. Remember that a thread running an ISAPI extension always impersonates an account on your system. Also remember that anonymous requests from a browser impersonate the default anonymous account on your Web server.
A thread running an ISAPI filter always runs under the context of the system account. Therefore, if your DLL is not loading, make sure your library, all dependent files, and the directory holding the files have the appropriate permissions.
One way to see if file or directory permissions are causing problems is to go into User Manager on your server (USRMGR.EXE), choose Policies, Audit , and set the Failure check box of File and Object Access, as shown in Figure 17.2.
![]()
How to set audit policies in User Manager.
Any process trying to access a file with insufficient security causes an event to appear in the Windows NT security event log. The error tells you which file is failing security and the user account that is trying to access it.
One last note: any thread running under the security context of the system account has no privileges over the network. So make sure your filter DLL and all dependent files are located on the same machine as your Web server.
![]()
Let's say you know that your ISAPI filter has properly exported its ISAPI entry point functions and that all dependent files your filter uses are in the search path. Now you want to register your ISAPI DLL with your Web server so that it loads when you restart your filter. You open the following registry key:
\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W3SVC\
Parameters
You examine the FilterDLLs entry and see that there is already a filter DLL in this key. By default, the sspifilt.dll secured sockets filter is placed in this registry key when IIS is installed. Where should you put your filter?
Unfortunately, Microsoft did not make the FilterDLLs registry value of the REG_MULTI_SZ type (a multilined text entry). Therefore, you'll need to string your filter names together. How you delimit them makes a world of difference.
The bottom line is: separate file names in the FilterDLLs registry entry with a comma. Using anything else either causes only the first filter DLL to load or causes no filter DLLs to load. In either case, there is no entry in the Windows NT system event log explaining the problem.
Remember that the order in which you specify your DLLs in the registry makes a difference. Your Web server calls your filter DLLs using the SF_NOTIFY_ORDER specified in each filter's GetFilterVersion() function. Then it uses the order specified in this registry entry to determine the priority for DLLs that have the same SF_NOTIFY_ORDER.
To guarantee that your filter gets called first, use SF_NOTIFY_ORDER_HIGH and make sure it is the first DLL in the FilterDLLs list. Conversely, to guarantee that your filter gets called last, use SF_NOTIFY_ORDER_LOW and make sure it is the last DLL in the FilterDLLs list.
![]()
Before you start coding your ISAPI extension or filter, you may want to read through this section to get a few tips and suggestions.
Since you probably do not have the source code to your Web server, you have to debug your ISAPI DLL using more creative methods. This section should give you a few ideas and some precautions you can take to minimize the amount of debugging you need to do later on in the development process.
Although this may be more of a debugging tip, I'm telling you this now to save you frustration later on. Before you start coding, make sure that your development machine is running Windows NT Server as well as Microsoft's IIS. This allows you to easily set up certain scenarios that may need phone calls to system administrators, or worse, long walks to other computers.
For example, you may want to change the security permissions for a particular document without affecting other Web users. Also, when you are developing ISAPI filters, you tend to take the server up and down a lot. Depending on your situation, this may be a nuisance for other users.
If you plan on doing a lot of database work, I'd recommend that you not place a development database server on your machine unless, of course, your production database will be on the same computer as your Web server. Most problems when you are developing against a database server on another computer have to do with security.
This is especially true if you are using Microsoft SQL Server with integrated security turned on. You can work out many of these problems early on if you start development with your database on another computer.
Although I probably already sound like a Microsoft salesman, I'll give you a piece of advice: you can buy from Microsoft the developer's network that gives you a "limited" version of Back Office, including Windows NT Server and SQL Server.
This version usually limits you to a total of 10 server connections. But that should be plenty when you are testing your ISAPI applications. You can then effectively simulate the platform for which you are developing.
If you prefer not to put Windows NT Server and IIS on your system and you plan to do your developing on Windows NT Workstation or Windows 95, I recommend you read the section on "Remote Debugging" later in this chapter. It gives you a step-by-step description explaining how you can attach Microsoft's Development Studio to IIS via a remote link.
I got this idea and a few others from a technical article in Microsoft's Knowledge Base, which I recommend reading as a supplement to this chapter. The article is "Tips for Debugging ISAPI DLLs" and the Article ID is Q152054. You can get this article from Microsoft's Web site or from the Developer's Network CD.
Since all DLLs use the heap of the calling process (in this case, the Web server), a memory problem in your ISAPI extension or filter may seem to be coming from your Web server. For example, an error in your routine that fills a dynamically allocated character buffer may overwrite pieces of data used internally by your Web server.
These problems may go undetected for quite some time before you track them down. Tracking memory leaks in your ISAPI DLL is even harder since you can't step through your Web server's source code.
A good way to ensure that your ISAPI extension filter does not overwrite your Web server's heap is to create your own heap. Creating your own heap has the following advantages:
Since you know your data needs best, you should decide how to manage them. If you are doing a lot of allocating and freeing of identically sized blocks of memory, you'll definitely want to create your own heap.
The Win32 HeapCreate() and HeapDestroy() functions, as their names imply, create and destroy a heap that is used only by your application. Once created, the HeapAlloc() and HeapFree()functions can be used to allocate and free memory in your new heap.
By default, all these heap functions are serialized. This means you don't have to worry about one thread allocating a block of memory in your heap while another thread is deleting it. The code behind these functions makes sure that this never happens.
Using your own heap also allows you to contain and detect memory leaks. You may find it advantageous to wrap the HeapAlloc() and HeapFree() functions with your own thread- safe functions that keep track of the amount of memory you have allocated. When your application terminates, you can check your heap size to see if you have forgotten to clean up any memory.
For those of you programming in C++, you can build new heap functions right into your classes. A class that is frequently allocated and deallocated is a good candidate for its own heap.
Your class keeps a pointer to its own heap in a private static data member. You override the new and delete operators to allocate memory in your class's own private heap.
Always remember that even though the Win32 heap functions are serialized, you still need to make your new and delete functions thread-safe. One tip is to have a static data member keep track of the number of instances of your class that are allocated. Then put a debug message in the destructor to inform you of this value.
Remember that building "smart" memory allocation routines up front may take you a bit of time. But it is well worth the effort when you are trying to nail down memory leaks in your ISAPI extension filter.
You should always check the return value of ISAPI functions (and of all Win32 functions). Almost all ISAPI functions return a TRUE or a FALSE, indicating their success. It is up to you, however, to call the Win32 GetLastError() function to retrieve the reason that the ISAPI function failed.
One of the more common ISAPI functions you'll call is GetServerVariable(). The pointer to this function is both the EXTENSION_CONTROL_BLOCK for ISAPI extensions and the HTTP_FILTER_CONTEXT for ISAPI filters.
Here is a list of possible error codes, as specified in WINERROR.H, that can be returned by GetServerVariable():
ERROR_INVALID_PARAMETER | The pointer to either the control block or the filter context is invalid. |
ERROR_INVALID_INDEX | The variable passed to GetServerVariable() does not exist. This variable was probably not supplied by the client. |
ERROR_INSUFFICIENT_BUFFER | The buffer supplied to GetServerVariable() was not big enough and no attempt was made to fill it. The size of the needed buffer is passed back from the function in the lpdwSize parameter of GetFilterVersion(). |
ERROR_MORE_DATA | The buffer supplied to GetServerVariable() was not big enough. The buffer was filled, but the size of the needed buffer is not returned. |
ERROR_NO_DATA | The data requested is not available from the server. In other words, the variable exists but is not associated with any data. |
C++ developers should take notice that although ISAPI functions do not throw their own exceptions, Microsoft's IIS does catch any exceptions that you may throw. If IIS catches an unhandled exception, it writes an error to the Windows NT application event log.
When writing ISAPI DLLs, you should always be aware that you are composing a multithreaded application. You need to protect all global and static data so that it is not corrupted by updates from separate threads.
Detecting problems from thread synchronization can be tricky. Most errors are not obvious and are hard to reproduce.
I recommend stress testing all your applications with both an automated stress-testing tool and heavy user testing. To stress-test a multithreaded application, you probably need to write another multithreaded application. Spending a little time configuring a testing tool or building a custom stress-testing application is worth it in the long run.
The new Win32 Internet functions like InternetOpen() and HttpOpenRequest() make it easy to program custom tools that access your Web server. You may even find it worthwhile to build an application that takes a log file as input. Then open a variable number of threads to reproduce all the commands in the log.
![]()
Knowing when and where to provide thread synchronization may not be obvious at first. Here are a few hints for writing multithread-safe ISAPI extensions and filters:
After a bit of practice, programming a multithreaded application will become second nature. Remember, threads are your friends. When used properly, they can make your application much more efficient. For more information on thread safety, see Chapter 18, "Making Your Extensions Thread-Safe."
The hardest thing about writing any DLL is debugging. You might even say that debugging an ISAPI DLL is twice as hard as debugging any other DLL.
The following problems come to mind when debugging an ISAPI filter or extension:
In this section, I describe a number of different techniques to help you with development and debugging. Following them will save you many hours of frustration.
When debugging an ISAPI extension, the first thing you want to do is to turn off extension caching. By default, Microsoft's IIS constantly analyzes the usage of ISAPI extensions.
If the server determines that a particular extension is called frequently, it keeps the extension in memory. This is great for performance but bad for debugging.
To have IIS unload your DLL after every call, open the registry key:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W3SVC\
Parameters
The CacheExtensions registry is by default set to 1, which means all ISAPI extension DLLs can be cached. Change this setting to 0 when you are debugging an ISAPI extension.
Do not leave this value set to 0 for your production Web server. If you do, you'll have a severe performance penalty as your filter is constantly loaded, unloaded, and reloaded. Obviously, this wouldn't be much better than writing a CGI application.
Normally, when you debug a DLL, you can inform your development environment of the executable that will be calling your DLL. Then, when you debug your DLL, the development environment runs the executable and stops at any break points you have put in the DLL.
The trouble with this scenario is obvious. The process that calls your ISAPI DLL is not a "normal" executable. It is a service. The difference between running an application as a service and running an application from the command line of a user account is as follows:
I mentioned earlier that when running as a service, Microsoft's IIS must use the local system account. This is true. But you can also run the server from the command line of a logged-in account. Great! Then it should be no problem to debug your DLL without leaving the development environment, right?
By now you should know that things are not that easy. First, I'll explain a couple of ways you can do this. Then I'll show you a few problems with running Microsoft's IIS interactively.
Configuring Visual C++ for Local Debugging
Running Microsoft's IIS from the Visual C++ development environment or from the command shell is easy. Begin by setting certain permissions on the user account that will be running the server.
Users in the administrator group by default have all the necessary user rights except two. You need to make sure that the two advanced user rights listed below are set before you try to run the service from your account. You set the check box marked Show Advanced User Rights to make these settings visible in User Manager.
The two advanced user rights allow the user to
Once these advanced user rights are set, your account should have the permissions to run Microsoft's IIS. If you are logged into an account that is affected by these changes, you need to log off and log back on for these changes to take effect.
Next, you need to tell the Visual C++ development environment which executable should be run to invoke your DLL. To do this, go into the Build, Settings dialog box.
Select the Debug property sheet and enter the path to your inetinfo.exe file in the Executable for debug session: edit box. Enter the -e W3SVC string in the Program arguments: edit box.
Figure 17.3 shows the settings to run IIS on my machine for interactive debugging.
The inetinfo.exe application is usually installed in the inetsvc directory. This executable is just a stub program that loads the appropriate Internet service. By specifying the -e W3SVC string on the command line, you are telling the inetinfo application to load only the Web server. The inetinfo application loads the W3SVC.DLL library, which holds the HTTP service.
![]()
Debug options for running IIS interactively.
Of course, you should also make sure that your Web server is loading your debugged image. If you are writing a filter, the FilterDLL's registry setting should point to the DLL in your development directory.
If you are writing an extension, the logical path that the browser is accessing must map to the physical path of the development directory holding your DLL. If this is not possible, see "Attaching the Debugger to the Service" later in this chapter.
Before you start the debugger, you should also make sure that all services started by the inetinfo application (Web server, ftp server, and gopher server) are not running. Since this involves stopping the Web server, debugging your extension interactively may not be suitable for your test environment.
You can now put break points in your code and execute the debugger from within the Visual C++ development environment with the normal Build, Debug, Go menu command. It may take a few seconds for your debugger to start the Web server and load your DLL, its symbols, and all the dependent files.
Configuring Visual C++ for Remote Debugging
Remote debugging is like local debugging. The only difference is that your debugger sends data to the debugged process over the network via the TCP/IP protocol. So you need an application supplied with Visual C++ for debugging tasks, such as starting and stopping the application, on the remote computer.
The advantage of remote debugging is that your development environment can be on a separate machine from your Web server. You can develop on a computer running Windows 95, copy your ISAPI extension or filter to a remote Windows NT server, and debug your application without having to visit the server. This is great for those of you who do can't run a Windows NT server on your desktop.
The disadvantage of remote debugging is that you are still running the Web server as an interactive application and not as a service. See the next section to learn what problems arise out of running IIS as an interactive application.
Since there are quite a few steps in setting up a remote debugging session, I break it into two sections: configuring the remote computer and configuring the local (development) computer.
Setting Up the Remote Computer
The Visual C++ debug monitor.
TCP/IP Settings for Remote Debugging.
Setting up the Local Computer
Additional DLLs for remote debugging.
Setting up remote debugging can be a hassle at first. But once set up, it is a powerful tool that allows you to debug from different operating systems on different computers. Now that I've explained how to interactively debug Microsoft's IIS, I'll discuss a few problems that may make you think twice about debugging this way.
Potential Problems
There are a couple of problems with running Microsoft's IIS interactively. Running the Web server from the command line is close to but not exactly like running the Web server on the local system account.
First off, you are running your Web server on an account that is different from the one normally used. Your account may have permissions to access a file, whereas the local system account does not.
This is especially true for network connections. Since the local system account has no authority over the network, it can't access files that are not on the local computer.
ODBC data sources are another tricky area. Since ODBC data sources by default are registered for a specific user, your Web server has access to that data source when running interactively but not when running as a service.
You can easily rectify this by specifying that your ODBC data source be set up as a System data source. Notice the button on the bottom of Figure 17.7, which is labeled System DSN This sets up a data source that any user, including the local system account, can use.
Notice the System DSN button.
The Microsoft Knowledge Base also says that authentication filters do not work properly when you run IIS interactively this way. Although I have had success debugging an authentication filter this way, I would nevertheless be wary about running IIS from your account to debug authentication filters.
Since there are probably a few other quirks that you'll only discover when running IIS as a service, I recommend that you do the initial phase of your development using interactive debugging. Then start attaching your debugger to the inetinfo application (see the next section) as the development process for your DLL winds down. This ensure that your development environment closely mirrors that of a production system.
As I have just discussed, running your Web server interactively from the debugger is a quick way to step through your ISAPI extension or filter DLL. Debugging this way, however, does not provide a real-life representation of how your Web server works.
To closely mimic a "production" system, have your Web server running as a service when you debug your code. The Win32 API and today's debuggers offer simple ways to debug an application that is executing.
In the next few sections, I describe how to use tools provided with the operating system, Visual C++, and assembly code to debug a running process. Each method attaches the debugger to a particular process and yields control to your debugger whenever a thread hits a break point in your code. The only requirement for this type of debugging is that your Web server be running on the same machine as your debugger.
Since the first two methods involve first starting your Web server and then attaching the debugger to the Web server process, it is impractical for debugging a filter's GetFilterVersion() function. This function is called before the Web server finishes its initialization. You have to use the third debugging method, hardcoding break points, to debug a filter's GetFilterVersion() function.
All the following debugging methods allow you to step through your source code as your code is executed. A debugger can do this only after your linker generates a program database file (.pdb file).
This file cross-references your DLL's executable code with lines of source code. It also lets you keep track of variables and the call stack. The file usually needs to reside in the same directory as your DLL.
Using TASKMGR
The first method for attaching your debugger to your Web server's process uses the Task Manager application supplied with Windows NT version 4.0. The Task Manager takes care of starting your debugger and attaching the process.
First, start your Web server. Next, run the Task Manager application (TASKMGR.EXE is in the Windows NT system32 directory) and select the Processes property sheet. You see a list of all processes that are executing on your machine. Right-clicking the inetinfo.exe application shows you a pop-up menu. Task Manager should now look like Figure 17.8.
Using Task Manager to debug inetinfo.exe.
Choosing the Debug menu command launches your debugger and attaches the inetinfo.exe application to it. After your debugger has started, you can load your ISAPI DLL's source file and put break points in your code.
The debugger monitors the running process and pauses when it reaches a break point. If you have trouble setting break points, you may want to try one of the following:
Note that once the debugger is attached to your Web server's process, there is no way to detach it short of stopping the server. This is usually not a problem, but keep it in mind when you have several people using your test Web server.
Running MSDEV
Another alternative for attaching the debugger to the Web server process is to use the Microsoft Visual C++ development environment executable command line parameters. Starting with Visual C++ 4.0, the development environment executable is MSDEV.EXE and is found by default in the \MSDEV\BIN directory. The command line parameter -p is an undocumented switch that tells the development environment to attach to a certain process.
The syntax is
MSDEV -p pid
where pid is the process id for the process you want to debug. This can be in decimal format (for instance, 155) or in hexadecimal format (for instance, 0x9B). When debugging ISAPI extensions or filters, you need to get the process id of the inetinfo.exe application using one of the following tools:
Once you start MSDEV using the -p command line argument, the development environment attaches itself to the inetinfo.exe application. You can now open your source code files and put in break points. If your break points are not triggering, read the previous section about entering your DLL file name in the development environment.
You may also find it useful to know that attaching the Microsoft Visual C++ development environment generates an .mdp file for every debugged application. For instance, attaching the debugger to inetinfo.exe creates the inetinfo.mdp file.
This file holds development environment information, including a list of source code files that have been opened, break points in these files, and project settings. If you save the .mdp file after you attach your debugger to the process, it loads automatically whenever you debug the same application.
Hardcoding Break Points
When all other alternatives to debugging have failed, you can always count on hardcoding a break-point statement into your code. This is not a flexible solution since you obviously can't place a new break point while the code is executing. But it's guaranteed to work.
Just put the following line into your source code:
#ifdef _DEBUG
#define DEBUG_BREAK _asm int 3
#else
#define DEBUG_BREAK
#endif
Now all you need to do when you want to hardcode a break point into your application is to put the DEBUG_BREAK keyword into your code. If you are running in debug mode, this embeds the int 3 assembly language command into your application.
When executed, this command causes software interrupt 3. When the operating system learns that this interrupt has occurred, Windows NT pops up the dialog box shown in Figure 17.9.
Entering the debugger from a user break-point dialog.
Pressing the OK button halts the Web server process. But pressing the Cancel button from this dialog box invokes your debugger and attaches it to the Web server process. If everything is configured properly, your debugger should put the cursor directly on the DEBUG_BREAK line in your source code file.
In case you were browsing through the Win32 reference and noticed the DebugBreak() function, forget about it. Using the DebugBreak() function from your ISAPI extension does indeed stop execution of the Web server.
But the debugger thinks that the break occurred in the operating system kernel and not in your source code. Therefore, you won't able to look at variables or the call stack of your DLL. Stick with the method outlined above.
![]()
You may want to debug your ISAPI extension or filter without using a Web server or a browser for two reasons:
In this section, I give you a few hints on writing your own stub application. I also talk about the one provided with this book: EyeSAPI. I am sure you'll find that developing ISAPI extensions is much easier when you can control all input to your application via a stub application.
Writing a Stub Application
There are quite a few things to consider before you write a stub application. For instance, your stub application must have the same interface as your Web server. Return codes must be the same, functions must be the same, and the function-calling sequence must be the same as your Web server's.
Writing a stub application for an ISAPI filter is harder than writing one for an extension. ISAPI filters are called with a "filter context" to achieve a browser session. Remember that the filter entry point is called numerous times during one browser request.
This means that your filter may depend on the calling process (normally the Web server but in this case the stub program) to keep state information that is consistent between function calls. Also, ISAPI filters have data parameters that are passed into the filter entry point. These parameters are associated with the notification type.
![]()
You'll be writing an application that does the following:
Only after you have programmed all these steps into an executable (and debugged it) can you finally debug your ISAPI application. Was it worth it? Probably not. Don't you wish this was already done for you? Probably yes.
Well, the EyeSAPI application on the CD-ROM does exactly what I described above for ISAPI extensions. For now, you need to debug ISAPI filters using the methods discussed earlier in this chapter or use your own stub application.
Using EyeSAPI
The EyeSAPI application gives you an alternate interface to your ISAPI extension. Testing different browser scenarios against your ISAPI extension is easy since all browser header fields can be manipulated directly from the EyeSAPI application. Once you start EyeSAPI, the main dialog looks like Figure 17.10.
The EyeSAPI application.
As you can see, many edit boxes allow you to customize the call to your extension. Using EyeSAPI allows you to simulate almost any type of browser action that your extension is faced with. Specifically, you can:
As a developer, you often output variables and messages that tell you the status of your application. While your application runs in debug mode, it might be a good idea to dump the contents of things like collection classes, or to print messages when objects are allocated or deallocated.
This proactive debugging reduces the amount of time you spend doing just-in-time debugging. And as I've already emphasized in this chapter, debugging an ISAPI DLL using a just-in-time debugger can be a real hassle.
You may also need to communicate errors and informational messages to whoever will be maintaining your ISAPI DLL. For instance, a user should be notified of memory exceptions or error results returned by your Web server.
Since your ISAPI extension or filter is loaded by a process without a desktop, much less a main window, finding ways to output messages can be quite a challenge. The three places where you may want to put messages relating to the status of your ISAPI DLL are log files, a debug window, and the Windows NT application event log.
Depending on the type of message, you'll probably want to display the output at a different destination. Table 17.1 gives you a guideline for the best place to output your information.
Table 17.1 Appropriate Places to Write ISAPI Informational Messages
Message Type | Log File | Debug Window | Event Log |
Debug Messages | X | X | |
Object/Variable Dumps | X | ||
Status Messages | X | X | X |
Error Messages | X |
In the rest of this section I explain some of the advantages and disadvantages of using each output type.
Writing to a Log File
The obvious place to write debugging information and messages is to a log file. I'm sure you're familiar with opening up a log file and appending messages to it using commands like fopen and fprintf. A log file may seem like a great place to write messages. But it has two problems: serialization and location.
Although you probably shouldn't be writing messages to files in a production system, it is a great time-saver to write messages to files for debug information. For instance, a failed assertion should not pop up a dialog box in your ISAPI extension. Since a failed assertion would never happen in a release version of your code, you may not want to write it to the Windows NT event log but to write such events to a debug file.
Another good candidate for file logging is an object dump. If you dump the contents of a container class whenever an item is added or deleted, you probably want this information in a file.
Displaying Info in a Debug Window
The next best thing to writing information to a file is to write it to a debug window. Notice that when I say window, I don't mention whose window.
Since you're aware that ISAPI extensions and filters are loaded by a process that does not own a window, you need to write to the window of another process. Fortunately, the Win32 API gives you functions to do this.
The Win32 OutputDebugString()function is declared as follows:
VOID OutputDebugString(LPCTSTR lpOutputString);
The lpOutputString is a pointer to a string, which, as you've probably guessed, holds the message to be displayed in a debug window. What is a debug window, you ask? It's an application that waits for certain operating system events indicating that another process has generated a debug message.
The debug window application gets the string for this message from the operating system and displays it in a window. The debugger provided with most compilers has the same function.
But you probably don't have your development environment on every test Web server. Luckily, the DBMON application supplied in the Win32 SDK has the same function. The DBMON application looks like a "blank" console application until a another process calls the OutputDebugString() function.
When this happens, DBMON displays the id of the process that generated the message, followed by the text of the message. Figure 17.11 shows how DBMON displays the messages generated by my authentication filter.
Sample debug output from DBMON.EXE.
Using the OutputDebugString() function is a simple way to have your application display debug messages-without running your ISAPI extension or filter in the debugger.
Recording to the Windows NT Application Event Log
By far the best destination for displaying informational messages to an administrator or Web server operator is the Windows NT event log. Writing to the event log is harder than writing to a file. But you get these benefits:
The only down side to writing to the Windows NT event log is setting up your messages. Although Microsoft created the Win32 event log functions for portability and internationalization, setting up the messages to put into the log is a hassle at first.
You might think that you can use the RegisterEventSource() and ReportEvent() functions right out of the box. But you need to do the following first:
Only after you do these steps can you start writing to the Windows NT event log. You'll want to refer to the Win32 and message compiler documentation to learn the exact steps.
The goal of this chapter is to give you information that makes the development of an ISAPI extension or filter easier. Some of the information in this chapter took me days to figure out. Let's hope that these are debugging tips and techniques that you would normally only learn from trial and error.
From here, you can explore the following chapters to learn more about ISAPI extensions and filters: