Read ListViewItem content from another process

You can get a PDF with this article.

Normal Windows GUI applications work with messages that are sent to a window or control and the control reacts on these messages. This technology can easily be used to control other applications. A quite nice start can give the CWindow article on CodeProject.

Whenever we want to control some other applications, I would suggest to use spy+ (or any other tool like that) which hooks up into the application to show us all messages that are processed. That way we can quickly find out, what messages are used and I think in most cases we will find, that the common dialogs are used. This makes it quite easy for developers, because we can always look up the messages, that the controls understand.

In my example, the control also knows the LVM_GETITEMW message. We can look up the available messages and some more informations inside the c/c++ header files. The information we need can be found inside the commctrl.h file.

When we have a closer look, we will find, that a structure LV_ITEM is required. So we first translate it to c#:

[StructLayout(LayoutKind.Sequential)]
unsafe struct LV_ITEM
{
  public Int32 mask;
  public Int32 iItem;
  public Int32 iSubItem;
  public Int32 state;
  public Int32 stateMask;
  public char* pszText;
  public Int32 cchTextMax;
  public Int32 iImage;
  public Int32 lParam;
  public Int32 iIndent;
}

Important: Inside this structure, we need to use pointers. Pointers in C# are unsafe so we have to allow unsafe code inside our project settings.

Next we need to check out the core message. The message itself is just a number. Inside the commctrl.h we find:

#define LVM_GETITEMW            (LVM_FIRST + 75)

So we check the header files for the definition of LVM_FIRST (0x1000) so we can define the values:

const int LVM_FIRST = 0x1000;
const int LVM_GETITEMW = LVM_FIRST + 75;

And now we have to check, what kind of Message we have to send to the control:

The mask entry of LV_ITEM must be set. LVIF_TEXT makes sense for us because we want to get the text. So we have to define that number, too:

const int LVIF_TEXT = 0x00000001;

We set the LV_ITEM.iItem to the item number we want to read and the iSubItem to the sub item to read. The next important step is to set the pointer to a memory area where the ListView should write the content to.

After this preparation we can send the message now – and we will be surprised: We get an error inside the application we wanted to control! What is going wrong?

Windows protects the memory of the different processes. One process is not allowed to simply write to memory allocated by some other process. So we have to solve this, too.

The solution is again quite straight forward:

  • First we get a handle to the process owning the listview. We can use OpenProcess for this.
  • Next we allocate memory which should be accessable by that process. This can be done with VirtualAllocEx. We allocate enough memory so that the LV_ITEM and the returned string fit inside the area.
  • Now we can create create a LV_ITEM inside our memory area. The string points to the allocated buffer + size of LV_ITEM – so the string begins directly behind the LV_ITEM.
  • Next we copy the LV_ITEM we created in our process memory to the memory block we allocated. This can be done with WriteProcessMemory.
  • Now we can send the message – this time there should be no error.
  • To get the string that was written to that memory area we have to copy it back to some memory of our process using ReadProcessMemory.
  • The last step is to use Marshal.PtrToStringUri to get the string we are interested in.

So let us have a look at some working code (Just Copy & Paste the code so you can see the long lines, too):

using System;
using System.Runtime.InteropServices;

namespace ListViewTest
{
    [StructLayout(LayoutKind.Sequential)]
    unsafe struct LV_ITEM {
        public Int32 mask;
        public Int32 iItem;
        public Int32 iSubItem;
        public Int32 state;
        public Int32 stateMask;
        public char* pszText;
        public Int32 cchTextMax;
        public Int32 iImage;
        public Int32 lParam;
        public Int32 iIndent;
    }

    [Flags()]
    public enum Win32ProcessAccessType {
        AllAccess = CreateThread | DuplicateHandle | QueryInformation | SetInformation | Terminate | VMOperation | VMRead | VMWrite | Synchronize,
        CreateThread = 0x2,
        DuplicateHandle = 0x40,
        QueryInformation = 0x400,
        SetInformation = 0x200,
        Terminate = 0x1,
        VMOperation = 0x8,
        VMRead = 0x10,
        VMWrite = 0x20,
        Synchronize = 0x100000
    }

