TraceSrv Requirements

[Previous] [Next]

First let's go over my design objectives for TraceSrv because the best way for you to understand TraceSrv is to see what I set out to accomplish. Here are the requirements that I started with:

  1. TraceSrv must be compatible with most common programming languages, including, at a minimum, C++, Visual Basic, Borland Delphi, Visual Basic for Applications, Java, JScript, and VBScript.
  2. TraceSrv must be very simple to use inside a programming language.
  3. TraceSrv must always be running so that any application can connect to it at any time.
  4. The trace statements of a program that runs on multiple machines should go to the same place.
  5. The trace statements should be viewable on multiple machines at the same time by trace viewer applications.
  6. The following trace statement options processing should be available:
    • Prefix the trace statement with the time the trace statement was received.
    • Prefix the trace statement with the number of the trace statement.
    • Prefix the process ID of the process that sent the trace statement.
    • Append a carriage return and line feed if needed.
    • Send the trace statement through to a kernel debugger where the TraceSrv process is running.

  7. If one of the TraceSrv options in requirement 6 is changed, all the currently active viewers should be notified so that all viewers, even on other machines, are coordinated with the current options.

At first glance, the TraceSrv requirements might look daunting because of the need for multilanguage programming and network development. I thought I could address multilanguage issues with a simple dynamic-link library (DLL) that anyone could load. Because I'm primarily a systems programmer, not a Web developer, my ignorance of VBScript and Java began to get in my way. Particularly when I looked at VBScript, I realized that no matter how much hacking I did, I wasn't going to get VBScript to call a DLL directly. The light finally started to dawn when I saw that VBScript supported CreateObject; I just needed a Component Object Model (COM) object, and VBScript would be able to use it just fine. Because COM works in almost all languages, I decided to make TraceSrv a simple COM object.

COM made the network programming problem go away fairly easily. You basically get COM+ for free. COM+ solves the "running all the time" problem because you can have your COM+ servers running as Microsoft Win32 services. The object is always ready if you use an automatic start service.

My first brush with COM+ services (then known as DCOM services) way back in the Microsoft Windows NT 4 alpha days was rather scary. Not only did you have to write the services—not the easiest thing in the world—but you also had to do all sorts of weird stuff with COM to get them hooked up. Fortunately, the Active Template Library (ATL) that comes with Microsoft Visual C++ 6 handles all the grunge work of writing COM+ services and even provides a wizard to help generate the code.

Once I figured out the basic development direction, I needed to define the interface for TraceSrv. TRACESRV.IDL, shown in Listing 11-1, is the main interface for TraceSrv. Basically, I use the Trace method of the ITrace interface to have a trace statement sent to TraceSrv. To accommodate the broadest number of languages, I decided to set the string type passed as a BSTR.

Listing 11-1 TRACESRV.IDL

