Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

[AHK_L] ControlGetTabs()


  • Please log in to reply
3 replies to this topic
Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
ControlGetTabs(Control, WinTitle="", WinText="")

Retrieves the text of tabs in a tab control.

Parameters:
Control - the HWND, ClassNN or text of the control.
WinTitle - same as ControlGet, but unused if Control is a HWND.
WinText - as above.

Returns:
An array of strings on success.
An empty string on failure.

Requirements:
AutoHotkey v1.1.
A compatible tab control.


ControlGetTabs(Control, WinTitle="", WinText="")
{
    static TCM_GETITEMCOUNT := 0x1304
         , TCM_GETITEM := A_IsUnicode ? 0x133C : 0x1305
         , TCIF_TEXT := 1
         , MAX_TEXT_LENGTH := 260
         , MAX_TEXT_SIZE := MAX_TEXT_LENGTH * (A_IsUnicode ? 2 : 1)

    static PROCESS_VM_OPERATION := 0x8
         , PROCESS_VM_READ := 0x10
         , PROCESS_VM_WRITE := 0x20
         , READ_WRITE_ACCESS := PROCESS_VM_READ |PROCESS_VM_WRITE |PROCESS_VM_OPERATION
         , PROCESS_QUERY_INFORMATION := 0x400
         , MEM_COMMIT := 0x1000
         , MEM_RELEASE := 0x8000
         , PAGE_READWRITE := 4

    if Control is not integer
    {
        ControlGet Control, Hwnd,, %Control%, %WinTitle%, %WinText%
        if ErrorLevel
            return
    }
    
    WinGet pid, PID, ahk_id %Control%

    ; Open the process for read/write and query info.
    hproc := DllCall("OpenProcess", "uint", READ_WRITE_ACCESS |PROCESS_QUERY_INFORMATION
                   , "int", false, "uint", pid, "ptr")
    if !hproc
        return
    
    ; Should we use the 32-bit struct or the 64-bit struct?
    if A_Is64bitOS
        try DllCall("IsWow64Process", "ptr", hproc, "int*", is32bit := true)
    else
        is32bit := true
    RPtrSize := is32bit ? 4 : 8
    TCITEM_SIZE := 16 + RPtrSize*3
    
    ; Allocate a buffer in the (presumably) remote process.
    remote_item := DllCall("VirtualAllocEx", "ptr", hproc, "ptr", 0
                         , "uptr", TCITEM_SIZE + MAX_TEXT_SIZE
                         , "uint", MEM_COMMIT, "uint", PAGE_READWRITE, "ptr")
    remote_text := remote_item + TCITEM_SIZE
    
    ; Prepare the TCITEM structure locally.
    VarSetCapacity(local_item, TCITEM_SIZE, 0)
    NumPut(TCIF_TEXT,       local_item, 0, "uint")
    NumPut(remote_text,     local_item, 8 + RPtrSize)
    NumPut(MAX_TEXT_LENGTH, local_item, 8 + RPtrSize*2, "int")
    
    ; Prepare the local text buffer.
    VarSetCapacity(local_text, MAX_TEXT_SIZE)
    
    ; Write the local structure into the remote buffer.
    DllCall("WriteProcessMemory", "ptr", hproc, "ptr", remote_item
          , "ptr", &local_item, "uptr", TCITEM_SIZE, "ptr", 0)
    
    tabs := []
    
    SendMessage TCM_GETITEMCOUNT,,,, ahk_id %Control%
    Loop % (ErrorLevel != "FAIL") ? ErrorLevel : 0
    {
        ; Retrieve the item text.
        SendMessage TCM_GETITEM, A_Index-1, remote_item,, ahk_id %Control%
        if (ErrorLevel = 1) ; Success
            DllCall("ReadProcessMemory", "ptr", hproc, "ptr", remote_text
                  , "ptr", &local_text, "uptr", MAX_TEXT_SIZE, "ptr", 0)
        else
            local_text := ""
        
        ; Store the value even on failure:
        tabs[A_Index] := local_text
    }
    
    ; Release the remote memory and handle.
    DllCall("VirtualFreeEx", "ptr", hproc, "ptr", remote_item
          , "uptr", 0, "uint", MEM_RELEASE)
    DllCall("CloseHandle", "ptr", hproc)
    
    return tabs
}
Usage example (SciTE required):
for i, tab in ControlGetTabs("SciTeTabCtrl1", "ahk_class SciTEWindow")
{
    MsgBox % tab
}
Any control with the class SysTabControl32 should also work.

Edited by Lexikos, 06 September 2015 - 01:25 AM.
Fixed x86<->x64 cross-compatibility


just me
  • Members
  • 1496 posts
  • Last active: Nov 03 2015 04:32 PM
  • Joined: 28 May 2011
Well done! I took your code and tried to make a class for remote memory, still a prototype. What do you think about it? Are there any risks when using it?

TCM_GETITEMCOUNT := 0x1304
TCM_GETITEM := A_IsUnicode ? 0x133C : 0x1305
TCIF_TEXT := 1
TCITEM_SIZE := 16 + A_PtrSize * 3
MAX_TEXT_LENGTH := 260
MAX_TEXT_SIZE := MAX_TEXT_LENGTH * (A_IsUnicode ? 2 : 1)
REMOTE_SIZE := TCITEM_SIZE + MAX_TEXT_SIZE

ControlClass := "TTntPageControl.UnicodeClass1"   ; PSPad
WindowClass := "ahk_class TfPSPad.UnicodeClass"   ; PSPad
ControlGet Control, Hwnd,, %ControlClass%, %WindowClass%
If (ErrorLevel)
   Return
