REALLY need help with SendMessage WM_GETCONTROLNAME

S

SQACSharp

I'm trying to get the control name of an editbox in another window.

The following code set the value "MyPassword" in the password EditBox
but it fail to return the control name of the EditBox. I'm sure the
problem is the way i'm using the sendmessage API, the return string and
the lParam return 0....is anybody have a clue? any sendmessage api
expert here?

[DllImport("User32.dll")]
public static extern Int32 FindWindow(String lpClassName,String
lpWindowName);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int SendMessage(int hWnd, int msg, int wParam,
IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr
childAfter, string className, string windowTitle);
[DllImport("user32.dll")]
public static extern UInt32
RegisterWindowMessage([MarshalAs(UnmanagedType.LPTStr)] String
lpString);
// ....

int hwnd = 0;
IntPtr hwndChild = IntPtr.Zero;
hwnd = FindWindow(null, "Login");
hwndChild = FindWindowEx((IntPtr)hwnd, IntPtr.Zero, "TEdit", "");
SetControlText((int)hwndChild, WM_SETTEXT, 0, "MyPassword");

//the following is not working as expected to get the control name
uint SendMsg;
SendMsg = RegisterWindowMessage("WM_GETCONTROLNAME");
long ReturnMsg;
IntPtr ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(IntPtr)));
Marshal.WriteIntPtr(ptr, IntPtr.Zero);
ReturnMsg=SendMessage((int)hwndChild, (int)SendMsg, 65536, ptr);
IntPtr CtrlName = Marshal.ReadIntPtr(ptr);
MessageBox.Show(CtrlName.ToString());
MessageBox.Show(ReturnMsg.ToString());
Marshal.FreeCoTaskMem(ptr);
 
W

Willy Denoyette [MVP]

| I'm trying to get the control name of an editbox in another window.
|
| The following code set the value "MyPassword" in the password EditBox
| but it fail to return the control name of the EditBox. I'm sure the
| problem is the way i'm using the sendmessage API, the return string and
| the lParam return 0....is anybody have a clue? any sendmessage api
| expert here?
|
| [DllImport("User32.dll")]
| public static extern Int32 FindWindow(String lpClassName,String
| lpWindowName);
| [DllImport("user32.dll", CharSet = CharSet.Auto)]
| public static extern int SendMessage(int hWnd, int msg, int wParam,
| IntPtr lParam);
| [DllImport("user32.dll", SetLastError = true)]
| public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr
| childAfter, string className, string windowTitle);
| [DllImport("user32.dll")]
| public static extern UInt32
| RegisterWindowMessage([MarshalAs(UnmanagedType.LPTStr)] String
| lpString);
| // ....
|
| int hwnd = 0;
| IntPtr hwndChild = IntPtr.Zero;
| hwnd = FindWindow(null, "Login");
| hwndChild = FindWindowEx((IntPtr)hwnd, IntPtr.Zero, "TEdit", "");
| SetControlText((int)hwndChild, WM_SETTEXT, 0, "MyPassword");
|
| //the following is not working as expected to get the control name
| uint SendMsg;
| SendMsg = RegisterWindowMessage("WM_GETCONTROLNAME");
| long ReturnMsg;
| IntPtr ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(IntPtr)));
| Marshal.WriteIntPtr(ptr, IntPtr.Zero);
| ReturnMsg=SendMessage((int)hwndChild, (int)SendMsg, 65536, ptr);
| IntPtr CtrlName = Marshal.ReadIntPtr(ptr);
| MessageBox.Show(CtrlName.ToString());
| MessageBox.Show(ReturnMsg.ToString());
| Marshal.FreeCoTaskMem(ptr);
|

You can't use locally allocated heap buffers when the HWND of the window you
send the GETCONTROLNAME to, belongs to a thread in another process. You have
to allocate the buffer in the other process memory space using OpenProcess
followed by VirtualAllocEx, after calling SendMessage you'll have to read
the buffer using ReadProcessMemory followed by a call to VirtualFreeEx.
Note also that you should carefully check the return values of the API's,
and beware that the other process must run in the same winsta/desktop pair,
that is, in the same logon session.

Willy.
 
S

SQACSharp

