Share via


PostCatReader.cpp

PostCatReader.cpp

Important This sample code may not fully verify that strings passed to it are in fact null-terminated, nor that the referenced string buffers are large enough to store the generated contents. Your production code should always verify the validity and size of data passed as null-terminated strings before using, copying or adding to them. Using more-safe versions of the standard C string-handling functions is also recommended.

//------------------------------------------------------------
//
// File: PostCatReader.cpp, Implementation of CPostCatReader
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// Contents: PostCatReader Class Implements IMailTransportPostCategorize, IEventIsCacheable
//
// Classes: CPostCatReader
//
// Functions:
//  CAttrEntry::operator new
//  CAttrList::AddEntry
//  CPostCatReader::IsCacheable
//  CPostCatReader::CheckIfSinkHasInitalised
//  CPostCatReader::CheckIfSinkHasInitalised
//  CPostCatReader::HrReadRegParams
//  CPostCatReader::PrintRequestedAttribsFromMsg
//  CPostCatReader::PrintRequestedAttribsFromRecips
//  CPostCatReader::PrintAttribVals
//  CPostCatReader::GetAttribPropertyOffSet
//  CPostCatReader::HrPrintData
//  CPostCatReader::GetAttributeValue
//  CPostCatReader::PrintAttrList
//
//-------------------------------------------------------------

#include "stdafx.h"
#include "CatReadEx.h"
#include "PostCatReader.h"
#include "errno.h"


/////////////////////////////////////////////////////////////////////////////
// CPostCatReader
//
//
//-------------------------------------------------------------
//
// Function: CPostCatReader::IsCacheable
//
// Synopsis: Implementation of the method for IEventIsCacheable.
//           If it returns S_OK, the sink is cacheable and will not
//           be loaded and unloaded every time.
//
// Arguments:
//           None
//
//
// Returns:
//           S_OK if successful
//
//-------------------------------------------------------------
HRESULT CPostCatReader::IsCacheable()
{
    return S_OK;
}


//+------------------------------------------------------------
//
// Function: CPostCatReader::CheckIfSinkHasInitalised
//
// Synopsis: Functions that checks the initialization state. If initialization was
//           attempted for the first time, it tries to initialize the sink. It remembers
//           the state, if it succeeded or not, and returns it.
//
// Arguments:
//           None
//
//
// Returns:
//           S_OK if successful
//
//-------------------------------------------------------------
HRESULT CPostCatReader::CheckIfSinkHasInitalised()
{
    PCFunctEnterEx((LPARAM)this, "CPostCatReader::CheckIfSinkHasInitalised");
    HRESULT hr  =   S_OK;
    BOOL fAcquireCS = FALSE;

    // Initialization is attempted only once.

    if(m_fTrySinkInit == TRUE)
        goto CLEANUP;

    EnterCriticalSection(&m_cs);
    fAcquireCS = TRUE;

    if(m_fTrySinkInit == FALSE) {
        hr = HrReadRegParams(&m_attrList);
        m_fSinkInitSuccess = SUCCEEDED(hr);
        m_fTrySinkInit = TRUE;
        if(FAILED(hr)) {
            PCErrorTraceEx((LPARAM)this,
                "CheckIfSinkHasInitalised failed with 0x%08lx",
                hr);
            goto CLEANUP;
        }
    }

CLEANUP:
    if(fAcquireCS)
        LeaveCriticalSection(&m_cs);
    PCFunctLeaveEx((LPARAM)this);
    return m_fSinkInitSuccess ? S_OK : S_FALSE;
}

