Chapter 13. Hello, ATL Server: A Modern C++ Web Platform
Throughout the previous 12 chapters, we’ve focused on ATL as a framework for building both COM components and user interfaces (via ATL’s windowing classes and ActiveX control support). Now we look at ATL as a modern platform for building web applications. That framework is a set of classes known collectively as ATL Server.
Although most of the discussion thus far has involved ATL’s COM support, ATL Server actually has very little to do with COM. Because its purpose is to handle Hypertext Transfer Protocol (HTTP) requests, the crux of ATL Server is built around Microsoft’s Internet Service API (ISAPI). The library just happens to use COM inside. We start by briefly reviewing Microsoft’s web platform. Then, we examine how ATL Server leverages that platform to provide a viable means of creating web applications and web services. There’s much more to ATL Server than can be covered in this book, so we just hit the high points in this chapter.
The Microsoft Web Platform (Internet Information Services)
When the World Wide Web began taking root in the early 1990s, many sites were little more than collections of Hypertext Markup Language (HTML) files and perhaps some image files. Clients with Internet browsers surfed to various sites using Universal Resource Locators (URLs) that took a form like this: http://www.example.com.
After typing the URL and sending the request through a maze of routers, the request finally ended up at a server somewhere. In the earliest days of the Web, the server was probably a UNIX box. Web technologies soon evolved to handle more elaborate requests (not just file requests). Through the development of the Common Gateway Interface and the inclusion of HTML tags representing standard GUI controls (such as combo boxes, push buttons, and text boxes), the Web became capable of handling interactive traffic. That is, a client could carry on a two-way conversation with web servers that extended beyond simple file requests. Of course, this capability led to the great upsurge in web sites during the late 1990s, giving rise to such enterprises as Amazon.com and Google.
The earliest means of supporting interactive capabilities over the Web were made available through the Common Gateway Interface (CGI). CGI worked by handling separate incoming requests with a new process for each request. Although the Microsoft platform supports the CGI, it doesn’t work quite as well as on a UNIX box. Starting new processes on a UNIX box is not as expensive as starting new processes on a Windows box. To compensate, Microsoft introduced the Internet Services API and its own new Internet server: Internet Information Services (IIS). The IIS strategy is that it’s much faster to load a DLL to respond to an HTTP request than it is to start a whole new process.
IIS and ISAPI
The IIS component is the heart of Microsoft’s web platform. Most modern web sites built around the Microsoft platform involve IIS and ISAPI DLLs. All the modern Microsoft web-based programming frameworks are extensions of the IIS/ISAPI architecture. Even classic ASP and the more modern ASP.NET rely on IIS and ISAPI at their core. And as you’ll soon see, ATL Server depends upon IIS and ISAPI as well.
Regardless of the programming framework used (raw sockets programming, ASP, ASP.NET, or ATL Server), processing web requests is similar from framework to framework. A component listens to port 80 for HTTP requests. When a request, comes in, the component parses the request and figures out what to do with it. The request might vary from sending back some specific HTML, to returning a graphics file, or perhaps even to invoking a method of some sort.
When programming to the modern Microsoft web-based frameworks, IIS is the component that listens to port 80. IIS handles some requests directly and delegates others to specific ISAPI extension DLLs to execute the request. By default, IIS handles requests for standard HTML files directly. As an alternative, a custom file extension might be mapped to a handwritten ISAPI DLL that executes the request.
Classic ASP and ASP.NET integrate into IIS as
ISAPI DLLs. IIS handles requests ASP files (*.asp
) by
mapping them to an ISAPI DLL named ASP.DLL
, which handles
the request by parsing the request string, loading the ASP file,
parsing the contents of the file, and servicing the request
according to the instructions given in the ASP file. ASP.NET files
(*.aspx
) are handled by an ISAPI DLL named
ASPNET_ISAPI.DLL
, which brings in the Common Language
Runtime to help it process requests.
To set the stage for understanding ATL Server, let’s take a look at how ISAPI extension DLLs work.
ISAPI Extension DLLs
Although IIS does a perfectly fine job responding to requests for standard web file types (such as HTML and JPG), its real power lies in the capability to extend your server by writing custom DLLs to respond to requests.
The core ISAPI infrastructure is actually fairly simple. ISAPI extension DLLs implement three entry points:
1BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer);
2
3DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB);
4
5BOOL WINAPI TerminateExtension(DWORD dwFlags);
These three methods are the hooks for writing
your web site using custom DLLs. GetExtensionVersion
is
called when IIS invokes the application for the first time.
GetExtensionVersion
must set the version number in the
HSE_VERSION_INFO
structure passed in and then return
trUE
for IIS to be capable of using your ISAPI DLL.
IIS calls the TerminateExtension
entry
point when IIS is ready to unload the DLL from its process. If you
don’t need to do any cleanup, the TerminateExtension
entry
point is optional.
The heart of the extension is the
HttpExtensionProc
function. Notice that
HttpExtensionProc
takes a single parameter: an
EXTENSION_CONTROL_BLOCK
structure. The structure includes
everything you’d ever want to know about a request, including the
kind of request made, the content of the request, the type of
content, a method for getting the server variables (for example,
information about the connection), a method for writing output to
the client, and a method for reading data from the body of the HTTP
request. Here’s the EXTENSION_CONTROL_BLOCK
:
1typedef struct _EXTENSION_CONTROL_BLOCK {
2 DWORD cbSize; // size of this struct.
3 DWORD dwVersion; // version info of this spec
4 HCONN ConnID; // Context number not to be modified!
5 DWORD dwHttpStatusCode; // HTTP Status code
6 CHAR lpszLogData[HSE_LOG_BUFFER_LEN]; // null terminated log
7 // info specific to this Extension DLL
8
9 LPSTR lpszMethod; // REQUEST_METHOD
10 LPSTR lpszQueryString; // QUERY_STRING
11 LPSTR lpszPathInfo; // PATH_INFO
12 LPSTR lpszPathTranslated; // PATH_TRANSLATED
13 DWORD cbTotalBytes; // Total bytes indicated from client
14 DWORD cbAvailable; // Available number of bytes
15 LPBYTE lpbData; // pointer to cbAvailable bytes
16 LPSTR lpszContentType; // Content type of client data
17
18 BOOL (WINAPI * GetServerVariable)(
19 HCONN hConn, LPSTR lpszVariableName,
20 LPVOID lpvBuffer, LPDWORD lpdwSize);
21
22 BOOL (WINAPI * WriteClient)(HCONN ConnID, LPVOID Buffer,
23 LPDWORD lpdwBytes, DWORD dwReserved);
24
25 BOOL (WINAPI * ReadClient)(HCONN ConnID, LPVOID lpvBuffer,
26 LPDWORD lpdwSize);
27
28 BOOL (WINAPI * ServerSupportFunction)(HCONN hConn,
29 DWORD dwHSERequest, LPVOID lpvBuffer, LPDWORD lpdwSize,
30 LPDWORD lpdwDataType);
31} EXTENSION_CONTROL_BLOCK, *LPEXTENSION_CONTROL_BLOCK;
When IIS receives a
request, it packages the information into the
EXTENSION_CONTROL_BLOCK
and passes a pointer to the
structure into the ISAPI DLL via the HttpExtensionProc
entry point. The ISAPI extension’s job is to parse the incoming
request into a useable form. After that, it’s completely up to the
ISAPI DLL to do whatever it wants to with the request. For example,
if the client makes some sort of request using query parameters
(perhaps a product lookup), the ISAPI DLL might use those
parameters to create a database query. The ISAPI DLL passes any
results back to the client using the pointer to the
WriteClient
method passed in through the extension
block.
If you’ve had any experience working with
frameworks such as classic ASP or ASP.NET, most of this structure
will appear familiar to you. For example, when you call
Write
through ASP’s intrinsic Response
object,
execution eventually ends up passing through the method pointed to
by WriteClient
.
The Simplest ISAPI Extension That Could Possibly Work [1]
Let’s take a look at a simple “Hello, World” ISAPI extension. The result looks like Figure 13.1.
Figure 13.1. Hello World, ISAPI style