WinGet Pid, PID, ahk_id %Control%

Remote := New RemoteMem(REMOTE_SIZE, Pid)
If !IsObject(Remote)
   MsgBox, ERROR!
RemoteText := Remote.GetPtr() + TCITEM_SIZE
VarSetCapacity(LocalVar, REMOTE_SIZE, 0)
NumPut(TCIF_TEXT,       LocalVar, 0, "UInt")
NumPut(RemoteText,      LocalVar, 8 + A_PtrSize)
NumPut(MAX_TEXT_LENGTH, LocalVar, 8 + A_PtrSize * 2, "Int")
Remote.PutLocal(LocalVar)

Tabs := []
SendMessage TCM_GETITEMCOUNT,,,, ahk_id %Control%
Loop % ((ErrorLevel != "FAIL") ? ErrorLevel : 0) {
   ; Retrieve the item text.
   SendMessage TCM_GETITEM, A_Index-1, Remote.GetPtr(),, ahk_id %Control%
   If (ErrorLevel = 1) ; Success
      Remote.GetLocal(LocalText, TCITEM_SIZE, MAX_TEXT_SIZE)
   Else
      LocalText := ""
   ; Store the value even on failure:
   Tabs[A_Index] := LocalText
}
Remote := ""
For I, V In Tabs
   MsgBox, Tab%I% = %V%
ExitApp
; ======================================================================================================================
Class RemoteMem {
   Class _Base_ {
      ; ================================================================================================================
      __Get() {
         Return False
      }
      ; ================================================================================================================
      __Set() {
         Return False
      }
      ; ================================================================================================================
      __New() {
         Return False
      }
      ; ================================================================================================================
      __Delete() {
         ; Release the remote memory and handle.
         If (This._Proc && This._Ptr)
            DllCall("VirtualFreeEx", "Ptr", This._Proc, "Ptr", This._Ptr, "UPtr", 0, "UInt", MEM_RELEASE)
         If (This._Proc)
            DllCall("CloseHandle", "Ptr", This._Proc)
      }
      ; ================================================================================================================
      PutLocal(ByRef LocalVar) {
         Size := VarSetCapacity(LocalVar)
         If (This._Size < Size)
            Return False
         If DllCall("WriteProcessMemory", "Ptr", This._Proc, "Ptr", This._Ptr, "Ptr", &LocalVar, "UPtr", This._Size
                  , "Ptr", 0)
            Return True
         Return False
      }
      ; ================================================================================================================
      GetLocal(ByRef LocalVar, Offset = 0, Size = "") {
         If (Offset < 0) || (Offset >= This._Size)
            Return False
         If (Size = "") 
            Size := This._Size - OffSet
         If (Size + Offset) > This._Size
            Return False
         VarSetCapacity(LocalVar, Size)
         If DllCall("ReadProcessMemory", "Ptr", This._Proc, "Ptr", This._Ptr + OffSet, "Ptr", &LocalVar, "UPtr", Size
                  , "Ptr", 0)
            Return True
         Return False
      }
      ; ================================================================================================================
      GetPtr() {
         Return This._Ptr
      }
      ; ================================================================================================================
      InitRemote() {
         If !(This._Proc) || !(This._Ptr)
            Return False
         VarSetCapacity(Init, This._Size, 0)
         Return This.PutLocal(Init)
      }
   }
   ; ===================================================================================================================
   __New(Size, Pid) {
      Static PROCESS_VM_OPERATION := 0x8
           , PROCESS_VM_READ := 0x10
           , PROCESS_VM_WRITE := 0x20
           , READ_WRITE_ACCESS := PROCESS_VM_READ |PROCESS_VM_WRITE |PROCESS_VM_OPERATION
           , MEM_COMMIT := 0x1000
           , PAGE_READWRITE := 4
      Process, Exist, %Pid%
      If !(ErrorLevel)
         Return False
      ; Open the process for allocating/reading/writing memory.
      HProc := DllCall("OpenProcess", "UInt", READ_WRITE_ACCESS, "Int", False, "UInt", Pid, "Ptr")
      If (HProc = 0)
         Return False
      ; Allocate a buffer in the (presumably) remote process.
      RemoteAddr := DllCall("VirtualAllocEx", "Ptr", HProc, "Ptr", 0, "UPtr", Size, "UInt", MEM_COMMIT
                          , "UInt", PAGE_READWRITE, "Ptr")
      If (RemoteAddr = 0) {
         DllCall("CloseHandle", "Ptr", HProc)
         Return False
      }
      This._Proc := HProc
      This._Ptr := RemoteAddr
      This._Size := Size
      This.Base := This._Base_
   } 
}

And IMO
ControlGet Control, Hwnd,, %Control%, %WinTitle%, %WinText%
        if (ErrorLevel = "FAIL")
            return
should be
if (ErrorLevel = [color=red]1[/color])


Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006

I took your code and tried to make a class for remote memory,

Good, you've saved me the effort. I was going to write a class resembling RemoteBuf, but lost interest fairly quickly.

Are there any risks when using it?

Yes - if you attempt to use it on a 64-bit process from a 32-bit process, you risk crashing either process. IsWow64Process can be used to guard against this - if it outputs "yes" for the current process (meaning it's 32-bit and is running on a 64-bit system) and "no" for the target process (which when combined with the previous point means it is 64-bit), abort.

And IMO (ErrorLevel = "FAIL") should be (ErrorLevel = 1)

Right. That's a copy-paste artifact.

ViATc
  • Members
  • 2 posts
  • Last active: Mar 21 2013 09:24 AM
  • Joined: 14 Oct 2011
good job !

I really like this function .