现在的位置: 首页 > 综合 > 正文

跨进程获取树控件节点信息

2013年10月14日 ⁄ 综合 ⁄ 共 7727字 ⁄ 字号 评论关闭

几天前在 myf1 上遇到人问如何获取其它程序中的树控件节点内容,当时就将要用到的几个 api 告诉了它,然后自己试了一试,成功!今天空闲时无意中又运行了它,结果却发现被探测的程序挂掉了!仔细回忆一下,原来测试时是在同一个 vfp 内运行两个表单,一个做测试对象,里面有一个 TreeView,另一个就是这个探测器;由于同在一个 vfp 进程内,所以没有发生错误。这次不同是,被探测的是 msdn 的窗口,与这个探测器是不同的两个进程,所以可能发生了存储器访问例外,导致被探测程序崩溃。原因找到了,解决方案也就出来了,关键就是当使用 SendMessage 发送获取节点消息时,指向 TVITEM 结构的指针必须指向树控件所在进程可以存取的地址空间内,这个可以先使用 GetWindowThreadProcessId 获取到树控件所在的进程 id,然后用 OpenProcess 打开该进程,之后使用 VirtualAllocEx 函数在它的进程地址空间内申请一块存储块用来存放 TVITEM 结构,最后用 Read/WriteProcessMemory 函数来进行进程间的数据交换 ;只是用 vfp 来实现还有一些麻烦的地方,首先是用 vfp 构造的结构块无法得到其地址,这样便无法直接使用 Read/WriteProcessMemory 这对函数,但是我们可以先使用 GlobalAlloc API 函数申请一块当前进程可访问的存储块并得到其地址指针,将它作为一个中转站,另外 vfp9 提供了 sys(2600) 函数让我们极大的简化了存储器的访问,不必再像以前那样使用 Heap* 序列加上 RtlMoveMemory 来交换数据;之后我们就可以通过 sys(2600) 函数加上这个中转站来与其它进程地址中的数据进行交换了。

下面是实现这个的示例代码,示例中只实现了获取最前面两个节点文本信息的方法,要获取其它节点可以继续使用 TVGN_NEXT 和 TVGN_CHILD 作为 TVM_GETNEXTITEM 消息的参数来遍历所有的节点并得到它们的 hItem,进而获取它们的节点信息。这里也只示例了获取节点文本的方法,其它信息相对来说就更简单了,因为都是同样从 TVITEM 结构中取成员的操作,而且都不涉及到字符串指针的操作。

运行后需要先用最下面的按钮获取到一个已存在的树控件 hWnd(你可以先打开一个带树控件的实例,例如 vfp 的帮助,其目录标签就有一个树控件),然后才能点“查看节点信息”按钮来提取节点文本。

发现一个问题,就是如果根节点没有展开的话,获取子结点文本时会失败,但这已不是代码的问题了,是树控件响应 SendMessage 消息时的错误。我想唯一的方法就是再发送 TVM_EXPAND 消息让它自动展开。

 

PUBLIC oForm
oForm = NEWOBJECT( "MyForm" )
oForm.Show()
READ EVENTS

DEFINE CLASS MyForm AS Form
  Height = 142
  Width = 312
  ShowWindow = 2
  DoCreate = .T.
  AutoCenter = .T.
  Caption = "Form1"
  MaxButton = .F.
  MinButton = .F.
  AllowOutput = .F.
  Name = "Form1"

ADD OBJECT lblClsName AS label WITH ;
  AutoSize = .T., ;
  BackStyle = 0, ;
  Caption = "当前控件类名", ;
  Height = 17, ;
  Left = 20, ;
  Top = 15, ;
  Width = 74, ;
  Name = "lblClsName"

ADD OBJECT txtClsName AS textbox WITH ;
  Height = 23, ;
  Left = 100, ;
  ReadOnly = .T., ;
  Top = 12, ;
  Width = 200, ;
  DisabledBackColor = RGB(255,255,255), ;
  Name = "txtClsName"

