Event Tracing Provider Framework  

Firing a variable length event

More than likely, most variable length events will involve having variable length strings. Obviously, this is not limited to strings and variable length data can include other binary-oriented data. It is typical for variable length data to be appended after all other fixed length data in the event’s data structure. The following example structure (VARIABLE_LEN_DATA) demonstrates this:

typedef struct _VariableLenData
{
    DWORD dwProcessID;
    DWORD dwThreadID;
    WCHAR szStringData[1]; // String1; String2; String3
    } *PVARIABLE_LEN_DATA, VARIABLE_LEN_DATA;

Not much difference from a fixed length custom data structure. The only difference is that the variable length strings are all accessed via the szStringData member. Whether a single variable length string or many of them, they are all accessed via the szStringData member. In our sample, we will encode three strings.

The following structure has the variable length custom data appended after the event’s header structure. Just like fixed length custom events.

typedef struct _VariableLenDataEvent
{
    EVENT_TRACE_HEADER Header;
    VARIABLE_LEN_DATA Data;
} *PEVENT_VARIABLE_LEN, EVENT_VARIABLE_LEN;

Now that the event’s structure is complete, the next step is to implement how the event data gets initialized. Instead of deriving from CBaseEvent, variable length events should derive from CVariableLengthBaseEvent. It has the same template parameters as CBaseEvent. Here’s a portion of the template class:

template <CLASS EventGuid, LPCGUID TEventData>
class CVariableLengthBaseEvent
{
protected:
    typedef TEventData _EventDataType;
    TEventData* m_pData;

public:
    ... ..
    ..
    void InitEventData(int nSize, int nEventType)
    {
        m_pData = reinterpret_cast<TEVENTDATA*>(malloc(nSize));
        if (m_pData)
        {
            ZeroMemory(m_pData, nSize);
            m_pData->Header.Size = nSize;
            m_pData->Header.Guid = *EventGuid;
            m_pData->Header.Flags = WNODE_FLAG_TRACED_GUID;
            m_pData->Header.Class.Type = nEventType;
        }
    }
    ... ...
    ....
    ..
};

The InitEventData method is intended to be called from the derived class’s constructor and it allocates enough memory for the event and initializes it. The event type is also set at the same time.

Let’s have a look at the CEventVariableLen class which was used earlier in SomeFunction and uses the event structure, EVENT_VARIABLE_LEN. MY_VARIABLE_LEN_EVENTCLASS is the event’s GUID. Notice how the event’s variable length size is calculated and how its custom data is setup.

class CEventVariableLen : 
    public CVariableLengthBaseEvent<EVENT_VARIABLE_LEN, 
        &MY_VARIABLE_LEN_EVENTCLASS>
{
public:
    CEventVariableLen(LPCWSTR lpszString1, LPCWSTR lpszString2, 
        LPCWSTR lpszString3) 
    { 
        // Firstly, you must calculate the size of event structure
        int nSize = sizeof(_EventDataType) + 
        StringSizeInBytes(lpszString1) + 
        StringSizeInBytes(lpszString2) + 
        StringSizeInBytes(lpszString3);
        
        // Initialize the event. This creates the structure in m_pData and 
        // sets up standard event details, like the event type, the event 
        // guid, etc...
        InitEventData(nSize, EVENT_TRACE_TYPE_INFO);
        
        // Set your event data
        m_pData->Data.dwProcessID = GetCurrentProcessId();
        m_pData->Data.dwThreadID = GetCurrentThreadId();
        
        // Notice how the string offsets are calculated. 
        CopyString(m_pData->Data.szStringData, lpszString1);
        
        CopyString(LPWSTR(int(m_pData->Data.szStringData) + 
        StringSizeInBytes(lpszString1)), lpszString2);
        
        CopyString(LPWSTR(int(m_pData->Data.szStringData) + 
        StringSizeInBytes(lpszString1) +
        StringSizeInBytes(lpszString2)), lpszString3);
        
        // Again, if you need to, you can set specific values in the event 
        // header through m_pData->Header.
        // Set text equivelent of the event for verbose debug output. You
        // don't have to do this, but it might be handy to do so?
        SetTextEvent("Process ID = 0x%X, Thread ID = 0x%X, String1 = '%ls',"\
            "String2 = '%ls', String3 = '%ls'\n", m_pData->Data.dwProcessID, 
            m_pData->Data.dwThreadID, lpszString1, lpszString2, lpszString3);
    }
};

The size of the event is determined by the size of the event structure, represented through _EventDataType, plus the variable length fields. It is best practice for strings to use wide characters (Unicode) and hence the method, StringSizeInBytes calculates how many bytes the string will occupy. Once the event’s size has been determined, the event’s data structure is allocated and initialized with InitEventData.

After the fixed length fields have been set, the next step is to carefully copy the strings into the variable portion of the structure. CopyString takes a starting pointer offset and copies the string passed in the second parameter. For the second and third strings, the correct pointer offset needs to be calculated by including previous variable length data sizes. Hence, StringSizeInBytes is used to correctly calculate the offset.

Finally, as you saw earlier, the CEventVariableLen class is used to fire an event using the ET_EVENT macro.

    ET_EVENT(CEventVariableLen(L"this string", L"that string", L"other string"));

Both StringSizeInBytes and CopyString are methods of CVariableLengthBaseEvent.

Contact Me   |  Developing WMI Solutions    |   Gwyn Cole's Developer Blog    |   Legal
 © 2003 Content by Gwyn Cole. All rights reserved.