Your help is really appreciated Willy since i'm trying to do this since
almost a week :(

Ok i try to understand how to use the openProcess, VirtualAllocEx,
ReadProcessMemory and VirtualFreeEx to solve my problem but i'm not
sure exactly how i'm suppose to use the ReadProcessMemory to retreive
the controlname returned by my sendmessage API call..This is what it
look like after spending 3-4 more hours on that :

System.Diagnostics.Process.Start("\"C:\\MyApplication.exe\"", "");
int hwnd = 0;
IntPtr hwndChild = IntPtr.Zero;
hwnd = FindWindow(null, "Login");
hwndChild = FindWindowEx((IntPtr)hwnd, IntPtr.Zero, "TEdit", "");
SetControlText((int)hwndChild, WM_SETTEXT, 0, "MyPassword!");

// The following is my nightmare to get the controlname....still not
working :(
System.Diagnostics.Process[] processes;
processes = System.Diagnostics.Process.GetProcesses();
uint ProcessId;
IntPtr hProcess = IntPtr.Zero;
const uint PROCESS_ALL_ACCESS = (uint)(0x000F0000L | 0x00100000L |
0xFFF);
foreach (System.Diagnostics.Process instance in processes)
{
if (instance.ProcessName == "MyApplication")
{
ProcessId = (uint)instance.Id;
hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0,
(uint)instance.Id);
}
}
if (hProcess == IntPtr.Zero)
throw new ApplicationException("Failed to access process");
const int dwBufferSize = 1024;
const uint MEM_COMMIT = 0x1000;
const uint MEM_RELEASE = 0x8000;
const uint PAGE_READWRITE = 0x04;
IntPtr lpRemoteBuffer = IntPtr.Zero;
IntPtr lpLocalBuffer = IntPtr.Zero;
IntPtr threadId = IntPtr.Zero;
lpLocalBuffer = Marshal.AllocHGlobal(dwBufferSize);
lpRemoteBuffer = VirtualAllocEx(hProcess, IntPtr.Zero, dwBufferSize,
MEM_COMMIT, PAGE_READWRITE);
if (lpRemoteBuffer == IntPtr.Zero)
throw new SystemException("Failed to allocate memory in remote
process");
Int32 SendMsg;
SendMsg = RegisterWindowMessage("WM_GETCONTROLNAME");
long ReturnMsg;
IntPtr ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(IntPtr)));
Marshal.WriteIntPtr(ptr, IntPtr.Zero);
MessageBox.Show(SendMsg.ToString());
ReturnMsg = SendMessage((int)hwndChild, SendMsg, 65536,
lpRemoteBuffer);
bool bSuccess;
bSuccess = ReadProcessMemory(hProcess, lpRemoteBuffer, lpLocalBuffer,
dwBufferSize,IntPtr.Zero);
if (!bSuccess)
throw new SystemException("Failed to read from process memory");
string retval;
retval = Marshal.PtrToStringAnsi(lpRemoteBuffer);
MessageBox.Show(retval); //This is not working....how I'm suppose to
read the string value returned by my sendmessage???

| I'm trying to get the control name of an editbox in another window.
|
| The following code set the value "MyPassword" in the password EditBox
| but it fail to return the control name of the EditBox. I'm sure the
| problem is the way i'm using the sendmessage API, the return string and
| the lParam return 0....is anybody have a clue? any sendmessage api
| expert here?
|
| [DllImport("User32.dll")]
| public static extern Int32 FindWindow(String lpClassName,String
| lpWindowName);
| [DllImport("user32.dll", CharSet = CharSet.Auto)]
| public static extern int SendMessage(int hWnd, int msg, int wParam,
| IntPtr lParam);
| [DllImport("user32.dll", SetLastError = true)]
| public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr
| childAfter, string className, string windowTitle);
| [DllImport("user32.dll")]
| public static extern UInt32
| RegisterWindowMessage([MarshalAs(UnmanagedType.LPTStr)] String
| lpString);
| // ....
|
| int hwnd = 0;
| IntPtr hwndChild = IntPtr.Zero;
| hwnd = FindWindow(null, "Login");
| hwndChild = FindWindowEx((IntPtr)hwnd, IntPtr.Zero, "TEdit", "");
| SetControlText((int)hwndChild, WM_SETTEXT, 0, "MyPassword");
|
| //the following is not working as expected to get the control name
| uint SendMsg;
| SendMsg = RegisterWindowMessage("WM_GETCONTROLNAME");
| long ReturnMsg;
| IntPtr ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(IntPtr)));
| Marshal.WriteIntPtr(ptr, IntPtr.Zero);
| ReturnMsg=SendMessage((int)hwndChild, (int)SendMsg, 65536, ptr);
| IntPtr CtrlName = Marshal.ReadIntPtr(ptr);
| MessageBox.Show(CtrlName.ToString());
| MessageBox.Show(ReturnMsg.ToString());
| Marshal.FreeCoTaskMem(ptr);
|

You can't use locally allocated heap buffers when the HWND of the window you
send the GETCONTROLNAME to, belongs to a thread in another process. You have
to allocate the buffer in the other process memory space using OpenProcess
followed by VirtualAllocEx, after calling SendMessage you'll have to read
the buffer using ReadProcessMemory followed by a call to VirtualFreeEx.
Note also that you should carefully check the return values of the API's,
and beware that the other process must run in the same winsta/desktop pair,
that is, in the same logon session.

Willy.
 
W

Willy Denoyette [MVP]

