TBBUTTON结构不能与SendMessage一起使用

Ele*_*ios 10 .net vb.net winapi marshalling readprocessmemory

我正在尝试发送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
Run Code Online (Sandbox Code Playgroud)

这是测试它们的代码:

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
Run Code Online (Sandbox Code Playgroud)

更新(2019年3月25日)

我之所以再次提出此要求,是因为现在我需要隐藏外部应用程序的系统托盘图标。所以这些天我又开始调查...

请注意@ Remy Lebeau的评论:

TB_GETBUTTON可以发送到另一个进程。您只需为其提供目标进程地址空间中存在的TBBUTTON的地址。使用VirtualAllocEx()分配它,然后发送消息,然后使用ReadProcessMemory()读取其内容。

我完全不确定如何重现他给出的步骤,但是经过大量研究后,我发现了一个显然可以执行此操作的代码,它似乎在读取过程存储器来检索图标文本:

但是,它是用C#编写的,使用unsafefixed关键字,我不确定如何以正确的方式完全翻译它。另外,作为个人观点,我觉得代码没有得到任何简化,并且我看到了带有“ b”,“ b2”和“ b4”等变种命名法的不良设计实践,但我并没有达到目的所有...

而且,如果有帮助,我也在C / C ++中发现了这一点:

在简历中,我要提供的是在VB.NET代码中重现@ Remy Lebeau指出的解决方案,或者翻译和简化我提到的C#代码。

这是我可以用一个代码转换器的帮助,请注意此代码的那一刻做了最好的IS NOT工作(这是坏/未完全转换到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
Run Code Online (Sandbox Code Playgroud)

从理论上讲,它将被这样称呼:

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)
Run Code Online (Sandbox Code Playgroud)

更新(13-APRIL-2019)

我试图在答案中转换@RbMm提供的C / C ++解决方案,但是,当我尝试在此行上编组TBBUTTON结构时,我通过System.AccessViolationException得到了一个相关的内存错误:

...
Dim ptbi As ToolBarButtonInfo = Marshal.PtrToStructure(Of ToolBarButtonInfo)(remoteBaseAddress)
...
Run Code Online (Sandbox Code Playgroud)

请注意,从我的角度来看,要确保问题的根源不是一个错误的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
Run Code Online (Sandbox Code Playgroud)

这是将代码即时转换(未经测试)到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);
    }

}
Run Code Online (Sandbox Code Playgroud)

TnT*_*nMn 5

编辑:最初发布的代码在假设 Intptr 可以被强制跨位数边界工作时是错误的。该错误已得到纠正。

如果可能的话,我还扩展了托盘按钮数据检索以检索按钮图标。代码项目文章:Shell Tray Info - 排列系统托盘图标被用作编写此 .Net 实现的基础。

请注意,检索到的 Icon 实例不拥有其各自的句柄,因为这些句柄仍归操作系统所有。


TBBUTTON 结构有点麻烦,因为字段的fsStyle大小会根据操作系统位数 (32/64) 变化。以下内容适用于我的系统 Win 10(64 位),适用于 x86 和 x64 编译。我对长度和明显的格式表示歉意(我使用 2 个字符制表位,所以确实弄乱了多个制表符的格式),但我希望展示示例中使用的所有代码。

首先是我的 TBBUTTON 声明。它被定义为基类、32 位操作系统的类和 64 位操作系统的类。基类有一个工厂方法(TBBUTTON.CreateForOS)来返回正确的实现。我通过声明字节占位符来接收编组结构并在需要时重新组装来处理不同的字段大小。

导入 System.Runtime.InteropServices

' Ref: https://learn.microsoft.com/en-us/windows/desktop/api/commctrl/ns-commctrl-tbbutton
' For info on native type size: Windows Data Types
'   https://learn.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://learn.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
Run Code Online (Sandbox Code Playgroud)

接下来是我的本机方法类。此类声明各种函数重载,以使互操作编组系统执行必要的分配/转换。

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
Run Code Online (Sandbox Code Playgroud)

下面定义了两个附加支持类。

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
Run Code Online (Sandbox Code Playgroud)

...

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 UIn