Code Sample: Is Your Process Using the Silverlight CLR?

In my previous post I described how multiple CLRs can now be run in the same process starting with Silverlight. In the sample video I had a small console application that could be used to track managed processes on the machine and which version(s) of the CLR they had in them. I’ve included the code for this tool below.

The CLR debugger APIs implement ICorPublishProcess to find managed processes, but these APIs have not been updated for Silverlight and are likely to be deprecated in the future. Instead, in my first version I used System.Diagnostics.Process. I found these wrappers to be extremely slow due to grabbing all file version information from all modules loaded. I’m sure this is pretty useful for a one time snapshot, but my tool needed to refresh itself very frequently. Because of this I wound up using p/invoke on top of kernel32!CreateToolhelp32Snapshot (first part of the code).

The rest of the code is pretty simple: every 3 seconds take a process list snapshot and search for CLR instances by name, add them into the process list, then display them to the console. The code recognizes the CLR as follows:

· mscorwks.dll is the name of the CLR engine for version 1.0, 1.1 and 2.0 (3.0 and 3.5 build on top of 2.0)

· coreclr.dll is the name of the Silverlight CLR engine

I didn't spend a huge amount of time on this code and would put more structure in it if I ever put it in production (I'd also replace the dipslay code with a decent GUI).  It’s nothing fancy, but it got the job done. Enjoy!

using System;

using System.Collections.Generic;

using System.Text;

using System.Runtime.InteropServices;

using Microsoft.Win32.SafeHandles;

using System.Runtime.ConstrainedExecution;

namespace EnumClrConsole

{

    //

    // The safe handle class is used to safely encapsulate win32 handles below

    //

    public class ToolHelpHandle : SafeHandleZeroOrMinusOneIsInvalid

    {

        private ToolHelpHandle()

            : base(true)

        {

        }

        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]

        override protected bool ReleaseHandle()