| Your help is really appreciated Willy since i'm trying to do this since
| almost a week :(
|
| Ok i try to understand how to use the openProcess, VirtualAllocEx,
| ReadProcessMemory and VirtualFreeEx to solve my problem but i'm not
| sure exactly how i'm suppose to use the ReadProcessMemory to retreive
| the controlname returned by my sendmessage API call..This is what it
| look like after spending 3-4 more hours on that :
|
| System.Diagnostics.Process.Start("\"C:\\MyApplication.exe\"", "");
| int hwnd = 0;
| IntPtr hwndChild = IntPtr.Zero;
| hwnd = FindWindow(null, "Login");
| hwndChild = FindWindowEx((IntPtr)hwnd, IntPtr.Zero, "TEdit", "");
| SetControlText((int)hwndChild, WM_SETTEXT, 0, "MyPassword!");
|
| // The following is my nightmare to get the controlname....still not
| working :(
| System.Diagnostics.Process[] processes;
| processes = System.Diagnostics.Process.GetProcesses();
| uint ProcessId;
| IntPtr hProcess = IntPtr.Zero;
| const uint PROCESS_ALL_ACCESS = (uint)(0x000F0000L | 0x00100000L |
| 0xFFF);
| foreach (System.Diagnostics.Process instance in processes)
| {
| if (instance.ProcessName == "MyApplication")
| {
| ProcessId = (uint)instance.Id;
| hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0,
| (uint)instance.Id);
| }
| }
| if (hProcess == IntPtr.Zero)
| throw new ApplicationException("Failed to access process");
| const int dwBufferSize = 1024;
| const uint MEM_COMMIT = 0x1000;
| const uint MEM_RELEASE = 0x8000;
| const uint PAGE_READWRITE = 0x04;
| IntPtr lpRemoteBuffer = IntPtr.Zero;
| IntPtr lpLocalBuffer = IntPtr.Zero;
| IntPtr threadId = IntPtr.Zero;
| lpLocalBuffer = Marshal.AllocHGlobal(dwBufferSize);
| lpRemoteBuffer = VirtualAllocEx(hProcess, IntPtr.Zero, dwBufferSize,
| MEM_COMMIT, PAGE_READWRITE);
| if (lpRemoteBuffer == IntPtr.Zero)
| throw new SystemException("Failed to allocate memory in remote
| process");
| Int32 SendMsg;
| SendMsg = RegisterWindowMessage("WM_GETCONTROLNAME");
| long ReturnMsg;
| IntPtr ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(IntPtr)));
| Marshal.WriteIntPtr(ptr, IntPtr.Zero);
| MessageBox.Show(SendMsg.ToString());
| ReturnMsg = SendMessage((int)hwndChild, SendMsg, 65536,
| lpRemoteBuffer);
| bool bSuccess;
| bSuccess = ReadProcessMemory(hProcess, lpRemoteBuffer, lpLocalBuffer,
| dwBufferSize,IntPtr.Zero);
| if (!bSuccess)
| throw new SystemException("Failed to read from process memory");
| string retval;
| retval = Marshal.PtrToStringAnsi(lpRemoteBuffer);
| MessageBox.Show(retval); //This is not working....how I'm suppose to
| read the string value returned by my sendmessage???
|
|

-OpenProcess should not expect to have PROCESS_ALL_ACCESS access privs. (you
shouldn't even expect to have any privilege at all), specify only the
minimum required which is PROCESS_VM_OPERATION, PROCESS_VM_REA and
PROCESS_VM_WRITE.

const short PROCESS_VM_OPERATION = 0x8;
const short PROCESS_VM_READ = 0x10;
const short PROCESS_VM_WRITE = 0x20;

- VirtualAllocEx must allocate the same buffer size as passed with
SendMessage, that is - 65536, which is the minimum that should be
allocated.
- You don't need a native heap allocated local buffer (lpLocalBuffer ),
point is that you allocate memory in the target process and read from there
using ReadProcessMemory specifying a (local) byte[] as destination.
...
uint bufferSize = 65536;
byte[] bytearray = new byte[buffSize];
...
ReadProcessMemory(...., lpRemoteBuffer, bytearray, bufferSize,...);

- The byte[] can be converted to a string using the appropriate GetString
method of the Encoding class. Note that using VirtualAllocEx requires a
Unicode based Windows OS (NT4 and higher), so you can use something like:

string s = Encoding.Default.GetString(bytearray).TrimEnd('0');
to convert the byte[] to string.

As a genera remark, you should take care passing machine independent IntPtr
arguments in SendMessage. That means tha you should declare the size
argument as an IntPtr and pass it "new IntPtr(65536)" as value. The same is
valid for the handle argument. So your declarartion should look something
like:

... SendMessage(IntPtr handle, uint msg, IntPtr wParam, IntPtr
lParam);

or better use a SafeHandle for your handle parameters...


Same remark for your other API declarations.

And finally, don't forget to call VirtualFreeEx and to check the return
values !!!!

Willy.
 
S

SQACSharp

OK, I modify the code according to all your comments.

Now I have a compiling error that prevent me to try the code with the
modification :

Error 1 - The best overloaded method match for
'TestWinAPI.MainForm.ReadProcessMemory(System.IntPtr, System.IntPtr,
System.IntPtr, int, System.IntPtr)' has some invalid