Writing the Extension
Starting with a Win32 DLL project (not an ATL
DLL project) named HelloISAPI, including the HttpExt.h
system header file in stdafx.h
brings in the ISAPI
extension types, which enables us to implement the
GetExtensionVersion
function:
1BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO *pVer) {
2 pVer->dwExtensionVersion = HSE_VERSION;
3 strncpy_s( pVer->lpszExtensionDesc,
4 HSE_MAX_EXT_DLL_NAME_LEN,
5 "Hello ISAPI Extension", _TRUNCATE );
6 return TRUE;
7}
All I did here was set
the ISAPI version number (HSE_VERSION
is a constant
defined in HttpExt.h
) and fill in the description field
for use by IIS. We use the HSE_VERSION
constant because
we’re reporting the version of the ISAPI API that we’re using, not
the version number of our extension DLL. This way, as IIS and ISAPI
changes, IIS can maintain backward-compatibility with older
extensions.
The meat of the extension is the
HttpExtensionProc
function:
1DWORD WINAPI HttpExtensionProc(EXTENSION_CONTROL_BLOCK *pECB) {
2 char *header =
3 "<html>"
4 "<head>"
5 "<title>Hello from ISAPI</title>"
6 "</head>"
7 "<body>"
8 "<h1>Hello from an ISAPI Extension</h1>";
9
10DWORD size = static_cast< DWORD >( strlen( header ) );
11pECB->WriteClient( pECB->ConnID, header, &size, 0 );
12
13char *intro = "<p>Your name is: ";
14size = static_cast< DWORD >( strlen( intro ) );
15pECB->WriteClient( pECB->ConnID, intro, &size, 0 );
16 size = static_cast< DWORD >(
17 strlen( pECB->lpszQueryString ) );
18 pECB->WriteClient( pECB->ConnID, pECB->lpszQueryString,
19 &size, 0 );
20
21 char *footer =
22 "</p>"
23 "</body>"
24 "</html>";
25
26 size = static_cast< DWORD >( strlen( footer ) );
27 pECB->WriteClient( pECB->ConnID, footer, &size, 0 );
28
29 return HSE_STATUS_SUCCESS;
30}
This method simply
builds up a series of strings and then sends them out to the
browser via the pECB->WriteClient()
method. We also
grab the query string (the stuff in the URL after the ?
)
by using the lpszQueryString
field in the
EXTENSION_CONTROL_BLOCK
.
In this case, the TerminateExtension
method doesn’t need to do anything and, thus, is technically
optional, but adding a minimal stub for completeness looks like
this:
1BOOL WINAPI TerminateExtension(DWORD dwFlags) {
2 return TRUE;
3}
Returning trUE
tells IIS that it can
unload this extension. If we had loaded anything that needed to be
cleaned up, the call to TerminateExtension
would be the
place to do it.
There’s one more thing we need to add to the
project before we can deploy it: We have to export the ISAPI entry
points. Normal practice is to add the
__declspec(dllexport)
declaration to each of the ISAPI
functions; this tells the compiler that we want the suitably
decorated function to be exported from the DLL. Unfortunately, when
you do this with an ISAPI extension, you get this:
1c:\helloisapi\helloisapi.cpp(24) : error C2375: 'GetExtensionVersion' :
2redefinition; different linkage
3 c:\program files\microsoft visual studio
48\vc\platformsdk\include\httpext.h(526) : see declaration of
5'GetExtensionVersion'
6c:\helloisapi\helloisapi.cpp(33) : error C2375: 'HttpExtensionProc' :
7redefinition; different linkage
8 c:\program files\microsoft visual studio
98\vc\platformsdk\include\httpext.h(527) : see declaration of
10'HttpExtensionProc'
11c:\helloisapi\helloisapi.cpp(65) : error C2375: 'TerminateExtension' :
12redefinition; different linkage
13 c:\program files\microsoft visual studio
148\vc\platformsdk\include\httpext.h(528) : see declaration of
15'TerminateExtension'
If we look inside HttpExt.h
on lines
526528, we see the problem:
1/************************************************************
2* Function Prototypes
3* o for functions exported from the ISAPI Application DLL
4************************************************************/
5
6
7BOOL WINAPI GetExtensionVersion( HSE_VERSION_INFO *pVer );
8DWORD WINAPI HttpExtensionProc( EXTENSION_CONTROL_BLOCK *pECB );
9BOOL WINAPI TerminateExtension( DWORD dwFlags );
The HttpExt.h
file includes prototypes
for the ISAPI entry points, but alas, the prototypes don’t include
__declspec(dllexport)
. When the compiler sees the
prototype without the export
directive and the actual
implementation with the directive, it gives the error I just showed
you.
To work around this, we need to do exports the
old-fashioned way: Add a .def
file to the project. This is
a separate file that explicitly lists the functions we want
exported from our DLL. As the number of exports grows, a
.def
file gets difficult to maintain, but we’ve got only
three exports here, and they’re the same for every ISAPI extension,
so it’s not too much of a burden.
The .def
file looks like this:
1;HelloISAPI.def : Declares the module parameters for the DLL.
2LIBRARY "HelloISAPI"
3
4EXPORTS
5 HttpExtensionProc
6 GetExtensionVersion
7 TerminateExtension
Configuring the Server
Next, we need to get the extension onto the server. Luckily, Visual Studio has some web-deployment tools built in to help, but when you’re starting from scratch, they’re not set up yet.
First things first: We need an IIS Virtual Directory. I recommend creating a directory that’s separate from your build directory for this (you’ll see why in a moment). The Internet Services Manager is a nice place to create virtual directories; the sample is called HelloISAPI. [2]
To create a virtual directory, first create a directory via Windows Explorer. It’s easiest to create it under C:inetpubwwwroot because permissions will be set correctly. Next, open the Internet Services Manager. Expand the treeview on the left until you see Default Web Site. Right-click this and choose New, Virtual Directory. Follow the wizard to name the virtual directory, and then choose the directory you just created. Click Next and then Finish.
Next, we need to configure permissions on the directory so that the ISAPI extension will be executed and, more important, so we can debug it. In Internet Services Manager, right-clicking the HelloISAPI virtual directory and choosing Properties yields Figure 13.2.
Figure 13.2. Virtual directory properties

We need to make sure two settings are correct in this dialog box. The first is the Execute Permissions drop-down list, which must be set to Scripts and Executables. (It defaults to Scripts Only.) Without this setting, instead of loading the extension and executing it, IIS attempts to download the DLL to your browser.
The second setting is the Application Protection drop-down list. For easy debugging, you want this set to Low. This causes IIS to load an extension directly into the IIS process. This is generally a bad thing to do on a production server because misbehaving extensions can take the IIS process down with it, but for debugging, it just makes things easier. Do this only on a development server, not on your production boxes! [3]
Some recommend that you debug in IIS out-of-process mode. Although I can see the appeal, setting that up involves mucking around with COM+ settings, which is often very complicated to get right. If you want to give it a try, take a look at http://www.west-wind.com/presentations/iis5Debug.htm (http://tinysells.com/53).
Configuring the Project
With a virtual directory created and configured to receive our ISAPI extension code, we want to configure Visual Studio to deploy the code on a successful build. In the project’s property pages, there’s an entry called Web Deployment (see Figure 13.3).
Figure 13.3. Web Deployment settings

