- PR -

タスクバーのタスクボタンの情報を取得したい

投稿者投稿内容
uni
会議室デビュー日: 2005/09/30
投稿数: 11
投稿日時: 2005-10-07 15:47
<つづき>フォームでの処理

コード:

Const LENG As Integer = 256 '取得する文字列の長さ(バイト)

Dim iTabCount As Integer = 0 'ボタンの数
Dim tItem As tagTCITEM
Dim dwSize As Integer = Marshal.SizeOf(tItem) 'tItemのサイズ(バイト)
Dim bytGetItem(dwSize + LENG) As Byte '取得した情報を入れる
Dim bytGetText(LENG) As Byte '取得した文字列情報を入れる
Dim pLocalShared As IntPtr = IntPtr.Zero '自プロセスの開始アドレスへのポインタ
Dim pSysShared As IntPtr = IntPtr.Zero '他プロセスの開始アドレスへのポインタ
Dim hProcess As IntPtr = IntPtr.Zero 'プロセスハンドル
Dim processId As Integer = 0 'プロセスID
Dim dwNumberOfBytes As Integer = 0 '読み書きされたバイト数

'ボタンの数取得
iTabCount = SendMessage(hWnd, TCM_GETITEMCOUNT, 0, 0)
'ボタンが無いとき終了
If iTabCount = 0 Then Return

'自プロセスのメモリ領域確保
pLocalShared = VirtualAlloc(IntPtr.Zero, dwSize + bytGetText.Length, MEM_RESERVE Or MEM_COMMIT, PAGE_READWRITE)

If IntPtr.op_Equality(pLocalShared, IntPtr.Zero) = False Then
'プロセスID取得
GetWindowThreadProcessId(hWnd, processId)
'プロセスハンドル取得
hProcess = OpenProcess(PROCESS_VM_OPERATION Or PROCESS_VM_READ Or PROCESS_VM_WRITE, False, processId)

If IntPtr.op_Equality(hProcess, IntPtr.Zero) = False Then
'他プロセスのメモリ領域確保
pSysShared = VirtualAllocEx(hProcess, IntPtr.Zero, dwSize + LENG, MEM_RESERVE Or MEM_COMMIT, PAGE_READWRITE)

If IntPtr.op_Equality(pSysShared, IntPtr.Zero) = False Then
'構造体初期化
With tItem
.mask = TCIF_TEXT
.pszText = New IntPtr(pSysShared.ToInt32 + dwSize)
.cchTextMax = LENG
End With

Dim ret As Boolean
'プロセスを書き込む
ret = WriteProcessMemory(hProcess, pSysShared, tItem, bytGetItem.Length, dwNumberOfBytes)
'タブの情報取得
ret = SendMessage(hWnd, TCM_GETITEM, 0, pSysShared)
'プロセスを読み込む
ret = ReadProcessMemory(hProcess, pSysShared, pLocalShared, bytGetItem.Length, dwNumberOfBytes)

'文字列を取得
'PtrToAnsi,PtrToAuto,PtrToUniともに文字化け。PtrToBSTRはNullReferenceExceptionになる。
Dim strItem As String = Marshal.PtrToStringAnsi(New IntPtr(pSysShared.ToInt32 + dwSize))

'メモリ解放
VirtualFreeEx(hProcess, pSysShared, 0, MEM_RELEASE)
End If
'メモリ解放
CloseHandle(hProcess)
End If
'メモリ解放
VirtualFree(pLocalShared, 0, MEM_RELEASE)
End If



[ メッセージ編集済み 編集者: uni 編集日時 2005-10-07 15:51 ]

[ メッセージ編集済み 編集者: uni 編集日時 2005-10-07 15:52 ]
uni
会議室デビュー日: 2005/09/30
投稿数: 11
投稿日時: 2005-10-07 16:01
すいません、文字列取得の部分を、
コード:
Dim strGetText As String = Marshal.PtrToStringAnsi(New IntPtr(pLocalShared.ToInt32 + dwSize))


