To retrieve the content of a list view in a foreign process is a complicated thing. Because the list view is in another process, and the LVM_GETITEM
message requires you to send a pointer of an LVITEM
structure, which must be allocated in the remote process's memory heap.
Here is the code:
The Implementation
// 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);
Appendix: Minimal Windows API Declarations
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
. – Mouton