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); } } } }}