By default, the Excluded From Build property is
set to Yes, which means that web deployment won’t happen. Changing
it to No enables web deployment, which, by default, uses the name
of the project at the IIS virtual directory name that will receive
the result of a successful buildin this example, our
HelloISAPI.DLL
file.
At this point, compiling the project and surfing
to http://localhost/HelloISAPI/HelloISAPI.dll?SomeName should get
back a result that looks like Figure 13.1. If you make changes to the project
and rebuild, VS is smart enough to tell ISAPI to unload our DLL so
that it can be replaced. This is also the reason that we wanted a
separate build directory from our virtual directory. If we compile
straight into the virtual directory, IIS typically has the DLL
locked. You would need to use a prebuild step to shut down IIS so
that Visual Studio could regenerate the .dll
file after it
compiles. By using web deployment, you don’t need to pay the
compile-time speed hit for resetting IIS until the compile
succeeds.
If you click F5 to start a debugging session, by default, you get the helpful Executable for Debug Session dialog box. We want to configure Visual Studio to automatically attach to IIS when debugging. To do this, navigate to the Debugging page of the project properties and set the options to match those shown in Figure 13.4.
Figure 13.4. Debugging settings

You need to change these two fields:
Command. Set this to inetinfo.exe, which is the name of the IIS process.
Attach. Set this to Yes. If it is set to No, the debugger attempts to start a second copy of inetinfo.exe. Yes means “Attach to already running copy.”
Now, to debug your extension, set your breakpoints wherever you want and click F5 to start the debugger. Visual Studio crunches for a while after it attaches to IIS and then looks like it’s doing nothing. At this point, start a browser and point it at your extension’s URL; your breakpoints should fire.
Wrapping ISAPI
You now have a simple extension written directly
to ISAPI, but programming against raw ISAPI is rather awkward. It
sure would be nice to have some more object-oriented wrappers.
Luckily, ATL Server provides exactly that. The
CServerContext
class is a COM object that provides a
wrapper around the ECB.
1class CServerContext :
2 public CComObjectRootEx<CComMultiThreadModel>,
3 public IHttpServerContext {
4public:
5 BEGIN_COM_MAP(CServerContext)
6 COM_INTERFACE_ENTRY(IHttpServerContext)
7 END_COM_MAP()
8
9 void Initialize(__in EXTENSION_CONTROL_BLOCK *pECB);
10
11 LPCSTR GetRequestMethod();
12 LPCSTR GetQueryString();
13 LPCSTR GetPathInfo();
14 LPCSTR GetScriptPathTranslated();
15 LPCSTR GetPathTranslated();
16 DWORD GetTotalBytes();
17 DWORD GetAvailableBytes();
18 BYTE *GetAvailableData();
19 LPCSTR GetContentType();
20 BOOL GetServerVariable(
21 LPCSTR pszVariableName,
22 LPSTR pvBuffer,
23 DWORD *pdwSize);
24
25 BOOL WriteClient(void *pvBuffer, DWORD *pdwBytes);
26 BOOL AsyncWriteClient(void *pvBuffer, DWORD *pdwBytes);
27 BOOL ReadClient(void *pvBuffer, DWORD *pdwSize);
28 BOOL AsyncReadClient(void *pvBuffer, DWORD *pdwSize);
29
30 BOOL SendRedirectResponse(__in LPCSTR pszRedirectUrl);
31 BOOL GetImpersonationToken(__out HANDLE * pToken);
32
33 BOOL SendResponseHeader(
34 LPCSTR pszHeader = "Content-Type: text/html\r\n\r\n",
35 LPCSTR pszStatusCode = "200 OK",
36 BOOL fKeepConn=FALSE);
37
38 BOOL DoneWithSession(__in DWORD dwHttpStatusCode);
39 BOOL RequestIOCompletion(__in PFN_HSE_IO_COMPLETION pfn,
40 DWORD *pdwContext);
41
42 BOOL TransmitFile(
43 HANDLE hFile,
44 PFN_HSE_IO_COMPLETION pfn,
45 void *pContext,
46 LPCSTR szStatusCode,
47 DWORD dwBytesToWrite,
48 DWORD dwOffset,
49 void *pvHead,
50 DWORD dwHeadLen,
51 void *pvTail,
52 DWORD dwTailLen,
53 DWORD dwFlags);
54
55 BOOL AppendToLog(LPCSTR szMessage, DWORD *pdwLen);
56
57 BOOL MapUrlToPathEx(LPCSTR szLogicalPath, DWORD dwLen,
58 HSE_URL_MAPEX_INFO *pumInfo);
59};
The
CServerContext
class provides a somewhat more convenient
wrapper for the ISAPI interface. Before you use it, you need to add
an ATL module object to your project and initialize it in DLL main.
This code takes care of the housekeeping:
1// Our module class definition
2class CHelloISAPI2Module :
3 public CAtlDllModuleT<CHelloISAPI2Module> {};
4
5// Required global instance of module.
6CHelloISAPI2Module _AtlModule;
7
8// Initialize / shutdown module on DLL load or unload
9BOOL APIENTRY DllMain( HMODULE hModule,
10 DWORD ul_reason_for_call, LPVOID lpReserved ) {
11 // Set up shared data for use by CServerContext
12 return _AtlModule.DllMain(ul_reason_for_call, lpReserved);
13}
Having initialized shared data that
CServerContext
makes use of, we can now take advantage of
CServerContext
:
1DWORD WINAPI HttpExtensionProc(EXTENSION_CONTROL_BLOCK *pECB) {
2 CComObject< CServerContext > *pCtx;
3 CComObject< CServerContext >::CreateInstance( &pCtx );
4 pCtx->Initialize( pECB );
5 // We use this smart pointer to ensure proper cleanup
6 CComPtr< IUnknown > spContextUnk( pCtx );
7
8 char *header =
9 "<html>"
10 "<head>"
11 "<title>Hello from ISAPI</title> "
12 "</head>"
13 "<body>"
14 "<h1>Hello from an ISAPI Extension</h1>";
15
16 DWORD size = static_cast< DWORD >( strlen( header ) );
17 pCtx->WriteClient( header, &size );
18
19 char *intro = "<p>Your name is: ";
20 size = static_cast< DWORD >( strlen( intro ) );
21 pCtx->WriteClient( intro, &size );
22
23 size = static_cast< DWORD >(
24 strlen( pCtx->GetQueryString( ) ) );
25 pCtx->WriteClient(
26 const_cast< LPSTR >(pCtx->GetQueryString( )), &size );
27
28 char *footer =
29 "\r\n</body>\r\n"
30 "</html>\r\n";
31
32 size = static_cast< DWORD >( strlen( footer ) );
33 pCtx->WriteClient( footer, &size );
34
35 return HSE_STATUS_SUCCESS;
36}
The bold lines show where we’ve replaced direct
calls to the ECB with calls via the context. The
WriteClient
method is at least a little shorter, but we
actually haven’t gained much at this point. Luckily, there’s more
API wrapping to be done.
Request and Response Wrappers
Anyone who has done ASP or
ASP.NET development is familiar with the Request
and
Response
objects. The former gives access to the contents
of the HTTP request: URL, query string, cookies, and so on. The
latter gives you a way to write data to the output stream. ATL
Server provides similar wrappers that make both reading and writing
from the ECB much more pleasant.
The CHttpRequest
object is a wrapper
object that lets you get at the contents of the HTTP request:
1class CHttpRequest : public IHttpRequestLookup {
2public:
3 // Constructs and initializes the object.
4 CHttpRequest(
5 IHttpServerContext *pServerContext,
6 DWORD dwMaxFormSize=DEFAULT_MAX_FORM_SIZE,
7 DWORD dwFlags=ATL_FORM_FLAG_NONE);
8
9 CHttpRequest(IHttpRequestLookup *pRequestLookup);
10
11 // Access to Query String parameters as a collection
12 const CHttpRequestParams& GetQueryParams() const;
13
14 // Get the entire raw query string
15 LPCSTR GetQueryString();
16
17 //... Other methods omitted - we'll talk about them later
18}; // class CHttpRequest
The CHttpRequest
object gives you easy
access to everything in the request. Using the
CHttpRequest
object simplifies getting at query string
variables. In the raw C++ extension code, I cheated and just passed
data (such as ?Chris
), to make using that data easier.
However, typical web pages take named query parameters separated
with the ampersand (&
) and potentially containing
special characters encoded in hex:
1http://localhost/HelloISAPI2/HelloISAP2.dll?name=Chris&motto=I%20Love%20ATL
Unfortunately, decoding and parsing query
strings in their full glory requires quite a bit of work to get
right, which is where ATL Server really starts to shine via the
CHttpRequest
class:
1DWORD WINAPI HttpExtensionProc(EXTENSION_CONTROL_BLOCK *pECB) {
2 CComObject< CServerContext > *pCtx;
3 CComObject< CServerContext >::CreateInstance( &pCtx );
4 pCtx->Initialize( pECB );
5 // We use this smart pointer to ensure proper cleanup
6 CComPtr< IUnknown > spContextUnk( pCtx );
7
8 CHttpRequest request( pCtx );
9
10 char *header =
11 "<html>"
12 "<head>"
13 "<title>Hello from ISAPI</title>"
14 "</head>"
15 "<body>"
16 "<h1>Hello from an ISAPI Extension</h1>";
17
18 DWORD size = static_cast< DWORD >( strlen( header ) );
19 pCtx->WriteClient( header, &size );
20
21 CStringA name;
22 name = request.GetQueryParams( ).Lookup( "name" );
23 if( name.IsEmpty( ) ) {
24 char *noName = "<p>You didn't give your name";
25 size = static_cast< DWORD >( strlen( noName ) );
26 pCtx->WriteClient( noName, &size );
27 }
28 else {
29 char *intro = "<p>Your name is: ";
30 size = static_cast< DWORD >( strlen( intro ) );
31 pCtx->WriteClient( intro, &size );
32 size = name.GetLength( );
33 pCtx->WriteClient( name.GetBuffer( ), &size );
34 }
35
36 char *footer =
37 "\r\n</p>\r\n"
38 "\r\n</body>\r\n"
39 "</html>\r\n";
40
41 size = static_cast< DWORD >( strlen( footer ) );
42 pCtx->WriteClient( footer, &size );
43
44 return HSE_STATUS_SUCCESS;
45}
The bold lines show where we’re calling into the
CHttpRequest
object and parsing the named query parameter
name
. If it’s encoded with embedded spaces or other
special characters, or if it’s included with other query
parameters, ATL Server parses it correctly.
One minor thing that might seem odd is the use
of CStringA
in the previous code sample. Why didn’t I just
use the plain CString
? If you look in the documentation
for CHttpRequestParams::Lookup
(the method I used to get
the query string values), you’ll see this:
1class CHttpRequestParams : ... {
2 ...
3 LPCSTR Lookup(LPCSTR szName) const;
4 ...
5};
Notice that the return type is LPCSTR
,
not LPCTSTR
. In general,
when dealing with strings coming in or going out via HTTP, ATL
Server explicitly uses 8-bit ANSI character strings.
The CString
class is actually a string
of TCHARs
. As you recall from Chapter 2, “Strings and Text,” this means that
the actual type of the underlying characters changes depending on
your compile settings for Unicode. However, the return type for the
Lookup
method never changes: It’s always ANSI. So, the
sample code uses CStringA
explicitly to say, “I don’t care
what the UNICODE compile setting is, I always want this to be 8-bit
characters.”
On the output side, ATL Server provides the
CHttpResponse
class:
1class CHttpResponse :
2 public IWriteStream,
3 public CWriteStreamHelper {
4public:
5 // Implementation: The buffer used to store the
6 // response before the data is sent to the client.
7 CAtlIsapiBuffer<> m_strContent;
8
9 // Numeric constants for the HTTP status codes
10 // used for redirecting client requests.
11 enum HTTP_REDIRECT {
12 HTTP_REDIRECT_MULTIPLE=300,
13 HTTP_REDIRECT_MOVED=301,
14 HTTP_REDIRECT_FOUND=302,
15 HTTP_REDIRECT_SEE_OTHER=303,
16 HTTP_REDIRECT_NOT_MODIFIED=304,
17 HTTP_REDIRECT_USE_PROXY=305,
18 HTTP_REDIRECT_TEMPORARY_REDIRECT=307
19 };
20
21 // Initialization
22
23 CHttpResponse();
24 CHttpResponse(IHttpServerContext *pServerContext);
25 BOOL Initialize(IHttpServerContext *pServerContext);
26 BOOL Initialize(IHttpRequestLookup *pLookup);
27
28 // Writing to the output
29
30 BOOL SetContentType(LPCSTR szContentType);
31 HRESULT WriteStream(LPCSTR szOut, int nLen, DWORD *pdwWritten);
32 BOOL WriteLen(LPCSTR szOut, DWORD dwLen);
33
34 // Redirect client to another URL
35 BOOL Redirect(LPCSTR szUrl,
36 HTTP_REDIRECT statusCode=HTTP_REDIRECT_MOVED,
37 BOOL bSendBody=TRUE);
38 BOOL Redirect(LPCSTR szUrl, LPCSTR szBody,
39 HTTP_REDIRECT statusCode=HTTP_REDIRECT_MOVED);
40
41 // Manipulate various response headers
42
43 BOOL AppendHeader(LPCSTR szName, LPCSTR szValue);
44 BOOL AppendCookie(const CCookie *pCookie);
45 BOOL AppendCookie(const CCookie& cookie);
46 BOOL AppendCookie(LPCSTR szName, LPCSTR szValue);
47 BOOL DeleteCookie(LPCSTR szName);
48
49 void ClearHeaders();
50 void ClearContent();
51
52 // Send the current buffer to the client
53 BOOL Flush(BOOL bFinal=FALSE);
54 ... other methods omitted for clarity
55 }
56}; // class CHttpResponse
The CHttpResponse
class provides all
the control you need to manipulate the response going back to the
client. But the best thing about CHttpResponse
isn’t in
this class definitionit’s in the base class,
CWriteStreamHelper
:
1class CWriteStreamHelper {
2public:
3 ... Other methods omitted for clarity
4
5 CWriteStreamHelper& operator<<(LPCSTR szStr);
6 CWriteStreamHelper& operator<<(LPCWSTR wszStr);
7 CWriteStreamHelper& operator<<(int n);
8 CWriteStreamHelper& operator<<(short int w);
9 CWriteStreamHelper& operator<<(unsigned int u);
10 CWriteStreamHelper& operator<<(long int dw);
11 CWriteStreamHelper& operator<<(unsigned long int dw);
12 CWriteStreamHelper& operator<<(double d);
13 CWriteStreamHelper& operator<<(__int64 i);
14 CWriteStreamHelper& operator<<(unsigned t64 i);
15 CWriteStreamHelper& operator<<(CURRENCY c);
16};
These
operator<<
overloads mean that
CHttpResponse
can be used much like C++ iostreams can, and
they make it much, much easier to build up your output. Using
CHttpResponse
, our final HttpExtensionProc
now
looks like this:
1DWORD WINAPI HttpExtensionProc(EXTENSION_CONTROL_BLOCK *pECB) {
2 CComObject< CServerContext > *pCtx;
3 CComObject< CServerContext >::CreateInstance( &pCtx );
4 pCtx->Initialize( pECB );
5 // We use this smart pointer to ensure proper cleanup
6 CComPtr< IUnknown > spContextUnk( pCtx );
7
8 CHttpRequest request( pCtx );
9 CHttpResponse response( pCtx );
10
11 response << "<html>" <<
12 "<head>" <<
13 "<title>Hello from ISAPI</title>" <<
14 "</head>" <<
15 "<body>" <<
16 "<h1>Hello from an ISAPI Extension</h1>";
17
18 CStringA name;
19 name = request.GetQueryParams( ).Lookup( "name" );
20 if( name.IsEmpty( ) ) {
21 response << "<p>You didn't give your name";
22 } else {
23 response << "<p>Your name is: " << name;
24 }
25
26 response << "</p>" <<
27 "</body>" <<
28 "</html>";
29
30 return HSE_STATUS_SUCCESS;
31}
This use of the
CHttpResponse
object gives us a much simpler programming
model. All those ugly casts are gone, and we don’t need to worry
about presizing anything.
The use of the CServerContext
,
CHttpRequest
, and CHttpResponse
objects does make
the basics of request processing easier, but other issues haven’t
yet been addressed. IIS does some subtle work with threading, and
if you don’t handle threads properly, you’ll destroy your server’s
performance. There’s also dealing with form fields and form
validation, something every web app has to do. Finally, does it
really make sense that you have to recompile your C++ code every
time you want to change some HTML?
To address these issues, let’s take a look at how ATL Server implements the ISAPI extension.
ATL Server
ISAPI DLLs provide the necessary interface to build web applications, but ISAPI is an extremely low-level way to work. It forces the developer to manage every detail of the web application, even if the requirement is just to slap a username on a “Hello, World” HTML page. ATL Server is a set of classes that simplify the development of ISAPI DLLs in the same style that the rest of ATL simplifies COM development: reasonable implementations of default functionality, and the capability to override those defaults efficiently when needed.
Figure 13.5 presents a very high-level view of the typical ATL Server application architecture.
Figure 13.5. ATL Server architecture