//-------------------------------------------------------------
//
// Function: CPostCatReader::OnMessagePostCategorize
//
// Synopsis: This event is fired for every message that is successfully
//           categorized. The sink will intercept the message at this point and attempt
//           to retrieve the requested attribute's values, which are saved in the mailmsg.
//
// Arguments:
//        [in]
//        IMailMsgProperties     *pIMailMsgProperties,
//        IMailTransportNotify   *pINotify,
//        PVOID                   pvNotifyContext
//
//        - all defined in smtpevent.idl
//
// Returns:
//            S_OK if successful
//
//-------------------------------------------------------------
HRESULT CPostCatReader::OnMessagePostCategorize(
        IMailMsgProperties     *pIMailMsgProperties,
        IMailTransportNotify   *pINotify,
        PVOID                   pvNotifyContext)
{
    HRESULT hr  =   S_OK;
    PCFunctEnterEx((LPARAM)this, "CPostCatReader::HrSinkInitialize");
    IMailMsgPropertyManagement * pIMailMsgPropertyManagement = NULL;

    if(S_OK != CheckIfSinkHasInitalised()) {
        PCErrorTraceEx((LPARAM)this, "Sink Not Initialized. CatReadEx skipping..");
        goto CLEANUP;
    }

    hr = pIMailMsgProperties->QueryInterface(
        IID_IMailMsgPropertyManagement,
        (PVOID *)&pIMailMsgPropertyManagement);
    if (FAILED(hr)) {
        PCErrorTraceEx( (LPARAM) this, "Failed pIMailMsgProperties->QueryInterface 0x%08lx",
            hr);
        goto CLEANUP;
    }

    ShowMessage("\n\n");
    ShowMessage("===================================================================");
    ShowMessage("Message");
    ShowMessage("*******");


    hr = PrintRequestedAttribsFromMsg(
        pIMailMsgProperties,
        pIMailMsgPropertyManagement);
    if(FAILED(hr)) {
        ShowMessage("Failed to get requested attributes from message");

    // Try to do the same for recipients.
            }
    hr = PrintRequestedAttribsFromRecips(
            pIMailMsgProperties,
            pIMailMsgPropertyManagement);
    if(FAILED(hr)) {
        ShowMessage("Failed to get requested attributes from recipients");
    }

    ShowMessage("\n\n");
CLEANUP:
    if (pIMailMsgPropertyManagement != NULL)
        pIMailMsgPropertyManagement->Release();

    PCFunctLeaveEx((LPARAM)this);
    return SUCCEEDED(hr) ? S_OK : hr;
}

//-------------------------------------------------------------
//
// Function: CPostCatReader::PrintRequestedAttribsFromMsg
//
// Synopsis: Prints the requested attributes found on the sender
//
// Arguments:
//          [in]
//          IMailMsgProperties * pIMailMsgProperties,
//          IMailMsgPropertyManagement * pIMailMsgPropertyManagement
//
//        - defined in smtpevent.idl
//
// Returns:
//  S_OK if successful
//
//-------------------------------------------------------------
HRESULT CPostCatReader::PrintRequestedAttribsFromMsg(
    IMailMsgProperties * pIMailMsgProperties,
    IMailMsgPropertyManagement * pIMailMsgPropertyManagement)
{
    HRESULT hr = S_OK;
    PCFunctEnterEx((LPARAM)this, "CPostCatReader::PrintRequestedAttributesFromMsg");
    TCHAR szSenderSmtpAddress[MAX_EMAIL_LENGTH];

    hr = pIMailMsgProperties->GetStringA(
        IMMPID_MP_SENDER_ADDRESS_SMTP,
        MAX_EMAIL_LENGTH,
        (TCHAR *)szSenderSmtpAddress);

    if(FAILED(hr) && hr != MAILMSG_E_PROPNOTFOUND ) {
        PCErrorTraceEx((LPARAM)this, "pIMailMsgProperties->GetStringA failed with 0x%08lx",
            hr);
        goto CLEANUP;
    }

    // For debugging, output the sender.

    if (SUCCEEDED(hr))
        ShowMessage("CatReadEx Detected Sender: %s", szSenderSmtpAddress);

    hr = PrintAttrList(
        pIMailMsgPropertyManagement,
        pIMailMsgProperties,
        NULL,
        0);

    if(FAILED(hr)) {
        PCErrorTraceEx((LPARAM)this, "PrintAttrList failed with 0x%08lx",
            hr);
        goto CLEANUP;
    }

CLEANUP:
    PCFunctLeaveEx((LPARAM)this);
    return hr;
}

