Reputation: 428
I'm currently working on automating a Win32 UI application that cannot be altered. So far my approach is using the standard message queue of the target application to inject my inputs. I've gotten quite far with that:
WM_COMMAND
worksTCM_GETITEMA
and activating them via virtual mouse clicks WM_LBUTTONDOWN
/WM_LBUTTONUP
worksWhere I'm stuck, however, is modifying the text of an editable ComboBox and its Edit control. I try using the WM_SETTEXT
message like so:
public static void SetText(IntPtr hWnd, string text) {
// Maximum item text buffer length
const int MAX_LEN = 512;
// Get process
uint ProcessId;
WinAPI.GetWindowThreadProcessId(hWnd, out ProcessId);
IntPtr process = WinAPI.OpenProcess(
WinAPI.ProcessAccessFlags.VMOperation | WinAPI.ProcessAccessFlags.VMRead |
WinAPI.ProcessAccessFlags.VMWrite | WinAPI.ProcessAccessFlags.QueryInformation,
false, ProcessId
);
if( process == IntPtr.Zero )
throw new Exception("Could not open process");
// Allocate memory in remote process
IntPtr farTextPtr = WinAPI.VirtualAllocEx(process, IntPtr.Zero, MAX_LEN,
WinAPI.AllocationType.Commit,
WinAPI.MemoryProtection.ReadWrite
);
try {
if( farTextPtr == IntPtr.Zero )
throw new Exception("Could not allocate memory in target process");
IntPtr nearTextPtr, pData;
int bytesRead;
// Write item text to remote memory (Unicode!)
nearTextPtr = Marshal.StringToHGlobalUni(text);
WinAPI.WriteProcessMemory(process, farTextPtr, nearTextPtr, MAX_LEN, out bytesRead);
Marshal.FreeHGlobal(nearTextPtr);
// Just for debugging purposes, read it back to verify it was set properly
pData = Marshal.AllocHGlobal(MAX_LEN);
WinAPI.ReadProcessMemory(process, farTextPtr, pData, MAX_LEN, out bytesRead);
text = Marshal.PtrToStringUni(pData);
Marshal.FreeHGlobal(pData);
// Set the text
int res = WinAPI.SendMessage(hWnd, Constants.WM_SETTEXT, IntPtr.Zero, farTextPtr);
if( res != 1 ) throw new Exception("SendMessage WM_SETTEXT failed");
} finally {
// Free remotely allocated memory
if( farTextPtr != IntPtr.Zero )
WinAPI.VirtualFreeEx(process, farTextPtr, 0, WinAPI.FreeType.Release);
WinAPI.CloseHandle(process);
}
}
This does not work! If I attach Spy++ to the target control I can see, that the message is received, but it is received with a different wParam
than I specified in the call to SendMessage
.
E.g. the call to VirtualAllocEx
returned a pointer within the target process of value 0x048b0000
. In the received message within Spy++ I see the value 0x0011AA88
which is wrong:
<000009> 0004065E S WM_SETTEXT lpsz:0011AA88 ("<random characters here>") [wParam:00000000 lParam:0011AA88]
Does the pointer somehow get altered? I do the same procedure in my routine to retrieve strings from controls, like with the TabControl. There it works flawlessly. Is there something different when using WM_SETTEXT
?
Upvotes: 1
Views: 1543
Reputation: 9786
For the combo, you should use the appropriate CB message from this list:
If the combo is owner drawn, it could be tricky to get the item text, but not impossible. CB_GETITEMDATA
will usually return a pointer to some structure you can inspect with ReadProcessMemory()
.
For CB_GETLBTEXT
and WM_SETTEXT
/WM_GETTEXT
, you do not need to use VirtualAllocEx()
and (Read|Write)ProcessMemory()
. You can pass regular local pointers and the OS will marshal them between processes. You only need to use the (Read|Write)ProcessMemory()
APIs to follow pointers more than one hop (as with structures that might be returned from CB_GETITEMDATA
if the combo is owner drawn).
Upvotes: 2
Reputation: 283684
Yes, WM_SETTEXT
is a standard Windows message and the OS will take care of copying the data to the target process. You must pass a pointer which is valid in your own process.
Upvotes: 6