I often use System.Diagnostics.Trace.WriteLine() debugging applications. In the past, if I needed to persist these statements to a log file programmatically I was using a TextWriterTraceListener object to do this. However, it turns out that TraceListeners only work for the current appdomain, which is a bit limiting.
I thought it would be cool to write a version of TextWriterTraceListener that worked more like DbgView.exe from SysInternals, i.e. that listened to Trace.WriteLine()s from all appdomains in all processes. Here is my first draft, which is basically a managed version of the DbMon sample code from MSDN. It's a console app that listens for calls to OutputDebugString() and echoes them to stdout.
Note: I've only tested this with .Net Framework 2.0 (Whidbey Beta 2). You'll probably need to tweak it significantly to get it to work with 1.0 or 1.1.
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Text;
using System.Threading;
namespace DebugMonitor
{
unsafe static class NativeMethods
{
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public UInt32 nLength;
public void* lpSecurityDescriptor;
public UInt32 bInheritHandle;
}
internal const uint PAGE_READWRITE = 0x04;
internal const uint FILE_MAP_READ = 0x0004;
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern IntPtr CreateFileMapping(IntPtr hFile,
SECURITY_ATTRIBUTES* lpFileMappingAttributes,
UInt32 flProtect, UInt32 dwMaximumSizeHigh,
uint dwMaximumSizeLow, string lpName);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern byte* MapViewOfFile(IntPtr hFileMappingObject, UInt32
dwDesiredAccess, UInt32 dwFileOffsetHigh, UInt32 dwFileOffsetLow,
UIntPtr dwNumberOfBytesToMap);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool UnmapViewOfFile(byte* lpBaseAddress);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool CloseHandle(IntPtr hHandle);
}
unsafe class Program
{
const int SharedMemorySize = 512;
static void Main(string[] args)
{
EventWaitHandle ackEvent = null;
EventWaitHandle readyEvent = null;
IntPtr sharedFile = IntPtr.Zero;
byte* sharedMemory = null;
uint lastPid;
uint* thisPid;
byte* theString;
bool didCR;
bool wasCreated;
try
{
EventWaitHandleSecurity ewhSec = new EventWaitHandleSecurity();
//Note: CLR doesn't recognize S-1-0 (bug?) so I'm using S-1-1-0 instead. Seems to work.
EventWaitHandleAccessRule rule = new EventWaitHandleAccessRule(new SecurityIdentifier("S-1-1-0"), EventWaitHandleRights.FullControl, AccessControlType.Allow);
ewhSec.AddAccessRule(rule);
FileSecurity fileSecurity = new FileSecurity();
FileSystemAccessRule fileRule = new FileSystemAccessRule(new SecurityIdentifier("S-1-1-0"), FileSystemRights.FullControl, AccessControlType.Allow);
fileSecurity.AddAccessRule(fileRule);
byte[] fileSecurityBytes = fileSecurity.GetSecurityDescriptorBinaryForm();
ackEvent = new EventWaitHandle(false,
EventResetMode.AutoReset,
"DBWIN_BUFFER_READY",
out wasCreated,
ewhSec);
if (!wasCreated)
{
Console.WriteLine("There is already an instance of this program or a similar monitor program (e.g. DebugView, DbMon) running.");
return;
}
readyEvent = new EventWaitHandle(false,
EventResetMode.AutoReset,
"DBWIN_DATA_READY",
out wasCreated,
ewhSec);
fixed (byte* fileSecurityPtr = fileSecurityBytes)
{
NativeMethods.SECURITY_ATTRIBUTES sa;
sa.bInheritHandle = 1;
sa.lpSecurityDescriptor = fileSecurityPtr;
sa.nLength = (uint)fileSecurityBytes.Length;
sharedFile = NativeMethods.CreateFileMapping(
new IntPtr(-1),
&sa,
NativeMethods.PAGE_READWRITE,
0,
4096,
"DBWIN_BUFFER");
}
if (sharedFile == IntPtr.Zero)
{
Exception innerException = new Win32Exception();
throw new ApplicationException("CreateFileMapping failed", innerException);
}
sharedMemory = NativeMethods.MapViewOfFile(sharedFile, NativeMethods.FILE_MAP_READ, 0, 0, new UIntPtr(SharedMemorySize));
if (sharedMemory == null)
{
Exception innerException = new Win32Exception();
throw new ApplicationException("MapViewOfFile failed", innerException);
}
thisPid = (uint*)sharedMemory;
theString = sharedMemory + sizeof(uint);
lastPid = 0xffffffff;
didCR = true;
ackEvent.Set();
char[] text = new char[SharedMemorySize];
while (true)
{
readyEvent.WaitOne();
if (lastPid != *thisPid)
{
lastPid = *thisPid;
if (!didCR)
{
Console.WriteLine();
didCR = true;
}
}
int length = 0;
fixed (char* buffer = text)
{
for (byte* p = theString; p < (sharedMemory + SharedMemorySize); ++p, ++length)
{
if (*p == 0)
{
break;
}
}
length = Encoding.ASCII.GetChars(theString, length, buffer, SharedMemorySize);
}
Console.Write(text, 0, length);
didCR = text.Length > 0 && text[text.Length - 1] == '\n';
ackEvent.Set();
}
}
catch (Exception exn)
{
Console.WriteLine(exn.ToString());
}
finally
{
if (ackEvent != null)
{
ackEvent.Close();
}
if (readyEvent != null)
{
readyEvent.Close();
}
if (sharedMemory != null)
{
NativeMethods.UnmapViewOfFile(sharedMemory);
}
if (sharedFile != IntPtr.Zero)
{
NativeMethods.CloseHandle(sharedFile);
}
}
}
}
}