/*--------------------------------------------------------------------- "Debugging Applications" (Microsoft Press) Copyright (c) 1997-2000 John Robbins -- All rights reserved. ---------------------------------------------------------------------*/ import "oaidl.idl"; import "ocidl.idl"; [ object , uuid ( 4D42A00C-7774-11D3-9F57-00C04FA34F2C ) , dual , helpstring ( "ITrace Interface" ) , pointer_default ( unique ) ] interface ITrace : IDispatch { [ id ( 1 ) , helpstring ( "method Trace" ) ] HRESULT Trace ( [ in ] BSTR bstrText ) ; [ id ( 2 ) , helpstring ( "method FullTrace" ) ] HRESULT FullTrace ( [ in ] BSTR bstrText , [ in ] long dwPID ) ; [ propget, id ( 3 ) , helpstring ( "property ShowTimeStamps" ) ] HRESULT ShowTimeStamps ( [ out, retval ] VARIANT_BOOL *pVal ) ; [ propput, id ( 3 ) , helpstring ( "property ShowTimeStamps" ) ] HRESULT ShowTimeStamps ( [ in ] VARIANT_BOOL newVal ) ; [ propget, id ( 4 ) , helpstring ( "property ShowTraceAsODS" ) ] HRESULT ShowTraceAsODS ( [ out, retval ] VARIANT_BOOL *pVal ) ; [ propput, id ( 4 ) , helpstring ( "property ShowTraceAsODS" ) ] HRESULT ShowTraceAsODS ( [ in ] VARIANT_BOOL newVal ) ; [ propget, id ( 5 ) , helpstring ( "property ShowItemNumber" ) ] HRESULT ShowItemNumber ( [ out, retval ] VARIANT_BOOL *pVal ) ; [ propput, id ( 5 ) , helpstring ( "property ShowItemNumber" ) ] HRESULT ShowItemNumber ( [ in ] VARIANT_BOOL newVal ) ; [ propget, id ( 6 ) , helpstring ( "property ShowPID" ) ] HRESULT ShowPID ( [ out, retval ] VARIANT_BOOL *pVal ) ; [ propput, id ( 6 ) , helpstring ( "property ShowPID" ) ] HRESULT ShowPID ( [ in ] VARIANT_BOOL newVal ) ; [ propget, id ( 7 ) , helpstring ( "property AddCRLF" ) ] HRESULT AddCRLF ( [ out, retval ] VARIANT_BOOL *pVal ) ; [ propput, id ( 7 ) , helpstring ( "property AddCRLF" ) ] HRESULT AddCRLF ( [ in ] VARIANT_BOOL newVal ) ; } ; [ uuid ( 4D42A000-7774-11D3-9F57-00C04FA34F2C ) , version ( 1.0 ) , helpstring ( "TraceSrv 1.0 Type Library" ) ] library TRACESRVLib { importlib ( "stdole32.tlb" ) ; importlib ( "stdole2.tlb" ) ; [ uuid ( 4D42A00E-7774-11D3-9F57-00C04FA34F2C ) , helpstring ( "_ITraceEvents Interface" ) ] dispinterface _ITraceEvents { properties: methods: [ id ( 1 ) , helpstring ( "method TraceEvent" ) ] HRESULT TraceEvent ( BSTR bstrText ) ; [ id ( 2 ) , helpstring ( "method ChangeShowTimeStamps" ) ] HRESULT ChangeShowTimeStamps ( VARIANT_BOOL bNewVal ) ; [ id ( 3 ) , helpstring ( "method ChangeShowTraceAsODS" ) ] HRESULT ChangeShowTraceAsODS ( VARIANT_BOOL bNewVal ) ; [ id ( 4 ) , helpstring ( "method ChangeShowItemNumber" ) ] HRESULT ChangeShowItemNumber ( VARIANT_BOOL bNewVal ) ; [ id ( 5 ) , helpstring ( "method ChangeShowPID" ) ] HRESULT ChangeShowPID ( VARIANT_BOOL bNewVal ) ; [ id ( 6 ) , helpstring ( "method ChangeAddCRLF" ) ] HRESULT ChangeAddCRLF ( VARIANT_BOOL bNewVal ) ; } ; [ uuid ( 4D42A00D-7774-11D3-9F57-00C04FA34F2C ) , helpstring ( "Trace Class" ) ] coclass Trace { [ default ] interface ITrace ; [ default, source ] dispinterface _ITraceEvents ; } ; } ; 

To write a trace statement viewer, all you need to do is handle the events from the _ITraceEvents interface. The TraceSrv properties, which implement the trace statement options requirement listed earlier in this section, are on the ITrace interface in case an application using TraceSrv might want to change them. When a TraceSrv property is changed, TraceSrv generates an event that a trace viewer should handle. The TraceView program I'll go over later shows how to handle each event that TraceSrv generates.

The ATL COM AppWizard cranked out a COM+ service that seemed to be almost 90 percent complete. The only parts that I had to code were the TraceSrv interface and the handlers. Most of the code that I wrote is in TRACE.H and TRACE.CPP, which are on the book's companion CD. Most of the work is setting and getting properties and firing events. The only out-of-the-ordinary function, CTrace::ProcessTrace, is shown in Listing 11-2. I do the processing on the trace strings in CTrace::ProcessTrace.

Listing 11-2 The CTrace::ProcessTrace function