に変更しました。
しかし strGetText="" になってしまいます。。。
見ている場所(アドレス)がおかしいのでしょうか?
そもそもそれ以前の問題でしょうか・・・
Hongliang
ぬし
会議室デビュー日: 2004/12/25
投稿数: 576
投稿日時: 2005-10-07 17:34
ん〜と、いくつか。

  • .NETで(アンマネージドの)メモリを確保する場合、VirtualAllocだのを使うよりMarshal.AllocCoTaskMem/AllocHGlobalを使った方がいいです。解放はMarshal.FreeCoTaskMem/FreeHGlobalを使用します。
  • ReadProcessMemoryを使う際、わざわざ構造体ごと読み込まず文字列部分だけ読み込む方が扱いやすいでしょう。
    コード:

    Dim textProcess As IntPtr = New IntPtr(pSysShared.ToInt64() + dwSize)
    Dim textLocal As IntPtr = Marshal.AllocCoTaskMem(LENG)
    ReadProcessMemory(hProcess, textProcess, textLocal, LENG, dwNumberOfBytes)
    Dim text As String = Marshal.PtrToStringAnsi(textLocal)
    Marshal.FreeCoTaskMem(textLocal)



何故Marshal.PtrToStringAnsiが失敗するか。SendMessageによって書き込まれる文字列先のアドレスは、VirtualAllocExで確保された、別プロセスのメモリ上の位置を指します。Marshal.PtrToStringAnsiは自分のプロセスのメモリ上の位置を捜しますから、当然そこに期待したバイト列は存在しません。必ずReadProcessMemoryで自分のプロセスのメモリ上にコピーする必要があります。uniさんのコードでは、構造体部分しかReadProcessMemoryされておらず、肝心の文字列が入った部分をReadProcessMemoryしていません。
上記コードのように文字列部分だけReadProcessMemoryするのが楽でしょうが、勿論他のフラグなども必要で構造体もまとめて取得したい場合もあるでしょう。
その際は、ReadProcessMemoryで構造体と文字列両方のサイズ合計分を読み取り、Marshal.PtrToStructureとMarshal.PtrToStringAnsi(こちらはポインタを構造体サイズ分進める必要がありますね)を行えばいいわけです。

[ メッセージ編集済み 編集者: Hongliang 編集日時 2005-10-07 17:37 ]
uni
会議室デビュー日: 2005/09/30
投稿数: 11
投稿日時: 2005-10-07 18:34
Hongliangさん、お返事ありがとうございます。

引用:

Hongliangさんの書き込み (2005-10-07 17:34) より:
[list][*].NETで(アンマネージドの)メモリを確保する場合、VirtualAllocだのを使うよりMarshal.AllocCoTaskMem/AllocHGlobalを使った方がいいです。解放はMarshal.FreeCoTaskMem/FreeHGlobalを使用します。



そうなのですか・・・。
実はVirutualAllocも使ったのが初めてで、まだ良く理解できていません・・・。
AllocCoTaskMemについては、勉強します。
(そもそもメモリ確保について勉強しなくてはなりませんが)

提示していただいたコードに書き換えて試したのですが、なぜか取得できません・・・。

また、おっしゃられたように、文字列部分だけを読もうと思って、
コード:
ReadProcessMemory(hProcess, New IntPtr(pSysShared.ToInt32 + dwSize), pLocalShared, LENG, dwNumberOfBytes)
Dim strGetText As String = Marshal.PtrToStringAnsi(pLocalShared)


のようにしてみたのですがだめでした。
もう一度最初からよくよく見直して、また試してみます。
(AllocTaskMemを使うようにするとともに)

せっかくいろいろと提示していただいたので、出来るまでとことんやりたいのですが、
残念ながら自宅に開発環境が無いため(ネットは見れるのですが)、この三連休はやることができません。
火曜日まで脳内でプロミングしながら考えてみようと思います。
またよろしくお願いいたします。
Hongliang
ぬし
会議室デビュー日: 2004/12/25
投稿数: 576
投稿日時: 2005-10-07 23:39
えーと、つい勢いで書いてしまったので載せてしまいます。
他プロセスへのTCM_GETITEM、解説とサンプル
uni
会議室デビュー日: 2005/09/30
投稿数: 11
投稿日時: 2005-10-11 11:35
お世話になっております。uniです。

