さて今回は毎度おなじみ@ITの会議室より、タスクバーのタスクボタンの情報を取得したいスレッドをネタに話を進めましょう。
私も以前タスクバーから特定アプリのタスクボタンを隠したいとか思って色々調べてみたことがあります。で、まず初めに知ったのがWindows XPからはタスクバーで使用されるコモンコントロールが変更され、もともとTabControlが使われていたのが代わりにToolBarを使われるようになったこと。この変更の波及は大きく、2000までだったらTabControlにTCM_GETITEMをTCIF_PARAMつきで投げてやれば、そのタブが扱うアプリのウィンドウハンドルが手に入ったんですが、XPではToolBarにTB_BUTTONINFOをTBIF_LPARAMつきで投げても良く分からない値しか取得できなくなったんです。おかげでウィンドウタイトルからFindWindowするハメになると言う……。
ま、そんな益体もない話はおいておいて、今回の話に入りましょう。
今まで、SendMessage関数をごく何気なく使っていました。構造体でもref(ByRef)やMarshal.StructureToPtrで普通に参照(アドレス)を渡していました。自分のプロセスのことならメモリアドレスは一意ですから何も問題はなかったんですね。しかし、他のプロセスということになると話は変わってきます。仮想アドレス空間はプロセスごとに用意され、これにプログラムはそれぞれ関数や変数を割り当てて、そのアドレスを使用します。Marshal.AllocCoTaskMemなどで返される値もこの仮想アドレス空間におけるアドレスです。
仮想アドレス空間はプロセスごとと言いました。つまりプロセスが異なれば、同じアドレスを違うものが指していることになるわけです。もちろんそんなの認めるわけにはいきませんから(あっさり暴走しちゃいます)、OSはあるプロセスが扱う仮想アドレス空間には他のプロセスからはアクセスできないようにしています。
ですから、例えば他のプロセスのあるタブコントロールにTCM_GETITEM送信したい場合、単純にTCITEMへの参照を渡すわけにはいきません。こちらのプロセスで作成した構造体の参照(アドレス)は、当然こちらのプロセスの仮想アドレス空間上のアドレスを指すわけで、それをSendMessageで送信してもあちらのプロセスはそのアドレスを処理できません。なんとかあちらのプロセスの仮想アドレス空間を扱う必要があります。
そこで登場するのが、今回のVirtualAllocEx、WriteProcessMemory、ReadProcessMemoryの各Win32API関数です。VirtualAllocExは、指定したプロセスの仮想アドレス空間にメモリ領域を確保する関数。Write/ReadProcessMemoryがその確保した領域とこちらのプロセスの間でデータをコピーする関数です。
大雑把な流れはこうです。
- 対象のプロセスのハンドルを、OpenProcess関数などで取得。
- VirtualAllocExで、対象のプロセスの仮想アドレス空間に構造体のメモリ領域を確保。
- TCM_GETITEMでTCIF_TEXTを使用する場合、その文字列分も確保する必要がある。
- WriteProcessMemoryで、VirtualAllocExで確保した領域に必要な構造体をコピー。
- SendMessageで目的のコントロールに送信。
- 勿論この際構造体アドレスに使用するのはVirtualAllocExで確保したアドレス。
- ReadProcessMemoryで結果をこちらのプロセスのメモリ空間にコピー。
これだけ分かれば後は細々としたプログラミング技術の話になるでしょう。
- TCM_GETITEMでTCIF_TEXTを取得する場合、自プロセスの話であれば、適当にmallocしたアドレスをTCITEMのpszTextに割り当て、それをSendMessageしてやればそのアドレスにタブの文字が入っていると言うことになります。別プロセスと言うことになると、やはり当然ながらpszTextに指定するのもその別プロセスの仮想アドレス空間のアドレスと言うことになりますから、VirtualAllocExが必要になります。二回も同じ関数を呼ぶのは馬鹿馬鹿しいですから、一回でまとめて構造体と文字列バッファを確保し、前半をTCITEM用、後半を文字列バッファ用として扱うのが便利です。このとき、文字列バッファの先頭アドレスは、VirtualAllocExで確保した全体のアドレスからTCITEM構造体のサイズ分進んだところと言うことになります。
- WriteProcessMemoryの第三引数は書き込むデータの先頭のポインタですが、これにByRef As TCITEM構造体を指定してやれば自動的にマーシャリングしてくれるので、TCITEMをそのまま渡すだけでOKになり、構造体ポインタを考える必要が無くなります。
- ReadProcessMemoryで受け取るには、下記のサンプルではIntPtr(=ポインタ)で受け取るようにし、こちら側のメモリ領域をMarshal.AllocCoTaskMemで確保しています。もう一つの手段として、IntPtrの代わりにByte配列を使用する方法があります。この場合、配列要素をアンマネージド関数から変更可能であることを指定するためにOut属性をbufferパラメータに指定する必要があります。
<Out> ByVal buffer As Byte()
後はDim textBuffer As Byte() = New Byte(textSize){}
とバッファを確保してReadProcessMemoryを呼ぶだけです。この場合Byte配列から文字列に変更するのにはEncodingクラス(Encoding.DefaultがANSIコードページのEncodingインスタンスを指します)を普通使用するでしょうが、そのままGetStringするだけでは恐らく後ろに大量のNULL文字が付加されるでしょう(Marshal.PtrToStringAnsiを使用した場合はNULL文字を発見次第変換を終了します)。TrimEndを使用するなどして巧く取り除いてください。 - System.Diagnostics名前空間のProcessクラスを使用すれば、メインウィンドウのウィンドウハンドルやプロセスIDを取得できます。が、巧く使うのは多少難しいかも知れません。またHandleプロパティは十分なアクセス許可がされているのかどうか不明です(PROCESS_VM_OPERATION、PROCESS_VM_READ、PROCESS_VM_WRITEの3つが必要です。一応Process.Handleの解説を読む限りフルアクセスの権限で取得しているっぽいのですが)。
今回は元スレッドに乗っかってVB.NETオンリーの記事です。尤も下調べはC#でしたのですが。
ところでこれ、元スレッドのそのまま答えというかそのままコードなんですが、こういうのいつもポストバックするかどうか悩むんですよねー。そのままコードは余り質問の答えにはしたくないので。とは言えまあこんな記事書いてる時点で結局書いたのを見せたいという願望があるのは事実ですが。
- 追記(2005/10/13)
- フォロー記事を書きましたので、そちらも参照してください。
Option Strict On Imports System Imports System.Diagnostics Imports System.Runtime.InteropServices '他のプロセスのタブコントロールにちょっかいをかけてみます。WinNT系限定です。 Public Class TabControlAccessor '指定プロセスの仮想アドレス空間のメモリを確保する Private Declare Auto Function VirtualAllocEx Lib "Kernel32.dll" ( _ ByVal process As IntPtr, ByVal address As IntPtr, _ ByVal size As Integer, ByVal allocationType As MemoryAllocTypes, _ ByVal protect As MemoryProtectTypes) As IntPtr 'VirtualAllocExで確保したメモリ領域を開放する Private Declare Auto Function VirtualFreeEx Lib "Kernel32.dll" ( _ ByVal process As IntPtr, ByVal address As IntPtr, _ ByVal size As Integer, ByVal freeType As MemoryFreeTypes) As Boolean '指定したプロセスのメモリ領域にデータをコピーする Private Declare Auto Function WriteProcessMemory Lib "Kernel32.dll" ( _ ByVal process As IntPtr, ByVal baseAddress As IntPtr, _ ByRef buffer As TabItem, ByVal size As Integer, _ ByRef writtenSize As Integer) As Boolean '指定したプロセスのメモリ領域のデータを呼び出し側プロセスのバッファにコピーする Private Declare Auto Function ReadProcessMemory Lib "Kernel32.dll" ( _ ByVal process As IntPtr, ByVal baseAddress As IntPtr, _ ByVal buffer As IntPtr, ByVal size As Integer, _ ByRef readSize As Integer) As Boolean '指定したプロセスのハンドルを取得する。 Private Declare Auto Function OpenProcess Lib "Kernel32.dll" ( _ ByVal accress As ProcessAccessTypes, ByVal inheritHandle As Boolean, _ ByVal processId As Integer) As IntPtr 'ハンドルを閉じる。 Private Declare Function CloseHandle Lib "Kernel32.dll" ( _ ByVal handle As IntPtr) As Boolean '指定したウィンドウにメッセージを送信する。 Private Declare Auto Function SendMessage Lib "User32.dll" ( _ ByVal window As IntPtr, ByVal msg As Integer, _ ByVal wParam As IntPtr, ByVal lParam As IntPtr) As Boolean '指定したウィンドウクラス・ウィンドウタイトルを持つトップレベルウィンドウを探す Private Declare Auto Function FindWindow Lib "User32.dll" ( _ ByVal windowClass As String, ByVal windowTitle As String) As IntPtr '特定の親ウィンドウの中の子ウィンドウを探す Private Declare Auto Function FindWindowEx Lib "User32.dll" ( _ ByVal parentWindown As IntPtr, ByVal childWindowAfter As IntPtr, _ ByVal windowClass As String, ByVal windowTitle As String) As IntPtr '指定したウィンドウのスレッドIDとプロセスIDを取得する Private Declare Function GetWindowThreadProcessId Lib "User32.dll" ( _ ByVal window As IntPtr, ByRef processId As Integer) As Integer 'タブコントロールのアイテムの情報。 Private Structure TabItem Public Mask As TabItemMask Public State As Integer Public StateMask As Integer Public Text As IntPtr Public TextMax As Integer Public ImageIndex As Integer Public LParam As IntPtr End Structure 'タブアイテムの取得する情報を表すマスク。 <Flags> Private Enum TabItemMask Text = 1 End Enum 'メモリを確保するタイプ <Flags> Private Enum MemoryAllocTypes Commit = &H1000 Reserve = &H2000 End Enum 'メモリのアクセス保護のタイプ <Flags> Private Enum MemoryProtectTypes ReadWrite = &H4 End Enum '開いたプロセスの許可されるアクセスのタイプ <Flags> Private Enum ProcessAccessTypes Operation = &H8 Read = &H10 Write = &H20 OperationReadWrite = Operation Or Read Or Write End Enum 'メモリの解放タイプ <Flags> Private Enum MemoryFreeTypes Release = &H8000 End Enum 'TCM_GETITEMメッセージ。TabItem.TextにAnsi文字列を代入する。 Private Const TcmGetItemAnsi As Integer = &H1300 + 5 Public Shared Sub Main(ByVal args() As string) If Not(Environment.OSVersion.Platform = PlatformID.Win32NT) Then Throw New PlatformNotSupportedException("NT系限定です。") End If 'この辺はプロセスIDとTabControlのハンドルを取得するためのコードです…… 'Windows 2000まではタスクバーがTabControlだったので絶好の解説対象だったんですが、 'Windows XPではタスクバーはToolBarになったためそうも行かなくなりました。 '下で取得しているのは自前で用意したTabControlを含むFormのプロセスです。 'このままでは動作しません。各自適当にやってください。Spy++とか便利です。 Dim p As Process = Process.GetProcessById(1360) Dim tabControl As IntPtr = FindWindowEx(p.MainWindowHandle, IntPtr.Zero, _ "WindowsForms10.SysTabControl32.app.0.378734a", Nothing) If tabControl.Equals(IntPtr.Zero) Then Console.WriteLine("ウィンドウが見つかりません。") Return End If 'プロセスIDからハンドルを取得 Dim hProcess As IntPtr = OpenProcess( _ ProcessAccessTypes.OperationReadWrite, False, p.Id) p.Dispose() '例えばWindows 2000でタスクバーのTabControlを取得するにはこんな感じでしょうか。 '動作未チェックです。 'Dim taskbar As IntPtr = FindWindow("Shell_TrayWnd", Nothing) 'taskbar = FindWindowEx(taskbar, IntPtr.Zero, "ReBarWindow32", Nothing) 'taskbar = FindWindowEx(taskbar, IntPtr.Zero, "MSTaskSwWClass", Nothing) 'Dim tabControl As IntPtr = FindWindowEx( _ ' taskbar, IntPtr.Zero, "SysTabControl32", Nothing) 'Dim processId As Integer = 0 'GetWindowThreadProcessId(tabControl, processId) 'Dim hProcess As IntPtr = OpenProcess( _ ' ProcessAccessTypes.OperationReadWrite, False, processId) 'ということでなんやかやあってhProcessとtabControlを取得。 '以降何かあっても確実にCloseHandleできるようにTry-Finally Try 'SendMessageに使用するTabItemを作成 Dim item As New TabItem() '一応取得する文字は512バイトを上限とする Const textSize As Integer = 512 '構造体のサイズ。 Dim itemSize As Integer = Marshal.SizeOf(item) '相手プロセスの仮想アドレス空間のメモリを確保。 'TabItem構造体と文字分の合計サイズをまとめて確保。 Dim processMemory As IntPtr = VirtualAllocEx(hProcess, IntPtr.Zero, _ itemSize + textSize, _ MemoryAllocTypes.Commit Or MemoryAlloctypes.Reserve, _ MemoryProtectTypes.ReadWrite) If processMemory.Equals(IntPtr.Zero) Then Console.WriteLine("VirtualAllocExに失敗しました。") Return End If '以降何かあっても確実にVirtualFreeExできるようにTry-Finally Try item.Mask = TabItemMask.Text 'item.TextはSendMessageで書き込まれる文字列の先頭アドレスを指定。 '構造体+文字列をまとめて確保したので、構造体分の直後から文字列に使用する item.Text = New IntPtr(processMemory.ToInt64() + itemSize) item.TextMax = textSize Dim accessedSize As Integer = 0 '書き込むのは構造体分だけでOK。 'WriteProcessMemoryそのものがByRef As TabItemと構造体分しか書き込まない If Not(WriteProcessMemory(hProcess, processMemory, _ item, itemSize, accessedSize)) Then Console.WriteLine("WriteProcessMemoryに失敗しました。") Return End If '確保した仮想アドレス空間の先頭(=TabItem構造体の先頭)を指定 'ちなみに第3引数は取得するタブのインデックス If Not(SendMessage(tabControl, TcmGetItemAnsi, New IntPtr(0), _ processMemory)) Then Console.WriteLine("SendMessageに失敗しました。") Return End If 'こちらのプロセス側に、結果をコピーするバッファを作成 Dim textBuffer As IntPtr = Marshal.AllocCoTaskMem(textSize) '以降何かあっても確実にMarshal.FreeCoTaskMemできるようにTry-Finally Try '仮想アドレスの文字列開始アドレスはitem.Textで指定したまま。 If Not(ReadProcessMemory(hProcess, item.Text, textBuffer, _ textSize, accessedSize)) Then Console.WriteLine("ReadProcessMemoryに失敗しました。") Return End If 'あとはAnsiとしてバイト列を文字列に変換。 Console.WriteLine(Marshal.PtrToStringAnsi(textBuffer)) Finally 'こちらプロセス側の文字列バッファを解放。 Marshal.FreeCoTaskMem(textBuffer) End Try Finally '仮想プロセス空間に確保したメモリを解放。 VirtualFreeEx(hProcess, processMemory, itemSize + textSize, _ MemoryFreeTypes.Release) End Try Finally '使い終わったハンドルは閉じる。 CloseHandle(hProcess) End Try End Sub End Class
<a href="https://tgswfastcashloans.com/">need cash now</a>
same day loans
<a href="https://tgswfastcashloans.com/">unsecured loans</a>
https://vekpaydayadvanceloanssamepayday.com/
<a href="https://resbestpersonalloansquickonline.com/">personal loan online</a>
loans people bad credit
<a href=https://resbestpersonalloansquickonline.com/>online personal loans</a>
cash advance loans <a href="https://quickcashfastcashpaydayloans.online/">best payday loans</a> ’
same day payday loan <a href="https://www.ljeionlinecashadvancefast.com/">quick cash loans</a> ’
online loans <a href="https://www.getispaydayloansonline.com/">quick loan</a> ’
online payday loans <a href="https://loansonlinevaec.org/">payday day loans</a> ’
best payday loans <a href="https://paydayloanxwer.com/">best payday loans</a> ’
unsecured personal loan <a href="https://personalloansxjil.org/">personal loan</a> ’
cash advance loans online <a href="https://cashadvancemuil.com/">fast payday loans</a> ’
payday loan online <a href="https://badcreditbert.com/">bad credit loans</a> ’
<a href="https://cashadvancemuil.com/">payday loans</a>
cash advance credit card
<a href="https://cashadvancemuil.com/">online payday loans</a> ’
online payday advance <a href="https://loansonlinevaec.org/">cash advance loan</a> ’
cash advance american express <a href="https://paydayloanxwer.com/">instant payday loans</a> ’
personal installment loans <a href="https://personalloansxjil.org/">installment loans near me</a> ’
small personal loans online <a href="https://cashadvancemuil.com/">small personal loans online</a> ’
car insurance <a href="https://cheapbcarinsurance.org/">quote car insurance</a> ’
pay day loan <a href="https://paydayloansonlinergc.org/">best payday loans</a> ’
payday day loans <a href="https://onlinepaydayloansrhv.org/">payday loan lenders</a> ’