IIS7 modules vs. IIS6 ISAPI: Memory Management

For post #3 in the IIS7 Modules vs. IIS6 ISAPI series, let’s look at one of the biggest trouble areas that bite ISAPI developers - managing memory.

If you develop software that uses Win32 APIs, you should be familiar with the feeling you get when you look at the MSDN documentation of a fairly simple function, and see that it returns a string or a set of bytes.

The problem, of course, is that you as a caller are responsible for allocating a buffer of a fixed size, and providing this buffer to the function so it can receive the data.

Sounds simple, right? Well, not really. Don’t forget that you also have the handle the case where the buffer is too small to receive the data, AND, you are responsible for cleaning up the buffer when you done.

Let’s take the ISAPI extension GetServerVariable function as an example - this function is frequently called by ISAPI extensions to obtain various request information, such as the normalized URL of the request:

BOOL WINAPI GetServerVariable( 
       HCONN hConn, 
       LPSTR lpszVariableName, 
       LPVOID lpvBuffer, 
       LPDWORD lpdwSizeofBuffer
);

Here is a simplified (!!!) code sample of a utility functions that uses GetServerVariable to obtain server variable values:

#define DEFAULT_BUFFER_SIZE   100

#define MAXIMUM_BUFFER_SIZE   512

 

HRESULT

GetServerVariableIIS6(

    EXTENSION_CONTROL_BLOCK *  pEcb,

    PSTR                       pszName,

    PSTR*                      ppszValue

)

{

    //

    //  Allocate a buffer of default size

    // 

    HRESULT hr        = S_OK;

    BOOL    fRet      = FALSE;

    DWORD   dwLength  = DEFAULT_BUFFER_SIZE;

    PSTR    pszBuffer = new CHAR[dwLength];

 

    if ( pszBuffer == NULL )

    {

        hr = HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY );

        goto Finished;

    }

 

    //

    //  Try to get the server variable

    //

    fRet = pEcb->GetServerVariable( pEcb->ConnID,

                                    pszName,

                                    pszBuffer,

                                    &dwLength );

    //

    //  Check for insufficient size, but only allow up to

    //  a bounded size for the value

    //

    if ( !fRet &&

           GetLastError() == ERROR_INSUFFICIENT_BUFFER &&

           dwLength < MAXIMUM_BUFFER_SIZE )

    {

        //

        //  Re-allocate the buffer to the required size

        //

        delete [] pszBuffer;

        pszBuffer = new CHAR[dwLength];

        if ( pszBuffer == NULL )

        {

            hr = HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY );

            goto Finished;

        }              

 

        //

        //  Try again

        //

        fRet = pEcb->GetServerVariable( pEcb->ConnID,

                                        pszName,

                                        pszBuffer,

                                        &dwLength );

    }

 

    //

    //  Check if we succeeded

    //

    if ( !fRet )

    {

        hr = HRESULT_FROM_WIN32( GetLastError() );

        goto Finished;

    }

 

    //

    //  Return the buffer

    //

    *ppszValue = pszBuffer;

    pszBuffer = NULL;

 

Finished:

 

    //

    //  Clean up the buffer if an error occurred

    //

    if ( pszBuffer != NULL )

    {

        delete [] pszBuffer;

        pszBuffer = NULL;

    }

 

    return hr;

}

So, let’s quickly recap what I had to do here:

  1. Allocate a buffer
  2. Call GetServerVariable to fill the buffer
  3. Check whether the value was too large, and if so, clean up the first buffer, and re-allocate the buffer to be the right size, but only if the size is not too big (never trust the client)
  4. Call ServerVariable again with the larger buffer
  5. If we failed to return the buffer to the client, clean up the buffer

Now, things are about to get a lot worse. You notice that I return the pointer to the allocated buffer from this function to the caller, making deallocating this buffer the responsibility of the caller. This is why I also don’t just allocate the initial buffer on the stack, like David does in his old sample for this code available here: http://blogs.msdn.com/david.wang/archive/2005/08/18/ISAPI-GetServerVariable.aspx.

This means, that the caller has to clean up the buffer when they are done with it. Imagine, that your code has to get a lot of server variables (as is the case a lot of the time).

You will pretty much also have to:

  1. Maintain a list of buffers you used to get server variables
  2. At the end of the request, go through that list and delete all the buffers

And you thought all you were going to do is get a few little server variables. Welcome to ISAPI hell.  