The ATL Server architecture divides your web application into three parts:
An ISAPI extension DLL
Server Response Files (.SRF), also referred to as stencils
Response DLLs
These three parts neatly divide up the details of implementing a web application. At the front of the stack is the ISAPI DLL. This is responsible for two things: interfacing with IIS (by implementing the ISAPI functions) and dispatching the incoming requests to the appropriate handler (more about handlers later).
In most cases, your application needs to return
HTML that is a combination of static and dynamic content. ATL
Server provides a custom extension mapping, .SRF
, to a
Server Response File. These files contain static text that’s sent
directly to the client, along with substitution markers that are
evaluated at runtime to generate the dynamic content.
The Response DLLs contain the code that evaluates the substitution markers, and this is where your application logic typically resides.
Another way to look at the architecture is this: The ISAPI DLL handles the plumbing. The SRF files are your presentation. The Response DLLs contain your business logic. Each of these concerns is separated so that you work on each individually as needed.
Hello ATL Server
I next rebuilt the Hello ISAPI using ATL Server directly. The first step was to create a new ATL Server project (as shown in Figure 13.6).
Figure 13.6. Creating an ATL Server project

The first page of the wizard is the usual “Welcome to the wizard” stuff. The next page, Project Settings (see Figure 13.7), is where things get interesting.
Figure 13.7. ATL Server Project Settings page

