Export (0) Print
Expand All
18 out of 419 rated this helpful - Rate this topic

Creating a Microsoft .NET Compact Framework-based Process Manager Application

.NET Compact Framework 1.0
 

Alex Yakhnin
IntelliProg, Inc.

March 2003

Applies to:
    Microsoft® .NET Compact Framework
    Microsoft® Visual Studio® .NET 2003

Summary: Learn how to marshal structures as byte arrays. (14 printed pages)

Download ProcessManager.msi.


Contents

Background
Structures as Byte Arrays?
Handling Processes
Display the Results
Conclusion

Background

The .NET Compact Framework provides managed interfaces to a substantial set of the Windows CE APIs, but there are some sections that have not been covered. This is where Platform Invoke (P/Invoke) services come to our rescue. P/Invoke is a service that enables managed code to call unmanaged functions such as those in the Windows CE API.

P/Invoke locates and invokes an exported function and marshals its arguments (integers, strings, arrays, structures, and so on) across the process boundaries as needed. The marshalling support in the .NET Compact Framework is a subset of that available on the full .NET Framework. For example, the .NET Compact Framework common language runtime cannot marshal objects within structures or reference types. This is called deep marshalling. However, if a structure contains simple types, they can be marshaled if the unmanaged code is able to conform to the structure. Therefore, in the cases when a native API function expects a complex structure that may include nested structures or pointers to other the structures, strings or some other not-blittable types, it is possible to provide conversion of the structures to the byte arrays and pass them as an arguments to the native functions. It is a little bit more work than just simply converting the structure declarations from C header files, but it is worthwhile in the end, especially if the required native function call is the only way to achieve the required functionality.

Structures as Byte Arrays?

What do we know about structures? In their most general form, structures are collections of variables under a single name. These variables can be of different types, and each has a name that is used to select it from the structure. For example the POINT structure has the following unmanaged definition:

typedef struct tagPOINT 
{
   LONG x; 
   LONG y; 
}POINT;

A structure is nothing more than a contiguous block of memory containing sequential data, formatted as per its declaration. We know that unmanaged LONG data type takes 4 bytes in memory. So, on our POINT example we should have an address in memory with 8 sequential bytes allocated for its x and y members. This leads us to another problem: from the 4 bytes in memory for a LONG data type which byte is the most significant byte (MSB)? Which is the least significant byte (LSB)? The answer to these questions can be found on MSDN: "Windows was designed around the little-endian architecture" which means "the little end is stored first". A number like 0x1234 is stored in memory in two consecutive bytes: the first byte contains the little end (0x34); the second byte contains the big end (0x12).

Handling Processes

The Windows CE platform contains a very useful DLL: toolhelp.dll, which implements a set of APIs that allow you to get information about processes and threads inside these processes running on the device. For example, the function CreateToolhelp32Snapshot takes a snapshot of the processes, the heaps, modules, and threads used by the processes. In order to retrieve information about the first process encountered in a system snapshot, the Process32First is used, and Process32Next afterwards. These functions return information about processes in the PPROCESSENTRY32 structure that has the following unmanaged definition:

typedef struct tagPROCESSENTRY32 
{
   DWORD dwSize; 
   DWORD cntUsage; 
   DWORD th32ProcessID; 
   DWORD th32DefaultHeapID; 
   DWORD th32ModuleID; 
   DWORD cntThreads; 
   DWORD th32ParentProcessID; 
   LONG pcPriClassBase; 
   DWORD dwFlags; 
   TCHAR szExeFile[MAX_PATH]; 
   DWORD th32MemoryBase;
   DWORD th32AccessKey;
} PROCESSENTRY32;

All members of this structure are blittable types except for the TCHAR array (szExeFile). For this reason, this structure cannot be automatically marshaled by the .NET Compact Framework. Here is where the knowledge that a structure is simply a byte array comes in handy. We can covert this structure to a byte array and pass that to the Windows CE API.

Let's start from creation the PROCESSENTRY32 class and inserting an offsets for each member defined in this structure:

private class PROCESSENTRY32
{
   // constants for structure definition
   private const int SizeOffset = 0;
   private const int UsageOffset = 4;
   private const int ProcessIDOffset=8;
   private const int DefaultHeapIDOffset = 12;
   private const int ModuleIDOffset = 16;
   private const int ThreadsOffset = 20;
   private const int ParentProcessIDOffset = 24;
   private const int PriClassBaseOffset = 28;
   private const int dwFlagsOffset = 32;
   private const int ExeFileOffset = 36;
   private const int MemoryBaseOffset = 556;
   private const int AccessKeyOffset = 560;
   private const int Size = 564; // the whole size of the structure
   private const int MAX_PATH = 260;

   // data members
   public uint dwSize; 
   public uint cntUsage; 
   public uint th32ProcessID; 
   public uint th32DefaultHeapID; 
   public uint th32ModuleID; 
   public uint cntThreads; 
   public uint th32ParentProcessID; 
   public int pcPriClassBase; 
   public uint dwFlags; 
   public string szExeFile;
   public uint th32MemoryBase;
   public uint th32AccessKey;

}

