Get ListView items from other windows - c#

I'm doing some project on c#.
I need to get i item from ListView window, handle of it I got by doing something like this
IntPtr par_hWnd = API.FindWindow(null, "Form1");
IntPtr child1 = API.FindWindowEx(par_hWnd, (IntPtr)0, null, null);
API is my static class with lots of dllimports from "user32.dll"
I am able to get count of items in this ListView:
IntPtr count = API.SendMessage(child1, API.LVM_GETITEMCOUNT, 0, 0);
Now i need to get the text of item, but the result somehow must be placed in the LVITEM Structure, I don't know how to call SendMessage correctly, and how to implement LVITEM in c#. Can't find examples for c#. Any help?

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);
}

I found a C# wrapper for WinAPIs that seems to provide access to the contents of an LV from any window.
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;
}
}
}
Also, I have also forked mwapi here and am trying to add some new functionality - Mainly centred around colouring ListView rows, but also adding some missing p/invokes etc.

Related

PInvoke ReadProcessMemory - Reading PEB with ReadProcessMemory: "Invalid handle"

I am trying to read the value of ImageBaseAddress in the PEB of a process I have created using Win32 API calls in C# with P/Invoke. However the call to ReadProcessMemory returns false, indicating it has failed. Inspecting with Visual Studio debugger I see the bytes array passed to the function is populated with zeroes. However a Win32Exception isn't raised, printing Win32Exception(Marshal.GetLastWin32Error()).Message gives The operation completed successfully.
Edit:
After addressing issues highlighted in the comments that I wasn't actually retrieving the latest Win32 error, I now see that the error I am getting is Invalid handle
Below is my code, and two screenshots to help illustrate my point.
using System.ComponentModel;
namespace ReadProcess
{
class Program
{
[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}
[StructLayout(LayoutKind.Sequential)]
internal struct STARTUPINFO
{
uint cb;
IntPtr lpReserved;
IntPtr lpDesktop;
IntPtr lpTitle;
uint dwX;
uint dwY;
uint dwXSize;
uint dwYSize;
uint dwXCountChars;
uint dwYCountChars;
uint dwFillAttributes;
uint dwFlags;
ushort wShowWindow;
ushort cbReserved;
IntPtr lpReserved2;
IntPtr hStdInput;
IntPtr hStdOutput;
IntPtr hStdErr;
}
[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_BASIC_INFORMATION
{
public IntPtr ExitStatus;
public IntPtr PebAddress;
public IntPtr AffinityMask;
public IntPtr BasePriority;
public IntPtr UniquePID;
public IntPtr InheritedFromUniqueProcessId;
}
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, int dwSize, out IntPtr lpNumberOfBytesRead);
[DllImport("ntdll.dll", SetLastError = true)]
static extern int NtQueryInformationProcess(IntPtr processHandle, int processInformationClass, IntPtr processInformation, uint processInformationLength, IntPtr returnLength);
[DllImport("kernel32.dll")]
static extern bool CloseHandle(IntPtr hProcess);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool CreateProcess(IntPtr lpApplicationName, string lpCommandLine, IntPtr lpProcAttribs, IntPtr lpThreadAttribs, bool bInheritHandles, uint dwCreateFlags, IntPtr lpEnvironment, IntPtr lpCurrentDir, [In] ref STARTUPINFO lpStartinfo, out PROCESS_INFORMATION lpProcInformation);
public static IntPtr GetPEBAddress(IntPtr hProcess)
{
//Allocate memory for a new PROCESS_BASIC_INFORMATION structure
IntPtr pbi = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(PROCESS_BASIC_INFORMATION)));
//Allocate memory for a long
IntPtr outLong = Marshal.AllocHGlobal(sizeof(long));
IntPtr outPtr = IntPtr.Zero;
int queryStatus = 0;
//Store API call success in a boolean
queryStatus = NtQueryInformationProcess(hProcess, 0, pbi, (uint)Marshal.SizeOf(typeof(PROCESS_BASIC_INFORMATION)), outLong);
//Close handle and free allocated memory
CloseHandle(hProcess);
Marshal.FreeHGlobal(outLong);
//STATUS_SUCCESS = 0, so if API call was successful querySuccess should contain 0 ergo we reverse the check.
if (queryStatus == 0)
outPtr = Marshal.PtrToStructure<PROCESS_BASIC_INFORMATION>(pbi).PebAddress;
//Free allocated space
Marshal.FreeHGlobal(pbi);
//Return pointer to PEB base address
return outPtr;
}
static void Main(string[] args)
{
STARTUPINFO startInfo = new STARTUPINFO();
PROCESS_INFORMATION procInfo = new PROCESS_INFORMATION();
CreateProcess((IntPtr)0, "notepad", (IntPtr)0, (IntPtr)0, false, 0x00000004, (IntPtr)0, (IntPtr)0, ref startInfo, out procInfo);
byte[] ImageBaseAddress = new byte[IntPtr.Size];
IntPtr lpNumberOfBytesRead;
IntPtr pPEB = GetPEBAddress(procInfo.hProcess);
ReadProcessMemory(procInfo.hProcess, pPEB + 16, ImageBaseAddress, 8, out lpNumberOfBytesRead);
string errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message;
Console.WriteLine("File handle: 0x{0:X16}", procInfo.hProcess);
Console.WriteLine("PEB base address: 0x{0:X16}", pPEB);
Console.WriteLine("Last Win32 Error: {0}", errorMessage);
}
}
}
Here is the output of the program compared with the output of !peb in a WinDBG session that is attached to the notepad process.
Here is a screenshot of Visual Studio debugger, the breakpoint is on the Console.WriteLine call, after ReadProcessMemory has been called. (edit: Win32 error is now outdated but this screenshot still highlights that I am correctly identifying the PEB base address).
You have many glaring errors in your PInvoke code.
Your primary issue: You cannot see the error code, as you are not checking GetLastWin32Error immediately after the call. It cannot wait as it gets overwritten quickly by other calls.
Furthermore, NtQueryInformationProcess doesn't use SetLastError at all, it returns a NTSTATUS. You can find the full enum list here.
Ideally you should force Unicode using CreateProcessW and CharSet.Unicode. Also use SafeFileHandle for the process and thread handles, and use using rather than CloseHandle to dispose them.
But in this case I recommend you just create the process using Process.Create. Then you don't need to manage that handle, you just need to put the Process into a using.
The call to ReadProcessMemory should use IntPtr.Size to match the buffer array.
Do not attempt manual marshalling unless you know what you are doing. You have memory leaks all over, and none of it is necessary as you can just pass in string and ref parameters where necessary.
Some IntPtr and uint types are wrong also.
[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_BASIC_INFORMATION
{
public int ExitStatus;
public IntPtr PebAddress;
public IntPtr AffinityMask;
public int BasePriority;
public IntPtr UniquePID;
public IntPtr InheritedFromUniqueProcessId;
}
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool ReadProcessMemory (
IntPtr hProcess,
IntPtr lpBaseAddress,
[Out] byte[] lpBuffer,
int dwSize,
out IntPtr lpNumberOfBytesRead);
[DllImport("ntdll.dll", SetLastError = false)]
static extern NtStatus NtQueryInformationProcess(
IntPtr processHandle,
int processInformationClass,
out PROCESS_BASIC_INFORMATION processInformation,
int processInformationLength,
out int returnLength);
public static IntPtr GetPEBAddress(SafeFileHandle hProcess)
{
var result = NtQueryInformationProcess(hProcess, 0, out var pbi, Marshal.SizeOf<PROCESS_BASIC_INFORMATION>(), out var outLong);
if(result != NtStatus.Success)
throw new Exception(result.ToString());
return pbi.PebAddress;
}
static void Main(string[] args)
{
try
{
using (var proc = Process.Start("notepad.exe"))
{
byte[] ImageBaseAddress = new byte[IntPtr.Size];
IntPtr pPEB = GetPEBAddress(proc.Handle);
if(!ReadProcessMemory(proc.Handle, pPEB + 16, ImageBaseAddress, IntPtr.Size, out var lpNumberOfBytesRead))
throw new Win32Exception(Marshal.GetLastWin32Error());
Console.WriteLine("PEB base address: 0x{0:X16}", pPEB);
// use ImageBaseAddress here ??
}
}
catch(Exception ex)
{
Console.WriteLine("Last Win32 Error: {0}", ex.Message);
}
}
I must say, this whole thing seems rather suspect, as you appear to be trying to read the PEB block. But the PEB block can and does change between different versions of Windows. In particular, offset 16 does not appear to line up with anything either in x64 or x86.

Access violation calling CreateProcess in C#

I am trying to write C# code that, running in an elevated process, creates a non-elevated process. The (only) answer to the SO question How to call CreateProcess() with STARTUPINFOEX from C# and re-parent the child contains ready-to-use code. I copied this code, but instead of creating a process, it throws an AccessViolationException. The exception text Attempted to read or write protected memory. This is often an indication that other memory is corrupt. does not help to identify the culprit, however from low-level debugging I can see that the processor is trying to read from memory at a very small address like 0x00000004 which, of course, goes wrong.
The code, briefly explained, retrieves a handle of the desktop process, then calls InitializeProcThreadAttributeList and UpdateProcThreadAttribute to initialize the respective fields of a STARTUPINFOEX struct which is then passed into the CreateProcess Windows API function. CreateProcess should then create the new process as a child of the desktop process.
This function expects in its 9th parameter a pointer to either a STARTUPINFO or a STARTUPINFOEX (which contains a STATUPINFO at its begin). If it's a STARTUPINFOEX, the 6th parameter should contain the EXTENDED_STARTUPINFO_PRESENT flag.
When I don't pass the EXTENDED_STARTUPINFO_PRESENT flag so that CreateProcess think it's being passed just a STARTUPINFO, all works fine except that the created process is elevated (as is the calling process). However, as soon as I add this flag, the access violation occurs. For hours I have tried modifying parameter attributes etc., but the problem persists. I have the essentially same code running in C++, so I know it can work. What am I doing wrong?
The code below doesn't contain elaborated error checking, but it should compile and demonstrate the problem out of the box.
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace RunNonElevatedCSharp
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
StartNonElevated(#"C:\WINDOWS\system32\cmd.exe", "");
}
public static int StartNonElevated(string strAppPath, string strCommandLine)
{
bool bSuccess = false;
IntPtr hShellProcess = IntPtr.Zero;
var pInfo = new PROCESS_INFORMATION();
var sInfoEx = new STARTUPINFOEX();
sInfoEx.StartupInfo.cb = Marshal.SizeOf(sInfoEx);
try
{
IntPtr hShellWnd = GetShellWindow();
if (hShellWnd == IntPtr.Zero)
{
return 0;
}
UInt32 pid;
if (GetWindowThreadProcessId(hShellWnd, out pid) == 0)
{
return 0;
}
hShellProcess = OpenProcess(PROCESS_CREATE_PROCESS, FALSE, pid);
if (hShellProcess == IntPtr.Zero)
{
return 0;
}
IntPtr nBufferSize = IntPtr.Zero;
InitializeProcThreadAttributeList(IntPtr.Zero, 1, 0, ref nBufferSize);
if (nBufferSize == IntPtr.Zero)
{
return 0;
}
sInfoEx.lpAttributeList = Marshal.AllocHGlobal(nBufferSize);
if (sInfoEx.lpAttributeList == IntPtr.Zero)
{
return 0;
}
if (!InitializeProcThreadAttributeList(sInfoEx.lpAttributeList, 1, 0, ref nBufferSize))
{
return 0;
}
if (!UpdateProcThreadAttribute(sInfoEx.lpAttributeList, 0, (IntPtr)PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, hShellProcess, (IntPtr)IntPtr.Size, IntPtr.Zero, IntPtr.Zero))
{
return 0;
}
// s1 and s2 may not be required
string s1 = "" + strAppPath + "";
string s2 = "";
// The next line causes an access violation unless you remove the 'EXTENDED_STARTUPINFO_PRESENT' flag
if (!CreateProcess(s1, s2, IntPtr.Zero, IntPtr.Zero, false, CREATE_NEW_CONSOLE | EXTENDED_STARTUPINFO_PRESENT | 0,
IntPtr.Zero, null, ref sInfoEx, out pInfo))
{
return 0;
}
bSuccess = true;
CloseHandle(pInfo.hThread);
CloseHandle(pInfo.hProcess);
return pInfo.dwProcessId;
}
finally
{
if (!bSuccess)
{
var lastError = Marshal.GetLastWin32Error();
Debug.WriteLine("Error: " + lastError.ToString());
}
if (sInfoEx.lpAttributeList != IntPtr.Zero)
{
DeleteProcThreadAttributeList(sInfoEx.lpAttributeList);
Marshal.FreeHGlobal(sInfoEx.lpAttributeList);
}
if (hShellProcess != IntPtr.Zero)
CloseHandle(hShellProcess);
}
}
[DllImport("User32.dll", SetLastError = true)]
public static extern IntPtr GetShellWindow();
[DllImport("User32.dll", SetLastError = true)]
public static extern UInt32 GetWindowThreadProcessId(IntPtr hWnd, out UInt32 lpdwProcessId);
[DllImport("Kernel32.dll", SetLastError = true)]
public static extern IntPtr OpenProcess(UInt32 dwDesiredAccess, int bInheritHandle, UInt32 dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)][return: MarshalAs(UnmanagedType.Bool)]
public static extern bool InitializeProcThreadAttributeList(IntPtr lpAttributeList, int dwAttributeCount, int dwFlags, ref IntPtr lpSize);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UpdateProcThreadAttribute(IntPtr lpAttributeList, uint dwFlags, IntPtr Attribute, IntPtr lpValue, IntPtr cbSize,
IntPtr lpPreviousValue, IntPtr lpReturnSize);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, EntryPoint = "CreateProcessW", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CreateProcess(string lpApplicationName, string lpCommandLine,
[In, Optional] IntPtr lpProcessAttributes, [In, Optional] IntPtr lpThreadAttributes,
bool bInheritHandles, uint dwCreationFlags,
[In, Optional] IntPtr lpEnvironment, [In, Optional] string lpCurrentDirectory,
[In] ref STARTUPINFOEX lpStartupInfo, [Out] out PROCESS_INFORMATION lpProcessInformation);
[DllImport("Kernel32.dll", SetLastError = true)]
public static extern int CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll")]
private static extern void DeleteProcThreadAttributeList(IntPtr lpAttributeList);
public const UInt32 PROCESS_CREATE_PROCESS = 0x0080;
public const int FALSE = 0;
public const int PROC_THREAD_ATTRIBUTE_PARENT_PROCESS = 0x00020000;
public const uint CREATE_NEW_CONSOLE = 0x00000010;
public const uint EXTENDED_STARTUPINFO_PRESENT = 0x00080000;
}
[StructLayout(LayoutKind.Sequential)]
public struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct STARTUPINFOEX
{
public STARTUPINFO StartupInfo;
public IntPtr lpAttributeList;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct STARTUPINFO
{
public Int32 cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public Int32 dwX;
public Int32 dwY;
public Int32 dwXSize;
public Int32 dwYSize;
public Int32 dwXCountChars;
public Int32 dwYCountChars;
public Int32 dwFillAttribute;
public Int32 dwFlags;
public Int16 wShowWindow;
public Int16 cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
}
When the access violation occurs, the program cannot continue; the "finally" block isn't executed.
Looking forward to any replies...
Hans
Your call to UpdateProcThreadAttribute() is wrong. The 4th parameter needs the address of hShellProcess, not the value. This is even stated as much in this answer to the other question you linked to (the code in the other question has the same bug):
Second, the lpValue parameter of the UpdateProcThreadAttribute function must be a pointer to the attribute value (in your case, parentHandle), not the value itself.
The documentation for PROC_THREAD_ATTRIBUTE_PARENT_PROCESS says:
The lpValue parameter is a pointer to a handle to a process to use instead of the calling process as the parent for the process being created. The process to use must have the PROCESS_CREATE_PROCESS access right.
You said that you copied the other answer's code, but your code does not look like the other answer's code, and certainly does not contain the fix that the other answer had provided.
In your code, change this:
if (!UpdateProcThreadAttribute(..., hShellProcess, ...))
To this:
IntPtr lpValue = IntPtr.Zero;
...
lpValue = Marshal.AllocHGlobal(IntPtr.Size);
Marshal.WriteIntPtr(lpValue, hShellProcess);
if (!UpdateProcThreadAttribute(..., lpValue, ...))
...
Marshal.FreeHGlobal(lpValue);

Set TreeViewItem checkbox state with SendMessage

I have a need of changing checkbox state in TreeView (exactly 'SysTreeView32') item owned by external application - for automation purposes. I already have TreeView handle and TreeViewItem handle. I have also found some examples how I can set checkbox state, but for some reason, it is not working (SendMessage returns 0 or crashes entire application). But to the code. What i tried already is this:
TVITEM struct:
[StructLayout(LayoutKind.Sequential, Pack = 8, CharSet = CharSet.Auto)]
internal struct TVITEM
{
public int mask;
public IntPtr hItem;
public int state;
public int stateMask;
[MarshalAs(UnmanagedType.LPTStr)]
public string pszText;
public int cchTextMax;
public int iImage;
public int iSelectedImage;
public int cChildren;
public IntPtr lParam;
}
pinvoke for SendMessage:
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int SendMessage(IntPtr hWnd, int msg, IntPtr wParam, ref TVITEM lParam);
And my method:
internal static void SetTreeNodeState(int treeViewHandler, int treeViewItemHandler, bool state)
{
TVITEM tvItem = new TVITEM();
tvItem.mask = TVIF_STATE | TVIF_HANDLE;
tvItem.hItem = (IntPtr)treeViewItemHandler;
tvItem.stateMask = TVIS_STATEIMAGEMASK;
tvItem.state = (state ? 2 : 1) << 12;
var result = SendMessage((IntPtr)treeViewHandler, TVM_SETITEMW, IntPtr.Zero, ref tvItem);
}
This is the closest approach (i think, at last i did not crash target application once). Of course i have tried to sniff messages for target tree view using Spy++. What concerns me is that Spy++ shows that LParam for SendMessage is actually "TVITEMEXW" but i can beryl find anything about that struct.
Generally i also tried same think with TVM_GETITEMW, but however i did not crash application, SendMessage always returns zero.
What i'm doing wrong?
When you send this particular message, you are expected to supply the address of a struct. Because the window is owned by a different process, the address you supply is not valid. Windows processes have isolated virtual memory address space. The address you supply is valid in your process, but only in your process.
In order to get around this, and send this message, you would need to allocate the memory in the target process, using VirtualAllocEx. You would also need to use WriteProcessMemory in order to populate the struct. You'd need to take care of any possible issues with struct layout if your process and the target process had different bitness. You'd need to do the same trick with members like pszText which themselves are pointers.
There are many questions here already that cover the subject of cross process message marshalling. I am sure that you will be able to locate them. Likewise there are a great many tutorials to be found on the web that you will find now that you are aware of the issue.
Perhaps a bigger problem is that the other process may not respond the way you expect to being poked from the outside in this way. Don't be at all surprised if you find it very challenging to be able to write your own cross process automation. Rather than doing so, why not use UI Automation?
Okey, thanks to David Heffernan, i figured this out. I Have created overload for SendMessage, that accepts lParam as object by ref:
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, IntPtr lpBuffer, uint nSize, out UIntPtr lpNumberOfBytesWritten);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] buffer, Int32 nSize, out IntPtr lpNumberOfBytesRead);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
private static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, AllocationType flAllocationType, MemoryProtection flProtect);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
private static extern bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress, int dwSize, AllocationType dwFreeType);
private static IntPtr SendMessage<T>(Process process, IntPtr hWnd, int msg, int wParam, ref T lParam)
{
uint size = (uint)Marshal.SizeOf(lParam);
byte[] buffer = new byte[size];
IntPtr processHandle = process.Handle;
IntPtr pPointer = VirtualAllocEx(processHandle, IntPtr.Zero, size, AllocationType.Commit | AllocationType.Reserve, MemoryProtection.ReadWrite);
IntPtr inputPtr = Marshal.AllocHGlobal((int)size);
IntPtr outputPtr = Marshal.AllocHGlobal((int)size);
Marshal.StructureToPtr(lParam, inputPtr, false);
WriteProcessMemory(processHandle, pPointer, inputPtr, size, out UIntPtr nNbBytesWritten);
IntPtr resultPtr = SendMessage(hWnd, msg, wParam, pPointer);
ReadProcessMemory(processHandle, pPointer, buffer, buffer.Length, out IntPtr nNbBytesRead);
Marshal.Copy(buffer, 0, outputPtr, (int)size);
T result = Marshal.PtrToStructure<T>(outputPtr);
lParam = result;
Marshal.FreeHGlobal(inputPtr);
Marshal.FreeHGlobal(outputPtr);
VirtualFreeEx(processHandle, pPointer, 0, AllocationType.Release);
return resultPtr;
}
Usage example
Set checkbox state for given tree view item:
internal static void SetTreeNodeState(IntPtr treeViewHandle, IntPtr treeViewItemHandle, bool state)
{
TVITEM tvItem = new TVITEM
{
mask = TVIF_STATE | TVIF_HANDLE,
hItem = treeViewItemHandle,
stateMask = TVIS_STATEIMAGEMASK,
state = (uint)(state ? 2 : 1) << 12
};
Process process = Process.GetProcessesByName("ProcessName")[0];
IntPtr ptr = SendMessage(process, treeViewHandle, TVM_SETITEMW, 0, ref tvItem);
}
Get checkbox state for given tree view item:
internal static bool GetTreeNodeState( IntPtr treeViewHandle, IntPtr treeViewItemHandle)
{
TVITEM tvItem = new TVITEM
{
mask = TVIF_STATE | TVIF_HANDLE,
hItem = treeViewItemHandle,
stateMask = TVIS_STATEIMAGEMASK,
state = 0
};
Process process = Process.GetProcessesByName("ProcessName")[0];
IntPtr ptr = SendMessage(process, treeViewHandle, TVM_GETITEMW, 0, ref tvItem);
if (ptr != IntPtr.Zero)
{
uint iState = tvItem.state >> 12;
return iState == 2 ? true : false;
}
return false;
}
TVITEM:
[StructLayout(LayoutKind.Sequential)]
internal struct TVITEM
{
public uint mask;
public IntPtr hItem;
public uint state;
public uint stateMask;
public IntPtr pszText;
public int cchTextMax;
public int iImage;
public int iSelectedImage;
public int cChildren;
public IntPtr lParam;
}