//-------------------------------------------------------------
//
// Function: CPostCatReader::PrintRequestedAttribsFromRecips
//
// Synopsis: Prints the requested attributes found on every recipient
//
// Arguments:
//          [in]
//          IMailMsgProperties * pIMailMsgProperties,
//          IMailMsgPropertyManagement * pIMailMsgPropertyManagement
//
//        - defined in smtpevent.idl
//
// Returns:
//  S_OK if successful
//
//-------------------------------------------------------------
HRESULT CPostCatReader::PrintRequestedAttribsFromRecips(
    IMailMsgProperties * pIMailMsgProperties,
    IMailMsgPropertyManagement * pIMailMsgPropertyManagement )
{
    HRESULT hr = S_OK;
    PCFunctEnterEx((LPARAM)this, "CPostCatReader::PrintRequestedAttribsFromRecips");
    IMailMsgRecipients * pIMailMsgRecipients = NULL;
    DWORD dwcRecipCount;
    DWORD dwRecipIndex;
    TCHAR szRecipientSmtpAddress[MAX_EMAIL_LENGTH];

    // Query the MailMsgRecipients interface.

    hr = pIMailMsgProperties->QueryInterface(
        IID_IMailMsgRecipients,
        (LPVOID *)&pIMailMsgRecipients);
    if(FAILED(hr)) {
        PCErrorTraceEx((LPARAM)this, "pIMailMsgProperties->QueryInterface failed with 0x%08lx",
            hr);
        goto CLEANUP;
    }

    // Get the total count of recipients in pIMailMsgRecipients.
    hr = pIMailMsgRecipients->Count(&dwcRecipCount);
    if(FAILED(hr)) {
        PCErrorTraceEx((LPARAM)this, "pIMailMsgRecipients->Count failed with 0x%08lx",
            hr);
        goto CLEANUP;
    }

    // Enumerate through the recipients.

    for (dwRecipIndex = 0; dwRecipIndex &#060 dwcRecipCount; dwRecipIndex++) {
        CAttrEntry * pCurrent = NULL;
        hr = pIMailMsgRecipients->GetStringA(
            dwRecipIndex,
            IMMPID_RP_ADDRESS_SMTP,
            MAX_EMAIL_LENGTH,
            (LPTSTR) szRecipientSmtpAddress);
        if(FAILED(hr) && hr != MAILMSG_E_PROPNOTFOUND) {
            PCErrorTraceEx((LPARAM)this, "pIMailMsgRecipients->GetStringA failed with 0x%08lx",
                hr);
            goto CLEANUP;
        }

        ShowMessage("Recipient");
        ShowMessage("*******");


        // For debugging, output the recipient address.
        if (SUCCEEDED(hr))
            ShowMessage("\nCatReadEx Detected Recipient #%ld : %s",
            dwRecipIndex+1,
            szRecipientSmtpAddress);

        // Traverse through the list of requested attributes and print out its values.

        hr = PrintAttrList(
            pIMailMsgPropertyManagement,
            NULL,
            pIMailMsgRecipients,
            dwRecipIndex);

        if(FAILED(hr)) {
            PCErrorTraceEx((LPARAM)this, "PrintAttrList failed with 0x%08lx",
            hr);
            goto CLEANUP;
        }
    }

CLEANUP:
    if (pIMailMsgRecipients != NULL)
        pIMailMsgRecipients->Release();

    PCFunctLeaveEx((LPARAM)this);
    return hr;
}

//-------------------------------------------------------------
//
// Function: CPostCatReader::PrintAttrList
//
// Synopsis: Prints the requested attributes from the list
//
// Arguments:
//          [in]
//          IMailMsgProperties * pIMailMsgProperties,
//          IMailMsgPropertyManagement * pIMailMsgPropertyManagement
//
//        - defined in smtpevent.idl
//
// Returns:
//  S_OK if successful
//
//-------------------------------------------------------------
HRESULT CPostCatReader::PrintAttrList(
    IMailMsgPropertyManagement * pIMailMsgPropertyManagement,
    IMailMsgProperties *pIMailMsgProperties,
    IMailMsgRecipients *pIMailMsgRecipients,
    DWORD dwRecipIndex) {

    HRESULT hr = S_OK;
    CAttrEntry *pCurrent = NULL;

    PCFunctEnterEx((LPARAM)this, "CPostCatReader::PrintAttrList");

    // Traverse through the list of requested attributes, and print out its values.

    pCurrent = m_attrList.GetHeadEntry();
    while(NULL != pCurrent) {
        hr = PrintAttribVals(
            pIMailMsgPropertyManagement,
            pIMailMsgProperties,
            pIMailMsgRecipients,
            dwRecipIndex,
            pCurrent);

        if(FAILED(hr)) {
            PCErrorTraceEx((LPARAM)this, "PrintAttribVals failed with 0x%08lx",
                hr);

            // Get the next attribute.
            hr = S_OK;
        }
        pCurrent = m_attrList.GetNextEntry(pCurrent);
    }

    PCFunctLeaveEx((LPARAM)this);
    return hr;
}