Error 2 - Argument '3': cannot convert from 'byte[]' to 'System.IntPtr'


Here is line of the error ::

bSuccess = ReadProcessMemory(hProcess, lpRemoteBuffer, ***ERROR***
bytearray, (int)bufferSize, NumRead);

Here is the code that use the bytearray :
//--------------------------
const int dwBufferSize = 65536;
uint bufferSize = 65536;
byte[] bytearray = new byte[bufferSize];

//....blablabla

IntPtr lpRemoteBuffer = IntPtr.Zero;
lpRemoteBuffer = VirtualAllocEx(hProcess, IntPtr.Zero, dwBufferSize,
MEM_COMMIT, PAGE_READWRITE);

//....blablabla

ReturnMsg = SendMessage((IntPtr)hwndChild, SendMsg, SizePtr, NumRead);

bSuccess = ReadProcessMemory(hProcess, lpRemoteBuffer, bytearray,
(int)bufferSize, NumRead);
//--------------------------


Any idea about how to convert from 'byte[]' to 'System.IntPtr ????

Thanks again for your time

Michel
 
W

Willy Denoyette [MVP]

See inline...

Willy.

| OK, I modify the code according to all your comments.
|
| Now I have a compiling error that prevent me to try the code with the
| modification :
|
| Error 1 - The best overloaded method match for
| 'TestWinAPI.MainForm.ReadProcessMemory(System.IntPtr, System.IntPtr,
| System.IntPtr, int, System.IntPtr)' has some invalid
|
| Error 2 - Argument '3': cannot convert from 'byte[]' to 'System.IntPtr'
|
|
You need to declare the argument as a byte[] not an IntPtr, something like
this:

static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr
lpBaseAddress,
[Out] byte [] lpBuffer, UIntPtr nSize, IntPtr
lpNumberOfBytesRead);


| Here is line of the error ::
|
| bSuccess = ReadProcessMemory(hProcess, lpRemoteBuffer, ***ERROR***
| bytearray, (int)bufferSize, NumRead);
|
| Here is the code that use the bytearray :
| //--------------------------
| const int dwBufferSize = 65536;
| uint bufferSize = 65536;
| byte[] bytearray = new byte[bufferSize];
|

No need to define the size a second time.
const int dwBufferSize = 65536;
byte[] bytearray = new byte[dwBufferSize ];


| //....blablabla
|
| IntPtr lpRemoteBuffer = IntPtr.Zero;
| lpRemoteBuffer = VirtualAllocEx(hProcess, IntPtr.Zero, dwBufferSize,
| MEM_COMMIT, PAGE_READWRITE);
|
| //....blablabla
|
| ReturnMsg = SendMessage((IntPtr)hwndChild, SendMsg, SizePtr, NumRead);
|
| bSuccess = ReadProcessMemory(hProcess, lpRemoteBuffer, bytearray,
| (int)bufferSize, NumRead);
| //--------------------------
|
|
| Any idea about how to convert from 'byte[]' to 'System.IntPtr ????
|
| Thanks again for your time
|
| Michel
|
 
S

SQACSharp

