Windows API Help - IPC

Associate
Joined
1 Feb 2006
Posts
1,868
Location
Reading
Hoping there are one or two windows developer gurus around that can help me with some IPC.

Currently developing an automation tool to test new desktop OS builds at work. Need to be able to open menu items on toolbarwindow32 windows (main menu of explorer windows). Now this requires retrieving information of all the buttons on the toolbar, obtaining the button ID of desired menu and sending a WM_COMMAND message with the ID. I'm having trouble retrieving button information.

Now im using openprocess, virtualallocex to allocate virtual memory in the target process, and then sending the TB_GETBUTTON message with a pointer to the allocated memory then reading out the result. Allocation and the send message works fine but reading the processmemory back out is failing but not returning an error code, getlastwin32error returns nothing either.

Sorry for vb code, not my choice

Declarations
Code:
<StructLayout(LayoutKind.Sequential)> _
        Public Structure TBBUTTON
            Public iBitmap As Integer
            Public idCommand As Integer
            Public fsState As Byte
            Public fsStyle As Byte
            'Public bReserved1 As Byte
            Public bReserved2() As Byte
            Public dwData As Integer
            Public iString As Integer
        End Structure

 <DllImport("kernel32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
        Private Shared Function VirtualAllocEx(ByVal hProcess As IntPtr, ByVal lpAddress As IntPtr, _
        ByVal dwSize As IntPtr, ByVal flAllocationType As VMemAllocType, ByVal flProtect As MemProtection) As IntPtr
        End Function

        <DllImport("kernel32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
        Private Shared Function VirtualFreeEx(ByVal hProcess As IntPtr, ByVal lpAddress As IntPtr, _
        ByVal dwSize As IntPtr, ByVal dwFreeType As UInteger) As <MarshalAs(UnmanagedType.Bool)> Boolean
        End Function

        <DllImport("kernel32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
        Public Shared Function WriteProcessMemory(ByVal hProcess As IntPtr, ByVal lpBaseAddress As IntPtr, ByVal lpBuffer As IntPtr, ByVal nSize As IntPtr, ByVal lpNumberOfBytesWritten As IntPtr) As Integer
        End Function

        <DllImport("kernel32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
        Public Shared Function ReadProcessMemory(ByVal hProcess As IntPtr, ByVal lpBaseAddress As Integer, ByVal lpBuffer As Integer, _
        ByVal iSize As IntPtr, ByVal lpNumberOfBytesRead As IntPtr) As Boolean
        End Function

        <DllImport("kernel32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
        Public Shared Function ReadProcessMemory(ByVal hProcess As IntPtr, ByVal lpBaseAddress As IntPtr, ByVal lpBuffer As Byte(), _
        ByVal iSize As IntPtr, ByVal lpNumberOfBytesRead As IntPtr) As Boolean
        End Function

        <DllImport("kernel32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
        Public Shared Function OpenProcess(ByVal dwDesiredAccess As ProcessFlags, ByVal bInheritHandle As Boolean, ByVal dwProcessId As UInteger) As IntPtr
        End Function

        <DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
        Private Shared Function GetWindowThreadProcessId(ByVal hwnd As IntPtr, ByRef lpdwProcessId As UInteger) As UInteger
        End Function

Function
Code:
Dim bInfo As TBBUTTON

            Dim localPtr As Integer
            Dim remotePtr As Integer
            Dim bInfoSize As Integer
            Dim res As Integer
            Dim hwndProcess As IntPtr
            Dim pid As UInteger
            Dim bText As String
            Dim tbCount As Integer
            Dim lret As Integer
            ReDim bInfo.bReserved2(1)
            tbCount = SendMessageSync(hwnd, Methods.TB_BUTTONCOUNT, IntPtr.Zero, IntPtr.Zero)
            bInfoSize = Marshal.SizeOf(bInfo)
            If tbCount > 0 Then
                GetWindowThreadProcessId(hwnd, pid)
                hwndProcess = OpenProcess(ProcessFlags.PROC_VM_OPREADWRITE, False, pid)
                remotePtr = VirtualAllocEx(hwndProcess, Nothing, bInfoSize, VMemAllocType.MEM_RESERVECOMMIT, MemProtection.PAGE_READWRITE)
                For tbIndex As Integer = 0 To tbCount - 1         
                    Dim hret As Integer = SendMessageSync(hwnd, Methods.TB_GETBUTTON, tbIndex, remotePtr)
                    gcInfo = GCHandle.Alloc(bInfo, GCHandleType.Pinned)
                    localPtr = gcInfo.AddrOfPinnedObject().ToInt32()
                    gcInfo.Free()

                    [COLOR="Red"]res = ReadProcessMemory(hwndProcess, remotePtr, localPtr, Len(bInfo), IntPtr.Zero)[/COLOR]

                    Dim str As String = Marshal.GetLastWin32Error()

                VirtualFreeEx(hwnd, remotePtr, Marshal.SizeOf(remotePtr), &H8000UI)
                CloseHandle(hwndProcess)
            End If

As mentioned above, the read process memoery call fails, the return value is -1 :confused: The documentation states it returns 0 for fail, non zero for pass.

Wondering if the problem lies in incorrect use of types in pinvoke calls or the struct tbbutton is incorrect, or my use of a pinned pointer is wrong, or whether I need to use adjusttokenpriviledges to ensure I have rights. Though I'm running under admin and examples similar to this don't use it.

Relevant documentation
Virtualallocex
Read Process Memory
TBBUTTON Struct
 
Seems to work for me when you change PROC_VM_OPREADWRITE (0x00000010 | 0x00000020) in OpenProcess to PROCESS_ALL_ACCESS (0x1F0FFF)

Edit, nvm I just realised the OP meant it included VMOperation as well.
As I don't know VB I converted your code to C# and ran it against the system tray's ToolbarWindow32 and receive 1/true as the result of ReadProcessMemory.
http://pastebin.com/m11d11802
 
Last edited:
Ty Vai, with your code I was able to get mine working, not sure exactly what change made the difference though.

Problem is I still can't sort my initial challenge of clicking a system menu item. Sending wm_command message doesn't work, tb_buttonpressed causes the button image to become pressed but does not cause the menu to popup. Any Ideas?

Secondly Im also having trouble retrieving the text of the button. I'm using the following code.
Code:
lret = SendMessageSync(hwnd, Methods.TB_GETBUTTONTEXT, bInfo.idCommand, nothing)   
                    bText = New StringBuilder(lret + 1)
                    res = ReadProcessMemory(hwndProcess, bInfo.iString, bText, lret, numBytesChanged)
The function succeeds but bText contains "&Fi" for file and "&E[nonsense chars]" for edit and so on. It is clearly retrieving the start of the text but copying over some other bytes instead of the rest of text. I tried an alternative using a pointer to a pinned gchandle which wraps a string, and calling TB_GETBUTTONTEXT with it as the last argument, but the same problem occurs where only first few chars are returned. (non nonsense chars this time)

Um any ideas? haha
 
I can get the hover text of each of the system tray icons with:

Code:
int stringSize = (int)SendMessage(hwnd, TB_GETBUTTONTEXT, (IntPtr)bInfo.idCommand, IntPtr.Zero);
stringSize++;

IntPtr remoteTextPtr = VirtualAllocEx(hwndProcess, IntPtr.Zero, (uint)stringSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
IntPtr localTextPtr = Marshal.AllocHGlobal(stringSize);

SendMessage(hwnd, TB_GETBUTTONTEXT, (IntPtr)bInfo.idCommand, remoteTextPtr);
res = ReadProcessMemory(hwndProcess, remoteTextPtr, localTextPtr, stringSize, out numberOfBytesChanged);

Console.WriteLine(Marshal.PtrToStringAnsi(localTextPtr));

VirtualFreeEx(hwndProcess, remoteTextPtr, (uint)0, MEM_RELEASE);
Marshal.FreeHGlobal(localTextPtr);
What does your code look like for the button clicking?
 
Last edited:
Thanks, I'll try that code after dinner.

The button click code I have tried is
Code:
hret = SendMessageAsync(winhwnd, Methods.WM_COMMAND, bInfo.idCommand, hwnd)
where winhwnd is the window handle and hwnd is the toolbar handle.

I have also tried Methods.TB_BUTTONPRESSED which merely makes it look like it has been pressed.

ps. sendmessageasync is merely a wrapper for postmessage
 
It didn't work for the system tray, but running against the taskbar's list of running application, where "ConsoleApplication1" is a windows explorer folder, the following causes the ConsoleApplication1 folder to be bought to the front as if it had been clicked:

Code:
[DllImport("user32.dll")]
public static extern IntPtr GetParent(IntPtr hWnd);
----------
string buttonName = Marshal.PtrToStringAnsi(localTextPtr);
if (buttonName == "ConsoleApplication1")
{
         IntPtr hwndParent = GetParent(hwnd);
         SendMessage(hwndParent, WM_COMMAND, (IntPtr)bInfo.idCommand, hwnd);
}
Where hwnd is the ToolbarWindow32 window handle.
 
Last edited:
Back
Top Bottom