HRESULT CTrace :: ProcessTrace ( BSTR bstrText , long dwPID) { // Always double-check everything. Trust nothing. ASSERT ( this ) ; ASSERT ( NULL != bstrText ) ; // The length of the input string. The length is found after the // pointer is validated. int iInputLen = 0 ; if ( NULL == bstrText ) { return ( Error ( IDS_NULLSTRINGPASSED , GUID_NULL , E_INVALIDARG ) ) ; } // I have some form of pointer in bstrText. Make sure that what the // pointer holds is valid. ASSERT ( FALSE == IsBadReadPtr ( bstrText , sizeof ( BSTR ) ) ) ; ASSERT ( L';\0'; != *bstrText ) ; if ( ( TRUE == IsBadReadPtr ( bstrText , sizeof ( BSTR ) ) ) || ( L';\0'; == *bstrText ) ) { return ( Error ( IDS_INVALIDSTRING , GUID_NULL , E_INVALIDARG ) ) ; } // Get the input length in characters now that the pointer is // validated. iInputLen = lstrlenW ( bstrText ) ; // Calculate the maximum number of bytes needed for the input // string. UINT uiSize = ( iInputLen * sizeof ( OLECHAR ) ) + k_SIZE_FULLFORMATBYTES ; // Grab the lock to protect the m_cOutput class. ObjectLock lock ( this ) ; // If this is the first call to ProcessTrace, m_lBuffSize is 0, so // this if block serves as the initial allocation point. if ( uiSize >= m_cOutput.BufferSize ( ) ) { // Delete the existing buffer, and allocate a bigger one. m_cOutput.Free ( ) ; // Allocate a buffer twice as large as the input string. The // input string is the largest seen so far, so bump up the // memory so that I do allocations only rarely. // I';ll take the trade-off of extra space not being used // over the time to allocate over and over. // Also, multiplying by 2 ensures that I keep the memory // size an even number. I';m working with Unicode characters // in this program, so I don';t want odd memory allocations. UINT uiAllocSize = uiSize * 2 ; // Make sure I get a minimum buffer size. The minimum buffer // size is 2 KB, so in most cases, I';ll execute the code // in this if block only once. if ( k_MIN_TRACE_BUFF_SIZE > uiAllocSize ) { uiAllocSize = k_MIN_TRACE_BUFF_SIZE ; } OLECHAR * pTemp = m_cOutput.Allocate ( uiAllocSize ) ; ASSERT ( NULL != pTemp ) ; if ( NULL == pTemp ) { return ( Error ( IDS_OUTOFMEMORY , GUID_NULL , E_OUTOFMEMORY ) ) ; } } // Everything checked out; now start the real work. // Increment the total. m_dwCurrCount++ ; // Is it time to wrap? if ( 100000 == m_dwCurrCount ) { m_dwCurrCount = 0 ; } // Have the marker pointer start at the beginning of the output // buffer. OLECHAR * pCurr = m_cOutput.GetDataBuffer ( ) ; if ( -1 == m_vbShowItemNumber ) { pCurr += wsprintfW ( pCurr , L"%05d " , m_dwCurrCount ) ; } if ( -1 == m_vbShowTimeStamps ) { // Show the timestamp based on the user';s locale (here at the // server, not at the client!). I force the timestamp to use // the 24-hour military time format. int iLen = GetTimeFormatW ( LOCALE_USER_DEFAULT , LOCALE_NOUSEROVERRIDE | TIME_FORCE24HOURFORMAT | TIME_NOTIMEMARKER , NULL , NULL , pCurr , k_SIZE_TIME ) ; ASSERT ( 0 != iLen ) ; // Move the pointer along, but remember to account for the // NULL character. pCurr += ( iLen - 1 ) ; // GetTimeFormat doesn';t tack on the extra space, so add the // space now. *pCurr = L'; ' ; pCurr++ ; } if ( -1 == m_vbShowPID ) { pCurr += wsprintfW ( pCurr , L"[%04X] " , dwPID ) ; } // Now put the actual message in and copy the NULL terminator as // well. lstrcpynW ( pCurr , bstrText , iInputLen + 1 ) ; // Move pCurr to point at the NULL terminator. pCurr += iInputLen ; // Check to see whether the string needs CRLFs. if ( -1 == m_vbAddCRLF ) { if ( ( L';\x0D'; != *( pCurr _ 2 ) ) || ( L';\x0A'; != *( pCurr _ 1 ) ) ) { *( pCurr ) = L';\x0D'; ; *( pCurr + 1 ) = L';\x0A'; ; pCurr += 2 ; *pCurr = L';\0'; ; } } // Is the input supposed to get shot to a kernel debugger? if ( -1 == m_vbShowTraceAsODS ) { OutputDebugStringW ( (OLECHAR*) m_cOutput ) ; } // Calculate the string';s length. m_cOutput.GetStringByteLength ( ) ; // Finally, let viewers know about the trace. #ifdef _DEBUG HRESULT hr = #endif Fire_TraceEvent ( m_cOutput ) ; #ifdef _DEBUG if ( ! SUCCEEDED ( hr ) ) { ASSERT ( SUCCEEDED ( hr ) ) ; TRACE ( _T ( "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" ) ) ; TRACE ( _T ( "TraceSrv Fire_TraceEvent failed!!\n" ) ) ; TRACE ( _T ( "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" ) ) ; } #endif return ( S_OK ) ; } 

Overall, the implementation of TraceSvr is straightforward. The Implement Connection Point command in ClassView makes handling the IConnectionPoint code a piece of cake. Compared with the Microsoft Visual C++ 5 ATL Proxy Generator, the Implement Connection Point command is a huge improvement.

I paid careful attention to the BSTR string processing. Because I could think of scenarios in which the trace statements would be coming in fast and furiously, I wanted to make sure the strings were handled as rapidly as possible. The CTrace:: ProcessTrace function in TRACE.CPP does a lot of string manipulation, especially considering the different items that I can place on the front and the end of the final string output by TraceSrv. I had originally used the CComBSTR class for the string manipulation. But when I started stepping through the code and looking at what CComBSTR did, I noticed that for almost every method and operator in the class, it allocated or deallocated memory each time with the SysXXXString functions. Although using CComBSTR is perfectly legitimate in some applications, using it in programs that do a good deal of string manipulation, such as TraceSrv, can result in some real performance problems.

To speed up the string processing, I wrote a simple class named CFastBSTR that handles the BSTR manipulation directly. The class is in FASTBSTR.H. Its sole job is to allocate a single buffer for the data and to play games with the leading size DWORD in the GetStringByteLength function. Some developers might argue that I should have stuck religiously with the semantics of Automation BSTRs, but I felt that in this case enhanced performance was more important than conservative programming. You can easily change the code in CFastBSTR to use the SysXXXString functions if the liberties I took with BSTR make you uncomfortable.

The only other detail I need to point out is that the project workspace has four different build configurations: debug and release for multibyte character builds and debug and release for Unicode builds. The multibyte builds allow you to register TraceSrv on Windows 98 machines. As I pointed out in Chapter 5, if you're targeting Windows 2000 exclusively, you should compile your programs to use full Unicode. Because I designed TraceSrv as a Windows 2000 service, which definitely won't run on Windows 98, you should compile the version you install on the server machine with one of the Unicode builds.

Now that you've seen a little of the TraceSrv code, I want to cover what happens after you build TraceSrv and want to use it. The Visual C++ 6_based project that's on the companion CD is basically the one that the ATL COM AppWizard generated, so the last step of the build is to register TraceSrv. The registration portions are all part of the ATL code that you get for free, but TraceSrv is registered only as a local server EXE. TraceSrv won't run as a Win32 service unless you specify the -Service command-line switch. Although I could've made the service registration part of the build, I chose not to because debugging a Win32 service without a kernel debugger such as SoftICE isn't simple. Also, if you're in the middle of a fix-compile-debug cycle, it's a real pain to have to shell out to a command prompt and run net stop tracesrv just to get the build to work. After you've done sufficient debugging and testing with TraceSrv running as a local server, you can register it and run it as a service.



Debugging Applications
Debugging Applications for MicrosoftВ® .NET and Microsoft WindowsВ® (Pro-Developer)
ISBN: 0735615365
EAN: 2147483647
Year: 2000
Pages: 122
Authors: John Robbins

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net