    [Flags]
    public enum Win32AllocationTypes {
        MEM_COMMIT = 0x1000,
        MEM_RESERVE = 0x2000,
        MEM_DECOMMIT = 0x4000,
        MEM_RELEASE = 0x8000,
        MEM_RESET = 0x80000,
        MEM_PHYSICAL = 0x400000,
        MEM_TOP_DOWN = 0x100000,
        WriteWatch = 0x200000,
        MEM_LARGE_PAGES = 0x20000000
    }

    [Flags]
    public enum Win32MemoryProtection {
        PAGE_EXECUTE = 0x10,
        PAGE_EXECUTE_READ = 0x20,
        PAGE_EXECUTE_READWRITE = 0x40,
        PAGE_EXECUTE_WRITECOPY = 0x80,
        PAGE_NOACCESS = 0x01,
        PAGE_READONLY = 0x02,
        PAGE_READWRITE = 0x04,
        PAGE_WRITECOPY = 0x08,
        PAGE_GUARD = 0x100,
        PAGE_NOCACHE = 0x200,
        PAGE_WRITECOMBINE = 0x400
    }

    public class ListViewItem {
        [DllImport("kernel32.dll")]
        internal static extern IntPtr OpenProcess(Win32ProcessAccessType dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwProcessId);
        [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
        internal static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, Win32AllocationTypes flWin32AllocationType, Win32MemoryProtection flProtect);
        [DllImport("kernel32.dll", SetLastError = true)]
        internal static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, ref LV_ITEM lpBuffer, uint nSize, out int lpNumberOfBytesWritten);
        [DllImport("kernel32.dll", SetLastError = true)]
        internal static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, IntPtr lpBuffer, int dwSize, out int lpNumberOfBytesRead);
        [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
        internal static extern bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress, int dwSize, Win32AllocationTypes dwFreeType);
        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        internal static extern bool CloseHandle(IntPtr hObject);
        [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto, SetLastError = true)]
        static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

        public string GetListViewItem(IntPtr hwnd, uint processId, int item, int subItem = 0)
        {
            const int dwBufferSize = 2048;
            const int LVM_FIRST = 0x1000;
            const int LVM_GETITEMW = LVM_FIRST + 75;
            const int LVIF_TEXT = 0x00000001;

            int bytesWrittenOrRead = 0;
            LV_ITEM lvItem;
            string retval;
            bool bSuccess;
            IntPtr hProcess = IntPtr.Zero;
            IntPtr lpRemoteBuffer = IntPtr.Zero;
            IntPtr lpLocalBuffer = IntPtr.Zero;

            try {
                lvItem = new LV_ITEM();
                lpLocalBuffer = Marshal.AllocHGlobal(dwBufferSize);
                hProcess = OpenProcess(Win32ProcessAccessType.AllAccess, false, processId);
                if (hProcess == IntPtr.Zero)
                    throw new ApplicationException("Failed to access process!");

                lpRemoteBuffer = VirtualAllocEx(hProcess, IntPtr.Zero, dwBufferSize, Win32AllocationTypes.MEM_COMMIT, Win32MemoryProtection.PAGE_READWRITE);
                if (lpRemoteBuffer == IntPtr.Zero)
                    throw new SystemException("Failed to allocate memory in remote process");

                lvItem.mask = LVIF_TEXT;
                lvItem.iItem = item;
                lvItem.iSubItem = subItem;
                unsafe {
                    lvItem.pszText = (char*)(lpRemoteBuffer.ToInt32() + Marshal.SizeOf(typeof(LV_ITEM)));
                }
                lvItem.cchTextMax = 500;

                bSuccess = WriteProcessMemory(hProcess, lpRemoteBuffer, ref lvItem, (uint)Marshal.SizeOf(typeof(LV_ITEM)), out bytesWrittenOrRead);
                if (!bSuccess)
                    throw new SystemException("Failed to write to process memory");

                SendMessage(hwnd, LVM_GETITEMW, IntPtr.Zero, lpRemoteBuffer);

                bSuccess = ReadProcessMemory(hProcess, lpRemoteBuffer, lpLocalBuffer, dwBufferSize, out bytesWrittenOrRead);

                if (!bSuccess)
                    throw new SystemException("Failed to read from process memory");

                retval = Marshal.PtrToStringUni((IntPtr)(lpLocalBuffer.ToInt32() + Marshal.SizeOf(typeof(LV_ITEM))));
            }
            finally {
                if (lpLocalBuffer != IntPtr.Zero)
                    Marshal.FreeHGlobal(lpLocalBuffer);
                if (lpRemoteBuffer != IntPtr.Zero)
                    VirtualFreeEx(hProcess, lpRemoteBuffer, 0, Win32AllocationTypes.MEM_RELEASE);
                if (hProcess != IntPtr.Zero)
                    CloseHandle(hProcess);
            }
            return retval;
        }

    }
}

