MonitorService knows quite a bit more about what is going on within the program than it actually logs to a file. Much of this information is state information that might not even make sense out of context. Simply knowing that one or another thread of the program is working can be at the very least interesting. And when you're debugging a multithreaded application, understanding the interactions between threads can be critical to understanding failures.
To implement the ability to get real-time trace information, I have added several data members and methods to the base class for CMonitorService , CPPService . These members and methods are
You'll find the complete source for CPPService.h and CPPService.cpp on the companion CD-ROM. The Trace method is at the core of the changes to CPPService . Trace is shown in Listing 15-11.
Listing 15-11
CPPService.cpp DWORDCPPService::Trace(LPCSTRtraceStr) { charszTs[255]; structtm*time_now; time_tsecs_now; time(&secs_now); time_now=localtime(&secs_now); strftime(szTs,30, "%Y-%m-%d%H:%M", time_now); EnterCriticalSection(&m_csTrace); //Loopback,sowekeep100inmemory if(m_traceTail==MAX_TRACES) { m_traceTail=0; m_traceHead=1; } if(m_traces[m_traceTail]!=0) { delete[]m_traces[m_traceTail]; } m_traces[m_traceTail]=newchar[strlen(traceStr)+ strlen(szTs)+10]; //Tracestringcontainstimestampfollowed //bythemessagetext. strcpy(m_traces[m_traceTail],szTs); strcat(m_traces[m_traceTail],":"); strcat(m_traces[m_traceTail],traceStr); if(m_debugging) { printf("\n%s",m_traces[m_traceTail]); } if(m_traceHead>m_traceTail) { m_traceHead++; if(m_traceHead==MAX_TRACES) { m_traceHead=0; } } m_traceTail++; LeaveCriticalSection(&m_csTrace); return((DWORD)m_traceHead); } |
The job of Trace is to place the string passed as traceStr into the m_traces pointer array. Before managing the m_traceHead , m_traceTail , and m_traces variables, the function enters a critical section object, m_csTrace . In classes derived from CPPService , other threads will manipulate the m_traceHead , m_traceTail , and m_traces variables, so we must serialize access to these variables. The critical section object is initialized and deleted in the constructor and destructor, respectively. The first and last messages are pointed to by m_traceHead and m_traceTail , respectively. These pointers are manipulated so that the most recent MAX_TRACES messages in the array are always available. These traces could have also been placed in a SQL Server table; however, making the trace messages available in this way presents a more interesting example. Finally, if the service is being debugged and run as a console mode application rather than as a service, the message is displayed on the console using printf .
I made changes to CMonitorService in two areas. First, the trace statements must be placed in the appropriate functions of the service. You'll find the complete code for the modified CMonitorService on the companion CD-ROM, and you'll find calls to the new Trace method of CPPService in DoTask and MonitorAlerts . The second area changed in CMonitorService is DoWork , shown in Listing 15-12.
Listing 15-12
MonitorService.cpp voidCMonitorService::DoWork(SOCKETs,WORDThreadNum) { chardiag[255]; chardata[2048]; BOOLfSuccess; intlen; staticintblksSent=0; intbytesRead; intmaxData=512; intnumSent=0; boolfinalSent=false; bytesRead=0L; memset((void*)data,'void CMonitorService::DoWork(SOCKET s, WORD ThreadNum) { char diag[255]; char data[2048]; BOOL fSuccess; int len; static int blksSent = 0; int bytesRead; int maxData = 512; int numSent = 0; bool finalSent = false; bytesRead = 0L; memset ((void *)data, '\0', 1024); do { if ((len = recv(s, ((char *)data), maxData, 0)) == SOCKET_ERROR) { fSuccess = FALSE; wsprintf(diag, "SOCKET ERROR", data); LogEvent(EVENTLOG_ERROR_TYPE, EVMSG_DEBUG, diag); } else { EnterCriticalSection(&m_csTrace); long curPacket = m_traceHead; while (len != SOCKET_ERROR && IsRunning()) { DATA_PACKET *d; d = (DATA_PACKET *)&data[0]; switch (d->ServiceCode) { case REQ_FIRST_TRACE: curPacket = m_traceHead+1; numSent = 0; // break; // Step through this. case REQ_NEXT_TRACE: if (curPacket >= MAX_TRACES) { curPacket = 0; } if (m_traces[curPacket] == 0 curPacket == (m_traceTail) (curPacket == MAX_TRACES-1 && m_traceHead == 0) && numSent < MAX_TRACES) { // Send a signal that we are done. d->DataLen = 0; len = send(s, (const char *)d, (14+d->DataLen)+1, 0); finalSent = true; } else { d->DataLen = strlen(m_traces[curPacket])+1; strncpy (d->Buffer, m_traces[curPacket], d->BufLen); d->Buffer[d->DataLen+1] = '\0'; len = send(s, (const char *)d, (14+d->DataLen)+1, 0); curPacket++; numSent++; } break; default: d->DataLen = 0; break; } if (!finalSent && len != SOCKET_ERROR) { // Get the next message from the client. do { len = recv(s, ((char *)data), maxData, 0); if (IsRunning() && len == 0) { Sleep(100); } } while (len == 0 && IsRunning()); } else { len = SOCKET_ERROR; } } LeaveCriticalSection(&m_csTrace); } } while (!(finalSent) && IsRunning() && len != SOCKET_ERROR && len != 0); closesocket (s); }',1024); do{ if((len=recv(s, ((char*)data), maxData,0))==SOCKET_ERROR) { fSuccess=FALSE; wsprintf(diag,"SOCKETERROR",data); LogEvent(EVENTLOG_ERROR_TYPE, EVMSG_DEBUG,diag); } else { EnterCriticalSection(&m_csTrace); longcurPacket=m_traceHead; while(len!=SOCKET_ERROR&&IsRunning()) { DATA_PACKET*d; d=(DATA_PACKET*)&data[0]; switch(d->ServiceCode) { caseREQ_FIRST_TRACE: curPacket=m_traceHead+1; numSent=0; //break;//Stepthroughthis. caseREQ_NEXT_TRACE: if(curPacket>=MAX_TRACES) { curPacket=0; } if(m_traces[curPacket]==0 curPacket==(m_traceTail) (curPacket==MAX_TRACES-1&& m_traceHead==0)&& numSent<MAX_TRACES) { //Sendasignalthatwearedone. d->DataLen=0; len=send(s,(constchar*)d, (14+d->DataLen)+1,0); finalSent=true; } else { d->DataLen=strlen(m_traces[curPacket])+1; strncpy(d->Buffer, m_traces[curPacket],d->BufLen); d->Buffer[d->DataLen+1]='void CMonitorService::DoWork(SOCKET s, WORD ThreadNum) { char diag[255]; char data[2048]; BOOL fSuccess; int len; static int blksSent = 0; int bytesRead; int maxData = 512; int numSent = 0; bool finalSent = false; bytesRead = 0L; memset ((void *)data, '\0', 1024); do { if ((len = recv(s, ((char *)data), maxData, 0)) == SOCKET_ERROR) { fSuccess = FALSE; wsprintf(diag, "SOCKET ERROR", data); LogEvent(EVENTLOG_ERROR_TYPE, EVMSG_DEBUG, diag); } else { EnterCriticalSection(&m_csTrace); long curPacket = m_traceHead; while (len != SOCKET_ERROR && IsRunning()) { DATA_PACKET *d; d = (DATA_PACKET *)&data[0]; switch (d->ServiceCode) { case REQ_FIRST_TRACE: curPacket = m_traceHead+1; numSent = 0; // break; // Step through this. case REQ_NEXT_TRACE: if (curPacket >= MAX_TRACES) { curPacket = 0; } if (m_traces[curPacket] == 0 curPacket == (m_traceTail) (curPacket == MAX_TRACES-1 && m_traceHead == 0) && numSent < MAX_TRACES) { // Send a signal that we are done. d->DataLen = 0; len = send(s, (const char *)d, (14+d->DataLen)+1, 0); finalSent = true; } else { d->DataLen = strlen(m_traces[curPacket])+1; strncpy (d->Buffer, m_traces[curPacket], d->BufLen); d->Buffer[d->DataLen+1] = '\0'; len = send(s, (const char *)d, (14+d->DataLen)+1, 0); curPacket++; numSent++; } break; default: d->DataLen = 0; break; } if (!finalSent && len != SOCKET_ERROR) { // Get the next message from the client. do { len = recv(s, ((char *)data), maxData, 0); if (IsRunning() && len == 0) { Sleep(100); } } while (len == 0 && IsRunning()); } else { len = SOCKET_ERROR; } } LeaveCriticalSection(&m_csTrace); } } while (!(finalSent) && IsRunning() && len != SOCKET_ERROR && len != 0); closesocket (s); }'; len=send(s,(constchar*)d, (14+d->DataLen)+1,0); curPacket++; numSent++; } break; default: d->DataLen=0; break; } if(!finalSent&&len!=SOCKET_ERROR) { //Getthenextmessagefromtheclient. do{ len=recv(s, ((char*)data), maxData,0); if(IsRunning()&&len==0) { Sleep(100); } }while(len==0&&IsRunning()); } else { len=SOCKET_ERROR; } } LeaveCriticalSection(&m_csTrace); } }while(!(finalSent)&&IsRunning()&& len!=SOCKET_ERROR&&len!=0); closesocket(s); } |
DoWork first tries to receive a package on the socket passed as the first parameter. Recall that this is a socket that has been retrieved by WSCommDispatcher and Thread , first described in the CommService example in Chapter 12. The buffer received should be a DATA_PACKET structure, also first described in Chapter 12. DATA_PACKET is shown here:
typedefstruct _DATA_PACKET { WORD ServerHandle; WORD ServiceCode; WORD ReturnCode; DWORD DataLen; DWORD BufLen; char Buffer[MAX_DATA_SIZE]; } DATA_PACKET; |
ServiceCode can be set to one of two values, REQ_FIRST_TRACE or REQ_NEXT_TRACE , requesting the first trace message or the next trace message, respectively. ServiceCode controls a switch statement. If the first trace message is requested , I set the local variable curPacket to m_traceHead and pass through to the second case statement (note the commented-out break after the REQ_FIRST_TRACE case). Depending on whether there is another message to send, I either send the message ”setting DataLen to the length of the message ”or I send a DATA_PACKET with DataLen set to 0. If there is no message to send, the local Boolean variable finalSent is set to true . After the switch statement, I try to receive another request from the client, breaking out only if recv returns SOCKET_ERROR or the IsRunning method of CPPService returns false. Finally, after all messages are sent, I leave the m_csTrace critical section and close the socket.
Look at the code in Listing 15-12. You can see that every loop is controlled by the return value of IsRunning , in addition to whatever other logic controls the loop. For instance, rather than just testing len to see if a socket error has occurred, the fragment below checks len as well as the return value of IsRunning.
while(len!=SOCKET_ERROR&&IsRunning()) { //Continuelooping |
This makes for a somewhat more complicated function, but it is essential to ensure that the function will return to DoWork whenever the service is stopped so that the thread running DoWork can terminate normally.
Once the service is running and tracing its operation, we need to get at the information. An ideal way to do this is from the Web. Look back at the Monitor Trace link in Figure 15-1. Clicking on this link leads to the screen shown in Figure 15-6. The screen displays all of the trace statements currently cached in the service.
Figure 15-6 The trace statements screen.
The information in this screen can be generated in a couple of ways. A server-side ActiveX control can generate the table information for an ASP page, using TCP/IP to communicate to CMonitorService . An alternative is to use an ISAPI extension. For this example, I will use ISAPI to create the entire page. Listing 15-13 shows ISAPIMonitor, the ISAPI extension I created to display the monitor trace information.
Listing 15-13
ISAPIMonitor.cpp //ISAPIMONITOR.CPP-ImplementationfileMonitorTrace //display #include"stdafx.h" #include"ISAPIMonitor.h" ///////////////////////////////////////////////////////////////////// //TheoneandonlyCWinAppobject //NOTE:Youcanremovethisobjectifyoualteryourprojecttono //longeruseMFCinaDLL. //CWinApptheApp; ///////////////////////////////////////////////////////////////////// //command-parsingmap BEGIN_PARSE_MAP(CISAPIMonitorExtension,CHttpServer) //TODO:insertyourON_PARSE_COMMAND()and //ON_PARSE_COMMAND_PARAMS()heretohookupyourcommands. //Forexample: ON_PARSE_COMMAND(Default,CISAPIMonitorExtension,ITS_EMPTY) DEFAULT_PARSE_COMMAND(Default,CISAPIMonitorExtension) END_PARSE_MAP(CISAPIMonitorExtension) ///////////////////////////////////////////////////////////////////// //TheoneandonlyCISAPIMonitorExtensionobject CISAPIMonitorExtensiontheExtension; ///////////////////////////////////////////////////////////////////// //CISAPIMonitorExtensionimplementation CISAPIMonitorExtension::CISAPIMonitorExtension() { } CISAPIMonitorExtension::~CISAPIMonitorExtension() { } BOOLCISAPIMonitorExtension::GetExtensionVersion(HSE_VERSION_INFO*pVer) { //Calldefaultimplementationforinitialization. CHttpServer::GetExtensionVersion(pVer); //Loaddescriptionstring. TCHARsz[HSE_MAX_EXT_DLL_NAME_LEN+1]; ISAPIVERIFY(::LoadString(AfxGetResourceHandle(), IDS_SERVER,sz,HSE_MAX_EXT_DLL_NAME_LEN)); _tcscpy(pVer->lpszExtensionDesc,sz); returnTRUE; } BOOLCISAPIMonitorExtension::TerminateExtension(DWORDdwFlags) { //Extensionisbeingterminated //TODO:Cleanupanyper-instanceresources. returnTRUE; } ///////////////////////////////////////////////////////////////////// //CISAPIMonitorExtensioncommandhandlers voidCISAPIMonitorExtension::Default(CHttpServerContext*pCtxt) { DWORDdwBytes=0; DATA_PACKETd; StartContent(pCtxt); WriteTitle(pCtxt); memset((char*)&d,'// ISAPIMONITOR.CPP - Implementation file Monitor Trace // display #include "stdafx.h" #include "ISAPIMonitor.h" ///////////////////////////////////////////////////////////////////// // The one and only CWinApp object // NOTE: You can remove this object if you alter your project to no // longer use MFC in a DLL. //CWinApp theApp; ///////////////////////////////////////////////////////////////////// // command-parsing map BEGIN_PARSE_MAP(CISAPIMonitorExtension, CHttpServer) // TODO: insert your ON_PARSE_COMMAND() and // ON_PARSE_COMMAND_PARAMS() here to hook up your commands. // For example: ON_PARSE_COMMAND(Default, CISAPIMonitorExtension, ITS_EMPTY) DEFAULT_PARSE_COMMAND(Default, CISAPIMonitorExtension) END_PARSE_MAP(CISAPIMonitorExtension) ///////////////////////////////////////////////////////////////////// // The one and only CISAPIMonitorExtension object CISAPIMonitorExtension theExtension; ///////////////////////////////////////////////////////////////////// // CISAPIMonitorExtension implementation CISAPIMonitorExtension::CISAPIMonitorExtension() { } CISAPIMonitorExtension::~CISAPIMonitorExtension() { } BOOL CISAPIMonitorExtension::GetExtensionVersion(HSE_VERSION_INFO* pVer) { // Call default implementation for initialization. CHttpServer::GetExtensionVersion(pVer); // Load description string. TCHAR sz[HSE_MAX_EXT_DLL_NAME_LEN+1]; ISAPIVERIFY(::LoadString(AfxGetResourceHandle(), IDS_SERVER, sz, HSE_MAX_EXT_DLL_NAME_LEN)); _tcscpy(pVer->lpszExtensionDesc, sz); return TRUE; } BOOL CISAPIMonitorExtension::TerminateExtension(DWORD dwFlags) { // Extension is being terminated // TODO: Clean up any per-instance resources. return TRUE; } ///////////////////////////////////////////////////////////////////// // CISAPIMonitorExtension command handlers void CISAPIMonitorExtension::Default(CHttpServerContext* pCtxt) { DWORD dwBytes = 0; DATA_PACKET d; StartContent(pCtxt); WriteTitle(pCtxt); memset((char *)&d, '\0', sizeof(DATA_PACKET)); // Of course, this should be a parameter, a // registry entry, or at least modified // in the source for your environment. CWinSockClientComm MyClient("DUAL", 5043); if (MyClient.Open() == -1) { *pCtxt << _T("<CENTER><H2>Sorry. Can't connect to "); *pCtxt << _T("CMonitorService<CENTER></H2>"); return; } *pCtxt << _T("<CENTER><H2>Trace Statements "); *pCtxt << _T("from <I>MonitorService</I></H2><BR>"); *pCtxt << _T("<TABLE WIDTH=85% BORDER=1 CELLSPACING=1 CELLPADDING =1>"); *pCtxt << _T("<TR><TD BGCOLOR=AQUA> "); *pCtxt << _T("Trace Information </TR></TD> "); // Initialize these. d.ServiceCode = REQ_FIRST_TRACE; d.DataLen = 16; do { d.BufLen = 1024; MyClient.Write((char *)&d, sizeof(d), &dwBytes); // Initialize this. d.DataLen = 0; memset(d.Buffer, '\0', sizeof(d.Buffer)); MyClient.Read((char *)&d, sizeof(d), &dwBytes); if (d.DataLen) { *pCtxt << _T("<TR><TD> "); *pCtxt << _T(d.Buffer); *pCtxt << _T("</TD></TR>"); d.ServiceCode = REQ_NEXT_TRACE; d.DataLen = 16; } } while (d.DataLen && dwBytes); MyClient.Close(); *pCtxt << _T("</TABLE> "); *pCtxt << _T("<FORM action=\"Default.asp\" "); *pCtxt << _T("method=POST id=form1 name=form1>"); *pCtxt << _T("<INPUT type=\"submit\" "); *pCtxt << _T("value=\"Return to Alert List\" "); *pCtxt << _T(" id=submit1 name =submit1>"); *pCtxt << _T("</FORM>"); EndContent(pCtxt); } // Do not edit the following lines, which are needed by ClassWizard. #if 0 BEGIN_MESSAGE_MAP(CISAPIMonitorExtension, CHttpServer) //{{AFX_MSG_MAP(CISAPIMonitorExtension) //}}AFX_MSG_MAP END_MESSAGE_MAP() #endif // 0 ///////////////////////////////////////////////////////////////////// // If your extension will not use MFC, you'll need this code to make // sure the extension objects can find the resource handle for the // module. If you convert your extension to not be dependent on MFC, // remove the comments around the following AfxGetResourceHandle() // and DllMain() functions, as well as the g_hInstance global. /****/ static HINSTANCE g_hInstance; HINSTANCE AFXISAPI AfxGetResourceHandle() { return g_hInstance; } BOOL WINAPI DllMain(HINSTANCE hInst, ULONG ulReason, LPVOID lpReserved) { if (ulReason == DLL_PROCESS_ATTACH) { g_hInstance = hInst; } return TRUE; } /****/',sizeof(DATA_PACKET)); //Ofcourse,thisshouldbeaparameter,a //registryentry,oratleastmodified //inthesourceforyourenvironment. CWinSockClientCommMyClient("DUAL",5043); if(MyClient.Open()==-1) { *pCtxt<<_T("<CENTER><H2>Sorry.Can'tconnectto"); *pCtxt<<_T("CMonitorService<CENTER></H2>"); return; } *pCtxt<<_T("<CENTER><H2>TraceStatements"); *pCtxt<<_T("from<I>MonitorService</I></H2><BR>"); *pCtxt<< _T("<TABLEWIDTH=85%BORDER=1CELLSPACING=1CELLPADDING=1>"); *pCtxt<<_T("<TR><TDBGCOLOR=AQUA>"); *pCtxt<<_T("TraceInformation</TR></TD>"); //Initializethese. d.ServiceCode=REQ_FIRST_TRACE; d.DataLen=16; do{ d.BufLen=1024; MyClient.Write((char*)&d,sizeof(d),&dwBytes); //Initializethis. d.DataLen=0; memset(d.Buffer,'// ISAPIMONITOR.CPP - Implementation file Monitor Trace // display #include "stdafx.h" #include "ISAPIMonitor.h" ///////////////////////////////////////////////////////////////////// // The one and only CWinApp object // NOTE: You can remove this object if you alter your project to no // longer use MFC in a DLL. //CWinApp theApp; ///////////////////////////////////////////////////////////////////// // command-parsing map BEGIN_PARSE_MAP(CISAPIMonitorExtension, CHttpServer) // TODO: insert your ON_PARSE_COMMAND() and // ON_PARSE_COMMAND_PARAMS() here to hook up your commands. // For example: ON_PARSE_COMMAND(Default, CISAPIMonitorExtension, ITS_EMPTY) DEFAULT_PARSE_COMMAND(Default, CISAPIMonitorExtension) END_PARSE_MAP(CISAPIMonitorExtension) ///////////////////////////////////////////////////////////////////// // The one and only CISAPIMonitorExtension object CISAPIMonitorExtension theExtension; ///////////////////////////////////////////////////////////////////// // CISAPIMonitorExtension implementation CISAPIMonitorExtension::CISAPIMonitorExtension() { } CISAPIMonitorExtension::~CISAPIMonitorExtension() { } BOOL CISAPIMonitorExtension::GetExtensionVersion(HSE_VERSION_INFO* pVer) { // Call default implementation for initialization. CHttpServer::GetExtensionVersion(pVer); // Load description string. TCHAR sz[HSE_MAX_EXT_DLL_NAME_LEN+1]; ISAPIVERIFY(::LoadString(AfxGetResourceHandle(), IDS_SERVER, sz, HSE_MAX_EXT_DLL_NAME_LEN)); _tcscpy(pVer->lpszExtensionDesc, sz); return TRUE; } BOOL CISAPIMonitorExtension::TerminateExtension(DWORD dwFlags) { // Extension is being terminated // TODO: Clean up any per-instance resources. return TRUE; } ///////////////////////////////////////////////////////////////////// // CISAPIMonitorExtension command handlers void CISAPIMonitorExtension::Default(CHttpServerContext* pCtxt) { DWORD dwBytes = 0; DATA_PACKET d; StartContent(pCtxt); WriteTitle(pCtxt); memset((char *)&d, '\0', sizeof(DATA_PACKET)); // Of course, this should be a parameter, a // registry entry, or at least modified // in the source for your environment. CWinSockClientComm MyClient("DUAL", 5043); if (MyClient.Open() == -1) { *pCtxt << _T("<CENTER><H2>Sorry. Can't connect to "); *pCtxt << _T("CMonitorService<CENTER></H2>"); return; } *pCtxt << _T("<CENTER><H2>Trace Statements "); *pCtxt << _T("from <I>MonitorService</I></H2><BR>"); *pCtxt << _T("<TABLE WIDTH=85% BORDER=1 CELLSPACING=1 CELLPADDING =1>"); *pCtxt << _T("<TR><TD BGCOLOR=AQUA> "); *pCtxt << _T("Trace Information </TR></TD> "); // Initialize these. d.ServiceCode = REQ_FIRST_TRACE; d.DataLen = 16; do { d.BufLen = 1024; MyClient.Write((char *)&d, sizeof(d), &dwBytes); // Initialize this. d.DataLen = 0; memset(d.Buffer, '\0', sizeof(d.Buffer)); MyClient.Read((char *)&d, sizeof(d), &dwBytes); if (d.DataLen) { *pCtxt << _T("<TR><TD> "); *pCtxt << _T(d.Buffer); *pCtxt << _T("</TD></TR>"); d.ServiceCode = REQ_NEXT_TRACE; d.DataLen = 16; } } while (d.DataLen && dwBytes); MyClient.Close(); *pCtxt << _T("</TABLE> "); *pCtxt << _T("<FORM action=\"Default.asp\" "); *pCtxt << _T("method=POST id=form1 name=form1>"); *pCtxt << _T("<INPUT type=\"submit\" "); *pCtxt << _T("value=\"Return to Alert List\" "); *pCtxt << _T(" id=submit1 name =submit1>"); *pCtxt << _T("</FORM>"); EndContent(pCtxt); } // Do not edit the following lines, which are needed by ClassWizard. #if 0 BEGIN_MESSAGE_MAP(CISAPIMonitorExtension, CHttpServer) //{{AFX_MSG_MAP(CISAPIMonitorExtension) //}}AFX_MSG_MAP END_MESSAGE_MAP() #endif // 0 ///////////////////////////////////////////////////////////////////// // If your extension will not use MFC, you'll need this code to make // sure the extension objects can find the resource handle for the // module. If you convert your extension to not be dependent on MFC, // remove the comments around the following AfxGetResourceHandle() // and DllMain() functions, as well as the g_hInstance global. /****/ static HINSTANCE g_hInstance; HINSTANCE AFXISAPI AfxGetResourceHandle() { return g_hInstance; } BOOL WINAPI DllMain(HINSTANCE hInst, ULONG ulReason, LPVOID lpReserved) { if (ulReason == DLL_PROCESS_ATTACH) { g_hInstance = hInst; } return TRUE; } /****/',sizeof(d.Buffer)); MyClient.Read((char*)&d,sizeof(d),&dwBytes); if(d.DataLen) { *pCtxt<<_T("<TR><TD>"); *pCtxt<<_T(d.Buffer); *pCtxt<<_T("</TD></TR>"); d.ServiceCode=REQ_NEXT_TRACE; d.DataLen=16; } }while(d.DataLen&&dwBytes); MyClient.Close(); *pCtxt<<_T("</TABLE>"); *pCtxt<<_T("<FORMaction=\"Default.asp\""); *pCtxt<<_T("method=POSTid=form1name=form1>"); *pCtxt<<_T("<INPUTtype=\"submit\""); *pCtxt<<_T("value=\"ReturntoAlertList\""); *pCtxt<<_T("id=submit1name=submit1>"); *pCtxt<<_T("</FORM>"); EndContent(pCtxt); } //Donoteditthefollowinglines,whichareneededbyClassWizard. #if0 BEGIN_MESSAGE_MAP(CISAPIMonitorExtension,CHttpServer) //{{AFX_MSG_MAP(CISAPIMonitorExtension) //}}AFX_MSG_MAP END_MESSAGE_MAP() #endif//0 ///////////////////////////////////////////////////////////////////// //IfyourextensionwillnotuseMFC,you'llneedthiscodetomake //suretheextensionobjectscanfindtheresourcehandleforthe //module.IfyouconvertyourextensiontonotbedependentonMFC, //removethecommentsaroundthefollowingAfxGetResourceHandle() //andDllMain()functions,aswellastheg_hInstanceglobal. /****/ staticHINSTANCEg_hInstance; HINSTANCEAFXISAPIAfxGetResourceHandle() { returng_hInstance; } BOOLWINAPIDllMain(HINSTANCEhInst,ULONGulReason, LPVOIDlpReserved) { if(ulReason==DLL_PROCESS_ATTACH) { g_hInstance=hInst; } returnTRUE; } /****/ |
The majority of Listing 15-13 is the Wizard-generated code. I have removed the reliance on MFC classes outside the MFC ISAPI classes, following the instructions in the source code as well as the extra steps described in Chapter 10.
The Default method is used to generate the HTML code required to display the monitor trace in the user 's browser. Recall that Default is created by the MFC ISAPI Extension Wizard as the entry point called when no specific function is requested on an incoming URL. If you need your ISAPI extension to do more than one thing, you should add other class members and change the parse map macros, as we saw in Chapter 10.
Default creates the headers for the table by streaming the HTML code through the pCtxt pointer passed as the sole parameter to Default. Default relies upon the CWinSockClientComm class first described in Chapter 12. Recall that this class descends from CClientComm , a generic base class. If CMonitorService used named pipes instead of WinSock, I could have used the same method calls after creating a CNPClientComm object rather than a CWinSockClientComm object. The constructor for CWinSockClientComm is called with the name of the server and the port number. The server must be running CMonitorService , and the port must match the port used by CMonitorService . I call the Open method of CWinSockClientComm to actually create the socket. If Open fails and returns -1, a message is written to the HTML output stream and the function exits.
The key to getting any communication working reliably between a client and server program is to ensure that both programs observe a known protocol. As with communication between people, if both parties talk or listen at once they aren't likely to be as productive as if they talked and listened in turn . Default ensures that the protocol is followed by calling the Write and Read methods in a loop in the opposite order CMonitorService calls them. As each message is read, Default creates a table row and cell to contain the message. After all messages are read, I call the Close method of CWinSockClientComm to end the connection. Finally I stream out the code to create a button at the bottom of the page that allows the user to return to the default screen, which shows tasks with active alerts.