//-------------------------------------------------------------
//
// Function: CPostCatReader::PrintAttribVals
//
// Synopsis: Prints attribute value
//
// Arguments:
//          [in]
//          IMailMsgPropertyManagement * pIMailMsgPropertyManagement,
//          IMailMsgProperties *pIMailMsgProperties,
//          IMailMsgRecipients *pIMailMsgRecipients,
//          DWORD dwRecipIndex,
//          CAttrEntry * pCurrent
//
// Returns:
//  S_OK if successful
//
//-------------------------------------------------------------
HRESULT CPostCatReader::PrintAttribVals(
    IMailMsgPropertyManagement * pIMailMsgPropertyManagement,
    IMailMsgProperties *pIMailMsgProperties,
    IMailMsgRecipients *pIMailMsgRecipients,
    DWORD dwRecipIndex,
    CAttrEntry * pCurrent)
{
    HRESULT hr = S_OK;
    PCFunctEnterEx((LPARAM)this, "CPostCatReader::PrintAttribVals");
    DWORD dwPropertyID;
    DWORD dwcValues;
    DWORD dwIndex = 0;


    // Finds the allocated property ID requested for this attribute.

    hr = GetAttribPropertyOffSet(
        pIMailMsgPropertyManagement,
        pIMailMsgProperties,
        pIMailMsgRecipients,
        dwRecipIndex,
        pCurrent,
        &dwPropertyID,
        &dwcValues);

    if(FAILED(hr)) {
        PCErrorTraceEx( (LPARAM) this, "Failed GetAttribPropertyOffSet 0x%08lx",
            hr);
        goto CLEANUP;
    }

    if(dwcValues == 0)

        // Check to see if any attribute values were found.

        ShowMessage("\tCatReadEx Did Not Find Any Values For Requested Attribute %s",
            pCurrent->GetAttrName());
    else
        ShowMessage("\tCatReadEx Found Requested Attribute %s with %ld values starting from PropId %ld",
            pCurrent->GetAttrName(),
            dwcValues,
            dwPropertyID);

    for (dwIndex = 0; dwIndex &#060 dwcValues; dwIndex++) {
        hr = GetAttributeValue(pIMailMsgPropertyManagement,
                pIMailMsgProperties,
                pIMailMsgRecipients,
                dwRecipIndex,
                pCurrent,
                dwPropertyID + dwIndex);

        if(FAILED(hr)) {
            PCErrorTraceEx( (LPARAM) this, "Failed GetAttributeValue 0x%08lx",
                hr);
            goto CLEANUP;
        }
    }

CLEANUP:
    PCFunctLeaveEx((LPARAM)this);
    return hr;
}