There are several ways to push these simple data types into a byte array. I've chosen to use the System.BitConverter class. This class converts base data types to an array of bytes, and arrays of bytes to base data types. By the way: the implementation of this class should take care of the byte order ("endianess") in which data is stored in any device. So we can add the following helper functions into the PROCESSENTRY32 class:

// utility:  get a uint from the byte array
private static uint GetUInt(byte[] aData, int Offset)
{
      return BitConverter.ToUInt32(aData, Offset);
}

// utility:  set a uint into the byte array
private static void SetUInt(byte[] aData, int Offset, int Value)
{
      byte[] buint = BitConverter.GetBytes(Value);
      Buffer.BlockCopy(buint, 0, aData, Offset, buint.Length);
}
// utility:  get a ushort from the byte array
private static ushort GetUShort(byte[] aData, int Offset)
{
      return BitConverter.ToUInt16(aData, Offset);
}

// utility:  set a ushort int the byte array
private static void SetUShort(byte[] aData, int Offset, int Value)
{
      byte[] bushort = BitConverter.GetBytes((short)Value);
      Buffer.BlockCopy(bushort, 0, aData, Offset, bushort.Length);
}

And for conversion of the string data type, we can use the System.Text.Encoding class:

// utility:  get a unicode string from the byte array
private static string GetString(byte[] aData, int Offset, int Length)
{
   String sReturn =  Encoding.Unicode.GetString(aData, Offset, Length);
   return sReturn;
}

// utility:  set a unicode string in the byte array
private static void SetString(byte[] aData, int Offset, string Value)
{
   byte[] arr = Encoding.ASCII.GetBytes(Value);
   Buffer.BlockCopy(arr, 0, aData, Offset, arr.Length);
}

At this point, we should be able to add the constructor to the PROCESSENTRY32 class that accepts the byte array and populates all the members of the "virtual" structure:

// create a PROCESSENTRY instance based on a byte array      
public PROCESSENTRY32(byte[] aData)
{
   dwSize = GetUInt(aData, SizeOffset);
   cntUsage = GetUInt(aData, UsageOffset);
   th32ProcessID = GetUInt(aData, ProcessIDOffset);
   th32DefaultHeapID = GetUInt(aData, DefaultHeapIDOffset);
   th32ModuleID = GetUInt(aData, ModuleIDOffset);
   cntThreads = GetUInt(aData, ThreadsOffset);
   th32ParentProcessID = GetUInt(aData, ParentProcessIDOffset);
   pcPriClassBase = (long) GetUInt(aData, PriClassBaseOffset);
   dwFlags = GetUInt(aData, dwFlagsOffset);
   szExeFile = GetString(aData, ExeFileOffset, MAX_PATH);
   th32MemoryBase = GetUInt(aData, MemoryBaseOffset);
   th32AccessKey = GetUInt(aData, AccessKeyOffset);
}

The API Reference suggests that the dwSize member be set with the size of the PROCESSENTRY32 structure; otherwise the call to the Process32First will fail. So, the ToByteArray method should take care of allocating the required memory and setting the dwSize member:

// create an initialized data array
public byte[] ToByteArray()
{
   byte[] aData;
   aData = new byte[Size];
   //set the Size member
   SetUInt(aData, SizeOffset, Size);
   return aData;
}

Let's just add a few properties to the PROCESSENTRY32 class to expose some of the most interesting members:

public string Name
{
   get
   {
      return szExeFile.Substring(0, szExeFile.IndexOf('\0'));
   }
}

public ulong PID
{
   get
   {
      return th32ProcessID;
   }
}

public ulong BaseAddress
{
   get
   {
      return th32MemoryBase;
   }
}

public ulong ThreadCount
{
   get
   {
      return cntThreads;
   }
}

And of course we should not forget about a P/Invoke function declarations:

private const int TH32CS_SNAPPROCESS = 0x00000002;
[DllImport("toolhelp.dll")]
public static extern IntPtr CreateToolhelp32Snapshot(uint flags, uint processid);
[DllImport("toolhelp.dll")]
public static extern int CloseToolhelp32Snapshot(IntPtr handle);
[DllImport("toolhelp.dll")]
public static extern int Process32First(IntPtr handle, byte[] pe);
[DllImport("toolhelp.dll")]
public static extern int Process32Next(IntPtr handle, byte[] pe);
[DllImport("coredll.dll")]
private static extern IntPtr OpenProcess(int flags, bool fInherit, int PID);
private const int PROCESS_TERMINATE = 1;
[DllImport("coredll.dll")]
private static extern bool TerminateProcess(IntPtr hProcess, uint ExitCode);
[DllImport("coredll.dll")]
private static extern bool CloseHandle(IntPtr handle);
private const int INVALID_HANDLE_VALUE = -1;

Next, we add the Process class that will utilize the functionality of the PROCESSENTRY32 class we have just created and hide the complexity of calling the Windows CE API from the client:

