Search code examples
c#.netwinapisyslistview32

Getting Text from SysListView32 in 64bit


Here is my code:

public static string ReadListViewItem(IntPtr lstview, 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(lstview, 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.iSubItem = 2;
        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(lstview, LVM_GETITEMText, 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 +
          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;
}

No matter what I do retval returns empty, although lpLocalBuffer doesn't.

Here is the definition of LV_ITEM:

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

I tried compiling for x86, x64, Any CPU, nothing seems to work at all!

Any idea why this might be happening?

C# + .NET 4, Windows 7 64bit.


Solution

  • Here's a different approach to doing this - use UI Automation. It does the cross-process, cross-bitness work for you, and will work against listviews, listboxes, or pretty much any other standard Windows UI. Here's a sample app that will get the HWND from the listview under the mouse pointer, and dump the items in it. It dumps just the name of each item; with Listviews, I think you can recurse into the fields in each item if you want.

    // Compile using: csc ReadListView.cs /r:UIAutomationClient.dll
    
    using System;
    using System.Windows.Automation;
    using System.Runtime.InteropServices;
    
    class ReadListView
    {
        public static void Main()
        {
            Console.WriteLine("Place pointer over listview and hit return...");
            Console.ReadLine();
    
            // Get cursor position, then the window handle at that point...
            POINT pt;
            GetCursorPos(out pt);
            IntPtr hwnd = WindowFromPoint(pt);
    
            // Get the AutomationElement that represents the window handle...
            AutomationElement el = AutomationElement.FromHandle(hwnd);
    
            // Walk the automation element tree using content view, so we only see
            // list items, not scrollbars and headers. (Use ControlViewWalker if you
            // want to traverse those also.)
            TreeWalker walker = TreeWalker.ContentViewWalker;
            int i = 0;
            for( AutomationElement child = walker.GetFirstChild(el) ;
                child != null; 
                child = walker.GetNextSibling(child) )
            {
                // Print out the type of the item and its name
                Console.WriteLine("item {0} is a \"{1}\" with name \"{2}\"", i++, child.Current.LocalizedControlType, child.Current.Name);
            }
        }
    
        [StructLayout(LayoutKind.Sequential)]
        private struct POINT
        {
            public int x;
            public int y;
        };
    
        [DllImport("user32.dll")]
        private static extern IntPtr WindowFromPoint(POINT pt);
    
        [DllImport("user32.dll")]
        private static extern int GetCursorPos(out POINT pt);
    }