The Project Settings page lets you choose what the
wizard generates. By default, you get two projects: an ISAPI
extension DLL (much like the one I already wrote) and a web
application DLL that contains the logic that drives the
.srf
file. This page also specifies what the virtual
directory should be (and the wizard automatically creates and
configures the virtual directory for us, saving a manual step).
The next page, Server Options (see Figure 13.8), lets you turn on various ATL Server services that the ISAPI DLL provides.
Figure 13.8. ATL Server project Server Options page

Each of the check boxes in the Server Options page corresponds to a service that ATL Server provides. One of the more important services is caching, and ATL Server provides several kinds of caches. The Blob Cache check box adds a service that lets you cache arbitrary chunks of memory (Binary Large OBjects) in memory in the web server. The File Cache check box adds a service that manages data that is stored in temporary files. The Data Source Cache check box adds a service that stores opened OLE DB connection objects. This way you can open the connection once and then just reuse it out of the cache, saving the cost of opening the connection on each request.
Session state is something else that most web applications need but that HTTP doesn’t support. Checking the Session Services check box lets you add one of the two session-state implementations in ATL Server. The OLE DB-Backed Session-State Services uses a database (accessed via OLE DB) to store the session information. If a database is undesirable, you can instead choose Memory-Backed Session-State Services, which stores the session state in memory on the web server. This is much faster to access, but because the data is stored in the web server, it’s not accessible from other machines; more specifically, if you’ve got a web farm, in-memory session state will be a problem.
The Predefined Performance Counters option causes the ISAPI DLL to automatically update Windows performance counters for the number of accesses, pages per second, and other such statistics.
The Browser Capabilities Support option turns on ATL Server’s support for identifying the client’s browser, which lets you, for example, output inline JavaScript only to browsers that support it.
I don’t need any of the server options for this particular project, so I left everything unchecked.
The next page, Application Options (see Figure 13.9), lets you specify options for the generated application DLL.
Figure 13.9. ATL Server Application Options page

Validation Support adds a skeleton
implementation of the ValidateAndExchange()
method to the
request-handler class. This method is called once before the
.srf
file is processed; this is the place to put input
validation code.
Stencil Processing Support provides a skeleton
REPLACEMENT_MAP
in the code and a sample replacement
method. The stencil processor uses this map in the class
declaration to find the proper method to execute when it hits a
replacement token in the .srf
file.
Create as Web Service adds a web service handler to the project instead of a regular HTML request handler (we discuss this one later).
The remaining two
options affect the generated sample .srf
file. The Use
Locale and Use Codepage check boxes add a sample locale and
codepage directive to the .srf
file.
In the sample app I’m writing, the defaults are appropriate.
The final page, Developer Support (see Figure 13.10), gives a couple miscellaneous options that affect several files in the generated code.
Figure 13.10. ATL Server Developer Support page