ADD OBJECT lblTvHwnd AS label WITH ;
  AutoSize = .T., ;
  BackStyle = 0, ;
  Caption = "树控件 hWnd", ;
  Height = 17, ;
  Left = 20, ;
  Top = 45, ;
  Width = 73, ;
  Name = "lblTvHwnd"

ADD OBJECT txtTvHwnd AS textbox WITH ;
  Alignment = 3, ;
  Height = 23, ;
  Left = 100, ;
  Top = 41, ;
  Width = 108, ;
  Name = "txtTvHwnd"

ADD OBJECT cmdView AS commandbutton WITH ;
  Top = 39, ;
  Left = 212, ;
  Height = 27, ;
  Width = 90, ;
  Caption = "查看节点信息", ;
  Name = "cmdView"

ADD OBJECT chkDetect AS checkbox WITH ;
  Top = 78, ;
  Left = 12, ;
  Height = 48, ;
  Width = 288, ;
  WordWrap = .T., ;
  Alignment = 0, ;
  Caption = (" 按下后将鼠标指针移到要获取 hWnd 的树控件上" ;
    + CHR(13) + " 获取成功后 hWnd 会显示在上面的文本框中"), ;
  Value = .F., ;
  Style = 1, ;
  Name = "chkDetect"

ADD OBJECT timer1 AS timer WITH ;
  Top = 104, ;
  Left = 0, ;
  Height = 23, ;
  Width = 23, ;
  Name = "Timer1"
 
PROCEDURE Init
  DECLARE Long
GetLastError IN WIN32API
  DECLARE Long GetCursorPos IN WIN32API String @ cPoint
  DECLARE Long WindowFromPoint IN WIN32API Long x, Long y
  DECLARE Long GetClassName IN WIN32API ;
    Long hWnd, String @ lpClassName, Long nMaxCount

  DECLARE Long GetWindowThreadProcessId IN WIN32API ;
    Long hWnd, Long @ lpdwProcessId
  DECLARE Long OpenProcess IN WIN32API ;
    Long dwDesiredAccess, Long bInheritHandle, Long dwProcessId
  DECLARE Long CloseHandle IN WIN32API Long hObject

  DECLARE Long VirtualAllocEx IN WIN32API ;
    Long hProcess, Long @ lpAddress, Long dwSize, ;
    Long flAllocationType, Long flProtect
  DECLARE Long VirtualFreeEx IN WIN32API ;
    Long hProcess, Long lpAddress, Long dwSize, Long dwFreeType
  DECLARE Long WriteProcessMemory IN WIN32API ;
    Long hProcess, Long lpBaseAddress, Long lpBuffer, ;
    Long nSize, Long @ lpNumberOfBytesWritten
  DECLARE Long ReadProcessMemory IN WIN32API ;
    Long hProcess, Long lpBaseAddress, Long lpBuffer, ;
    Long nSize, Long @ lpNumberOfBytesWritten
  DECLARE Long GlobalAlloc IN WIN32API Long uFlags, Long dwBytes
  DECLARE Long GlobalFree IN WIN32API Long hMem

  DECLARE Long SendMessage IN WIN32API AS sendmsg_nn ;
    Long, Long, Long, Long
  DECLARE Long
SendMessage IN WIN32API AS sendmsg_nc ;
    Long, Long, Long, String @
ENDPROC

PROCEDURE Unload
  CLEAR EVENTS
ENDPROC

PROCEDURE cmdView.Click
  Thisform
.GetNodeInfo()
ENDPROC

PROCEDURE Timer1.Timer
  LOCAL hWnd
