TBBUTTON 结构不适用于 SendMessage
TBBUTTON struct not working with SendMessage
我正在尝试发送 TB_GETBUTTON 消息以获取有关此工具栏控件内的红色标记按钮的信息:
(系统托盘通知区域)
问题是,当我发送消息时,Explorer 会自行刷新,这非常烦人,因为所有桌面都会刷新,而且我也没有获得正确的值TBBUTTON structure definition that I'm using, I tested three different definitions, those with unions from pinvoke.net, and the one published here @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 can be sent to another process. You just have to give it
the address of a TBBUTTON that exists in the target process's address
space. Use VirtualAllocEx() to allocate it, then send the message,
then use ReadProcessMemory() to read its content.
我完全不确定如何重现他给出的步骤,但经过大量调查后,我发现了一个显然可以做到这一点的代码,它似乎读取进程内存以检索图标文本:
- Get ToolTip Text from Icon in System Tray
但是,它是用 C# 编写的,使用 unsafe
和 fixed
关键字,我不确定如何以正确的方式完全翻译它。此外,作为个人意见,我觉得代码没有以任何方式简化,并且我看到使用 var 命名法的糟糕设计实践,如 "b"、"b2" 和 "b4" 我不完全没有达到他们的目的...
而且,如果有帮助,我还在 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++ 解决方案,但是,我通过 [=109 得到了相关的内存错误=] 当我尝试在这一行编组 TBBUTTON 结构时:
...
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);
}
}
您无法在另一个进程中将TB_GETBUTTON
消息发送到windows并获得有效结果,因为TB_GETBUTTON
需要操作指向结构的指针,而window 执行此操作的消息必须编组结构。很少 windows 消息会这样做。 TB_GETBUTTONCOUNT
有效,因为它不需要编组。
我们也可以发送 TB_GETBUTTONINFOW
for get the button text. if we know what text must be for button - we can detect it by text and delete it via TB_DELETEBUTTON
. the pointer to TBBUTTONINFOW
of course must be valid in context of process, which own the hwnd. the pszText inside TBBUTTONINFOW
当然必须在目标进程的上下文中有效。
为了实现这一点 - 最好创建部分并将其映射到自身和目标进程中。这让内存操作变得容易。
BOOL IsTargetButton(PCWSTR pszText);
HWND GetNotificationWnd()
{
HWND hwnd;
(hwnd = FindWindow(L"Shell_TrayWnd", 0)) &&
(hwnd = FindWindowExW(hwnd, 0, L"TrayNotifyWnd", 0)) &&
(hwnd = FindWindowExW(hwnd, 0, L"SysPager", 0)) &&
(hwnd = FindWindowExW(hwnd, 0, L"ToolbarWindow32", 0));
return hwnd;
}
void deleteInTray()
{
if (HWND hwnd = GetNotificationWnd())
{
if (ULONG iIndex = SendMessageW(hwnd, TB_BUTTONCOUNT, 0, 0))
{
ULONG dwProcessId;
if (GetWindowThreadProcessId(hwnd, &dwProcessId))
{
if (HANDLE hProcess = OpenProcess(PROCESS_VM_OPERATION, FALSE, dwProcessId))
{
HANDLE hSection;
LARGE_INTEGER Size = { 1 };
if (0 <= ZwCreateSection(&hSection, SECTION_ALL_ACCESS, 0, &Size, PAGE_READWRITE, SEC_COMMIT, 0))
{
SIZE_T ViewSize = 0;
union {
PVOID RemoteBaseAddress;
PBYTE pb2;
};
union {
PVOID BaseAddress;
TBBUTTONINFO* ptbi;
PBYTE pb;
};
if (0 <= ZwMapViewOfSection(hSection, NtCurrentProcess(), &(BaseAddress = 0),
0, 0, 0, &ViewSize, ViewUnmap, 0, PAGE_READWRITE))
{
if (0 <= ZwMapViewOfSection(hSection, hProcess, &(RemoteBaseAddress = 0),
0, 0, 0, &ViewSize, ViewUnmap, 0, PAGE_READWRITE))
{
ptbi->cbSize = sizeof(TBBUTTONINFO);
ptbi->dwMask = TBIF_BYINDEX|TBIF_TEXT;
ptbi->pszText = (PWSTR)(pb2 + sizeof(TBBUTTONINFO));
ptbi->cchText = ((int)ViewSize - sizeof(TBBUTTONINFO)) / sizeof(WCHAR);
PWSTR pszText = (PWSTR)(pb + sizeof(TBBUTTONINFO));
do
{
if (0 <= SendMessageW(hwnd, TB_GETBUTTONINFOW, --iIndex, (LPARAM)RemoteBaseAddress))
{
DbgPrint("%u: %S\n", iIndex, pszText);
if (IsTargetButton(pszText))
{
SendMessageW(hwnd, TB_DELETEBUTTON, iIndex, 0);
}
}
} while (iIndex);
ZwUnmapViewOfSection(hProcess, RemoteBaseAddress);
}
ZwUnmapViewOfSection(NtCurrentProcess(), BaseAddress);
}
CloseHandle(hSection);
}
CloseHandle(hProcess);
}
}
}
}
}
编辑:最初 posted 的代码错误地假设 Intptr 可以被强制转换为跨位数边界工作。该错误已得到纠正。
如果可能,我还扩展了托盘按钮数据检索以检索按钮图标。代码项目文章:Shell Tray Info - Arrange your system tray icons 被用作编写此 .Net 实现的基础。
请注意,检索到的图标实例不拥有它们各自的句柄,因为它们仍然属于 OS。
TBBUTTON 结构有点麻烦,因为 fsStyle
之后的字段会根据 OS 位数 (32/64) 改变其大小。以下适用于我的系统 Win 10(64 位),适用于 x86 和 x64 编译。对于长度和明显的格式,我深表歉意(我使用了 2 个字符制表位,因此确实混淆了多个制表符的格式),但我希望展示示例中使用的所有代码。
首先是我的 TBBUTTON 声明。它被定义为 base class 和 32 位的 class 和 64 位的 class OS。基础 class 有一个工厂方法 (TBBUTTON.CreateForOS
) 来 return 正确的实现。我通过声明字节占位符来接收编组结构并在需要时重新 assemble 来处理不同的字段大小。
进口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
接下来是我的本地方法 class。 class 声明了各种函数重载,让互操作编组系统执行必要的 allocations/conversions.
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
下面定义了两个额外的支持 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
我在有限测试中观察到的一件事是,附加字符间歇性地 post-pended 到检索到的按钮文本上。我尝试将内存归零,但这没有帮助。因此在搜索要删除的目标按钮时使用 StartsWith
。
我正在尝试发送 TB_GETBUTTON 消息以获取有关此工具栏控件内的红色标记按钮的信息:
(系统托盘通知区域)
问题是,当我发送消息时,Explorer 会自行刷新,这非常烦人,因为所有桌面都会刷新,而且我也没有获得正确的值TBBUTTON structure definition that I'm using, I tested three different definitions, those with unions from pinvoke.net, and the one published here @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 can be sent to another process. You just have to give it the address of a TBBUTTON that exists in the target process's address space. Use VirtualAllocEx() to allocate it, then send the message, then use ReadProcessMemory() to read its content.
我完全不确定如何重现他给出的步骤,但经过大量调查后,我发现了一个显然可以做到这一点的代码,它似乎读取进程内存以检索图标文本:
- Get ToolTip Text from Icon in System Tray
但是,它是用 C# 编写的,使用 unsafe
和 fixed
关键字,我不确定如何以正确的方式完全翻译它。此外,作为个人意见,我觉得代码没有以任何方式简化,并且我看到使用 var 命名法的糟糕设计实践,如 "b"、"b2" 和 "b4" 我不完全没有达到他们的目的...
而且,如果有帮助,我还在 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 在
...
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);
}
}
您无法在另一个进程中将TB_GETBUTTON
消息发送到windows并获得有效结果,因为TB_GETBUTTON
需要操作指向结构的指针,而window 执行此操作的消息必须编组结构。很少 windows 消息会这样做。 TB_GETBUTTONCOUNT
有效,因为它不需要编组。
我们也可以发送 TB_GETBUTTONINFOW
for get the button text. if we know what text must be for button - we can detect it by text and delete it via TB_DELETEBUTTON
. the pointer to TBBUTTONINFOW
of course must be valid in context of process, which own the hwnd. the pszText inside TBBUTTONINFOW
当然必须在目标进程的上下文中有效。
为了实现这一点 - 最好创建部分并将其映射到自身和目标进程中。这让内存操作变得容易。
BOOL IsTargetButton(PCWSTR pszText);
HWND GetNotificationWnd()
{
HWND hwnd;
(hwnd = FindWindow(L"Shell_TrayWnd", 0)) &&
(hwnd = FindWindowExW(hwnd, 0, L"TrayNotifyWnd", 0)) &&
(hwnd = FindWindowExW(hwnd, 0, L"SysPager", 0)) &&
(hwnd = FindWindowExW(hwnd, 0, L"ToolbarWindow32", 0));
return hwnd;
}
void deleteInTray()
{
if (HWND hwnd = GetNotificationWnd())
{
if (ULONG iIndex = SendMessageW(hwnd, TB_BUTTONCOUNT, 0, 0))
{
ULONG dwProcessId;
if (GetWindowThreadProcessId(hwnd, &dwProcessId))
{
if (HANDLE hProcess = OpenProcess(PROCESS_VM_OPERATION, FALSE, dwProcessId))
{
HANDLE hSection;
LARGE_INTEGER Size = { 1 };
if (0 <= ZwCreateSection(&hSection, SECTION_ALL_ACCESS, 0, &Size, PAGE_READWRITE, SEC_COMMIT, 0))
{
SIZE_T ViewSize = 0;
union {
PVOID RemoteBaseAddress;
PBYTE pb2;
};
union {
PVOID BaseAddress;
TBBUTTONINFO* ptbi;
PBYTE pb;
};
if (0 <= ZwMapViewOfSection(hSection, NtCurrentProcess(), &(BaseAddress = 0),
0, 0, 0, &ViewSize, ViewUnmap, 0, PAGE_READWRITE))
{
if (0 <= ZwMapViewOfSection(hSection, hProcess, &(RemoteBaseAddress = 0),
0, 0, 0, &ViewSize, ViewUnmap, 0, PAGE_READWRITE))
{
ptbi->cbSize = sizeof(TBBUTTONINFO);
ptbi->dwMask = TBIF_BYINDEX|TBIF_TEXT;
ptbi->pszText = (PWSTR)(pb2 + sizeof(TBBUTTONINFO));
ptbi->cchText = ((int)ViewSize - sizeof(TBBUTTONINFO)) / sizeof(WCHAR);
PWSTR pszText = (PWSTR)(pb + sizeof(TBBUTTONINFO));
do
{
if (0 <= SendMessageW(hwnd, TB_GETBUTTONINFOW, --iIndex, (LPARAM)RemoteBaseAddress))
{
DbgPrint("%u: %S\n", iIndex, pszText);
if (IsTargetButton(pszText))
{
SendMessageW(hwnd, TB_DELETEBUTTON, iIndex, 0);
}
}
} while (iIndex);
ZwUnmapViewOfSection(hProcess, RemoteBaseAddress);
}
ZwUnmapViewOfSection(NtCurrentProcess(), BaseAddress);
}
CloseHandle(hSection);
}
CloseHandle(hProcess);
}
}
}
}
}
编辑:最初 posted 的代码错误地假设 Intptr 可以被强制转换为跨位数边界工作。该错误已得到纠正。
如果可能,我还扩展了托盘按钮数据检索以检索按钮图标。代码项目文章:Shell Tray Info - Arrange your system tray icons 被用作编写此 .Net 实现的基础。
请注意,检索到的图标实例不拥有它们各自的句柄,因为它们仍然属于 OS。
TBBUTTON 结构有点麻烦,因为 fsStyle
之后的字段会根据 OS 位数 (32/64) 改变其大小。以下适用于我的系统 Win 10(64 位),适用于 x86 和 x64 编译。对于长度和明显的格式,我深表歉意(我使用了 2 个字符制表位,因此确实混淆了多个制表符的格式),但我希望展示示例中使用的所有代码。
首先是我的 TBBUTTON 声明。它被定义为 base class 和 32 位的 class 和 64 位的 class OS。基础 class 有一个工厂方法 (TBBUTTON.CreateForOS
) 来 return 正确的实现。我通过声明字节占位符来接收编组结构并在需要时重新 assemble 来处理不同的字段大小。
进口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
接下来是我的本地方法 class。 class 声明了各种函数重载,让互操作编组系统执行必要的 allocations/conversions.
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
下面定义了两个额外的支持 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
我在有限测试中观察到的一件事是,附加字符间歇性地 post-pended 到检索到的按钮文本上。我尝试将内存归零,但这没有帮助。因此在搜索要删除的目标按钮时使用 StartsWith
。