Generate TODO Comments tells the wizard to add
TODO
comments to the code to mark spots where you’ll want
to change things. Attributed Code tells the wizard to output code
with ATL attributes. Custom Assert and Trace Handling Support adds
extra code to the debug build of the project that allows trace
messages to be output to the WinDbg debugger. Again, the defaults
are fine for what I need for the sample.
After clicking Finish and waiting for Visual
Studio to crunch, I had a solution containing two projects. The
first contained the code for the new ISAPI extension. This project
was already configured with an appropriate .def
file to
export the ISAPI methods and set up for web deployment.
The ISAPI DLL implementation was as follows:
1// HelloAtlServerIsapi.cpp : Defines the entry point
2// for the DLL application.
3
4
5#include "stdafx.h"
6// For custom assert and trace handling with WebDbg.exe
7#ifdef _DEBUG
8CDebugReportHook g_ReportHook;
9#endif
10
11class CHelloAtlServerModule
12 : public CAtlDllModuleT<CHelloAtlServerModule> { };
13
14CHelloAtlServerModule _AtlModule;
15
16typedef CIsapiExtension<> ExtensionType;
17
18// The ATL Server ISAPI extension
19ExtensionType theExtension;
20
21
22// Delegate ISAPI exports to theExtension
23
24extern "C"
25DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB) {
26 return theExtension.HttpExtensionProc(lpECB);
27}
28
29extern "C"
30BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer) {
31 return theExtension.GetExtensionVersion(pVer);
32}
33
34extern "C"
35BOOL WINAPI TerminateExtension(DWORD dwFlags) {
36 return theExtension.TerminateExtension(dwFlags);
37}
38
39#ifdef _MANAGED
40#pragma managed(push, off)
41#endif
42
43// DLL Entry Point
44
45extern "C"
46BOOL WINAPI DllMain(HINSTANCE hInstance,
47 DWORD dwReason, LPVOID lpReserved) {
48 hInstance;
49 return _AtlModule.DllMain(dwReason, lpReserved);
50}
51#ifdef _MANAGED
52#pragma managed(pop)
53#endif
The ISAPI DLL provides
the implementation of the three ISAPI exports, but the
implementations here don’t do any work. Instead, they forward the
calls to a global instance of CIsapiExtension
, which is
the class that implements all the ISAPI plumbing.
The #pragma managed(push, off)
line is
there in case you’re using managed C++. The DllMain
enTRy
point must be in native code for Windows to properly call it at
load time. The #pragma
turns managed code off the
DllMain
function, and then the second #pragma managed(pop)
turns managed code back on. The mixture of
managed and native code is beyond the scope of this book, but it is
nice to know that the ATL engineers considered it.
For this particular project, I don’t need to
touch the ISAPI DLL. The interesting part is in the Application DLL
project. This project contains two important things. The first is
the HelloAtlServer.srf
file:
1<html>
2{{// use MSDN's "ATL Server Response File Reference" to
3learn about SRF files.}}
4{{handler HelloAtlServer.dll/Default}}
5<head>
6</head>
7<body>
8This is a test: {{Hello}}<br>
9</body>
10</html>
Much as ASP files are HTML with the addition of
<% %>
markers, .srf
files are HTML with the
addition of {{ }}
markers that indicate that substitutions
should be performed at that point. Two kinds of substitution
markers exist: directives (such as the {{hander.. }}
marker) that control the execution of the stencil engine, and
substitution markers that indicate where text should be replaced.
Additionally, comments are also available (the {{// }}
form) and are handy for putting notes into your code that don’t get
sent back to clients in the HTML.
To make this project’s page match the output
from my previous one, I edited the default .srf
file to
look like this instead:
1<html>
2{{handler HelloAtlServer.dll/Default}}
3<head>
4 <title>Hello from ATL Server</title>
5</head>
6<body>
7<h1>Hello from ATL Server</h1>
8{{if NameGiven}}
9<p>Your name is {{Name}}
10{{else}}
11<p>You didn't give your name!
12{{endif}}
13</p>
14</body>
15</html>
This .srf
file uses both flow control (the
if
block) and substitution (the Name
block). An
.srf
file is connected to request handler classes via the
handler marker. In this file, the handler marker says that the
request-handler class is in the Hello-AtlServer.dll
file,
and it’s marked with the name Default
. The
HelloAtlServer.cpp
file shows how the name
Default
is mapped to an actual C++ class:
1// HelloAtlServer.cpp
2...
3
4BEGIN_HANDLER_MAP()
5 HANDLER_ENTRY("Default", CHelloAtlServerHandler)
6END_HANDLER_MAP()
The HANDLER_MAP()
in an ATL Server
project serves the same purpose as the OBJECT_MAP()
in an
ATL project; [4] it maps a key (in this case, the handler
name) to a specific class.
Or at
least, as the OBJECT_MAP()
used to serve. Later, we talk
about a macro that lets you avoid having to maintain the
HANDLER_MAP()
, but the wizard-generated code doesn’t use
it.
The CHelloAtlServer
class is where we
actually put the substitutions. The default version output from the
wizard looks like this:
1// HelloAtlServer.h : Defines the ATL Server
2// request handler class
3
4#pragma once
5
6class CHelloAtlServerHandler
7 : public CRequestHandlerT<CHelloAtlServerHandler> {
8public:
9 BEGIN_REPLACEMENT_METHOD_MAP(CHelloAtlServerHandler)
10 REPLACEMENT_METHOD_ENTRY("Hello", OnHello)
11 END_REPLACEMENT_METHOD_MAP()
12
13 HTTP_CODE ValidateAndExchange() {
14 // TODO: Put all initialization and validation code here
15
16 // Set the content-type
17 m_HttpResponse.SetContentType("text/html");
18
19 return HTTP_SUCCESS;
20 }
21
22protected:
23 // Here is an example of how to use a replacement
24 // tag with the stencil processor
25 HTTP_CODE OnHello(void) {
26 m_HttpResponse << "Hello World!";
27 return HTTP_SUCCESS;
28 }
29};
The
REPLACMENT_METHOD_MAP()
macros give the mappings between
the markers in the .srf
file and the actual methods in the
class that get called at substitution time.
The class also includes the
ValidateAndExchange()
method. This method gets called once
at the start of page processing, and this is the place where you
can process and validate your inputs.
To implement our “Hello, World” page, we need to
first check to see if a name
parameter was passed on the
query string, pull out the name, and provide implementations for
the NameGiven
and Name
substitutions:
1class CHelloAtlServerHandler
2 : public CRequestHandlerT<CHelloAtlServerHandler> {
3
4public:
5 BEGIN_REPLACEMENT_METHOD_MAP(CHelloAtlServerHandler)
6 REPLACEMENT_METHOD_ENTRY("NameGiven", OnNameGiven)
7 REPLACEMENT_METHOD_ENTRY("Name", OnName)
8 END_REPLACEMENT_METHOD_MAP()
9
10 HTTP_CODE ValidateAndExchange() {
11 m_name = m_HttpRequest.GetQueryParams( ).Lookup( "name" );
12 return HTTP_SUCCESS;
13 }
14protected:
15 HTTP_CODE OnNameGiven( ) {
16 if( m_name.IsEmpty( ) ) {
17 return HTTP_S_FALSE;
18 }
19 return HTTP_SUCCESS;
20 }
21
22 HTTP_CODE OnName( ) {
23 m_HttpResponse << m_name;
24 return HTTP_SUCCESS;
25 }
26
27private:
28 // Storage for the name in the query string
29 CStringA m_name;
30};
The m_name
member stores the name retrieved from the query string. To retrieve
it, I used the m_HttpRequest
member provided by the
CRequestHandlerT
base class. This is an instance of the
CHttpRequest
class that has already been initialized by
the time ValidateAndExchange
is called.
The OnName
method is called when the
{{Name}}
substitution is hit in the .srf
file. It
uses the m_HttpResponse
member from the base class, which
is a ready-to-go instance of CHttpResponse
. It simply
writes the name to the output. All successful substitution methods
must return HTTP_SUCCESS
, or the processing will be
aborted with an error.
The OnNameGiven
method is slightly
different. This is used from the {{if ...}}
block and is
used to control which chunk of HTML is actually output from the
.srf
file. For a method that must return true or false,
true is indicated by an HTTP_SUCCESS
return code.
HTTP_S_FALSE
[5] indicates a false value. Any other
return value aborts the processing. The HTTP code returned to the
client depends on the actual return value. HTTP_FAIL
is
probably the most common; its returns an HTTP code 500 (Server
Error).
This is
analogous to the S_OK
and S_FALSE
return codes
for HRESULTS
. It would have been nice if the ATL Server
team had simply reused hrESULTS
for their error codes, but
no such luck.
Because the wizard enables web deployment,
compiling the project automatically deploys it as well. Pressing F5
causes Visual Studio to automatically attach to IIS, and brings up
a browser showing the result of my .srf
file
processing [6] (see Figure 13.11). Tweaking the URL by adding
?name=Chris%20Tavares
gave the result in Figure 13.12.
You might be wondering why the ATL Server project gives us a browser window when we debug, but the straight ISAPI project doesn’t. The ATL Server implementation of the ISAPI extension has special hooks for debugging in the implementation. This is combined with Visual Studio’s Web Services debugger to automatically attach to IIS and launch the browser. Without those (completely undocumented) hooks, we’re stuck with attaching to IIS as previously described.
Figure 13.11. SRF processing, no name on query string

Figure 13.12. SRF processing with name on query string

So, what does the wizard-generated ATL Server
project buy us? First, it saves a lot of effort in configuring the
project: setting up virtual directories, configuring web
deployment, and setting up for debugging. Next, we get a clean
separation between the ISAPI plumbing and the business logic. ATL
Server provides a high-performance implementation of ISAPI
applications out of the box, one that would take a great deal of
effort to do correctly by hand. Finally, we get a good separation
of presentation and logic. We no longer need to recompile just to
tweak the HTML; all we need to do is change the .srf
file
and redeploy that file to the web server.
ATL Server also gave me something that is rather subtle but very important. Consider the URL that was used. For the “by hand” project, the URL was this:
1http://localhost/HelloISAPI/HelloISAPI.dll
For the ATL Server project, the URL was this:
1http://localhost/HelloAtlServer/HelloAtlServer.srf
Notice that the first URL references the DLL
directly, whereas the second one goes to the .srf
file.
The big deal here is simply this: In the home-grown version, if I
want multiple web pages, I need to provide the logic to map URLs
(or query strings) to particular pages myself. ATL Server, on the
other hand, automatically uses the appropriate DLLs when you
reference an .srf
file. If you want a new page, just drop
in a new .srf
file. The ATL Server Project Wizard
configured the .srf
extension to map to the ISAPI DLL, as
shown in Figure 13.13.
Figure 13.13. IIS extension mappings for an ATL Server project

IIS automatically routes any request for an
.srf
file in this virtual directory to the ISAPI
extension; at that point, ATL Server takes over and routes the
request to the correct .srf
file and request handlers.
Web Services in ATL Server
ATL Server does more than generate HTML: It generates XML as well. More specifically, you can use ATL Server to implement web services using the same ISAPI infrastructure it uses to generate HTML pages.
Whenever I look at a new web services stack (which has happened quite frequently in recent years), I like to start by building a simple service that will convert strings to upper- or lowercase, and that will return the length of a string. This lets me concentrate on the plumbing without worrying much about the implementation.
I started by creating a new ATL Server project. Visual Studio shows two icons on the New Project dialog box (see Figure 13.6) for ATL Server. I used the ATL Server project icon in the previous example. It turns out that the two project wizards are almost identical. The only difference between the two is the Create as Web Service check box on the Application Options page (see Figure 13.9). With the ATL Server Web Service project, this check box is on by default.
One other important difference exists. The marshalling code between XML and C++ is complicated and requires a lot of code to be generated. Instead of swamping your projects with generated code that’s hard to edit and update, the ATL Server team built the code generation into ATL attributes. This means that, for all practical purposes, web services are the only part of ATL that requires the use of attributed code. When the Create as Web Service check box is set, the Developer Support page (see Figure 13.10) has the Attributed check box set and disabled. [7]
If you haven’t already done so, you might like to read up on ATL attributes in Appendix D, “Attributed ATL.”
I created a project called StringLib for my new web service and got the expected ISAPI and Application projects, just like my previous one. The ISAPI project contained this code:
1// StringLibIsapi.cpp : Defines the entry point for
2// the DLL application.
3#include "stdafx.h"
4// For custom assert and trace handling with WebDbg.exe
5#ifdef _DEBUG
6CDebugReportHook g_ReportHook;
7#endif
8[ module(name="MyStringLib", type=dll) ];
9[ emitidl(restricted) ];
10
11typedef CIsapiExtension<> ExtensionType;
12// The ATL Server ISAPI extension
13ExtensionType theExtension;
14// Delegate ISAPI exports to theExtension
15//
16extern "C" DWORD WINAPI HttpExtensionProc(
17 LPEXTENSION_CONTROL_BLOCK lpECB) {
18 return theExtension.HttpExtensionProc(lpECB);
19}
20
21extern "C" BOOL WINAPI GetExtensionVersion(
22 HSE_VERSION_INFO* pVer) {
23 return theExtension.GetExtensionVersion(pVer);
24}
25
26extern "C" BOOL WINAPI TerminateExtension(DWORD dwFlags) {
27 return theExtension.TerminateExtension(dwFlags);
28}
The module
attribute generates the type
definition for the ATL module class. The emitidl
attribute
is used to prevent anything in that file after that point from
going into the generated IDL file. The rest of the file is pretty
much identical to the unattributed version. As with the
HelloATLServer project, I didn’t need to touch the ISAPI
extension.
The interesting stuff ended up in the
StringLib.h
file in the application DLL project:
1// StringLib.h
2...
3namespace StringLibService {
4// all struct, enum, and typedefs for your web service
5// should go inside the namespace
6
7// IStringLibService - web service interface declaration
8//
9[ uuid("5CEAB050-F80B-4054-8E1B-43510E61B8CE"),
10 object ]
11__interface IStringLibService {
12 // HelloWorld is a sample ATL Server web service method.
13 // It shows how to declare a web service method and
14 // its in-parameters and out-parameters
15 [id(1)] HRESULT HelloWorld([in] BSTR bstrInput,
16 [out, retval] BSTR *bstrOutput);
17 // TODO: Add additional web service methods here
18};
19 // StringLibService - web service implementation
20 //
21 [ request_handler(name="Default", sdl="GenStringLibWSDL"),
22 soap_handler(
23 name="StringLibService",
24 namespace="urn:StringLibService",
25 protocol="soap" ) ]
26 class CStringLibService :
27 public IStringLibService {
28 public:
29 // This is a sample web service method that shows how
30 // to use the soap_method attribute to expose a method
31 // as a web method
32 [ soap_method ]
33 HRESULT HelloWorld(/*[in]*/ BSTR bstrInput,
34 /*[out, retval]*/ BSTR *bstrOutput) {
35 CComBSTR bstrOut(L"Hello ");
36 bstrOut += bstrInput;
37 bstrOut += L"!";
38 *bstrOutput = bstrOut.Detach();
39
40 return S_OK;
41 }
42 // TODO: Add additional web service methods here
43}; // class CStringLibService
44
45} // namespace StringLibService
This default demonstrates quite well how you
build web services with ATL Server. You start with an IDL interface
and specify the inputs and outputs using various COM
types. [8] Then, you create a class that implements
that interface and add the request_handler
and
soap_handler
attributes to tell the ATL Server plumbing
that this is a web service implementation. Finally, you implement
the interface methods, decorating each one with the
soap_method
attribute to wire up the incoming XML requests
to the appropriate methods.
Check the ATL Server documentation for details about which types are actually supported in web services. In general, scalars (numbers, strings) and arrays and structures of scalars are supported. Arbitrary interface pointers are not supported in web services.
So, I removed the sample code, and created my interface:
1[ uuid("5CEAB050-F80B-4054-8E1B-43510E61B8CE"),
2 object ]
3__interface IStringLibService {
4 [id(1)] HRESULT ToUpper([in] BSTR bstrInput,
5 [out, retval] BSTR *pbstrOutput);
6 [id(2)] HRESULT ToLower([in] BSTR bstrInput,
7 [out, retval] BSTR *pbstrOutput );
8 [id(3)] HRESULT GetLength([in] BSTR bstrInput,
9 [out, retval] long *pResult );
10};
And the implementation class:
1[ request_handler(name="Default", sdl="GenStringLibWSDL"),
2 soap_handler(
3 name="StringLibService",
4 namespace="urn:StringLibService",
5 protocol="soap"
6 ) ]
7class CStringLibService :
8 public IStringLibService {
9public:
10
11 [soap_method]
12 HRESULT ToUpper( BSTR bstrInput, BSTR *pbstrOutput ) {
13 CComBSTR result( bstrInput );
14 HRESULT hr = result.ToUpper( );
15 if( FAILED( hr ) ) return hr;
16 *pbstrOutput = result.Detach( );
17 return S_OK;
18 }
19
20 [soap_method]
21 HRESULT ToLower( BSTR bstrInput, BSTR *pbstrOutput ) {
22 CComBSTR result( bstrInput );
23 HRESULT hr = result.ToLower( );
24 if( FAILED( hr ) ) return hr;
25 *pbstrOutput = result.Detach( );
26 return S_OK;
27 }
28
29 [soap_method]
30 HRESULT GetLength( BSTR bstrInput, long *pResult ) {
31 *pResult = ::SysStringLen( bstrInput );
32 return S_OK;
33 }
34};
Aside from the attributes, this code wouldn’t look out of place in any COM server implementation.
To use the web service in question, you need a WSDL file. ATL Server automatically generates the WSDL; it’s accessible at http://localhost/StringLib/StringLib.dll?Handler=GenStringLibWSDL.
Consuming a Web Service in C++
After my web service was deployed, I wanted to test it. You can call most web services from almost every language. Because this is a C++ book, I created a C++ client. Luckily, Visual Studio includes a code generator that makes it fairly easy to make calls on a web service.
I started by creating a Win32 Console application in Visual Studio. To bring in the web service, I used the Add Web Service feature of Visual Studio to read the WSDL file from the web service and generate a proxy class [9] that wraps access to the web service. The rest of the C++ code simply collected some input and called the web service:
The Add Web Service function calls into a command-line utility named sproxy.exe. Look in the documentation for details on how to customize proxy-class generation.
1#include <iostream>
2#include <string>
3#include <atlconv.h>
4
5using namespace std;
6
7class CoInit {
8public:
9 CoInit( ) { ::CoInitialize( 0 ); }
10 ~CoInit( ) { ::CoUninitialize( ); }
11};
12
13void _tmain(int argc, _TCHAR* argv[]) {
14 CoInit coInit;
15
16 cout << "Enter a string: ";
17 string input;
18 getline( cin, input );
19
20 StringLibService::CStringLibService proxy;
21
22 CComBSTR bstrInput( input.c_str( ) );
23 CComBSTR bstrToLower;
24 HRESULT hr = proxy.ToLower( bstrInput, &bstrToLower );
25 if( FAILED( hr ) ) {
26 cerr << "Call to ToLower failed with HRESULT " << hr;
27 return -1;
28 }
29 cout << "This string in upper case is:" <<
30 CW2A( bstrToLower ) << endl;
31
32 CComBSTR bstrToUpper;
33 hr = proxy.ToUpper( bstrInput, &bstrToUpper );
34 if( FAILED( hr ) ) {
35 cerr << "Call to ToUpper failed with HRESULT " << hr;
36 return -1;
37 }
38 cout << "This string in lower case is:" <<
39 CW2A( bstrToUpper ) << endl;
40
41 int length;
42 hr = proxy.GetLength( bstrInput, &length );
43 if( FAILED( hr ) ) {
44 cerr << "Call to GetLength failed with HRESULT " << hr;
45 return -1;
46 }
47 cout << "This string is " << length <<
48 " characters long." << endl;
49}
The lines in bold show the calls to the web
service. Calling via the proxy acts much like a call into a COM
object: All calls return an hrESULT
, the actual return
value is given via an out
parameter, and there are special
rules for memory management (which are explained in the MSDN
documentation). Because the web service client proxy uses the
Microsoft XML 3.0 Server XMLHTTP
object by default, I
needed to initialize COM before making the calls.
This code simply calls all three of the methods in the web service. Figure 13.14 shows the result of running the test harness.
Figure 13.14. Output from Web Service test client

SOAP Box: Why ATL Server Does Web Services Poorly
So, it’s possible to implement web servicesand do so in a way that’s fairly comfortable to COM developers. What’s not to like? A lot, as it turns out.
When using ATL Server Web Services, you have absolutely no control over the actual XML that gets sent out. ATL Server maps your COM interface into RPC-encoded SOAP; you can’t hand it an XML schema. You can’t even tweak the WSDL after the fact. You’re stuck with what ATL Server gives you.
Now, perhaps you’re thinking that this isn’t too bad. Maybe you’re defining a new service and don’t need to conform to an existing XML schema. Unfortunately, you might be in for problem even then.
SOAP uses two basic styles to map the XML in the SOAP document to the underlying programming model. ATL Server uses the RPC encoding. This is a set of rules defined in the SOAP specification that say how an integer, string, array, and so on is represented in XML. This encoding does not use the XML Schema definition (although it does use the XSD type system) because the XSD specification wasn’t finished when the SOAP spec shipped.
You would think that the RPC style would be sufficient, but the SOAP spec was sufficiently ambiguous that different vendors implemented RPC encoding in different and incompatible ways. With ATL Server adopting RPC encoding, you’re in for interop trouble.
The other option is to use document-literal encoding. With doc literal, you simply treat the body of the SOAP document as XML. This turns out to be much more flexible and interoperable than RPC encoding. In fact, in the current SOAP 1.2 specification, [10] Section 5 (which defines RPC encoding) is completely optional. More toolkits are moving toward doing doc literalstyle web services and are leaving out RPC encoding altogether. [11]
Available at www.w3.org/TR/soap12-part1/ (http://tinysells.com/54).
See “The Argument Against SOAP Encoding,” by Tim Ewald, at ` http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnsoap/html/argsoape.asp <http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnsoap/html/argsoape.asp>`__ (http://tinysells.com/55), for more details about the evils of RPC encoding in web services.
As a result, I simply can’t recommend using ATL Server Web Services in any serious application. It might be useful if you have existing C++ code that has to be exposed as a web service now, now, now! But for anything that needs to interop with other environments, you’re better off choosing a web service layer that supports more modern web services styles such as doc-literal and XML Schema.
Summary
ATL Server is an extension of ATL that enables
you to write ISAPI applications and bring your C++ skills to the
World Wide Web. An ATL Server project consists of an ISAPI DLL that
implements the ISAPI entry points for IIS, a set of .``srf``
files that include the HTML to send back to the client, and an
application DLL that provides request handlers that fill in the
substitutions in the .srf
files.
The ATL Server ISAPI implementation provides you with a built-in thread pool for high-performance web applications. ATL Server also provides optional caching mechanisms to further improve performance.
An ATL Server request handler is used to
validate the incoming requests and (in concert with the
.srf
files) generate the output. ATL Server provides the
CHttpRequest
class that wraps the HTTP request.
CHttpRequest
provides access to query string parameters,
form variables, cookies, and a whole lot more.
CHttpResponse
wraps the output and gives you the
capability to easily generate output to the client in the style of
a C++ iostream.
ATL Server can also be used for quick web service wrappers around existing COM code.
In the next chapter, we take a look at how ATL Server does all this under the hood.