Thanks again for your help :) but it still return an empty string :(

Look like there is already a problem when calling :
ReturnMsg = SendMessage((IntPtr)hwndChild, SendMsg, SizePtr, NumRead);

MessageBox.Show("returnmsg=" + ReturnMsg);
MessageBox.Show("NumRead=" + NumRead);

Both value return 0 .....This is not the expected value...
Did you still see something wrong in my code?

Here is the code modified after your last comment :


[DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
public static extern IntPtr GetWindow(IntPtr hWnd, int uCmd);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr
childAfter, string className, string windowTitle);
[DllImport("User32.dll")]
public static extern int FindWindow(String lpClassName,String
lpWindowName);
[DllImport("user32.dll", EntryPoint = "SendMessage")]
public static extern int SetControlText( int hwnd, uint wMsg, int
wParam, string lParam);
[DllImport("user32.dll")]
public static extern UInt32
RegisterWindowMessage([MarshalAs(UnmanagedType.LPTStr)] String
lpString);
[DllImport("kernel32.dll")]
public static extern IntPtr OpenProcess(UInt32 dwDesiredAccess,Int32
bInheritHandle,UInt32 dwProcessId);
[DllImport("kernel32")]
static extern bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress, int
dwSize,uint dwFreeType);
[DllImport("kernel32")]
static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr
lpAddress,int dwSize, uint flAllocationType, uint flProtect);
[DllImport("kernel32")]
static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr
lpBaseAddress,[Out] byte[] lpBuffer, UIntPtr nSize, IntPtr
lpNumberOfBytesRead);
System.Diagnostics.Process.Start("\"C:\\MyApp.exe\"", "");
int hwnd = 0;
IntPtr hwndChild = IntPtr.Zero;
hwnd = FindWindow(null, "Login");
hwndChild = FindWindowEx((IntPtr)hwnd, IntPtr.Zero, "TEdit", "");
if (hwndChild == IntPtr.Zero)
MessageBox.Show("Not found");
SetControlText((int)hwndChild, WM_SETTEXT, 0, "xxx"); //This work to
set the text so
hwndChild is valid and point to the right object
System.Diagnostics.Process[] processes;
processes = System.Diagnostics.Process.GetProcesses();
uint ProcessId;
IntPtr hProcess = IntPtr.Zero;
const short PROCESS_VM_OPERATION = 0x8;
const short PROCESS_VM_READ = 0x10;
const short PROCESS_VM_WRITE = 0x20;
foreach (System.Diagnostics.Process instance in processes)
{
if (instance.ProcessName == "PatientIndex")
{
ProcessId = (uint)instance.Id;
hProcess = OpenProcess(PROCESS_VM_OPERATION |
PROCESS_VM_READ | PROCESS_VM_WRITE, 0, (uint)instance.Id);
}
}
if (hProcess == IntPtr.Zero)
throw new ApplicationException("Failed to access process");
const int dwBufferSize = 65536;
byte[] bytearray = new byte[dwBufferSize];
const uint MEM_COMMIT = 0x1000;
const uint MEM_RELEASE = 0x8000;
const uint PAGE_READWRITE = 0x04;
IntPtr lpRemoteBuffer = IntPtr.Zero;
IntPtr threadId = IntPtr.Zero;
lpRemoteBuffer = VirtualAllocEx(hProcess, IntPtr.Zero, dwBufferSize,
MEM_COMMIT, PAGE_READWRITE);
if (lpRemoteBuffer == IntPtr.Zero)
throw new SystemException("Failed to allocate memory in remote
process");
System.UInt32 SendMsg;
SendMsg = RegisterWindowMessage("WM_GETCONTROLNAME");
long ReturnMsg;
IntPtr NumRead = IntPtr.Zero;
IntPtr SizePtr = (IntPtr)65536;
ReturnMsg = SendMessage((IntPtr)hwndChild, SendMsg, SizePtr, NumRead);

MessageBox.Show("returnmsg=" + ReturnMsg);
MessageBox.Show("NumRead=" + NumRead);
bool bSuccess;
UIntPtr BufferSize = (UIntPtr)dwBufferSize;
bSuccess = ReadProcessMemory(hProcess, lpRemoteBuffer, bytearray,
(UIntPtr)dwBufferSize, NumRead);
if (!bSuccess)
throw new SystemException("Failed to read from process memory");
string CtrlName = Encoding.Default.GetString(bytearray).TrimEnd('0');
MessageBox.Show(CtrlName); //This still return an empty string instead
of returning the name of the control : "PwEdit"
VirtualFreeEx(hProcess, lpRemoteBuffer, 0, MEM_RELEASE);


Thanks again!!
 
W

Willy Denoyette [MVP]

SQACSharp said:
Thanks again for your help :) but it still return an empty string :(

Look like there is already a problem when calling :
ReturnMsg = SendMessage((IntPtr)hwndChild, SendMsg, SizePtr, NumRead);

MessageBox.Show("returnmsg=" + ReturnMsg);
MessageBox.Show("NumRead=" + NumRead);

Both value return 0 .....This is not the expected value...
Did you still see something wrong in my code?

Here is the code modified after your last comment :


[DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
public static extern IntPtr GetWindow(IntPtr hWnd, int uCmd);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr
childAfter, string className, string windowTitle);
[DllImport("User32.dll")]
public static extern int FindWindow(String lpClassName,String
lpWindowName);
[DllImport("user32.dll", EntryPoint = "SendMessage")]
public static extern int SetControlText( int hwnd, uint wMsg, int
wParam, string lParam);
[DllImport("user32.dll")]
public static extern UInt32
RegisterWindowMessage([MarshalAs(UnmanagedType.LPTStr)] String
lpString);
[DllImport("kernel32.dll")]
public static extern IntPtr OpenProcess(UInt32 dwDesiredAccess,Int32
bInheritHandle,UInt32 dwProcessId);
[DllImport("kernel32")]
static extern bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress, int
dwSize,uint dwFreeType);
[DllImport("kernel32")]
static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr
lpAddress,int dwSize, uint flAllocationType, uint flProtect);
[DllImport("kernel32")]
static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr
lpBaseAddress,[Out] byte[] lpBuffer, UIntPtr nSize, IntPtr
lpNumberOfBytesRead);
System.Diagnostics.Process.Start("\"C:\\MyApp.exe\"", "");
int hwnd = 0;
IntPtr hwndChild = IntPtr.Zero;
hwnd = FindWindow(null, "Login");
hwndChild = FindWindowEx((IntPtr)hwnd, IntPtr.Zero, "TEdit", "");
if (hwndChild == IntPtr.Zero)
MessageBox.Show("Not found");
SetControlText((int)hwndChild, WM_SETTEXT, 0, "xxx"); //This work to
set the text so
hwndChild is valid and point to the right object
System.Diagnostics.Process[] processes;
processes = System.Diagnostics.Process.GetProcesses();
uint ProcessId;
IntPtr hProcess = IntPtr.Zero;
const short PROCESS_VM_OPERATION = 0x8;
const short PROCESS_VM_READ = 0x10;
const short PROCESS_VM_WRITE = 0x20;
foreach (System.Diagnostics.Process instance in processes)
{
if (instance.ProcessName == "PatientIndex")
{
ProcessId = (uint)instance.Id;
hProcess = OpenProcess(PROCESS_VM_OPERATION |
PROCESS_VM_READ | PROCESS_VM_WRITE, 0, (uint)instance.Id);
}
}
if (hProcess == IntPtr.Zero)
throw new ApplicationException("Failed to access process");
const int dwBufferSize = 65536;
byte[] bytearray = new byte[dwBufferSize];
const uint MEM_COMMIT = 0x1000;
const uint MEM_RELEASE = 0x8000;
const uint PAGE_READWRITE = 0x04;
IntPtr lpRemoteBuffer = IntPtr.Zero;
IntPtr threadId = IntPtr.Zero;
lpRemoteBuffer = VirtualAllocEx(hProcess, IntPtr.Zero, dwBufferSize,
MEM_COMMIT, PAGE_READWRITE);
if (lpRemoteBuffer == IntPtr.Zero)
throw new SystemException("Failed to allocate memory in remote
process");
System.UInt32 SendMsg;
SendMsg = RegisterWindowMessage("WM_GETCONTROLNAME");
long ReturnMsg;
IntPtr NumRead = IntPtr.Zero;
IntPtr SizePtr = (IntPtr)65536;
ReturnMsg = SendMessage((IntPtr)hwndChild, SendMsg, SizePtr, NumRead);

MessageBox.Show("returnmsg=" + ReturnMsg);
MessageBox.Show("NumRead=" + NumRead);
bool bSuccess;
UIntPtr BufferSize = (UIntPtr)dwBufferSize;
bSuccess = ReadProcessMemory(hProcess, lpRemoteBuffer, bytearray,
(UIntPtr)dwBufferSize, NumRead);
if (!bSuccess)
throw new SystemException("Failed to read from process memory");
string CtrlName = Encoding.Default.GetString(bytearray).TrimEnd('0');
MessageBox.Show(CtrlName); //This still return an empty string instead
of returning the name of the control : "PwEdit"
VirtualFreeEx(hProcess, lpRemoteBuffer, 0, MEM_RELEASE);


Thanks again!!


I'm missing the declaration of SendMessage in the code you posted, but I see that you expect
that SendMessage returns a long:

ReturnMsg = SendMessage(...)

ReturnMsg should be an IntPtr, change your function declaration accordingly. Note that on
success the NumRead should indicate the # of bytes returned.


Willy.
 
S

SQACSharp

I change ReturnMsg to a IntPointer
IntPtr ReturnMsg = IntPtr.Zero;

Here is the missing declaration for SendMessage :
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(IntPtr handle, uint msg, IntPtr
wParam, IntPtr
lParam);

But it still return 0 for and 0 bytes read....



Michel
 
S

SQACSharp

the function RegisterWindowMessage("WM_GETCONTROLNAME");
return 49589..... If I call RegisterWindowMessage("WM_BLABLABLA"); it
also return 49589 ??????

Is this return value is a kind of non found return value????
Are you sure I can use registerWindowMessage to get the int value of
const "WM_GETCONTROLNAME" ???


System.UInt32 SendMsg;
SendMsg = RegisterWindowMessage("WM_GETCONTROLNAME");
MessageBox.Show(SendMsg.ToString());
 
W

Willy Denoyette [MVP]

|I change ReturnMsg to a IntPointer
| IntPtr ReturnMsg = IntPtr.Zero;
|
| Here is the missing declaration for SendMessage :
| [DllImport("user32.dll", CharSet = CharSet.Auto)]
| public static extern IntPtr SendMessage(IntPtr handle, uint msg, IntPtr
| wParam, IntPtr
| lParam);
|
| But it still return 0 for and 0 bytes read....
|
|
|
| Michel
|

This should work, provided the target copied the Name into the shared
buffer, that is the target must handle the WM_GETCONTROLNAME message!!!!
THIS IS TRUE FOR WINDOWS FORMS APPLICATIONS, but in general it's NOT the
case for native Windows applications!.

....
int size = 65356;
IntPtr retSize = SendMessage(hwnd, msg, new IntPtr(size), bufferMem);
if(retSize != IntPtr.Zero) // retSize should hold the number of TCHAR
returned
{
int nSize = (int)retSize * sizeof(char);
byteArray = new byte[nSize]; //allocate array to hold the control
name in UNICODE char's
retVal = ReadProcessMemory(processHandle, bufferMem, byteArray, new
IntPtr(nSize), out written);
if(!retval)
// failed...
else
// success, copy byte array to string..
...
}

Willy.
 
W

Willy Denoyette [MVP]

| the function RegisterWindowMessage("WM_GETCONTROLNAME");
| return 49589..... If I call RegisterWindowMessage("WM_BLABLABLA"); it
| also return 49589 ??????
|
| Is this return value is a kind of non found return value????
| Are you sure I can use registerWindowMessage to get the int value of
| const "WM_GETCONTROLNAME" ???
|
|
| System.UInt32 SendMsg;
| SendMsg = RegisterWindowMessage("WM_GETCONTROLNAME");
| MessageBox.Show(SendMsg.ToString());
|

No, the value returned is the unique "message ID" produced by the system,
it's used to identify the WM_GETCONTROLNAME message. Each application
calling RegisterWindowMessage("WM_GETCONTROLNAME");
will get the same value back.
If you send this ID using SendMessage, the another application, provided she
has called RegisterWindowMessage("WM_GETCONTROLNAME")) and installed a
handler for this message,
knows that she has to return the "Control Name" corresponding to the hwnd
into the shared buffer.



