May 2004 - Posts

Crossing the process boundry with .NET

Every so often my post Hacking my way across the process boundary gets some attention. Mostly in the form of requests for a .NET version of this technique. Now out of laziness more than anything else I have not actually taken the time or effort to do the conversion until now. So for those that need to access ListView or TreeView data from another process here is a simple example of one possible way to do it. Since I used C# for the example, I could very well have used unsafe code blocks to do some of the work, however I decided to avoid this making this example applicable to VB.NET developers as well. If you would like to see a version using unsafe code blocks, drop me a note and I will get round to it. To keep the sample short I have removed anything but the most rudamentry error checking. For an explanation of this code, please refere to the original post sighted above.

using System;
using System.Runtime.InteropServices;
using System.Text;
public class CrossProcessMemory
{
  const int LVM_GETITEM = 0x1005;
  const int LVM_SETITEM = 0x1006;
  const int LVIF_TEXT   = 0x0001;
  const uint PROCESS_ALL_ACCESS = (uint)(0x000F0000L | 0x00100000L | 0xFFF);
  const uint MEM_COMMIT         = 0x1000;
  const uint MEM_RELEASE        = 0x8000;
  const uint PAGE_READWRITE     = 0x04;

  [DllImport("user32.dll")]
  static extern bool SendMessage(IntPtr hWnd, Int32 msg, Int32 wParam, IntPtr lParam);
  
  [DllImport("user32")]
  static extern IntPtr GetWindowThreadProcessId( IntPtr hWnd, out int lpwdProcessID );    
  
  [DllImport("kernel32")]
  static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, 
    int dwProcessId);

  [DllImport("kernel32")]
  static extern IntPtr VirtualAllocEx( IntPtr hProcess, IntPtr lpAddress, 
    int dwSize, uint flAllocationType, uint flProtect);

  [DllImport("kernel32")]
  static extern bool VirtualFreeEx( IntPtr hProcess, IntPtr lpAddress, int dwSize, 
    uint dwFreeType );

  [DllImport("kernel32")]
  static extern bool WriteProcessMemory( IntPtr hProcess, IntPtr lpBaseAddress, 
    ref LV_ITEM buffer, int dwSize, IntPtr lpNumberOfBytesWritten );

  [DllImport("kernel32")]
  static extern bool ReadProcessMemory( IntPtr hProcess, IntPtr lpBaseAddress, 
    IntPtr lpBuffer, int dwSize, IntPtr lpNumberOfBytesRead );

  [DllImport("kernel32")]
  static extern bool CloseHandle( IntPtr hObject );

  [StructLayout(LayoutKind.Sequential)]
  public struct LV_ITEM
  {
    public uint   mask; 
    public int    iItem; 
    public int    iSubItem; 
    public uint   state; 
    public uint   stateMask; 
    public IntPtr pszText; 
    public int    cchTextMax; 
    public int    iImage;
  }

  public static string ReadListViewItem( IntPtr hWnd, int item )
  {
    const int dwBufferSize = 1024;
          
    int         dwProcessID;
    LV_ITEM     lvItem;      
    string      retval;
    bool        bSuccess;
    IntPtr      hProcess        = IntPtr.Zero;
    IntPtr      lpRemoteBuffer  = IntPtr.Zero;
    IntPtr      lpLocalBuffer   = IntPtr.Zero;
    IntPtr      threadId        = IntPtr.Zero;
          
    try
    {
      lvItem = new LV_ITEM();
      lpLocalBuffer = Marshal.AllocHGlobal(dwBufferSize);
      // Get the process id owning the window
      threadId = GetWindowThreadProcessId( hWnd, out dwProcessID );
      if ( (threadId == IntPtr.Zero) || (dwProcessID == 0) )
        throw new ArgumentException( "hWnd" );

      // Open the process with all access
      hProcess = OpenProcess( PROCESS_ALL_ACCESS, false, dwProcessID );
      if ( hProcess == IntPtr.Zero )
        throw new ApplicationException( "Failed to access process" );

      // Allocate a buffer in the remote process
      lpRemoteBuffer = VirtualAllocEx( hProcess, IntPtr.Zero, dwBufferSize, MEM_COMMIT, 
        PAGE_READWRITE );
      if ( lpRemoteBuffer == IntPtr.Zero )
        throw new SystemException( "Failed to allocate memory in remote process" );
      
      // Fill in the LVITEM struct, this is in your own process
      // Set the pszText member to somewhere in the remote buffer,
      // For the example I used the address imediately following the LVITEM stuct
      lvItem.mask = LVIF_TEXT;
      lvItem.iItem = item;
      lvItem.pszText = (IntPtr)(lpRemoteBuffer.ToInt32() + Marshal.SizeOf(typeof(LV_ITEM)));
      lvItem.cchTextMax = 50;

      // Copy the local LVITEM to the remote buffer
      bSuccess = WriteProcessMemory( hProcess, lpRemoteBuffer, ref lvItem, 
        Marshal.SizeOf(typeof(LV_ITEM)), IntPtr.Zero );
      if ( !bSuccess )
        throw new SystemException( "Failed to write to process memory" );

      // Send the message to the remote window with the address of the remote buffer
      SendMessage( hWnd, LVM_GETITEM, 0, lpRemoteBuffer);
      
      // Read the struct back from the remote process into local buffer
      bSuccess = ReadProcessMemory( hProcess, lpRemoteBuffer, lpLocalBuffer, dwBufferSize, 
        IntPtr.Zero );
      if ( !bSuccess )
        throw new SystemException( "Failed to read from process memory" );
      
      // At this point the lpLocalBuffer contains the returned LV_ITEM structure
      // the next line extracts the text from the buffer into a managed string
      retval = Marshal.PtrToStringAnsi((IntPtr)(lpLocalBuffer.ToInt32() + 
        Marshal.SizeOf(typeof(LV_ITEM))));
    }
    finally
    { 
      if ( lpLocalBuffer != IntPtr.Zero )
        Marshal.FreeHGlobal( lpLocalBuffer );
      if ( lpRemoteBuffer != IntPtr.Zero )
        VirtualFreeEx( hProcess, lpRemoteBuffer, 0, MEM_RELEASE ); 
      if ( hProcess != IntPtr.Zero )
        CloseHandle( hProcess );
    }
    return retval;
  }
}

