Raise Crimson Events from .NET Part 4 & 5 - Wrapping the API
In parts 1, 2 and 3 we looked at putting together the XML manifest for the events and channels we wish to use. Since there is no managed code support in the .NET framework, we need to use Platform Invoke (PInvoke) - There is a good PInvoke tutorial at https://msdn.microsoft.com/library/default.asp?url=/library/en-us/csref/html/vcwlkPlatformInvokeTutorial.asp.
The methods we need to invoke can be found in the advapi32.dll, so we can use the following DllImport attribute [DllImport("advapi32.dll", CharSet = CharSet.Auto)]. The methods of interest are EventRegister, EventUnregister and EventWrite. (Note: there are other methods that may be useful, but these are the ones I used in this sample).
We also need a few data structures defining - EVENT_DESCRIPTOR and EVENT_DATA_DESCRIPTOR. The code i used in the sample is shown below:
using System;
using System.Runtime.InteropServices;
using System.Reflection;
namespace NetInstrumentation
{
[StructLayout(LayoutKind.Sequential)]
public struct EVENT_DESCRIPTOR
{
public ushort Id;
public byte Version;
public byte Channel;
public byte Level;
public byte Opcode;
public ushort Task;
public ulong Keyword;
}
[StructLayout(LayoutKind.Sequential)]
public struct EVENT_DATA_DESCRIPTOR
{
public ulong Ptr;
public uint Size;
public uint Reserved;
}
[StructLayout(LayoutKind.Sequential)]
public struct EVENT_FILTER_DESCRIPTOR
{
public ulong Ptr;
public uint Size;
public uint Reserved;
}
public delegate void EnableCallback(
ref Guid SourceId,
uint IsEnabled,
byte Level,
long MatchAnyKeyword,
ulong MatchAllKeyword,
ref EVENT_FILTER_DESCRIPTOR FilterData,
uint CallbackContext);
public class UnifiedEventingAPI
{
[DllImport("advapi32.dll", CharSet = CharSet.Auto)]
public static extern uint EventRegister(ref Guid ProviderId,
EnableCallback EnableCallback,
uint CallbackContext,
ref ulong RegHandle);
[DllImport("advapi32.dll", CharSet = CharSet.Auto)]
public static extern uint EventUnregister(ulong RegHandle);
[DllImport("advapi32.dll", CharSet = CharSet.Auto)]
public static extern uint EventWrite(ulong handle,
ref EVENT_DESCRIPTOR EventDescriptor,
uint UserDataCount,
ref EVENT_DATA_DESCRIPTOR UserData);
[DllImport("advapi32.dll", CharSet = CharSet.Auto)]
public static extern uint EventActivityIdControl(ulong controlCode,
ref Guid activityId);
[DllImport("advapi32.dll", CharSet = CharSet.Auto)]
public static extern bool EventProviderEnabled(ulong RegHandle,
byte Level,
ulong Keyword);
private UnifiedEventingAPI()
{}
}
}
Logging the event
If you implement the class above you should be able to figure out how to raise the events. All you need is to create the appropriate EVENT_DESCRIPTORS and use the EventWrite method declared above as shown in the sample below.
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
namespace NetInstrumentation
{
public class CrimsonWrapper : IDisposable
{
private static Guid myGuid =
new Guid("{9c878495-5572-474e-97b6-ae8072b18037}");
private ulong myHandle = 0;
public CrimsonWrapper()
{
UnifiedEventingAPI.EventRegister(ref myGuid, null, 0, ref myHandle);
}
public unsafe void WriteEvent(Exception ex)
{
EVENT_DESCRIPTOR myEventDescriptor = new EVENT_DESCRIPTOR();
myEventDescriptor.Id=0x64;
myEventDescriptor.Version=0x1;
myEventDescriptor.Channel=0x9;
myEventDescriptor.Level=0x4;
myEventDescriptor.Opcode=0x0;
myEventDescriptor.Task=0x0;
myEventDescriptor.Keyword = 0x8000000000000000;
EVENT_DATA_DESCRIPTOR[] myUserData = new EVENT_DATA_DESCRIPTOR[1];
myUserData[0].Reserved=0;
myUserData[0].Size=(uint)(ex.Message.Length + 1) * 2;
myUserData[0].Ptr=(ulong)
Marshal.StringToCoTaskMemUni(ex.Message).ToPointer();;
UnifiedEventingAPI.EventWrite(myHandle, ref myEventDescriptor, 1, ref myUserData[0]);
}
#region IDisposable Members
public void Dispose()
{
UnifiedEventingAPI.EventUnregister(myHandle);
}
#endregion
}
}
The first thing to note with the above code is the guid. This is the same guid from the manifest.
Next when the EVENT_DESCRIPTOR properties are populated, you should note that we use the values from the EventSchema.h file. (In C++ we would include this file and simply use the symbols).
Testing
Here is the code we use to fire the event:
CrimsonWrapper _wrapper = new CrimsonWrapper();
ApplicationException ex = new ApplicationException(“My First Vista Event Log Entry”);
_wrapper.WriteEvent(ex);
Conclusion
Whilst the code in these last few posts are not production quality code, it should be enough to show you how to access some of the new features of Vista from managed code.
Some ideas on where to take this next? Why not build your own attribute application block, or tap into the existing tracing capabilities in .NET and add Crimson as a new trace handler.
Do let me know how you get on.
This posting is provided "AS IS" with no warranties, and confers no rights.
Comments
- Anonymous
November 08, 2006
I was going through your blog and I found some interesting topic like new VISTA Event Log.It is nice. But I had one doubt, like how to get event properties like Username, event-type, log type, time ticks etc. I the XML strings, some User ID is given for UserName (guess), if so how that is translated to User Name. I traversed through EVT_VARIANT after calling EvtRender, I got source name and computer name but other event properties are missing. If you can explain it in C++, it will be helpfull for me. Brgds Biju Thomas