//-------------------------------------------------------------
//
// Function: CPostCatReader::GetAttributeValue
//
// Synopsis: Gets attribute value
//
// Arguments:
//          [in]
//          IMailMsgPropertyManagement * pIMailMsgPropertyManagement,
//          IMailMsgProperties *pIMailMsgProperties,
//          IMailMsgRecipients *pIMailMsgRecipients,
//          DWORD dwRecipIndex,
//          CAttrEntry * pCurrent
//          DWORD dwPropertyID
//
// Returns:
//  S_OK if successful
//
//-------------------------------------------------------------
HRESULT CPostCatReader::GetAttributeValue(IMailMsgPropertyManagement * pIMailMsgPropertyManagement,
    IMailMsgProperties *pIMailMsgProperties,
    IMailMsgRecipients *pIMailMsgRecipients,
    DWORD dwRecipIndex,
    CAttrEntry * pCurrent,
    DWORD dwPropertyID)     {

    HRESULT hr = S_OK;
    DWORD dwcbAlloc;
    PBYTE pbAttributeValue = NULL;
    BYTE bSilly;
    DWORD dwcb;
    PCFunctEnterEx((LPARAM)this, "CPostCatReader::GetAttributeValue");

    // You know the property ID for the value. Do a dummy call on the GetProperty
    // to get the length for this value in bytes. You can then allocate memory
    // accordingly.

    if (pIMailMsgRecipients)
        hr = pIMailMsgRecipients->GetProperty(
            dwRecipIndex,
            dwPropertyID,
            0,
            &dwcbAlloc,
            &bSilly);
    else
        hr = pIMailMsgProperties->GetProperty(
            dwPropertyID,
            0,
            &dwcbAlloc,
            &bSilly);

    if(SUCCEEDED(hr) || (hr == MAILMSG_E_PROPNOTFOUND)) {
        PCDebugTraceEx((LPARAM)this, "Error Did Not Find Expected Value for Attribute %s - PropId %ld",
            pCurrent->GetAttrName(),
            dwPropertyID);

        ShowMessage("Error Did not find expected Value for Attribute %s - PropId %ld",
            pCurrent->GetAttrName(),
            dwPropertyID);

        hr = MAILMSG_E_PROPNOTFOUND;
        goto CLEANUP;

    } else if(FAILED(hr) && (hr != HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER))) {
            PCErrorTraceEx((LPARAM)this, "GetProperty failed with 0x%08lx",
                hr);
            goto CLEANUP;
    }

    // Allocate for the value.

    pbAttributeValue = new BYTE[dwcbAlloc];
    if(pbAttributeValue == NULL) {
        hr = E_OUTOFMEMORY;
        PCErrorTraceEx((LPARAM)this, "pbAttributeValue alloc failed for %ld bytes 0x%08lx",
            dwcbAlloc,
            hr);
        goto CLEANUP;
    }

    // Get the attribute value now (strings are not null terminated).

    if (pIMailMsgRecipients) {
        hr = pIMailMsgRecipients->GetProperty(
            dwRecipIndex,
            dwPropertyID,
            dwcbAlloc,
            &dwcb,
            pbAttributeValue);
    }
    else
        hr = pIMailMsgProperties->GetProperty(
            dwPropertyID,
            dwcbAlloc,
            &dwcb,
            pbAttributeValue);

    if(FAILED(hr))  {
        PCErrorTraceEx((LPARAM)this, "GetProperty failed with 0x%08lx",
                hr);
        goto CLEANUP;
    }

    _ASSERT(dwcb = dwcbAlloc);

    // Dump data.

    hr = HrPrintData(dwcbAlloc,
        pbAttributeValue);

    if(FAILED(hr)){
        PCErrorTraceEx((LPARAM)this, "HrPrintData failed with 0x%08lx",
            hr);
        goto CLEANUP;
    }

    CLEANUP:
    if(pbAttributeValue != NULL)
        delete [] pbAttributeValue;
    PCFunctLeaveEx((LPARAM)this);
    return hr;
}

//-------------------------------------------------------------
//
// Function: CPostCatReader::GetAttribPropertyOffSet
//
// Synopsis: GetAttribPropertyOffSet - Returns the property ID
//           offset to where the attribute values are stored
// Arguments:
//          [in]
//          IMailMsgPropertyManagement * pIMailMsgPropertyManagement,
//          IMailMsgProperties *pIMailMsgProperties,
//          IMailMsgRecipients *pIMailMsgRecipients,
//          DWORD dwRecipIndex,
//          CAttrEntry * pCurrent
//
// Returns:
//          S_OK if successful
//
//-------------------------------------------------------------
HRESULT CPostCatReader::GetAttribPropertyOffSet(
    IMailMsgPropertyManagement * pIMailMsgPropertyManagement,
    IMailMsgProperties *pIMailMsgProperties,
    IMailMsgRecipients *pIMailMsgRecipients,
    DWORD dwRecipIndex,
    CAttrEntry * pCurrent,
    DWORD *pdwPropertyID,
    DWORD *pdwcValues)
{
    HRESULT hr = S_OK;
    PCFunctEnterEx((LPARAM)this, "CPostCatReader::GetAttribPropertyOffSet");
    DWORD dwPropertyID;
    DWORD dwcValues;

    hr = pIMailMsgPropertyManagement->AllocPropIDRange(
        m_attrList.GetAttrGuid(pCurrent),
        2,
        &dwPropertyID);

    if (FAILED(hr)) {
        PCErrorTraceEx( (LPARAM) this, "Failed pIMailMsgPropertyManagement->AllocPropIDRange 0x%08lx",
            hr);
        goto CLEANUP;
    }

    if(pIMailMsgRecipients)
        hr = pIMailMsgRecipients->GetDWORD(
            dwRecipIndex,
            dwPropertyID,
            &dwcValues);
    else
       hr = pIMailMsgProperties->GetDWORD(
           dwPropertyID,
            &dwcValues);

    if(FAILED(hr)) {

        // This is not a fatal error in the case where a recipient or sender is not
        // in the directory service (DS).
        // Treat this case as if there were no values for the attribute.

        dwcValues = 0;
    }

    *pdwcValues = dwcValues;

    if (dwcValues == 0) {

        // There were no values for the requested attribute.

        hr = S_FALSE;
        goto CLEANUP;
    }

    _ASSERT(SUCCEEDED(hr));

    if (dwcValues > 1) {

        // This is a multivalued attribute. Get its GUID.

        DWORD dwcbAlloc = sizeof(GUID);
        DWORD dwcb;
        GUID gGuid;
        ZeroMemory(&gGuid, sizeof(GUID));

        if(pIMailMsgRecipients)
           hr = pIMailMsgRecipients->GetProperty(
               dwRecipIndex,
               dwPropertyID+1,
                dwcbAlloc,
                &dwcb,
                (LPBYTE) &gGuid);
        else
            hr = pIMailMsgProperties->GetProperty(
                dwPropertyID+1,
                dwcbAlloc,
                &dwcb,
                (LPBYTE) &gGuid);

        if(FAILED(hr)) {
            PCErrorTraceEx((LPARAM)this, "%s->GetProperty failed with 0x%08lx",
                pIMailMsgRecipients ? "pIMailMsgRecipients" : "pIMailMsgProperties",
                hr);
            goto CLEANUP;
        }
        _ASSERT(dwcb == sizeof(GUID));

        // Get the allocated property ID associated with this GUID.

        hr = pIMailMsgPropertyManagement->AllocPropIDRange(
            gGuid,
            dwcValues,
            pdwPropertyID);
        if (FAILED(hr)) {
            PCErrorTraceEx( (LPARAM) this, "Failed pIMailMsgPropertyManagement->AllocPropIDRange 0x%08lx",
                hr);
            goto CLEANUP;
        }

    }
    else
        *pdwPropertyID = dwPropertyID+1;

    CLEANUP:

    PCFunctLeaveEx((LPARAM)this);
    return hr;
}