, cPoint, x, y, iLen, cBuff

  m.cPoint = REPLICATE( CHR(0), 8 )
  GetCursorPos( @ m.cPoint )
  m.x = CTOBIN( SUBSTR( m.cPoint, 1, 4 ), "rs" )
  m.y = CTOBIN( SUBSTR( m.cPoint, 5, 4 ), "rs" )
  m.hWnd = WindowFromPoint( m.x, m.y )

  m.iLen = 260
  m.cBuff = REPLICATE( CHR(0), m.iLen )
  m.iLen = GetClassName( m.hWnd, @ m.cBuff, m.iLen )
  m.cBuff = LEFT( m.cBuff, m.iLen )

  Thisform.txtClsName.Value = m.cBuff
  IF ( "TreeView" $ m.cBuff )
    STORE TRANSFORM( m.hWnd ) TO _ClipText, Thisform.txtTvHwnd.Value
  ENDIF
ENDPROC

PROCEDURE chkDetect.ProgrammaticChange
  Thisform
.Timer1.Interval = IIF( This.Value, 100, 0 )
ENDPROC

PROCEDURE chkDetect.InteractiveChange
  This
.ProgrammaticChange()
ENDPROC

PROCEDURE GetNodeInfo
  #define TVGN_ROOT     0x0000
  #define TVGN_NEXT     0x0001
  #define TVGN_PREVIOUS 0x0002
  #define TVGN_PARENT   0x0003
  #define TVGN_CHILD    0x0004

  #define TV_FIRST                  0x1100
  #define TVM_GETNEXTITEM           TV_FIRST + 10

  #define PROCESS_VM_OPERATION      0x0008
  #define PROCESS_VM_READ           0x0010
  #define PROCESS_VM_WRITE          0x0020
  #define PROCESS_QUERY_INFORMATION 0x0400

  LOCAL hWnd, iPid, hProc, cText, cMsg, hItem

  This.chkDetect.Value = .F.          && 关闭取树控件句柄的计时器

  m.hWnd = INT( VAL( Thisform.txtTvHwnd.Value )) && 树控件句柄
  m.iPid = 0
  GetWindowThreadProcessId( m.hWnd, @ m.iPid )   && 取树控件所在进程 id
  IF ( 0 == m.iPid )
    MESSAGEBOX( "获取进程 id 失败" )
    RETURN .F.
  ENDIF

  m.hProc = OpenProcess( ;                       && 打开该进程
    BITOR( PROCESS_VM_OPERATION, PROCESS_VM_READ, ;
           PROCESS_VM_WRITE, PROCESS_QUERY_INFORMATION ), ;
    0, m.iPid )
  IF ( 0 == m.hProc )
    MESSAGEBOX( "打开进程失败" )
    RETURN .F.
  ENDIF

  *!* 先找到根节点的 hItem
  m.hItem = sendmsg_nn( m.hWnd, TVM_GETNEXTITEM, TVGN_ROOT, 0 )
  *!* 取它的文字
  m.cText = This.GetNodeText( m.hWnd, m.hProc, m.hItem )
  IF !ISNULL( m.cText )
    m.cMsg = "根节点文字: " + m.cText
    *!* 再找根节点的第一个子结点
    m.hItem = sendmsg_nn( m.hWnd, TVM_GETNEXTITEM, TVGN_CHILD, m.hItem )
    *!* 取它的文字
    m.cText = This.GetNodeText( m.hWnd, m.hProc, m.hItem )
    IF !ISNULL( m.cText )
      m.cMsg = m.cMsg + 0h0d0a + "第一个子结点文字: " + m.cText
    ENDIF
    MESSAGEBOX
( m.cMsg )
  ENDIF

  CloseHandle( m.hProc ) && 关闭打开的进程
ENDPROC
 
PROCEDURE
GetNodeText
LPARAMETERS thWnd, thProc, thItem

  #define TV_FIRST           0x1100
  #define TVM_GETITEM        TV_FIRST + 12

  #define TVIF_TEXT          0x0001
  #define TVIF_IMAGE         0x0002
  #define TVIF_PARAM         0x0004
  #define TVIF_STATE         0x0008
  #define TVIF_HANDLE        0x0010
  #define TVIF_SELECTEDIMAGE 0x0020
  #define TVIF_CHILDREN      0x0040

  #define GMEM_FIXED         0x0000

  #define PAGE_READWRITE     0x04
  #define MEM_COMMIT         0x1000
  #define MEM_RELEASE        0x8000

  LOCAL cStru, pText, pStru, pTransit, iSize, cText