Willy.
 
S

SQACSharp

Thanks again for your help Willy but it still not working at all.

1 - I'm still thinking that the problem is the message.
RegisterWindowMessage("WM_GETCONTROLNAME"); and
RegisterWindowMessage("WM_BLABLABLA"); should not return the same value
: 49589 it make no sense.

2- You said that the target must handle the WM_GETCONTROLNAME
message....you mean the object must have a "name" property? In my case
all my object have a "name" property.

since i'm trying to solve this problem for almost two weeks, i need to
find another solution because WM_GETCONTROLNAME is defenitivly not
working (my code is the same as the last code you post in the thread)


Here is my problem :
I try to create an automated testing tools (like rational robot,
silktest, winrunner, ...etc...)
I can do everything except identifying the object by name or by it's
property.

Question 1 : Is there any other way to get the name property of the
control? By example if I want to get the "hint" , "Left", "top" , "
BorderStyle" , "visible" , ... How i'm suppose to do that?

Question 2 : How the automated testing tool like rational robot can
retrieve the object name without sending API message. In spy++ If I
log the message sended to my application while an automation tool (like
rational robot) is retreiving the properties of a control the only
message sended look like
<00171> 000203A2 R message:0xC1C6 [Registered:"SQA Dispatch Message8"]
lResult:00000000
<00172> 000203A2 S message:0xC1C6 [Registered:"SQA Dispatch Message8"]
wParam:00000000 lParam:0012F91C
.....
<00189> 000203A2 S message:0xBD33 [User-defined:WM_USER+47411]
wParam:2501056D lParam:000203A2
<00190> 000203A2 R message:0xBD33 [User-defined:WM_USER+47411]
lResult:B110103E

