TBBUTTON 結構不適用於 SendMessage
我正在嘗試發送**TB_GETBUTTON**消息以獲取有關此工具欄控制項內以紅色標記的按鈕的資訊:
(系統托盤通知區域)
問題是當我發送消息時,資源管理器會自行刷新,這很煩人,因為所有桌面都會刷新,而且我使用的**TBBUTTON結構定義沒有得到正確的值,我測試了三個不同的定義,來自pinvoke.net的工會,以及@David Heffernan在此**發布的工會。
我在 64 位 Windows 10 中執行下面的程式碼,並在我的項目屬性中設置了 x64 配置。
如何修復結構和煩人的系統刷新?
這些是我正在使用的相關定義:
Const WM_USER As Integer = &H400 Const TB_BUTTONCOUNT As Integer = (WM_USER + 24) Const TB_GETBUTTON As Integer = (WM_USER + 23) ' Toolbar values are defined in "CommCtrl.h" Windows SDK header files. <StructLayout(LayoutKind.Sequential)> Public Structure TBBUTTON64 Public iBitmap As Integer Public idCommand As Integer Public fsState As Byte Public fsStyle As Byte <MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst:=6)> ' 6 on x64 Public bReserved As Byte() Public dwData As UIntPtr Public iString As IntPtr End Structure <DllImport("User32.dll", SetLastError:=True)> Public Shared Function SendMessage(ByVal hwnd As IntPtr, ByVal msg As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr ) As IntPtr End Function <SuppressUnmanagedCodeSecurity> <DllImport("User32.dll", SetLastError:=True, CharSet:=CharSet.Auto, BestFitMapping:=False, ThrowOnUnmappableChar:=True)> Public Shared Function FindWindow(ByVal lpClassName As String, ByVal lpWindowName As String ) As IntPtr End Function <SuppressUnmanagedCodeSecurity> <DllImport("User32.dll", SetLastError:=True, CharSet:=CharSet.Auto, BestFitMapping:=False, ThrowOnUnmappableChar:=True)> Public Shared Function FindWindowEx(ByVal hwndParent As IntPtr, ByVal hwndChildAfter As IntPtr, ByVal strClassName As String, ByVal strWindowName As String ) As IntPtr End Function這是測試它們的程式碼:
Dim tskBarHwnd As IntPtr = NativeMethods.FindWindow("Shell_TrayWnd", Nothing) Dim systrayBarHwnd As IntPtr = NativeMethods.FindWindowEx(tskBarHwnd, IntPtr.Zero, "TrayNotifyWnd", Nothing) Dim sysPagerHwnd As IntPtr = NativeMethods.FindWindowEx(systrayBarHwnd, IntPtr.Zero, "SysPager", Nothing) Dim ntfyBarHwnd As IntPtr = NativeMethods.FindWindowEx(sysPagerHwnd, IntPtr.Zero, "ToolbarWindow32", Nothing) Dim buttonCount As Integer = NativeMethods.SendMessage(ntfyBarHwnd, TB_BUTTONCOUNT, IntPtr.Zero, IntPtr.Zero).ToInt32() For index As Integer = 0 To (buttonCount - 1) Dim btInfo As New TBBUTTON64 Dim alloc As IntPtr = Marshal.AllocHGlobal(Marshal.SizeOf(GetType(TBBUTTON64))) Marshal.StructureToPtr(btInfo, alloc, fDeleteOld:=True) NativeMethods.SendMessage(ntfyBarHwnd, TB_GETBUTTON, New IntPtr(index), alloc) Marshal.PtrToStructure(Of TBBUTTON64)(alloc) Marshal.FreeHGlobal(alloc) ' This line always prints "00000" Console.WriteLine(btInfo.iBitmap & btInfo.fsState & btInfo.fsStyle & btInfo.idCommand & btInfo.iString.ToInt32()) Next index更新(2019 年 3 月 25 日)
我回到了這個需求,因為現在我需要隱藏外部應用程序的系統托盤圖示。所以我這幾天又開始調查了……
請注意**@Remy Lebeau**的評論:
TB_GETBUTTON 可以發送到另一個程序。您只需為其提供目標程序地址空間中存在的 TBBUTTON 的地址。使用 VirtualAllocEx() 分配它,然後發送消息,然後使用 ReadProcessMemory() 讀取其內容。
我完全不確定如何重現他給出的步驟,但經過大量調查後,我發現了一個顯然可以做到這一點的程式碼,它似乎讀取了程序記憶體來檢索圖示文本:
但是,它是用 C# 編寫的,使用
unsafeandfixed關鍵字,我不確定如何以正確的方式完全翻譯它。另外,就個人觀點而言,我覺得程式碼沒有以任何方式簡化,並且我看到使用“b”、“b2”和“b4”等 var 命名法的糟糕設計實踐,我不明白它們的目的全部…而且,如果有幫助,我還在 C/C++ 中找到了這個:
在簡歷中,我要求的是在 VB.NET 程式碼中重現**@Remy Lebeau**指出的解決方案,或者翻譯和簡化我提到的 C# 程式碼。
這是我目前在程式碼轉換器的幫助下能做的最好的事情,請注意此程式碼不起作用(它已損壞/未完全轉換為 VB.NET):
Private Function GetTBButton(ByVal hToolbar As IntPtr, ByVal i As Integer, ByRef tbButton As ToolBarButton64, ByRef text As String, ByRef ipWindowHandle As IntPtr) As Boolean ' One page Const BUFFER_SIZE As Integer = &H1000 Dim localBuffer(BUFFER_SIZE - 1) As Byte Dim processId As Integer = 0 Dim threadId As Integer = NativeMethods.GetWindowThreadProcessId(hToolbar, processId) Dim hProcess As IntPtr = NativeMethods.OpenProcess(ProcessAccessRights.AllAccess, False, processId) If hProcess = IntPtr.Zero Then Debug.Assert(False) Return False End If Dim ipRemoteBuffer As UIntPtr = NativeMethods.VirtualAllocEx(hProcess, IntPtr.Zero, New UIntPtr(BUFFER_SIZE), MemoryAllocationType.Commit, MemoryProtectionOptions.ReadWrite) If ipRemoteBuffer = UIntPtr.Zero Then Debug.Assert(False) Return False End If ' TBButton 'INSTANT VB TODO TASK: There is no equivalent to a 'fixed' block in VB: ' fixed (TBBUTTON* pTBButton = &tbButton) Dim ipTBButton As New IntPtr(pTBButton) Dim b As Integer = CInt(Math.Truncate(NativeMethods.SendMessage(hToolbar, TB.GETBUTTON, CType(i, IntPtr), ipRemoteBuffer))) If b = 0 Then Debug.Assert(False) Return False End If ' this is fixed Dim dwBytesRead As Int32 = 0 Dim ipBytesRead As New IntPtr(& dwBytesRead) 'INSTANT VB TODO TASK: There is no VB equivalent to 'sizeof': Dim b2 As Boolean = NativeMethods.ReadProcessMemory(hProcess, ipRemoteBuffer, ipTBButton, New UIntPtr(CUInt(Math.Truncate(Marshal.SizeOf(tbButton)))), ipBytesRead) If Not b2 Then Debug.Assert(False) Return False End If 'INSTANT VB NOTE: End of the original C# 'fixed' block. ' button text 'INSTANT VB TODO TASK: There is no equivalent to a 'fixed' block in VB: ' fixed (byte* pLocalBuffer = localBuffer) Dim ipLocalBuffer As New IntPtr(pLocalBuffer) Dim chars As Integer = CInt(Math.Truncate(NativeMethods.SendMessage(hToolbar, TB.GETBUTTONTEXTW, CType(tbButton.idCommand, IntPtr), ipRemoteBuffer))) If chars = -1 Then Debug.Assert(False) Return False End If ' this is fixed Dim dwBytesRead As Integer = 0 Dim ipBytesRead As New IntPtr(& dwBytesRead) Dim b4 As Boolean = NativeMethods.ReadProcessMemory(hProcess, ipRemoteBuffer, ipLocalBuffer, New UIntPtr(BUFFER_SIZE), ipBytesRead) If Not b4 Then Debug.Assert(False) Return False End If text = Marshal.PtrToStringUni(ipLocalBuffer, chars) If text = " " Then text = String.Empty End If 'INSTANT VB NOTE: End of the original C# 'fixed' block. NativeMethods.VirtualFreeEx(hProcess, ipRemoteBuffer, UIntPtr.Zero, MemoryFreeType.Release) NativeMethods.CloseHandle(hProcess) Return True End Function理論上它會這樣稱呼:
Dim sysTrayHwnd As IntPtr = NotificationAreaUtil.Hwnd Dim btIndex As Integer = 0 Dim tbButton As New ToolBarButton64() ' TBBUTTON struct for a x64 process Dim text As String Dim ipHwnd As IntPtr GetTBButton(sysTrayHwnd, btIndex , tbButton, text, ipHwnd)更新(2019 年 4 月 13 日)
我嘗試在此答案中轉換@RbMm 提供的 C/C++ 解決方案,但是,當我嘗試在此行編組 TBBUTTON 結構時,我通過System.AccessViolationException收到相關的記憶體錯誤:
... Dim ptbi As ToolBarButtonInfo = Marshal.PtrToStructure(Of ToolBarButtonInfo)(remoteBaseAddress) ...請注意,為了確保問題的根源不是我這邊的錯誤 TBBUTTONINFOW 定義,
Marshal.PtrToStructure()我只是使用該Marshal.ReadInt32()函式嘗試讀取特定偏移量上的單個欄位,而不是使用該函式,我得到了同樣的錯誤。可能我做錯了什麼,因為我不管理 C/C++。這是我在 VB.NET 中進行程式碼轉換的嘗試:
(我將省略共享 P/Invoke 定義以簡化程式碼範例)
Dim sysTray As IntPtr = NotificationAreaUtil.Hwnd Dim pid As Integer If (NativeMethods.GetWindowThreadProcessId(sysTray, pid) <> 0) Then Dim hProcess As IntPtr = NativeMethods.OpenProcess(ProcessAccessRights.VirtualMemoryOperation, False, pid) If (hProcess <> IntPtr.Zero) Then Dim hSection As IntPtr Dim pageSize As ULong = 81920 ' LARGE_INTEGER Dim viewSize As IntPtr ' SIZE_T Dim baseAddress As IntPtr ' PVOID Dim remoteBaseAddress As IntPtr ' PVOID If (NativeMethods.NtCreateSection(hSection, SectionAccessRights.AllAccess, IntPtr.Zero, pageSize, MemoryProtectionOptions.ReadWrite, SectionAttributes.Commit, IntPtr.Zero) = NTStatus.SUCCESS) Then If (NativeMethods.NtMapViewOfSection(hSection, NativeMethods.GetCurrentProcess(), baseAddress, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, viewSize, ViewOfSectionInherit.ViewUnmap, MemoryAllocationType.Default, MemoryProtectionOptions.ReadWrite) = NTStatus.SUCCESS) Then If (NativeMethods.NtMapViewOfSection(hSection, hProcess, remoteBaseAddress, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, viewSize, ViewOfSectionInherit.ViewUnmap, MemoryAllocationType.Default, MemoryProtectionOptions.ReadWrite) = NTStatus.SUCCESS) Then Dim btIndex As Integer = 3 ' Button index from which I'll try to retrieve a valid TBBUTTONINFOW struct. ' Const TBIF_BYINDEX As Integer = &H80000000 ' Const TBIF_TEXT As Integer = &H2 If (NativeMethods.SendMessage(sysTray, ToolbarMessages.GetButtonInfoUnicode, New IntPtr(btIndex), remoteBaseAddress) <> IntPtr.Zero) Then ' AT THIS LINE THROWS THE ACCESSVIOLATIONEXCEPTION. Dim ptbi As ToolBarButtonInfo = Marshal.PtrToStructure(Of ToolBarButtonInfo)(remoteBaseAddress) Console.WriteLine(ptbi.CommandId) Console.WriteLine(Marshal.PtrToStringUni(ptbi.Text)) Else Throw New Win32Exception(Marshal.GetLastWin32Error()) End If NativeMethods.NtUnmapViewOfSection(hProcess, remoteBaseAddress) End If NativeMethods.NtUnmapViewOfSection(NativeMethods.GetCurrentProcess(), baseAddress) End If NativeMethods.NtClose(hSection) End If NativeMethods.CloseHandle(hProcess) End If End If這是上面程式碼到 C# 的程式碼轉換(即時且未經測試):
IntPtr sysTray = NotificationAreaUtil.Hwnd; int pid = 0; if (NativeMethods.GetWindowThreadProcessId(sysTray, pid) != 0) { IntPtr hProcess = NativeMethods.OpenProcess(ProcessAccessRights.VirtualMemoryOperation, false, pid); if (hProcess != IntPtr.Zero) { IntPtr hSection = System.IntPtr.Zero; ulong pageSize = 81920; // LARGE_INTEGER IntPtr viewSize = System.IntPtr.Zero; // SIZE_T IntPtr baseAddress = System.IntPtr.Zero; // PVOID IntPtr remoteBaseAddress = System.IntPtr.Zero; // PVOID if (NativeMethods.NtCreateSection(hSection, SectionAccessRights.AllAccess, IntPtr.Zero, pageSize, MemoryProtectionOptions.ReadWrite, SectionAttributes.Commit, IntPtr.Zero) == NTStatus.SUCCESS) { if (NativeMethods.NtMapViewOfSection(hSection, NativeMethods.GetCurrentProcess(), baseAddress, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, viewSize, ViewOfSectionInherit.ViewUnmap, MemoryAllocationType.Default, MemoryProtectionOptions.ReadWrite) == NTStatus.SUCCESS) { if (NativeMethods.NtMapViewOfSection(hSection, hProcess, remoteBaseAddress, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, viewSize, ViewOfSectionInherit.ViewUnmap, MemoryAllocationType.Default, MemoryProtectionOptions.ReadWrite) == NTStatus.SUCCESS) { int btIndex = 3; // Button index from which I'll try to retrieve a valid TBBUTTONINFOW struct. if (NativeMethods.SendMessage(sysTray, ToolbarMessages.GetButtonInfoUnicode, new IntPtr(btIndex), remoteBaseAddress) != IntPtr.Zero) { // AT THIS LINE THROWS THE ACCESSVIOLATIONEXCEPTION. ToolBarButtonInfo ptbi = Marshal.PtrToStructure<ToolBarButtonInfo>(remoteBaseAddress); Console.WriteLine(ptbi.CommandId); Console.WriteLine(Marshal.PtrToStringUni(ptbi.Text)); } else { throw new Win32Exception(Marshal.GetLastWin32Error()); } NativeMethods.NtUnmapViewOfSection(hProcess, remoteBaseAddress); } NativeMethods.NtUnmapViewOfSection(NativeMethods.GetCurrentProcess(), baseAddress); } NativeMethods.NtClose(hSection); } NativeMethods.CloseHandle(hProcess); } }
編輯:最初發布的程式碼錯誤地假設 Intptr 可以被強制跨越位數邊界工作。該錯誤已得到糾正。
如果可能,我還擴展了托盤按鈕數據檢索以檢索按鈕圖示。程式碼項目文章:Shell Tray Info - 排列系統托盤圖示被用作編寫此 .Net 實現的基礎。
請注意,檢索到的 Icon 實例不擁有它們各自的句柄,因為這些句柄仍歸作業系統所有。
TBBUTTON 結構有點痛苦,因為欄位
fsStyle根據作業系統位數(32/64)改變其大小。以下適用於 x86 和 x64 編譯的系統 Win 10(64 位)。對於長度和明顯的格式,我深表歉意(我使用 2 個字元製表位,所以確實混淆了多個製表符的格式),但我希望展示範例中使用的所有程式碼。首先是我的 TBBUTTON 聲明。它被定義為基類和 32 位的類和 64 位作業系統的類。基類有一個工廠方法 (
TBBUTTON.CreateForOS) 來返回正確的實現。我通過聲明字節佔位符來處理不同的欄位大小以接收編組結構並在需要時重新組裝。導入 System.Runtime.InteropServices
' Ref: https://docs.microsoft.com/en-us/windows/desktop/api/commctrl/ns-commctrl-tbbutton ' For info on native type size: Windows Data Types ' https://docs.microsoft.com/en-us/windows/desktop/WinProg/windows-data-types ' typedef struct _TBBUTTON { ' int iBitmap; ' int idCommand; ' Byte fsState; ' Byte fsStyle; ' #If ... ' Byte bReserved[6]; - 64 bit ' #Else ' BYTE bReserved[2]; - 32 bit ' #End If ' DWORD_PTR dwData; ' DWORD_PTR = ULONG_PTR 32/64 bits OS=> 4/8 bytes ' INT_PTR iString; ' 32/64 bits OS => 4/8 bytes ' ref: How to Display Tooltips for Buttons ' https://docs.microsoft.com/en-us/windows/desktop/Controls/display-tooltips-for-buttons ' Set the tooltip text as the iString member of the TBBUTTON structure for each button. ' so iString is a pointer to the Tooltip text ' } TBBUTTON, *PTBBUTTON, *LPTBBUTTON; <StructLayout(LayoutKind.Sequential)> Friend MustInherit Class TBBUTTON Public iBitmap As Int32 Public idCommand As Int32 Public fsState As NativeMethods.ToolBars.TBSTATE Public fsStyle As Byte Protected bReserved0 As Byte Protected bReserved1 As Byte Public Shared ReadOnly Property Is64Bit As Boolean Get Return Environment.Is64BitOperatingSystem End Get End Property Public Shared Function CreateForOS() As TBBUTTON Dim ret As TBBUTTON = Nothing If AppBitnessMatchesOS() Then If Environment.Is64BitOperatingSystem Then ret = New TBBUTTON64 Else ret = New TBBUTTON32 End If Else Throw New Exception($"Application is {If(Environment.Is64BitProcess, 64, 32)} bits and OS is {If(Environment.Is64BitOperatingSystem, 64, 32)}. Bitnesses much match.") End If Return ret End Function Private Shared Function AppBitnessMatchesOS() As Boolean Return Environment.Is64BitProcess.Equals(Environment.Is64BitOperatingSystem) End Function Public ReadOnly Property MarshalSize As IntPtr Get Return New IntPtr(Marshal.SizeOf(Me)) End Get End Property Public MustOverride ReadOnly Property Reserved As Byte() Public MustOverride ReadOnly Property DwData As IntPtr Public MustOverride ReadOnly Property IString As IntPtr End Class <StructLayout(LayoutKind.Sequential)> Friend NotInheritable Class TBBUTTON32 : Inherits TBBUTTON Private _dwData As IntPtr Private _iString As IntPtr Public Overrides ReadOnly Property Reserved As Byte() Get Return New Byte() {bReserved0, bReserved1} End Get End Property Public Overrides ReadOnly Property DwData As IntPtr Get Return _dwData End Get End Property Public Overrides ReadOnly Property IString As IntPtr Get Return _iString End Get End Property End Class <StructLayout(LayoutKind.Sequential)> Friend NotInheritable Class TBBUTTON64 : Inherits TBBUTTON Protected bReserved2 As Byte Protected bReserved3 As Byte Protected bReserved4 As Byte Protected bReserved5 As Byte Private _dwData As IntPtr Private _iString As IntPtr Public Overrides ReadOnly Property Reserved As Byte() Get Return New Byte() {bReserved0, bReserved1, bReserved2, bReserved3, bReserved4, bReserved5} End Get End Property Public Overrides ReadOnly Property DwData As IntPtr Get Return _dwData End Get End Property Public Overrides ReadOnly Property IString As IntPtr Get Return _iString End Get End Property End Class接下來是我的本地方法類。此類聲明了各種函式重載,以讓互操作封送系統執行必要的分配/轉換。
Imports System.Diagnostics.CodeAnalysis Imports System.Runtime.ConstrainedExecution Imports System.Runtime.InteropServices Imports System.Security Friend Class NativeMethods Public Const WM_User As Int32 = &H400 Public Shared Sub FreeHGlobal(ptr As IntPtr) If ptr <> IntPtr.Zero Then Marshal.FreeHGlobal(ptr) End If End Sub Public Class ToolBars #Region "Constants" ' values from CommCtrl.h ''' <summary>Retrieves a count of the buttons currently in the toolbar. </summary> Public Const TB_BUTTONCOUNT As Int32 = WM_User + 24 Public Const TB_GETBUTTON As Int32 = WM_User + 23 Public Const TB_DELETEBUTTON As Int32 = WM_User + 22 Private Const TB_GETBUTTONINFOW As Int32 = WM_User + 63 Private Const TB_SETBUTTONINFOW As Int32 = WM_User + 64 Private Const TB_GETBUTTONINFOA As Int32 = WM_User + 65 Private Const TB_SETBUTTONINFOA As Int32 = WM_User + 66 ''' <summary> The cbSize and dwMask members of this structure must be filled in prior to sending this message.</summary> Public Const TB_GETBUTTONINFO As Int32 = TB_GETBUTTONINFOW ''' <summary> The cbSize and dwMask members of this structure must be filled in prior to sending this message.</summary> Public Const TB_SETBUTTONINFO As Int32 = TB_SETBUTTONINFOW Public Const TB_GETBUTTONTEXTA As Int32 = WM_User + 45 Public Const TB_GETBUTTONTEXTW As Int32 = WM_User + 75 Public Const TB_GETBUTTONTEXT As Int32 = TB_GETBUTTONTEXTW Public Const TB_GETSTRINGW As Int32 = WM_User + 91 Public Const TB_GETSTRINGA As Int32 = WM_User + 92 ''' <summary>This message returns the specified string from the toolbar's string pool. It does not necessarily correspond to the text string currently being displayed by a button.</summary> Public Const TB_GETSTRING As Int32 = TB_GETSTRINGW ''' <summary>wParam and lParam must be zero. returns handle to the image list, or NULL if no image list is set.</summary> Public Const TB_GETIMAGELIST As Int32 = WM_User + 49 ''' <summary>Retrieves the index of the bitmap associated with a button in a toolbar. ''' wParam=Command identifier of the button whose bitmap index is to be retrieved.</summary> Public Const TB_GETBITMAP As Int32 = WM_User + 44 <DllImport("comctl32.dll", SetLastError:=True)> Public Shared Function ImageList_GetIcon(himl As IntPtr, imageIndex As Int32, flags As UInt32) As IntPtr End Function <DllImport("comctl32.dll", SetLastError:=True)> Public Shared Function ImageList_GetImageCount(himl As IntPtr) As Int32 End Function #End Region Public Enum TBSTATE As Byte CHECKED = &H1 PRESSED = &H2 ENABLED = &H4 HIDDEN = &H8 INDETERMINATE = &H10 WRAP = &H20 ELLIPSES = &H40 MARKED = &H80 End Enum End Class Public Class User32 #Region "Utility Methods" Public Shared Function GetNotificationAreaToolBarHandle() As IntPtr Dim hWndTray As IntPtr = FindWindow("Shell_TrayWnd", Nothing) If hWndTray <> IntPtr.Zero Then hWndTray = FindWindowEx(hWndTray, IntPtr.Zero, "TrayNotifyWnd", Nothing) If hWndTray <> IntPtr.Zero Then hWndTray = FindWindowEx(hWndTray, IntPtr.Zero, "SysPager", Nothing) If hWndTray <> IntPtr.Zero Then hWndTray = FindWindowEx(hWndTray, IntPtr.Zero, "ToolbarWindow32", Nothing) Return hWndTray End If End If End If Return IntPtr.Zero End Function Public Shared Function GetTaskBarHandle() As IntPtr Dim hWndTray As IntPtr = FindWindow("Shell_TrayWnd", Nothing) If hWndTray <> IntPtr.Zero Then hWndTray = FindWindowEx(hWndTray, IntPtr.Zero, "TrayNotifyWnd", Nothing) If hWndTray <> IntPtr.Zero Then 'hWndTray = FindWindowEx(hWndTray, IntPtr.Zero, "SysPager", Nothing) If hWndTray <> IntPtr.Zero Then 'hWndTray = FindWindowEx(hWndTray, IntPtr.Zero, "ToolbarWindow32", Nothing) End If End If End If Return hWndTray End Function #End Region <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Unicode)> Public Shared Function FindWindow(ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr End Function <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Unicode)> Public Shared Function FindWindowEx(ByVal hwndParent As IntPtr, ByVal hwndChildAfter As IntPtr, ByVal lpszClass As String, ByVal lpszWindow As String) As IntPtr End Function <DllImport("User32.dll", SetLastError:=True, CharSet:=CharSet.Unicode)> Public Shared Function GetWindowThreadProcessId(ByVal hWnd As IntPtr, <System.Runtime.InteropServices.Out()> ByRef lpdwProcessId As Int32) As Int32 End Function #Region "SendMessage Overloads" <DllImport("User32.dll", CharSet:=CharSet.Unicode, EntryPoint:="SendMessage")> Public Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal msg As Int32, ByVal wParam As IntPtr, ByVal lParam As System.Text.StringBuilder) As Int32 End Function <DllImport("User32.dll", CharSet:=CharSet.Unicode, EntryPoint:="SendMessage")> Public Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal msg As Int32, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr End Function <DllImport("User32.dll", CharSet:=CharSet.Unicode)> Public Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal msg As Int32, ByVal wParam As Int32, ByVal lParam As Int32) As Int32 End Function <DllImport("User32.dll", CharSet:=CharSet.Unicode)> Public Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal msg As Int32, ByVal wParam As Int32, ByVal lParam As IntPtr) As Boolean End Function #End Region <StructLayout(LayoutKind.Sequential)> Friend Structure ICONINFO ''' <summary>Specifies whether this structure defines an icon or a cursor. A value of TRUE specifies an icon; FALSE specifies a cursor.</summary> Public fIcon As Boolean ''' <summary>Specifies the x-coordinate of a cursor's hot spot. If this structure defines an icon, the hot spot is always in the center of the icon, and this member is ignored.</summary> Public xHotspot As Int32 ''' <summary>Specifies the y-coordinate of the cursor's hot spot. If this structure defines an icon, the hot spot is always in the center of the icon, and this member is ignored.</summary> Public yHotspot As Int32 ' ''' <summary>(HBITMAP) Specifies the icon bitmask bitmap. If this structure defines a black and white icon, this bitmask is formatted so that the upper half is the icon AND bitmask and the lower half is the icon XOR bitmask. Under this condition, the height should be an even multiple of two. If this structure defines a color icon, this mask only defines the AND bitmask of the icon.</summary> Public hbmMask As IntPtr ' ''' <summary>(HBITMAP) Handle to the icon color bitmap. This member can be optional if this structure defines a black and white icon. The AND bitmask of hbmMask is applied with the SRCAND flag to the destination; subsequently, the color bitmap is applied (using XOR) to the estination by using the SRCINVERT flag.</summary> Public hbmColor As IntPtr End Structure <DllImport("user32.dll")> Shared Function GetIconInfo(ByVal hIcon As IntPtr, ByRef piconinfo As ICONINFO) As Boolean End Function End Class Public Class Kernel32 <DllImport("kernel32.dll", SetLastError:=True)> <ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)> <SuppressUnmanagedCodeSecurity> Public Shared Function CloseHandle(ByVal hObject As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean End Function #Region "OpenProcess" <DllImport("kernel32.dll", SetLastError:=True)> Public Shared Function OpenProcess(ByVal processAccess As ProcessAccessFlags, ByVal bInheritHandle As Boolean, ByVal processId As Int32) As IntPtr End Function <Flags> Public Enum ProcessAccessFlags As UInt32 All = &H1F0FFF Terminate = &H1 CreateThread = &H2 VirtualMemoryOperation = &H8 VirtualMemoryRead = &H10 VirtualMemoryWrite = &H20 DuplicateHandle = &H40 CreateProcess = &H80 SetQuota = &H100 SetInformation = &H200 QueryInformation = &H400 QueryLimitedInformation = &H1000 Synchronize = &H100000 End Enum #End Region #Region "ReadProcessMemory Overloads" <DllImport("kernel32.dll", SetLastError:=True)> Public Shared Function ReadProcessMemory( ByVal hProcess As IntPtr, ByVal lpBaseAddress As IntPtr, ByVal lpBuffer As IntPtr, <MarshalAs(UnmanagedType.SysInt)> ByVal iSize As IntPtr, <MarshalAs(UnmanagedType.SysInt)> ByRef lpNumberOfBytesRead As IntPtr) As Boolean End Function <DllImport("kernel32.dll", SetLastError:=True, EntryPoint:="ReadProcessMemory")> Public Shared Function ReadProcessMemory( ByVal hProcess As IntPtr, ByVal lpBaseAddress As IntPtr, ByRef lpBuffer As Int32, <MarshalAs(UnmanagedType.SysInt)> ByVal iSize As IntPtr, <MarshalAs(UnmanagedType.SysInt)> ByRef lpNumberOfBytesRead As IntPtr) As Boolean End Function <DllImport("kernel32.dll", SetLastError:=True, EntryPoint:="ReadProcessMemory")> Public Shared Function ReadProcessMemory( ByVal hProcess As IntPtr, ByVal lpBaseAddress As IntPtr, ByRef lpBuffer As Int64, <MarshalAs(UnmanagedType.SysInt)> ByVal iSize As IntPtr, <MarshalAs(UnmanagedType.SysInt)> ByRef lpNumberOfBytesRead As IntPtr) As Boolean End Function <DllImport("kernel32.dll", SetLastError:=True, EntryPoint:="ReadProcessMemory")> Public Shared Function ReadProcessMemory( ByVal hProcess As IntPtr, ByVal lpBaseAddress As IntPtr, ByVal lpBuffer As TBBUTTON, <MarshalAs(UnmanagedType.SysInt)> ByVal iSize As IntPtr, <MarshalAs(UnmanagedType.SysInt)> ByRef lpNumberOfBytesRead As IntPtr) As Boolean End Function <DllImport("kernel32.dll", SetLastError:=True, EntryPoint:="ReadProcessMemory")> Public Shared Function ReadProcessMemory( ByVal hProcess As IntPtr, ByVal lpBaseAddress As IntPtr, ByVal lpBuffer As TrayData, <MarshalAs(UnmanagedType.SysInt)> ByVal iSize As IntPtr, <MarshalAs(UnmanagedType.SysInt)> ByRef lpNumberOfBytesRead As IntPtr) As Boolean End Function #End Region #Region "VirtualAllocEx" <DllImport("kernel32.dll", SetLastError:=True, ExactSpelling:=True)> Shared Function VirtualAllocEx(ByVal hProcess As IntPtr, ByVal lpAddress As IntPtr, <MarshalAs(UnmanagedType.SysInt)> ByVal dwSize As IntPtr, <MarshalAs(UnmanagedType.U4)> ByVal flAllocationType As AllocationType, ByVal flProtect As MemoryProtection) As IntPtr End Function <Flags> Public Enum AllocationType As UInt32 Commit = &H1000 Reserve = &H2000 Decommit = &H4000 Release = &H8000 Reset = &H80000 Physical = &H400000 TopDown = &H100000 WriteWatch = &H200000 LargePages = &H20000000 End Enum <Flags> Public Enum MemoryProtection As UInt32 Execute = &H10 ExecuteRead = &H20 ExecuteReadWrite = &H40 ExecuteWriteCopy = &H80 NoAccess = &H1 [ReadOnly] = &H2 ReadWrite = &H4 WriteCopy = &H8 GuardModifierflag = &H100 NoCacheModifierflag = &H200 WriteCombineModifierflag = &H400 End Enum #End Region #Region "VirtualFreeEx" <DllImport("kernel32.dll")> Public Shared Function VirtualFreeEx(ByVal hProcess As IntPtr, ByVal lpAddress As IntPtr, <MarshalAs(UnmanagedType.SysInt)> ByVal dwSize As IntPtr, ByVal dwFreeType As FreeType) As Boolean End Function ''' <summary>helper method to release memory allocated with VirtualAllocEx</summary> ''' <param name="lpAddress">ptr received from VirtualAllocEx</param> ''' <param name="hProcess">ptr to process received from OpenProcess</param> Public Shared Sub ReleaseVirtualAlloc(ByRef lpAddress As IntPtr, hProcess As IntPtr) If lpAddress <> IntPtr.Zero Then VirtualFreeEx(hProcess, lpAddress, Nothing, FreeType.RELEASE) lpAddress = IntPtr.Zero End If End Sub <Flags()> Public Enum FreeType As UInt32 DECOMMIT = &H4000 RELEASE = &H8000 End Enum #End Region End Class End Class下面定義了兩個額外的支持類。
Friend Class TrayButtonInfo Public Property Icon As Icon Public Property Index As Int32 Public Property CommandID As Int32 Public Property DisplayText As String = String.Empty Public Property ProcessFound As Boolean = False Public Property ProcessName As String = String.Empty Public Property ProcessID As Int32 Public Property State As NativeMethods.ToolBars.TBSTATE End Class…
Imports System.Runtime.InteropServices <StructLayout(LayoutKind.Sequential)> Friend Class TrayData Public hWnd As IntPtr Public uID As Int32 Public uCallbackMessage As UInt32 Public Reservered0 As UInt32 Public Reservered1 As UInt32 Public hIcon As IntPtr Public ReadOnly Property OwningProcess As Process Get Dim ret As Process = Nothing If hWnd <> IntPtr.Zero Then Dim processIDOfButton As Int32 Dim threadId As Int32 = NativeMethods.User32.GetWindowThreadProcessId(hWnd, processIDOfButton) Try ' Process.GetProcessById can throw an exception if the id is not found ret = Process.GetProcessById(processIDOfButton) Catch ex As Exception ' eat it End Try End If Return ret End Get End Property End Class現在是實際的範常式式碼。此程式碼準備了一個
TrayButtonInfo實例列表,可以搜尋這些實例以找到匹配的按鈕。它還顯示了此搜尋的範例以及如何刪除按鈕。我試圖在評論中解釋程式碼,但請隨時詢問任何不清楚的地方。Imports System.Runtime.InteropServices Public Class Form1 Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click Dim retrievedNotificationButtons As List(Of TrayButtonInfo) = PrepareButtonInfoList() ' Now we have filled retrievedNotificationButtons with the information for all the buttons ' "Realtek HD Audio Manager" is just for testing on my machine. ' apply criteria for finding the button you want to delete ' note that the button can be restored by restarting Explorer Dim targetBtn As TrayButtonInfo = retrievedNotificationButtons.FirstOrDefault(Function(info) info.DisplayText.StartsWith("Realtek HD Audio Manager")) If targetBtn IsNot Nothing Then ' delete the button ' the following statement will delete the button if uncommented 'Dim toolBarHwnd As IntPtr = NativeMethods.User32.GetNotificationAreaToolBarHandle() 'NativeMethods.User32.SendMessage(toolBarHwnd, NativeMethods.ToolBars.TB_DELETEBUTTON, targetBtn.Index, 0) End If DataGridView1.DataSource = retrievedNotificationButtons End Sub Private Function PrepareButtonInfoList() As List(Of TrayButtonInfo) Dim ret As List(Of TrayButtonInfo) ' create a TBButon structure appropriate for the OS bitness Dim btn As TBBUTTON = TBBUTTON.CreateForOS Dim toolBarProcessHandle As IntPtr Dim toolBarButtonProcessMemoryPtr As IntPtr ' obtain window handle of the notification area toolbar Dim toolBarHwnd As IntPtr = NativeMethods.User32.GetNotificationAreaToolBarHandle() ' obtain id of process that owns the notification area toolbar, threadId is of no consequence Dim proccessIDOwningToolBar As Int32 Dim threadId As Int32 = NativeMethods.User32.GetWindowThreadProcessId(toolBarHwnd, proccessIDOwningToolBar) Try ' obtain handle to the toolbar process that will allow allocating,reading and writing toolBarProcessHandle = NativeMethods.Kernel32.OpenProcess(NativeMethods.Kernel32.ProcessAccessFlags.VirtualMemoryOperation Or NativeMethods.Kernel32.ProcessAccessFlags.VirtualMemoryRead Or NativeMethods.Kernel32.ProcessAccessFlags.VirtualMemoryWrite, False, proccessIDOwningToolBar) ' allocate memory in the toolbar process to hold the TBButton ' need ReadWrite access due to TB_GETBUTTON writing to the allocated memory toolBarButtonProcessMemoryPtr = NativeMethods.Kernel32.VirtualAllocEx(toolBarProcessHandle, Nothing, btn.MarshalSize, NativeMethods.Kernel32.AllocationType.Commit, NativeMethods.Kernel32.MemoryProtection.ReadWrite) ' now we can request the toolbar to fill the allocated memory with a TBButton structure ' for each button in the notifiaction area. ' determine how many toolbar buttons are visible notification area contains Dim buttonCount As Int32 = NativeMethods.User32.SendMessage(toolBarHwnd, NativeMethods.ToolBars.TB_BUTTONCOUNT, 0, 0) ret = New List(Of TrayButtonInfo)(buttonCount) For btnIndex As Int32 = 0 To buttonCount - 1 Dim btnInfo As New TrayButtonInfo With {.Index = btnIndex} ret.Add(btnInfo) If NativeMethods.User32.SendMessage(toolBarHwnd, NativeMethods.ToolBars.TB_GETBUTTON, btnIndex, toolBarButtonProcessMemoryPtr) Then ' the toolbar owning process has successfully filled toolBarButtonProcessMemoryPtr ' use a customize ReadProcessMemory that takes a TBButtonBase instance as the destination buffer If NativeMethods.Kernel32.ReadProcessMemory(toolBarProcessHandle, toolBarButtonProcessMemoryPtr, btn, btn.MarshalSize, Nothing) Then ' btn has been loaded, get the data btnInfo.CommandID = btn.idCommand btnInfo.State = btn.fsState ' Note that per the documentation, TBBUTTON.iString can contain a pointer the Tooltip Text ' In testing it does, but I have not found out how to determine the length of the string ' without the length, a guess on the size of process memory to read must be made and that ' seems unwise when accessing memory. ' GetButtonText use the TB_GETBUTTONTEXT message. This message's documentation indicates ' that the retrieved text may differ from the Tooltip text. As Tooltip text can be provided ' via several mechanisms, this makes sense. btnInfo.DisplayText = GetButtonText(btnInfo.CommandID, toolBarHwnd, toolBarProcessHandle) ' get the process pointed to by dwData ' according to: Code Project article: A tool to order the window buttons in your taskbar ' https://www.codeproject.com/Articles/10497/A-tool-to-order-the-window-buttons-in-your-taskbar ' this is a pointer to the window handle of the process that owns the button ' while I can find no documentation that this is true, it appears to work GetButtonData(btn, toolBarProcessHandle, btnInfo) End If ' ReadProcessMemoryToTBButton End If Next Finally ' cleanup handles If toolBarProcessHandle <> IntPtr.Zero Then NativeMethods.Kernel32.ReleaseVirtualAlloc(toolBarButtonProcessMemoryPtr, toolBarProcessHandle) End If If toolBarProcessHandle <> IntPtr.Zero Then NativeMethods.Kernel32.CloseHandle(toolBarProcessHandle) toolBarProcessHandle = IntPtr.Zero End If End Try Return ret End Function Private Function GetButtonText(CommandID As Int32, toolBarWindowHandle As IntPtr, toolBarProcessHandle As IntPtr) As String Dim ret As String = String.Empty '1st determine the number of characters to retrieve Dim lenText As Int32 = NativeMethods.User32.SendMessage(toolBarWindowHandle, NativeMethods.ToolBars.TB_GETBUTTONTEXT, New IntPtr(CommandID), IntPtr.Zero).ToInt32 If lenText > 0 Then Dim ptrToText As IntPtr Dim localBuffer As IntPtr Try Dim numBytes As New IntPtr((lenText * 2) + 1) ' Unicode 2 bytes per character + 1 for null terminator 'need to allocate the string in the process space ptrToText = NativeMethods.Kernel32.VirtualAllocEx(toolBarProcessHandle, Nothing, numBytes, NativeMethods.Kernel32.AllocationType.Commit, NativeMethods.Kernel32.MemoryProtection.ReadWrite) Dim receivedLen As Int32 = NativeMethods.User32.SendMessage(toolBarWindowHandle, NativeMethods.ToolBars.TB_GETBUTTONTEXT, New IntPtr(CommandID), ptrToText).ToInt32 localBuffer = Marshal.AllocHGlobal(numBytes) ' allocate local buffer to receive bytes from process space If NativeMethods.Kernel32.ReadProcessMemory(toolBarProcessHandle, ptrToText, localBuffer, numBytes, Nothing) Then ret = Marshal.PtrToStringUni(localBuffer) End If Finally ' release handles to unmanaged memory NativeMethods.Kernel32.ReleaseVirtualAlloc(ptrToText, toolBarProcessHandle) NativeMethods.FreeHGlobal(localBuffer) End Try End If Return ret End Function Private Sub GetButtonData(btn As TBBUTTON, toolBarProcessHandle As IntPtr, btnInfo As TrayButtonInfo) Dim data As TrayData Try data = New TrayData() Dim dataMarshalSize As New IntPtr(Marshal.SizeOf(data)) If NativeMethods.Kernel32.ReadProcessMemory(toolBarProcessHandle, btn.DwData, data, dataMarshalSize, Nothing) Then ' use GetIconInfo to validate icon handle Dim iconInfo As New NativeMethods.User32.ICONINFO If NativeMethods.User32.GetIconInfo(data.hIcon, iconInfo) Then btnInfo.Icon = Icon.FromHandle(data.hIcon) End If Using p As Process = data.OwningProcess If p IsNot Nothing Then btnInfo.ProcessFound = True btnInfo.ProcessID = p.Id btnInfo.ProcessName = p.ProcessName End If End Using End If Catch ex As Exception Debug.Print(ex.Message) End Try End Sub End Class我在有限的測試中觀察到的一件事是,附加字元會間歇性地附加到檢索到的按鈕文本上。我嘗試將記憶體歸零,但這沒有幫助。因此
StartsWith在搜尋要刪除的目標按鈕時使用。