*!* typedef struct _TVITEM { // 节点信息结构
*!*
  UINT mask;
*!*
  HTREEITEM hItem;
*!*
  UINT state;
*!*
  UINT stateMask;
*!*
  LPTSTR pszText;
*!*
  int cchTextMax;
*!*
  int iImage;
*!*
  int iSelectedImage;
*!*
  int cChildren;
*!*
  LPARAM lParam;
*!*
  int iIntegral;
*!* #if (_WIN32_IE >= 0x0600)
*!*
  UINT uStateEx;
*!*
  HWND hwnd;
*!*
  int iExpandedImage;
*!* #endif
*!* } TVITEM

*!* -------------------------------------------------------------------------------
*!* 由于进程只能存取属于自己的地址空间, 我们需要将结构复制到树控件所在的进程空间内,
*!* 又因为我们无法得到 cStru 的地址, 所以要先申请一块用于中转的存储块并复制过去
*!* 我们不得不绕一个大弯来完成这项工作!!!
*!* -------------------------------------------------------------------------------
*!* 下面注释中, A 代表 vfp 程序所在进程, B 代表树控件所在进程
*!* -------------------------------------------------------------------------------

  *!* 先在 B 中分配一块存储器用于存放结构和节点文字信息(属于 B)
  m.pStru = VirtualAllocEx( m.thProc, 0, 0x300, MEM_COMMIT, PAGE_READWRITE )
  IF ( 0 == m.pStru )
    MESSAGEBOX( "分配存储器失败 !" )
    RETURN ""
  ENDIF

  m.pText = m.pStru + 0x100
  *!* 我们只获取节点的文字信息
  m.cStru = BINTOC( TVIF_TEXT, "rs" ) ;            && 构造节点信息结构
    + BINTOC( m.thItem, "rs" ) + REPLICATE( CHR(0), 2*4 ) ;
    + BINTOC( m.pText, "rs" ) + BINTOC( 0x200, "rs" ) ;
    + REPLICATE( CHR(0), 8*4 )
  m.iSize = LEN( m.cStru )

  *!* 申请一块用于中转的存储块(属于 A), 先申请大一点, 以后还要用来存储节点文字
  m.pTransit = GlobalAlloc( GMEM_FIXED, 0x200 )
  SYS( 2600, m.pTransit, m.iSize, m.cStru )        && 将 cStru 复制过去

  m.cText = ""
  *!* 将中转站中的结构复制到 B 中刚分配的存储块内
  IF ( 0 == WriteProcessMemory( m.thProc, m.pStru, m.pTransit, m.iSize, 0 ))
    MESSAGEBOX( "复制结构到树控件所在进程失败 !" )
  ELSE
   
*!* 发消息给 B 中树控件获取节点信息
    IF ( 0 == sendmsg_nn( m.thWnd, TVM_GETITEM, 0, m.pStru ))
      MESSAGEBOX( "取节点信息失败 !" )
    ELSE
     
*!* 将获取的信息从 B 中复制回我们的中转存储器
      ReadProcessMemory( m.thProc, m.pText, m.pTransit, 0x200, 0 )
      m.cText = SYS( 2600, m.pTransit, 0x200 )
      m.cText = LEFT( m.cText, AT( CHR(0), m.cText )-1 )
    ENDIF
  ENDIF

  *!* 释放分配的存储块
  GlobalFree( m.pTransit )
  VirtualFreeEx( m.thProc, m.pStru, 0, MEM_RELEASE )
  RETURN m.cText
ENDPROC

ENDDEFINE

 

抱歉!评论已关闭.