//-------------------------------------------------------------
//
// Function: CPostCatReader::HrPrintData
//
// Synopsis: Utility function to dump the binary data to the debugger
//
// Arguments:
//          [in]
//          DWORD dwcbData,
//          PBYTE pbData
//
// Returns:
//          S_OK if successful
//
//-------------------------------------------------------------
HRESULT CPostCatReader::HrPrintData(
    DWORD dwcbData,
    PBYTE pbData)
{
    PCFunctEnterEx((LPARAM)this, "CPostCatReader::HrPrintData");
    HRESULT hr = S_OK;
    DWORD dwLineOffset = 0;
    DWORD dwHexOffset = 0;
    #define MAX_BUFFER 1024

    #define PRINTBYTE( byte ) \
        *psz++ = szHexDigits[ (byte) >> 4 ]; \
        *psz++ = szHexDigits[ (byte) & 0xF ];


    OutputDebugString("Offset     Data (Hex)                                        ASCII\r\n"
                    "-----------------------------------------------------------------------------\r\n");

    // Print the line offset.

    while(dwLineOffset &#060 dwcbData)
    {
        LONG lCount = 0;
        CHAR szHexDigits[] = "0123456789ABCDEF";
        CHAR szBuffer[MAX_BUFFER];
        LPSTR psz = szBuffer;

        for(lCount = sizeof(dwLineOffset) - 1; lCount >= 0; lCount--)
        {
            PRINTBYTE( ((PBYTE)&dwLineOffset)[lCount] );
        }
        *psz++ = ' ';
        *psz++ = ' ';
        *psz++ = ' ';


        // Print the hex data.

        dwHexOffset = dwLineOffset;
        while((dwHexOffset - dwLineOffset) &#060 16)
        {
            if(dwHexOffset &#060 dwcbData)
            {
                PRINTBYTE( pbData[dwHexOffset] );
                *psz++ = ' ';
            }
            else
            {
                *psz++ = ' ';
                *psz++ = ' ';
                *psz++ = ' ';
            }
            dwHexOffset++;
        }
        *psz++ = ' ';
        *psz++ = ' ';

        // Print the ASCII data.

        dwHexOffset = dwLineOffset;
        while((dwHexOffset - dwLineOffset) &#060 16)
        {
            if(dwHexOffset &#060 dwcbData)
            {
                *psz++ = (pbData[dwHexOffset] >= 32) ? pbData[dwHexOffset] : '.';
            }
            dwHexOffset++;
        }
        *psz++ = '\r';
        *psz++ = '\n';
        *psz = '\0';
        _ASSERT(lstrlen(szBuffer) &#060 sizeof(szBuffer));
        OutputDebugString(szBuffer);

        dwLineOffset += 16;
    }

    OutputDebugString("\r\n");

    PCFunctLeaveEx((LPARAM)this);
    return hr;
} // HrPrintData