Look like there is no WM_GETCONTROLNAME message and they are able to
get the controlname?? how ?

Is there any other way to get the "name" property of a control???
 
W

Willy Denoyette [MVP]

| Thanks again for your help Willy but it still not working at all.
|
| 1 - I'm still thinking that the problem is the message.
| RegisterWindowMessage("WM_GETCONTROLNAME"); and
| RegisterWindowMessage("WM_BLABLABLA"); should not return the same value
| : 49589 it make no sense.
|


You don't seem to understand what RegisterWindowsMessage is all about.
When you call:

int r1 = NativeMethods.RegisterWindowMessage("WM_GETCONTROLNAME");
int r2 = NativeMethods.RegisterWindowMessage("WM_BlaBla");
in the same application you will get two different Message ID's Back from
the system.

The first application that calls RegisterWindowMessage gets an int back that
identifies THIS particular message (say "WM_GETCONTROLNAME"), every
successive application which calls the RegisterWindowMessage specifying
"WM_GETCONTROLNAME", will get the same ID back.
The "message ID" is freed by the system after ALL applications that have
called RegisterWindowMessage("WM_GETCONTROLNAME") have terminated. That
means that an application (started after this) who calls
RegisterWindowMessage("BlaBla") will probably get the same ID previously
assigned to identify WM_GETCONTROLNAME back.

The period between the first call of
RegisterWindowMessage("WM_GETCONTROLNAME");
and the time the last application that has called
RegisterWindowMessage("WM_GETCONTROLNAME"); terminates, is called a session.
I and I said before, messageID are guaranteed to remain constant within a
session only.