It turns out that managing buffers to operate ISAPI APIs a lot of the time becomes the main theme of your development. Sure, you’ll say, you’ve written this code so many times, you have a solid pattern and maybe even a library you’ve written to do it. And you probably spent quite a few hours debugging memory leaks and access violations doing it.

Enter IIS7.

In IIS7, we made one simple improvement - we took over the management of memory for server APIs that return buffers. Instead of you keeping track of buffers with server variable values, the IIS server core does it (hey, it has to do it anyway).

Many IIS7 APIs return pointers to already allocated core server memory that contains the values you are retrieving.

With IIS7, you don’t need a utility function, or a single new or delete statement to get a server variable. You simply to this:

IHttpContext*    pContext;

PCSTR        pszValue = NULL;

DWORD       dwLength = 0;

//

//  Get the server variable

//

hr = pContext->GetServerVariable( "AUTH_USER",

                                  &pszValue,

                                  &dwLength );

if ( FAILED( hr ) )

{

    goto Finished;

}

NOTE: The dwLength parameter is not an input parameter - it receives the length of the server variable value as a convenience to you, so that you don’t need to call strlen().

After successful completion, The pszValue points to a buffer that can be used for the duration of the request. After the request is finished, the server will automatically reclaim the memory so you never need to worry about cleaning it up.

BONUS - Unicode Support: The server provides an overload of this function that returns Unicode server variable values. This replaces the really painful way to get Unicode values with ISAPI - call the GetServerVariable with “UNICODE_variablename”, and treat the returned bytes as WCHAR even though the buffer is passed in as a CHAR buffer.

Overall, the IIS7 memory management model basically means that much of the time, you don’t need to:

  1. Allocate your own memory
  2. Check for insufficient buffer sizes
  3. Manage and clean up your own memory

This post got a little long, but really I cannot overstate how much grief this saves you when writing a module … letting you concentrate on the reason why you are actually writing it.

Until next time …

Published 01 November 06 09:57 by Mike Volodarsky
Filed under: , ,

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

# Tmarq said on December 19, 2006 2:48 PM:
Posted on 11/1/2006 10:29 PM:

Nice post! go mikey
# AJ said on December 19, 2006 2:48 PM:
Posted on 11/9/2006 4:15 PM:

Awesome stuff!
# cobro said on March 12, 2009 8:48 AM:
Awesome stuff!

Leave a Comment

(required) 
(optional)
(required) 
Enter the code you see below


About Mike Volodarsky

For the past 5 years, I was the core Program Manager for Microsoft ASP.NET 2.0 and IIS 7.0 products. I drove the design and development of the IIS 7.0 web server core, the IIS FastCGI support, the AppCmd command line tool, the ASP.NET Integrated pipeline, and other special projects around server security, performance, and scalability. Now, I am working on my own on cutting edge web server tech on top of the Microsoft IIS platform, and continue blogging about it here.

About me



For the past 5 years, I was the core server Program Manager for the IIS 7.0 and ASP.NET 2.0 products at Microsoft.
Now, I work on advanced web server tech using IIS 7.0, .NET, and Windows Server 2008 and write about it in this blog.

View Michael Volodarsky's profile on LinkedIn

Writings



TechNet Magazine
>Top 10 Performance Improvements in IIS 7.0

MSDN Magazine
>IIS 7.0: Build Web Server Solutions with End-To-End Extensibility
>IIS 7.0: Enhance Your Apps with the Integrated ASP.NET Pipeline
>IIS 7.0: Explore The Web Server For Windows Vista And Beyond
>Design and Deploy Secure Web Apps with ASP.NET 2.0 and IIS 6.0
>Fast, Scalable, and Secure Session State Management for Your Web Applications


Tools and Modules

LeechGuard
IconHandler 2.0
DirectoryListing
HttpRedirection
IIS Auth for Wordpress
iisschema.exe
PortCheck.exe v2.0

Popular Posts

- ASP.NET 2.0 Breaking Changes on IIS 7.0
- Develop IIS7 modules and handlers with .NET
- Troubleshoot IIS7 errors like a pro
- Troubleshooting 503 / "service unavailable" errors
- Troubleshooting "server not found" errors
- Create IIS7 sites, applications, and virtual directories
- Run Ruby on Rails with IIS FastCGI
- VS Debugging of ASP.NET applications on Windows Vista
- Stop hot-linking with IIS and ASP.NET

Tags

Search

Go

This Blog

Archives

Good IIS Blogs

Disclaimer

These postings are provided as is with no warranties, and confer no rights. The views expressed in this blog are entirely my own.

Syndication