Hongliangさん、”解説とサンプル”、拝見させていただきました。ありがとうございます。
しかし、相変わらずテキストが取得できません・・・。
Byte配列でReadProcessMemoryを受け取る方法も試してみたのですが、中身は全部空でした。
私の環境では取得できないんじゃ、と思いたくなるのですが、そんなはず無いですよね。
どこか間違ってるのだと思うのですが、どこが違うか分かりません・・・。
(プロセスIDはSpyで確認した限り、合っています。)
毎度長々と申し訳ないのですが、よろしくお願いします。

コード:
    'プロセスID取得
    GetWindowThreadProcessId(hWnd, processId)
    'プロセスハンドル取得
    hProcess = OpenProcess(PROCESS_VM_OPERATION Or PROCESS_VM_READ Or PROCESS_VM_WRITE, _
    False, processId)
    '相手プロセスのメモリ領域確保
    pSysShared = VirtualAllocEx(hProcess, IntPtr.Zero, dwSize + TEXT_MAX, _
    MEM_COMMIT Or MEM_RESERVE, PAGE_READWRITE)
    '構造体初期化
    With tItem
      .mask = TCIF_TEXT
      .pszText = New IntPtr(pSysShared.ToInt64 + dwSize)
      .cchTextMax = TEXT_MAX
    End With
    'プロセスを書き込む
    WriteProcessMemory(hProcess, pSysShared, tItem, dwSize, dwNumberOfBytes)
    'タブの情報を取得(とりあえず0番目のタブ)
    SendMessage(hWnd, TCM_GETITEM, New IntPtr(0), pSysShared)
    '自プロセスに結果をコピーするバッファを作成
    Dim textBuff As IntPtr = Marshal.AllocCoTaskMem(TEXT_MAX)
    'プロセスを読み込む - (1)ポインタで取得
    ReadProcessMemory(hProcess, tItem.pszText, textBuff, TEXT_MAX, dwNumberOfBytes)
    strText = Marshal.PtrToStringAnsi(textBuff)
    '自プロセスのコピー先バッファ解放
    Marshal.FreeCoTaskMem(textBuff)
    '相手プロセスのメモリ領域解放
    VirtualFreeEx(hProcess, pSysShared, dwSize + TEXT_MAX, MEM_RELEASE)
    'プロセスハンドル閉じる
    CloseHandle(hProcess)

Hongliang
ぬし
会議室デビュー日: 2004/12/25
投稿数: 576
投稿日時: 2005-10-11 13:20
取りあえず、この部分のコードには間違いはなさそうです。
同じウィンドウハンドルでTCM_GETITEMCOUNTは成功するんですよね?

まず、どの関数が失敗しているのか返値で確認してください。
そして、GetLastError関数またはMarshal.GetLastWin32Errorメソッド(これを使用する場合、DllImport属性を使ったときは属性のSetLastErrorフィールドをTrueに設定する必要があります。VB.NETのDeclare構文なら自動的にTrueになります)を使用してエラーを特定してください。

また、FormにTabControl(とTabPage)を一つ載せただけのアプリケーションを作ってみて、そちらでも試してみてください。
.NETのTabControlは所詮コモンコントロールに薄いラップをしただけのものですので問題なくTCM_系のメッセージを使用できます。クラス名が特殊ですからFindWindowExするのにSpy++必須ですが。

//しかし私は理論は分かるけどタブバーで実験はできないので(NT4/2000もってないから)的確な助言はできないんですよねー。
uni
会議室デビュー日: 2005/09/30
投稿数: 11
投稿日時: 2005-10-11 18:39
Hongliangさん、ありがとうございます。

TCM_GETITEMCOUNTやTCM_GETCURSELは成功しています。

関数は、VirtualFreeExが失敗していました(正しい第3引数は0でした。すみません)。
しかしそれ以外の関数は失敗していないもようです(戻値はTrueになっています)。

また、タブコントロールにタブページを付けただけの別アプリケーションでは、
正しくテキストが取得できました(複数ページでも成功)。

同じことをしているのになぜ取得できないのでしょう。。。

スキルアップ/キャリアアップ(JOB@IT)