Please feel free to send me a note if you have any thoughts or problems with this.

Advertisements
Posted in Software Development | 3 Comments

Reading a ranged property from Active Directory using C#

You can find a PDF with this article.

Whenever reading data from Active Directory, the data must be read in multiple pages if there is a lot of data to be sent. A DirectorySearcher automatically reads all data without any extra line of code (Ok, this is not fully correct. We have to set the PageSize property. If we do not set this property, the number of results is limited!).

But as soon as we want to get all data from a property, this is something we have to do on our own. This can be done with a DirectorySearcher instance. But for this we have to understand, how ranged properties work with a DirectorySearcher.

A ranged property can be requested through adding the range to the property name: So if we want to check some group members, we do not ask for “member” – instead we can ask for “member;range=1000-*”

But when we play around with these ranged properties, we will find behaviours that could be unexpected:

So one thing to take care of: The range that comes back can be different than the range we requested. The easiest situation for this could be to request 1000 elements but the server only wants to give back 500. So a request of “member;range=1000-2000” could give us a property “mamber;range=1000-1500” instead.

Another important thing to take care of: When we request more elements that available, we might get an error. So if we request “member;range=1000-2000” but there are only 1500 member available, we could receive an error. (In which case we need to query from 1000-* to receive the last elements!)

So with this knowledge it should be quite easy to write some code that reads a full property with taking full care of paging if required:

  • We request a paged property with ;range=0-* and then put the results of this query into a List.
  • We cannot know if we got all results or not so we simply start to read another page: So if we got 500 elements, we try to read from 500 – * so we get more elements if there are more.
  • We continue till we no longer get any data back.

So a solution that reads a full property could be (Copy & Paste the code so you can also read the lines that are to long):

using System;
using System.Collections.Generic;
using System.DirectoryServices;
using System.Globalization;
using System.Linq;

namespace RangedProperty
{
    public class RangedProperty {
        public const string RangeSeparator = "-";
        public const string RangeKey = ";range=";
        public const string RangedAttributeFormat = "{0}" + RangeKey + "{1}" + RangeSeparator + "{2}";