public class Process
{
private string processName;
   private IntPtr handle;
   private int threadCount;
   private int baseAddress;

   //default constructor
   public Process()
   {

   }
   
   //private helper constructor
   private Process(IntPtr id, string procname, int threadcount, int baseaddress)
   {
      handle = id;
      processName = procname;
      threadCount = threadcount;
      baseAddress = baseaddress;
   }

   public static Process[] GetProcesses()
   {
      //temp ArrayList
ArrayList procList = new ArrayList();

      IntPtr handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

      if ((int)handle > 0)
      {
         try
         {
            PROCESSENTRY32 peCurrent;
            PROCESSENTRY32 pe32 = new PROCESSENTRY32();
            //Get byte array to pass to the API calls
            byte[] peBytes = pe32.ToByteArray();
            //Get the first process
            int retval = Process32First(handle, peBytes);
            while(retval == 1)
            {
               //Convert bytes to the class
               peCurrent = new PROCESSENTRY32(peBytes);
               //New instance of the Process class
               Process proc = new Process(new IntPtr((int)peCurrent.PID), 
                              peCurrent.Name, (int)peCurrent.ThreadCount, 
                              (int)peCurrent.BaseAddress);

               procList.Add(proc);

               retval = Process32Next(handle, peBytes);
            }
         }
         catch(Exception ex)
         {
            throw new Exception("Exception: " + ex.Message);
         }
         //Close handle
         CloseToolhelp32Snapshot(handle); 

         return (Process[])procList.ToArray(typeof(Process));

     }
     else
     {
        throw new Exception("Unable to create snapshot");
     }


   }

}

In the code above, the GetProcesses method populates the process list array by making calls to the CreateToolhelp32Snapshot, Process32First and Process32Next unmanaged functions. It allocates the required memory for the structure by calling:

byte[] peBytes = pe32.ToByteArray();

and then passes it to the Process32First and Process32Next functions:

int retval = Process32First(handle, peBytes);

Since we declared the second parameter in these API as byte[], the marshaller is going to pin the memory block we allocated and pass a pointer to this memory to the native function. The Process32First and Process32Next will populate this memory block with data about a process, so everything what's left for us is to extract the appropriate values from the byte array:

//Convert bytes to the class
peCurrent = new PROCESSENTRY32(peBytes);

//New instance of the Process class
Process proc = new Process(new IntPtr((int)peCurrent.PID), 
                       peCurrent.Name, (int)peCurrent.ThreadCount, 
                       (int)peCurrent.BaseAddress);

Evidently, we need to add property declarations to make values available for the client that is going to use the Process class:

//ToString implementation for ListBox binding
public override string ToString()
{
   return processName;
}

public int BaseAddress
{
   get
   {
      return baseAddress;
   }
}

public int ThreadCount
{
   get
   {
      return threadCount;
   }
}

public IntPtr Handle
{
   get
   {
      return handle;
   }
}

public string ProcessName
{
   get
   {
      return processName;
   }
}

public int BaseAddess
{
   get
   {
      return baseAddress;
   }
}

Display the Results

Using the Process class is pretty straightforward task in your client application. I've added a ListBox and a couple of Buttons on the Form, named them cmdRefresh and cmdEndTask and added just one line of code in the Form's constructor, just right after InitializeComponent:

public Form1()
{
   //
   // Required for Windows Form Designer support
   //
   InitializeComponent();

   // Populate the process list
   lstProcess.DataSource = Process.GetProcesses();
}

Since we overrode the ToString() method in the Process class, the ListBox's binding logic will pick up ProcessName as a DisplayMember.

The code that is executed in the Click events of the buttons should be clear-cut as well:

private void cmdEndTask_Click(object sender, System.EventArgs e)
{
   //Get selected process
   Process proc = (Process)lstProcess.SelectedItem;
   proc.Kill();
   //Refresh process list
   lstProcess.DataSource = null;
   lstProcess.DataSource = Process.GetProcesses();         
}

private void cmdRefresh_Click(object sender, System.EventArgs e)
{
   lstProcess.DataSource = null;
   lstProcess.DataSource = Process.GetProcesses();         
}

When the user selects the item in the lstProcess ListBox we display the Process handle, BaseAddress and ThreadCount values by using Label controls on the form:

Figure 1. The final product

private void lstProcess_SelectedIndexChanged(object sender, 
                                                       System.EventArgs e)
{
   //Get selected process
   Process proc = (Process)lstProcess.SelectedItem;
   //Populate labels
   lblId.Text = "Process ID: " + proc.Handle;
   lblThreadCount.Text = "Thread Count: " + proc.ThreadCount;
   lblBaseAddress.Text = "Base Address: " + proc.BaseAddess;
}

Conclusion

Even though all marshalling scenarios are not explicitly supported by the .NET Compact Framework it is still possible to accomplish more sophisticated tasks. Developer's can create wrapper classes for the native structures, converting them to byte arrays to enable all marshalling scenarios.

Did you find this helpful?
(1500 characters remaining)
Thank you for your feedback
Show:
© 2014 Microsoft. All rights reserved.