Performance - String Equality

Before I even start this blog, I want to point out that the results contained in here are relative and would hardly alter the overall performance of your application except is some very rare and extreme cases. This was merely and intellectual exercise for which I am making my results available.

In my current role I am often assessing performance bottle necks within existing code and as you might know from my other blog entries I subscribe to the school of "profile profile profile optimize test test test profile profile profile…" As part of developing my understanding of potential performance bottle necks I often inspect the IL for any unexpected code being generated.

While readers of my blog (are there any?) will know that my language of choice is C#, but currently I find myself doing more and more VB.NET code. And it is with VB.NET that I find many instances where the convenience of the language sometimes yields less than effective runtime code for the unsuspecting developer. While there are a number of these instances which I hope to be discussing over the next few blogs I want to start with the one that surprised me. And that is the simple equality operator which in VB.NET is ‘=’ and for C# it is ‘==’.

When the C# compiler encounters a == operator on strings it generates a call to the op_Equality method of the string class. When the VB.NET compiler encounters the seemingly corresponding = operator it rather generates a call to Microsoft.VisualBasic.CompilerServices.StringType.StrCmp, this is to maintain a functional compatibility with VB6 code. My immediate reaction was to determine the impact of this difference in terms of performance, particularly when invoked in a lengthy loop which is primarily composed of string comparisons. So I devised a series of simple tests, these are by no means complete and lack some obvious permutations such as reference equality, but I believe they do cover a fair number of typical cases within an application.

Test 1 : Compare two instances of a string with the same binary pattern

Test 2 : Compare two instances of a string with a binary pattern differing only towards the end of the string

Test 3 : Compare a string against a null or VB.NET nothing

These tests where carried out using the following comparison operations, where S1 and S2 are two strings and the samples use VB.NET syntax.

· Microsoft.VisualBasic.CompilerServices.StringType.StrCmp()

S1 = S2

· op_Equality

S1.op_Equality(S2)

· instance.Equals

S1.Equals(S2)

· String.Equals

String.Equals(S1, S2)

I also performed the tests using a different length of string, the first being 11 characters and the second run contained 100 characters.

The following graphs show the relative performance of these tests.


The tests where run using VS.NET 2003, VB.NET .NET, Framework 1.1 running Windows 2003.

Conclusion

While I would never suggest that VB.NET developers abandon the ‘=’ equals operator because of the inefficiency. I would definitely bear these results in mind when performing an operation which has high volumes and is string comparison intensive in which case I would consider using the String.Equals as an alternative.

Related Post: Performance - Case insensitive string comparisons

Pocket PC

Well it has been awhile since my last post and there have been a number of contributing factor, not the least of which has been some internet downtime here at home. Of course there is also the preasures of those real life things like WORK. The next three months look like they are going to be hell.

But, my most fun excuse must be the gift that my wife recently purchased for me, for a tech junkie or is that .net junkie like me the coolest gift is something electronic, and she hit the nail on the head with a iPAQ PocketPC. All in all this is a really cool device. The coolest feature is that it integrated with my wireless network. Only one piece of advice I would offer before you spend 3 hours trying to get the wireless networking to work, perform the firmware upgrade. After the firmware upgrade everything just works. Though this is the first PocketPC device I have owned, I have done a fair amount of development using the emulators and it has been really fun to see some of the stuff work on the real thing. For now I will be using C++ for the development on the PocketPC, but soon I will delve into the Compact Framework and post here about that experience.