        public static string Escape(Guid? value)
        {
            if (!value.HasValue)
                return @"0";

            return value.Value.ToByteArray().Aggregate("", (current, idByte) => current + (@"\" + idByte.ToString("x2", CultureInfo.InvariantCulture)));
        }

        public static string GetResultName(SearchResult result, string propertyName)
        {
            string resultName = (from string name in result.Properties.PropertyNames
                                 where name.StartsWith(propertyName + RangeKey, StringComparison.OrdinalIgnoreCase)
                                 select name).FirstOrDefault();

            if (resultName != null)
                return resultName;

            resultName = (from string name in result.Properties.PropertyNames
                          where String.Compare(name, propertyName, StringComparison.OrdinalIgnoreCase) == 0
                          select name).FirstOrDefault();

            return resultName;
        }

        public static bool ParseRangedPropertyName(string propertyName, out int start, out int end)
        {
            start = 0;
            end = 0;
            int rangeLocation = propertyName.IndexOf(RangeKey, StringComparison.OrdinalIgnoreCase);
            int seperatorLocation = propertyName.IndexOf(RangeSeparator, StringComparison.OrdinalIgnoreCase);

            if (rangeLocation <= 0 || seperatorLocation <= 0
                || !Int32.TryParse(propertyName.Substring(rangeLocation + RangeKey.Length, seperatorLocation - rangeLocation - RangeKey.Length), out start))
                return false;

            if (propertyName.Substring(seperatorLocation + 1).Equals("*"))
                end = Int32.MaxValue;

            if (end != Int32.MaxValue && !Int32.TryParse(propertyName.Substring(seperatorLocation + 1), out end))
                    return false;

            return true;
        }

        public static T[] Read<T>(DirectorySearcher searcher, Guid objectId, string propertyName)
        {
            searcher.Filter = "(objectGUID=" + Escape(objectId) + ")";
            SearchResult result;
            try {
                searcher.PropertiesToLoad.Add(String.Format(RangedAttributeFormat, propertyName, 0, "*"));
                result = searcher.FindOne();
            }
            catch (DirectoryServicesCOMException adsError)
            {
                if (adsError.ErrorCode == -2147016672)
                {
                    searcher.PropertiesToLoad.Clear();
                    searcher.PropertiesToLoad.Add(propertyName);
                    result = searcher.FindOne();
                }
                else {
                    throw;
                }
            }

            if (result == null)
                return null;

            var results = new List<T>();
            var resultPropertyName = GetResultName(result, propertyName);
            if (!String.IsNullOrEmpty(resultPropertyName))
            {
                results.AddRange(result.Properties[resultPropertyName].Cast<T>().ToArray());

                int start, end;
                if (ParseRangedPropertyName(resultPropertyName, out start, out end))
                {
                    if (end != Int32.MaxValue)
                    {
                        var lastRange = false;
                        while (!lastRange)
                        {
                            searcher.PropertiesToLoad.Clear();
                            searcher.PropertiesToLoad.Add(String.Format(RangedAttributeFormat, propertyName, results.Count, "*"));
                            try {
                                result = searcher.FindOne();
                            }
                            catch (DirectoryServicesCOMException ex)
                            {
                                if (ex.ErrorCode == -2147016672)
                                {
                                    break;
                                }
                                throw; 
                            }

                            resultPropertyName = GetResultName(result, propertyName);
                            if (!String.IsNullOrEmpty(resultPropertyName))
                            {
                                T[] values = result.Properties[resultPropertyName].Cast<T>().ToArray();
                                results.AddRange(values);
                                if (values.Length == 0)
                                    lastRange = true;
                            }
                        }
                    }
                }
            }

            return results.ToArray();
        }
    }
}

Feel free to send me (konrad@neitzel.de) a small note if you found this usefull or you have any further questions.

Posted in Software Development | 2 Comments

Keep Windows Forms application alive till all forms were closed

Inside the forums I found some people asking how they could keep the application active when they open a new form and close the old one.

The main problem is, that the standard program.cs simply calls
Appliation.Run (new Form1());

As soon as this new Form1 is closed, the application will quit.

After thinking a little bit I just played around and had a small class which could be used to replace the call to Application.Run. It is just a small thing and I am not really sure that it is usefull.

To use this class just insert it into your solution. Maybe change the namespace so it fits your needs.

Afterwards you just replace the Application.Run call with the FormsApplication.Run call and whenever you open a new form you call FormsApplication.AddForm. Thats it!

(Copy & Paste the code so you can also read the lines that are to long)

using System;
using System.Collections.Generic;
using System.Windows.Forms;

namespace FormsApplicationTest
{
    /// <summary>
    /// Manages open Forms and makes sure that the ApplicationContext of the main loop always has an open form till the last form is closed.
    /// </summary>
    /// <remarks>
    /// To use this class: Simply replace the Application.Run call in program.cs with FormsApplication.Run and make sure that you use AddForm on all opened forms.
    /// </remarks>
    public class FormsApplication
    {
        private static ApplicationContext _appContext;
        private static readonly List<Form> _forms = new List<Form>();