According the behavior you notice, I can tell that your application is the
first and only one to call RegisterWindowMessage("WM_GETCONTROLNAME");. This
means that there is no other program waiting for WM_GETCONTROLNAME messages,
forcibly you won't get anything back from the other program when calling
SendMessage(..).
As I said before, Windows Forms applications register this WM_GETCONTROLNAME
message as part of their initialization, other Windows applications have to
register WM_GETCONTROLNAME explicitly and must handle the message
accordingly!
Windows Forms does this to overcome an issue with the naming scheme used to
identify a windows class, class names look like :
"WindowsForms10.EDIT.app.0.33c0d9d" and this name might change for every new
application invocation. So Windows Forms has added a Name attribute to it's
form elements and returns this Name when receiving a SendMessage requesting
WM_GETCONTROLNAME.
So it looks like the other application is not Windows Forms, so you
can't/don't have to use this technique, you just need to enumerate a Windows
tree using the know Windows API's, like FindWindows, EnumWindows,
GetClassName etc...

Willy.
 
S

SQACSharp

damn.... Theses API functions are not well documented and finding a
working c# usage example is almost impossible.

If I want to enumerate all the controls (windows) in a windows, like
SPY ++?

[DllImport("User32.dll")]
public static extern int FindWindow(String lpClassName,String
lpWindowName);

public delegate bool CallBack(IntPtr hwnd, int lParam);

[DllImport("user32"), SuppressUnmanagedCodeSecurityAttribute]
public static extern int EnumWindows(CallBack x, int y);

IntPtr hwnd = FindWindow(null, "Login");
EnumWindows(???)

What is delegate?
What is this callback?
How the hell i'm suppose to call the enumwindows to get the control of
my application?

After looking for examples in the net for guessing how to call the
EnumWindow function, I still have no clue.

Did you have an example or a something to start with to list the
control in my login window ? (This window contain 2 textbox, 2 labels
and 2 buttons)

I also check another way to interact with the object of my
application....why we cannot reference the external for using code like
this :
IntPtr hwnd = FindWindow(null, "Login");
Form MyExternalForm = FromHandle(hwnd);
MessageBox.Show(MyExternalForm.label1.text)
 
S

SQACSharp

OK I finally figure out how to use the enumwindow , enumchildwindow
functions

Dont hesitate if you know how to retrieve properties from an object
that come from an external window by using it's handle...

Ex: name, focus, caption, left, right, blablabla


Thanks again for everything....
 
W

Willy Denoyette [MVP]

| damn.... Theses API functions are not well documented and finding a
| working c# usage example is almost impossible.
|
| If I want to enumerate all the controls (windows) in a windows, like
| SPY ++?
|
| [DllImport("User32.dll")]
| public static extern int FindWindow(String lpClassName,String
| lpWindowName);
|
| public delegate bool CallBack(IntPtr hwnd, int lParam);
|
| [DllImport("user32"), SuppressUnmanagedCodeSecurityAttribute]
| public static extern int EnumWindows(CallBack x, int y);
|



| IntPtr hwnd = FindWindow(null, "Login");
| EnumWindows(???)
|
| What is delegate?
| What is this callback?
| How the hell i'm suppose to call the enumwindows to get the control of
| my application?
|
| After looking for examples in the net for guessing how to call the
| EnumWindow function, I still have no clue.
|

Once again, please pay attention to your API declarations, here is a sample
that illustrates how to enumerate the top level windows.

using System;
using System.Runtime.InteropServices;

// Callback must return a bool, see description of EnumWindowsProc in MSDN
public delegate bool CallBack(IntPtr hwnd, IntPtr lParam);

public sealed class Application {

[DllImport("user32"), SuppressUnmanagedCodeSecurity]
public static extern int EnumWindows(CallBack x, int y);

public static void Main()
{
CallBack myCallBack = new
CallBack(Application.ReportTopLevelWindows);
EnumWindows(myCallBack, 0);
}
// must return true or false, true = continue enumeration, false = stop
enumeration
public static bool ReportTopLevelWindows(IntPtr hwnd, IntPtr lParam) {
// use hwnd here...

return true;
}
}


Note also that all Windows API's are described in the platform sdk docs and
on MSDN. These API's are C style API's, using them from C# requires a
correct interop declaration and some knowledge of their functionality. So,
what you are trying is to use an API set from a language which is not
designed to consume this set, if you are not willing to spend some time with
the interop stuff, I suggest you to write this in C/C++ using the the
managed extentions or C++/CLI.


Willy.
 
S

SQACSharp

Thanks again willy, now i can browse all the object in my window!

The only thing i'm still playing with is to be able to retreive the
properties of an object with it's handle. I try GetProps, GetProp API
is it the correct API function to do this?

Michel
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top