        {

            return NativeMethods.CloseHandle(handle);

        }

    }

    //

    // The following p/invoke wrappers are used to get the list of process and modules

    // running inside each process.

    //

    public class NativeMethods

    {

        [DllImport("kernel32.dll", SetLastError = true)]

        static public extern bool CloseHandle(IntPtr hHandle);

        [DllImport("kernel32.dll")]

        static public extern bool Module32First(ToolHelpHandle hSnapshot, ref MODULEENTRY32 lpme);

        [DllImport("kernel32.dll")]

        static public extern bool Module32Next(ToolHelpHandle hSnapshot, ref MODULEENTRY32 lpme);

        [DllImport("kernel32.dll")]

        static public extern bool Process32First(ToolHelpHandle hSnapshot, ref PROCESSENTRY32 lppe);

        [DllImport("kernel32.dll")]

        static public extern bool Process32Next(ToolHelpHandle hSnapshot, ref PROCESSENTRY32 lppe);

        [DllImport("kernel32.dll", SetLastError = true)]

        static public extern ToolHelpHandle CreateToolhelp32Snapshot(SnapshotFlags dwFlags, uint th32ProcessID);

        public const short INVALID_HANDLE_VALUE = -1;

        [Flags]

        public enum SnapshotFlags : uint

      {

            HeapList = 0x00000001,

            Process = 0x00000002,

            Thread = 0x00000004,

            Module = 0x00000008,

            Module32 = 0x00000010,

            Inherit = 0x80000000,

            All = 0x0000001F

        }

        [StructLayoutAttribute(LayoutKind.Sequential)]

        public struct PROCESSENTRY32

        {

            public uint dwSize;

            public uint cntUsage;

            public uint th32ProcessID;

            public IntPtr th32DefaultHeapID;

            public uint th32ModuleID;

            public uint cntThreads;

            public uint th32ParentProcessID;

            public int pcPriClassBase;

            public uint dwFlags;

            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]

           public string szExeFile;

        };

        [StructLayoutAttribute(LayoutKind.Sequential)]

        public struct MODULEENTRY32

        {

            public uint dwSize;

            public uint th32ModuleID;

            public uint th32ProcessID;

        public uint GlblcntUsage;

            public uint ProccntUsage;

            IntPtr modBaseAddr;

            public uint modBaseSize;

            IntPtr hModule;

            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]

            public string szModule;

            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]

            public string szExePath;

        };

    }

    //

    // The ProcessList is used to track all running processes that have managed code

    // running in them. Each RootProcess item in turn tracks all CLR instances running

    // inside the process, which as of Silverlight could be more than one.

    //

    class Program

    {

        public Dictionary<string, RootProcess> ProcessList;

        public Program()

        {

            ProcessList = new Dictionary<string, RootProcess>();

        }

        //

        // Build will find all running processes and search them for an instance of

        // the CLR engine. mscorwks.dll is the enging for CLR V1.0 -> 2.0 (which includes V3.0 and 3.5).

        // coreclr.dll is the new Silverlight CLR engine.

        //

        public void Build()

        {

            // Reset the old list of running processes and then get a list of everything running.

            ProcessList.Clear();

            ToolHelpHandle hProcessSnap;

            NativeMethods.PROCESSENTRY32 pe32 = new NativeMethods.PROCESSENTRY32();

            hProcessSnap = NativeMethods.CreateToolhelp32Snapshot(NativeMethods.SnapshotFlags.Process, 0);

            if (hProcessSnap.IsInvalid)

            {

                Console.WriteLine("Failed on call to CreateToolhelp32Snapshot");

                return;

            }

            pe32.dwSize = (uint)System.Runtime.InteropServices.Marshal.SizeOf(pe32);

            if (NativeMethods.Process32First(hProcessSnap, ref pe32))

            {

                do

                {

                    // For each running process, get the list of modules. If it has CLR running

                    // inside, add it to the running list.

                    List<ClrInstance> list = new List<ClrInstance>();

                    if (GetClrList(ref list, pe32.th32ProcessID))

                    {

                        string key = String.Format("{0}:{1}", pe32.szExeFile, pe32.th32ProcessID.ToString());

                        // If this process is already in the list, use that instance.

                        RootProcess p;

                        if (ProcessList.ContainsKey(key))

                        p = ProcessList[key];

                        // Otherwise create a new entry.

                        else

                        {

                            p = new RootProcess(pe32.th32ProcessID.ToString(), pe32.szExeFile);

                      this.ProcessList.Add(key, p);

                        }

                        foreach (ClrInstance c in list)

                        {

                            p.AddClr(c);

                        }

                    }

                } while (NativeMethods.Process32Next(hProcessSnap, ref pe32));

            }

            hProcessSnap.Close();

        }

        // Counter data for display.

        int iCounter = 0;

        int iLastLineCount = 0;

        int iLineCount;

        private void WriteLine(string str, params Object[] o)

        {

            ++iLineCount;

            Console.WriteLine(str, o);

        }

        // The display method dumps the process tree to the screen in a pretty

        // dumb way. This routine could easily be replaced with a GUI.

        private void DisplayProcessList()

        {

            iLineCount = 0;

            Console.CursorLeft = 0;

            Console.CursorTop = 0;

            WriteLine("{0}", ++iCounter);

            foreach (KeyValuePair<string, RootProcess> rp in ProcessList)

            {

                String ProcPid = String.Format("{0} - {1}", rp.Value.ProcName, rp.Value.pid);

                WriteLine("{0,-79}", ProcPid);

                foreach (ClrInstance c in rp.Value.rgClr)

   {

                    WriteLine(" {0,-75}", c.ClrName);

                }

            }

            for (int i = iLastLineCount - iLineCount; i > 0; --i)

            {

                Console.WriteLine("{0,-79}", " ");

            }

       iLastLineCount = iLineCount;

        }

        // Returns true if this is a managed process.

        bool GetClrList(ref List<ClrInstance> list, uint pid)

        {

            bool IsManaged = false;

            ToolHelpHandle hModuleSnap;

         NativeMethods.MODULEENTRY32 me32 = new NativeMethods.MODULEENTRY32();

            hModuleSnap = NativeMethods.CreateToolhelp32Snapshot(NativeMethods.SnapshotFlags.Module, pid);

            if (hModuleSnap.IsInvalid)

                return (IsManaged);

            me32.dwSize = (uint)System.Runtime.InteropServices.Marshal.SizeOf(me32);

            if (NativeMethods.Module32First(hModuleSnap, ref me32))

            {

                do

                {

                    // mscorwks.dll == a CLR of version V1.0, V1.1, V2.0

                    // coreclr.dll == Silverlight

                    if (String.Compare(me32.szModule, "mscorwks.dll", true) == 0 ||

                        String.Compare(me32.szModule, "coreclr.dll", true) == 0)

                    {

                        ClrInstance c = new ClrInstance(me32.szExePath);

                        list.Add(c);

                        IsManaged = true;

                    }

                } while (NativeMethods.Module32Next(hModuleSnap, ref me32));

            }

            hModuleSnap.Close();

            return (IsManaged);

        }

        static void Main(string[] args)

        {

            Program p = new Program();

            while (true)

            {

          p.Build();

                p.DisplayProcessList();

                System.Threading.Thread.Sleep(3000);

            }

        }

    }

    public class RootProcess

    {

        public string pid;

        public string ProcName;

        public List<ClrInstance> rgClr;

        public RootProcess(string inpid, string inproc)

        {

            pid = inpid;

            ProcName = inproc;

            rgClr = new List<ClrInstance>();

        }

        public void AddClr(ClrInstance clr)

        {

   rgClr.Add(clr);

        }

    }

    public class ClrInstance

    {

        public string ClrName;

        public ClrInstance(string strClr)

        {

            ClrName = strClr;

        }

    }

}