        /// <summary>
        /// Run the Application Loop
        /// </summary>
        public static void Run()
        {
            _appContext = new ApplicationContext();
            Application.Run(_appContext);
        }

        /// <summary>
        /// Run the Application Loop with a given context.
        /// </summary>
        public static void Run(ApplicationContext context)
        {
            _appContext = context;
            if (context.MainForm != null)
            {
                _forms.Add(context.MainForm);
                context.MainForm.Closed += ClosedEventHandler;
            }
            Application.Run(context);
        }

        /// <summary>
        /// Run the Application Loop with a given form.
        /// </summary>
        public static void Run(Form form)
        {
            _forms.Add(form);
            form.Closed += ClosedEventHandler;
            _appContext = new ApplicationContext();
            _appContext.MainForm = form;
            Application.Run(_appContext);
        }

        /// <summary>
        /// Event handler that removes a form when it is closed
        /// </summary>
        private static void ClosedEventHandler(object sender, EventArgs e)
        {
            var form = (Form)sender;
            _forms.Remove(form);
            if (_appContext.MainForm == form && _forms.Count > 0)
                _appContext.MainForm = _forms[0];
        }

        /// <summary>
        /// Add a form to the list of known forms.
        /// </summary>
        /// <param name="form">Form to add to the list.</param>
        public static void AddForm(Form form)
        {
            _forms.Add(form);
            form.Closed += ClosedEventHandler;
        }
    }
}

Feel free to send me a small note (konrad@neitzel.de) if you found this usefull.

Posted in Software Development | Leave a comment

Events in multithreaded Windows Forms applications.

You can get a PDF with this article and the code is part of my blog solution available at https://neitzel.de:4443/svn/Blog.

When writing  a multi threaded Windows Forms application, it is very important to make all changes to the controls inside the UI Thread. This is nothing new and the solution can be found on MSDN and in a lot of blogs: Some controls are taking care for us already. A good example can be the BackgroundWorker with the ProgressChanged and RunWorkerCompleted events.

As soon as these offered solutions are not suitable, the developer starts to check InvokeRequired and, if required, the invoke is called.

This can end up in a lot of coding. And often it is simply not required. When subscribing to an event, it is most of the time known, if the event is fired from another thread or not. So when I want to subscribe to an event in a Windows Forms application, that is fired by another thread, I already know: an invoke is required.

Here the new functions of C# are very nice, because I can get rid of all the InvokeRequired checks. The subscription could directly be done like this:

Syntax:

someObject.AnEvent += ((sender, args) => Invoke(new EventHandler<TheEventArgs>(MyFunction), sender, args));

  • sender, args: lamda identifiers
  • someObject is an instance of any class that defines an event and where I expect that the event is fired by some other thread.
  • AnEvent is an event that has TheEventArgs as Parameter
  • TheEventArgs derives from EventArgs and is the type of the event argument.
  • MyFunction is the function that should handle the event with this signature:
    void MyFunction(object sender, TheEventArgs e)

I hope this was an interesting topic. One small disadvantage is, that the anonymous function is not available to remove the listener cleanly from the event. (In this Windows Forms topic it should not be that important. Before closing a Form you will take care of all threads / business objects connected to the form.)

Feel free to send me your feedback. Either use this page directly or send me a message at konrad@neitzel.de

Konrad Neitzel

Posted in Software Development | Leave a comment

Starting an english blog now …

Welcome to my blog.

I was thinking a lot, how I could add some english blog entries to my existing blog but dividing everything up with categories wasn’t a solution that I could accept.

So I am very happy, that WordPress offers multiple blogs for one person. If you are interested in my german blog entries, you could find them at http://kneitzel.wordpress.com.

I am planing to write some blogs about interesting software development topics (mainly covering the software development on windows with C#)

Posted in Uncategorized | Leave a comment