GetMenuItemInfo, but i´m getting a 1456 - "Menu item not found"

im trying to know if a menuItem is disabled or enabled, but i´m getting a 1456 - "Menu item not found"
what am I doing wrong
in the first part is the declaration of the win32 libraries.
menuIndex is a parameter int
submenuIndex is another parameter int
[StructLayout(LayoutKind.Sequential)]
struct MENUITEMINFO
{
public uint cbSize;
public uint fMask;
public uint fType;
public uint fState;
public uint wID;
public IntPtr hSubMenu;
public IntPtr hbmpChecked;
public IntPtr hbmpUnchecked;
public IntPtr dwItemData;
public string dwTypeData;
public uint cch;
public IntPtr hbmpItem;
// return the size of the structure
public static uint sizeOf
{
get { return (uint)Marshal.SizeOf(typeof(MENUITEMINFO)); }
}
}
[DllImport("user32.dll")]
private static extern IntPtr GetMenu(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern IntPtr GetSubMenu(IntPtr hMenu, int nPos);
[DllImport("user32.dll")]
private static extern uint GetMenuItemID(IntPtr hMenu, int nPos);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool GetMenuItemInfo(IntPtr hMenu, int uItem, bool fByPosition, ref MENUITEMINFO lpmii);
....
IntPtr menu = GetMenu(handle);
IntPtr subMenu = GetSubMenu(menu, menuIndex);
uint menuItemID = GetMenuItemID(subMenu, submenuIndex);
MENUITEMINFO itemInfo = new MENUITEMINFO();
uint MIIM_STATE = 0x00000001;
itemInfo.cbSize = MENUITEMINFO.sizeOf;
itemInfo.fMask = MIIM_STATE;
if (!GetMenuItemInfo(menu, (int)submenuIndex, false, ref itemInfo))
{
uint erro = GetLastError();
//erro = 1456
throw new Exception("Ocorreu um erro ao obter informações do Menu Centura - Cod: "+Marshal.GetLastWin32Error().ToString() +"\n http://msdn.microsoft.com/en-us/library/windows/desktop/ms681381(v=vs.85).aspx");
}
if (itemInfo.fState == MFS_DISABLED)
throw new Exception("Disabled");
PostMessage(handle, 0x0111, (IntPtr)menuItemID, IntPtr.Zero);
You are passing false for the fByPosition argument, so you need to pass a menu ID (menuItemID), not an index (submenuIndex). You also need to pass a handle to the menu that contains the item (subMenu, not menu).
The documentation says
fByPosition [in]
Type: BOOL
The meaning of uItem. If this parameter is FALSE, uItem is a menu item identifier. Otherwise, it is a menu item position. See Accessing Menu Items Programmatically for more information.
Either of these might work:
GetMenuItemInfo(subMenu, (int)submenuIndex, true, ref itemInfo)
GetMenuItemInfo(subMenu, (int)menuItemID, false, ref itemInfo)

How to debug memory related errors from using PInvokes in Silverlight

After running my silverlight 5.0 application with several PInvokes in it for 5 minutes or so, I get the following error:
Attempted to read or write protected memory
Likely, I'm getting a memory leak somewhere.
The problem is how do I debug this? I don't get a stacktrace at all so I can't pinpoint the offending code exactly.
By commenting out code and rerunning application several times, I think I managed to track down the problem but I can't figure out the problem.
Potentially Offending Code:
private void InitUSBEvents()
{
const string clsName = "SLUsbClass";
const string wndName = "SLUsbWindow";
Win32.WNDCLASSEX wndClassEx = new Win32.WNDCLASSEX();
wndClassEx.cbSize = Marshal.SizeOf(wndClassEx);
wndClassEx.lpszClassName = clsName;
wndClassEx.lpfnWndProc = WndProc;
rClassAtomValue = Win32.RegisterClassEx2(ref wndClassEx);
windowHandle = Win32.CreateWindowEx2(0, rClassAtomValue, wndName, 0, 0, 0, 0, 0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
Win32Usb.RegisterKeyboardUsbEvents(windowHandle);
}
[AllowReversePInvokeCalls]
private IntPtr WndProc(IntPtr hWnd, WM msg, IntPtr wParam, IntPtr lParam)
{
switch (msg)
{
case WM.INPUT:
//Console.WriteLine("Key Event");
break;
default:
return Win32.DefWindowProc(hWnd, msg, wParam, lParam);
}
return IntPtr.Zero;
}
PInvoke Related Declarations:
public class Win32
{
[DllImport("user32.dll", SetLastError = true, EntryPoint = "CreateWindowEx")]
public static extern IntPtr CreateWindowEx(
WindowStylesEx dwExStyle,
string lpClassName,
string lpWindowName,
WindowStyles dwStyle,
int x,
int y,
int nWidth,
int nHeight,
IntPtr hWndParent,
IntPtr hMenu,
IntPtr hInstance,
IntPtr lpParam);
// Create a window, but accept a atom value.
[DllImport("user32.dll", SetLastError = true, EntryPoint = "CreateWindowEx")]
public static extern IntPtr CreateWindowEx2(
WindowStylesEx dwExStyle,
UInt16 lpClassName,
string lpWindowName,
WindowStyles dwStyle,
int x,
int y,
int nWidth,
int nHeight,
IntPtr hWndParent,
IntPtr hMenu,
IntPtr hInstance,
IntPtr lpParam);
[DllImport("kernel32.dll", EntryPoint = "LocalAlloc")]
internal static extern IntPtr LocalAlloc_NoSafeHandle(
LocalMemoryFlags uFlags, IntPtr sizetdwBytes);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern IntPtr LocalFree(IntPtr hMem);
[DllImport("user32.dll", SetLastError = true, EntryPoint = "RegisterClassEx")]
public static extern UInt16 RegisterClassEx2([In] ref WNDCLASSEX lpwcx);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.U2)]
public static extern short RegisterClassEx([In] ref WNDCLASSEX lpwcx);
[DllImport("user32.dll")]
public static extern IntPtr DefWindowProc(IntPtr hWnd, WM uMsg, IntPtr wParam, IntPtr lParam);
}
public class Win32Usb
{
public static bool RegisterKeyboardUsbEvents(IntPtr hWnd)
{
//Create an array of all the raw input devices we want to
//listen to. In this case, only keyboard devices.
//RIDEV_INPUTSINK determines that the window will continue
//to receive messages even when it doesn't have the focus.
RAWINPUTDEVICE[] rid = new RAWINPUTDEVICE[1];
rid[0].usUsagePage = 0x01;
rid[0].usUsage = 0x06;
rid[0].dwFlags = RIDEV_INPUTSINK;
rid[0].hwndTarget = hWnd;
return RegisterRawInputDevices(rid, (uint)rid.Length, (uint)Marshal.SizeOf(rid[0]));
}
[StructLayout(LayoutKind.Explicit)]
internal class RAWINPUT
{
[FieldOffset(0)]
public RAWINPUTHEADER header;
[FieldOffset(16)]
public RAWMOUSE mouse;
[FieldOffset(16)]
public RAWKEYBOARD keyboard;
[FieldOffset(16)]
public RAWHID hid;
}
[StructLayout(LayoutKind.Sequential)]
internal class RAWINPUTDEVICELIST
{
public IntPtr hDevice;
[MarshalAs(UnmanagedType.U4)]
public int dwType;
}
[StructLayout(LayoutKind.Sequential)]
internal struct RAWINPUTDEVICE
{
[MarshalAs(UnmanagedType.U2)]
public ushort usUsagePage;
[MarshalAs(UnmanagedType.U2)]
public ushort usUsage;
[MarshalAs(UnmanagedType.U4)]
public int dwFlags;
public IntPtr hwndTarget;
}
}
I can't really find anything wrong with the PInvoke declarations either. What are some good strategies to debug PInvoke related errors on Silverlight? Or is there a way to force the stacktrace to output? (I've tried turning on all exceptions to be thrown in Debug->Exception)
The WndProc delegate that you are passing in the wndClassEx struct is being garbage collected. When you pass a delegate to unmanaged code that delegate will only be kept alive for the time of the call to the unmanaged function. Once the call ends there is no longer reference to it in your Silverlight application so the garbage collection considers it dead and will collect it.
This example of creating a window class in Silverlight caches the WndProc delegate in a static method so that it does not get garbage collected.
Also see this (older, but still valid) MSDN article on P/Invoke.
#shf301 is correct that you need to take steps to stop your delegate from being collected. I won't attempt to repeat his point here.
However, on top of that issue, your structs are incorrectly defined. They will work under 32 bit, but not under 64 bit. The rule of thumb with FieldOffset is that you only ever use it with an offset of 0. This rule is what allows you to write code that works on both 32 and 64 bit. In your code you used an offset of 16 which is fine for 32 bit, but not 64 bit. Define your structures following this pattern:
[StructLayout(LayoutKind.Explicit)]
struct RAWINPUTUNION
{
[FieldOffset(0)]
public RAWMOUSE mouse;
[FieldOffset(0)]
public RAWKEYBOARD keyboard;
[FieldOffset(0)]
public RAWHID hid;
}
[StructLayout(LayoutKind.Sequential)]
struct RAWINPUT
{
public RAWINPUTHEADER header;
public RAWINPUTUNION data;
}

Categories