从其他窗口获取ListView项目

9

我正在进行一些关于C#的项目。 我需要从ListView窗口获取第i项,我通过类似以下方式获取其句柄:

IntPtr par_hWnd = API.FindWindow(null, "Form1");
IntPtr child1 = API.FindWindowEx(par_hWnd, (IntPtr)0, null, null);

API 是我的静态类,其中包含许多来自 "user32.dll" 的 DLL 导入函数。我能够获取此 ListView 中项目的计数:

IntPtr count = API.SendMessage(child1, API.LVM_GETITEMCOUNT, 0, 0);

现在我需要获取项目文本,但结果必须以 LVITEM 结构体的形式呈现,我不知道如何正确地调用 SendMessage,也不知道如何在 C# 中实现 LVITEM。找不到 C# 的示例。请帮忙!

2个回答

8

在外部进程中检索列表视图的内容是一件复杂的事情。因为列表视图位于另一个进程中,而LVM_GETITEM消息需要您发送指向LVITEM结构的指针,该结构必须在远程进程的内存堆中分配

以下是代码:

实现

// firstly we have the handle to the list view:
var listViewPtr = this.GetListViewHandle();

// get the ID of the process who owns the list view
WinAPI.GetWindowThreadProcessId(listViewPtr, out var processId);

// open the process
var processHandle = WinAPI.OpenProcess(
    WinAPI.ProcessAccessFlags.VirtualMemoryOperation
    | WinAPI.ProcessAccessFlags.VirtualMemoryRead
    | WinAPI.ProcessAccessFlags.VirtualMemoryWrite,
    false,
    processId);

// allocate buffer for a string to store the text of the list view item we wanted
var textBufferPtr = WinAPI.VirtualAllocEx(
    processHandle,
    IntPtr.Zero,
    WinAPI.MAX_LVMSTRING,
    WinAPI.AllocationType.Commit,
    WinAPI.MemoryProtection.ReadWrite);

var itemId = 0; // the item (row) index
var subItemId = 1; // the subitem (column) index

// this is the LVITEM we need to inject
var lvItem = new WinAPI.LVITEM
{
    mask = (uint)WinAPI.ListViewItemFilters.LVIF_TEXT,
    cchTextMax = (int)WinAPI.MAX_LVMSTRING,
    pszText = textBufferPtr,
    iItem = itemId,
    iSubItem = subItemId
};

// allocate memory for the LVITEM structure in the remote process
var lvItemSize = Marshal.SizeOf(lvItem);
var lvItemBufferPtr = WinAPI.VirtualAllocEx(
    processHandle,
    IntPtr.Zero,
    (uint)lvItemSize,
    WinAPI.AllocationType.Commit,
    WinAPI.MemoryProtection.ReadWrite);

// to inject the LVITEM structure, we have to use the WriteProcessMemory API, which does a pointer-to-pointer copy. So we need to turn the managed LVITEM structure to an unmanaged LVITEM pointer
// first allocate a piece of unmanaged memory ...
var lvItemLocalPtr = Marshal.AllocHGlobal(lvItemSize);

// ... then copy the managed object into the unmanaged memory
Marshal.StructureToPtr(lvItem, lvItemLocalPtr, false);

// and write into remote process's memory
WinAPI.WriteProcessMemory(
    processHandle,
    lvItemBufferPtr,
    lvItemLocalPtr,
    (uint)lvItemSize,
    out var _);

// tell the list view to fill in the text we desired
WinAPI.SendMessage(listViewPtr, (int)WinAPI.ListViewMessages.LVM_GETITEMTEXT, itemId, lvItemBufferPtr);

// read the text. we allocate a managed byte array to store the retrieved text instead of AllocHGlobal-ing a piece of unmanaged memory, because CLR knows how to marshal between a pointer and a byte array
var localTextBuffer = new byte[WinAPI.MAX_LVMSTRING];
WinAPI.ReadProcessMemory(
    processHandle,
    textBufferPtr,
    localTextBuffer,
    (int)WinAPI.MAX_LVMSTRING,
    out var _);

// convert the byte array to a string. assume the remote process uses Unicode
var text = Encoding.Unicode.GetString(localTextBuffer);
// the trailing zeros are not cleared automatically
text = text.Substring(0, text.IndexOf('\0'));

// finally free all the memory we allocated, and close the process handle we opened
WinAPI.VirtualFreeEx(processHandle, textBufferPtr, 0, WinAPI.AllocationType.Release);
WinAPI.VirtualFreeEx(processHandle, lvItemBufferPtr, 0, WinAPI.AllocationType.Release);
Marshal.FreeHGlobal(lvItemLocalPtr);

WinAPI.CloseHandle(processHandle);

附录:最小化的Windows API声明

static class WinAPI
{

    public enum ListViewMessages
    {
        LVM_GETITEMTEXT = 0x104B
    }

    public enum ListViewItemFilters : uint
    {
        LVIF_TEXT = 0x0001,
    }

    public const uint MAX_LVMSTRING = 255;

    [StructLayoutAttribute(LayoutKind.Sequential)]
    public struct LVITEM
    {
        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 IntPtr lParam;
    }

    [DllImport("user32.dll")]
    public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, IntPtr lParam);

    [DllImport("user32.dll")]
    public static extern uint GetWindowThreadProcessId(IntPtr hWnd, ref uint lpdwProcessId);


    [Flags]
    public enum ProcessAccessFlags : uint
    {
        VirtualMemoryOperation = 0x0008,
        VirtualMemoryRead = 0x0010,
        VirtualMemoryWrite = 0x0020,
    }

    [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
    public static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, AllocationType flAllocationType, MemoryProtection flProtect);

    [Flags]
    public enum AllocationType
    {
        Commit = 0x1000,
        Release = 0x8000,
    }

    [Flags]
    public enum MemoryProtection
    {
        ReadWrite = 0x0004,
    }

    [DllImport("kernel32.dll")]
    public static extern IntPtr OpenProcess(ProcessAccessFlags processAccess, bool bInheritHandle,  uint processId);

    [DllImport("kernel32.dll")]
    public static extern bool CloseHandle(IntPtr hHandle);

    [DllImport("kernel32.dll")]
    public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, IntPtr lpBuffer, uint nSize, out int lpNumberOfBytesWritten);

    [DllImport("kernel32.dll")]
    public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] buffer, int dwSize, out IntPtr lpNumberOfBytesRead);

    [DllImport("kernel32.dll")]
    public static extern bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress, int dwSize, AllocationType dwFreeType);
}

谢谢您的回答。它运行得很好。可以使用Marshal.PtrToStringAuto进行简化。 - Martin Prikryl
顺便说一下,当针对64位目标时似乎无法工作。 - Martin Prikryl

4
我找到了一个C#的包装器,似乎可以从任何窗口访问LV的内容。
ManagedWinapi
using ManagedWinapi.Windows;
using System;

namespace TestApp
{
    class Program
    {
        static void Main(string[] args)
        {
            // Create a SystemWindow object from the HWND of the ListView
            SystemWindow lvWindow = new SystemWindow((IntPtr)0x6d1d38);

            // Create a ListView object from the SystemWindow object
            var lv = SystemListView.FromSystemWindow(lvWindow);

            // Read text from a row
            var text = lv[0].Title;
        }
    }
}

此外,我还在此处分叉了mwapi(链接:https://github.com/evilC/mwinapi),并尝试添加一些新功能——主要是围绕ListView行颜色的着色,但也包括添加一些缺失的p / invokes等。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接