C# 使用PInvoke声明实现代码示例时遇到问题

C# 使用PInvoke声明实现代码示例时遇到问题,c#,tooltip,pinvoke,tray,C#,Tooltip,Pinvoke,Tray,关于我的问题,我在这里引用以下线索: 我基本上想做与OP相同的事情,但与响应此线程的其他一些用户不同,在添加缺少的PInvoke声明(这些声明没有在代码示例中明确定义)后,我很难让代码正常工作。我组装了一个类文件,试图将所有内容放在一起,并引用了PInvoke.net中的声明(我不确定用户michalczerwinski是这样做的,还是在使用某种PInvoke库)。以下是我目前掌握的情况: using System; using System.Collections.Generic; usin

关于我的问题,我在这里引用以下线索:

我基本上想做与OP相同的事情,但与响应此线程的其他一些用户不同,在添加缺少的PInvoke声明(这些声明没有在代码示例中明确定义)后,我很难让代码正常工作。我组装了一个类文件,试图将所有内容放在一起,并引用了PInvoke.net中的声明(我不确定用户michalczerwinski是这样做的,还是在使用某种PInvoke库)。以下是我目前掌握的情况:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using System.Threading.Tasks;


namespace Example
{

    public static class TrayTooltip
    {
        [Flags]
        public enum ProcessAccessFlags : uint
        {
            All = 0x001F0FFF,
            Terminate = 0x00000001,
            CreateThread = 0x00000002,
            VirtualMemoryOperation = 0x00000008,
            VirtualMemoryRead = 0x00000010,
            VirtualMemoryWrite = 0x00000020,
            DuplicateHandle = 0x00000040,
            CreateProcess = 0x000000080,
            SetQuota = 0x00000100,
            SetInformation = 0x00000200,
            QueryInformation = 0x00000400,
            QueryLimitedInformation = 0x00001000,
            Synchronize = 0x00100000
        }

        [Flags]
        public enum AllocationType
        {
            Commit = 0x1000,
            Reserve = 0x2000,
            Decommit = 0x4000,
            Release = 0x8000,
            Reset = 0x80000,
            Physical = 0x400000,
            TopDown = 0x100000,
            WriteWatch = 0x200000,
            LargePages = 0x20000000
        }

        [Flags]
        public enum MemoryProtection
        {
            Execute = 0x10,
            ExecuteRead = 0x20,
            ExecuteReadWrite = 0x40,
            ExecuteWriteCopy = 0x80,
            NoAccess = 0x01,
            ReadOnly = 0x02,
            ReadWrite = 0x04,
            WriteCopy = 0x08,
            GuardModifierflag = 0x100,
            NoCacheModifierflag = 0x200,
            WriteCombineModifierflag = 0x400
        }

        [Flags]
        public enum TB
        {
            WM_USER = 0x0400,
            GETBUTTON = (WM_USER + 23),
            GETBUTTONTEXTW = (WM_USER + 75),
            BUTTONCOUNT = (WM_USER + 24)
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        public struct TBBUTTON
        {
            public int cbSize;
            public int dwMask;
            public int idCommand;
            public int iImage;
            public byte fsState;
            public byte fsStyle;
            public short cx;
            public IntPtr lParam;
            public IntPtr pszText;
            public int cchText;
        }

        public static class User32
        {
            [DllImport("user32.dll", SetLastError = true)]
            public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

            // When you don't want the ProcessId, use this overload and pass IntPtr.Zero for the second parameter
            [DllImport("user32.dll")]
            public static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr ProcessId);

            [DllImport("user32.dll")]
            public static extern int SendMessage(int hWnd, uint Msg, int wParam, int lParam);

            [DllImport("user32.dll", CharSet = CharSet.Auto)]
            public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, StringBuilder lParam);

            [DllImport("user32.dll", CharSet = CharSet.Auto)]
            public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, [MarshalAs(UnmanagedType.LPWStr)] string lParam);

            [DllImport("user32.dll", CharSet = CharSet.Auto)]
            public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, [MarshalAs(UnmanagedType.LPWStr)] string lParam);

            [DllImport("user32.dll", CharSet = CharSet.Auto)]
            public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, ref IntPtr lParam);

            [DllImport("user32.dll", CharSet = CharSet.Auto)]
            public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, IntPtr lParam);
        }

        public static class Kernel32
        {
            [DllImport("kernel32.dll", SetLastError = true)]
            public static extern IntPtr OpenProcess(ProcessAccessFlags processAccess, bool bInheritHandle, int processId);
            public static IntPtr OpenProcess(Process proc, ProcessAccessFlags flags)
            {
                return OpenProcess(flags, false, proc.Id);
            }

            [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
            public static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, AllocationType flAllocationType, MemoryProtection flProtect);

            [DllImport("kernel32.dll", SetLastError = true)]
            public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, int dwSize, out IntPtr lpNumberOfBytesRead);

            [DllImport("kernel32.dll", SetLastError = true)]
            public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out, MarshalAs(UnmanagedType.AsAny)] object lpBuffer, int dwSize, out IntPtr lpNumberOfBytesRead);

            [DllImport("kernel32.dll", SetLastError = true)]
            public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, IntPtr lpBuffer, int dwSize, out IntPtr lpNumberOfBytesRead);

            [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
            public static extern bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress, int dwSize, AllocationType dwFreeType);

            [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
            public static unsafe extern bool VirtualFreeEx(IntPtr hProcess, byte* pAddress, int size, AllocationType freeType);

            [DllImport("kernel32.dll", SetLastError = true)]
            [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
            [SuppressUnmanagedCodeSecurity]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool CloseHandle(IntPtr hObject);
        }

        [DllImport("user32.dll", SetLastError = true)]
        static extern IntPtr FindWindowEx(IntPtr hWndParent, IntPtr hWndChildAfter, string lpClassName, string lpWindowName);

        [DllImport("user32.dll", SetLastError = true)]
        static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

        static IntPtr GetSystemTrayHandle()
        {
            IntPtr hWndTray = FindWindow("Shell_TrayWnd", null);
            if (hWndTray != IntPtr.Zero)
            {
                hWndTray = FindWindowEx(hWndTray, IntPtr.Zero, "TrayNotifyWnd", null);
                if (hWndTray != IntPtr.Zero)
                {
                    hWndTray = FindWindowEx(hWndTray, IntPtr.Zero, "SysPager", null);
                    if (hWndTray != IntPtr.Zero)
                    {
                        hWndTray = FindWindowEx(hWndTray, IntPtr.Zero, "ToolbarWindow32", null);
                        return hWndTray;
                    }
                }
            }

            return IntPtr.Zero;
        }

        private static unsafe bool GetTBButton(IntPtr hToolbar, int i, ref TBBUTTON tbButton, ref string text, ref IntPtr ipWindowHandle)
        {
            // One page
            const int BUFFER_SIZE = 0x1000;

            byte[] localBuffer = new byte[BUFFER_SIZE];

            UInt32 processId = 0;
            UInt32 threadId = User32.GetWindowThreadProcessId(hToolbar, out processId);

            IntPtr hProcess = Kernel32.OpenProcess(ProcessAccessFlags.All, false, (int)processId);
            if (hProcess == IntPtr.Zero) { Debug.Assert(false); return false; }

            IntPtr ipRemoteBuffer = Kernel32.VirtualAllocEx(hProcess, IntPtr.Zero, (uint)BUFFER_SIZE, AllocationType.Commit, MemoryProtection.ReadWrite);

            if (ipRemoteBuffer == IntPtr.Zero) { Debug.Assert(false); return false; }

            // TBButton
            fixed (TBBUTTON* pTBButton = &tbButton)
            {
                IntPtr ipTBButton = new IntPtr(pTBButton);

                int b = (int)User32.SendMessage(hToolbar, (int)TB.GETBUTTON, i, ref ipRemoteBuffer);
                if (b == 0)
                {
                    Debug.Assert(false);
                    return false;
                }

                // this is fixed
                Int32 dwBytesRead = 0;
                IntPtr ipBytesRead = new IntPtr(&dwBytesRead);

                bool b2 = Kernel32.ReadProcessMemory(hProcess, ipRemoteBuffer, ipTBButton, sizeof(TBBUTTON), out ipBytesRead);

                if (!b2)
                {
                    Debug.Assert(false);
                    return false;
                }
            }

            // button text
            fixed (byte* pLocalBuffer = localBuffer)
            {
                IntPtr ipLocalBuffer = new IntPtr(pLocalBuffer);

                int chars = (int)User32.SendMessage(hToolbar, (int)TB.GETBUTTONTEXTW, tbButton.idCommand, ipRemoteBuffer);
                if (chars == -1) { Debug.Assert(false); return false; }

                // this is fixed
                Int32 dwBytesRead = 0;
                IntPtr ipBytesRead = new IntPtr(&dwBytesRead);

                bool b4 = Kernel32.ReadProcessMemory(
                    hProcess,
                    ipRemoteBuffer,
                    ipLocalBuffer,
                    BUFFER_SIZE,
                    out ipBytesRead);

                if (!b4) { Debug.Assert(false); return false; }

                text = Marshal.PtrToStringUni(ipLocalBuffer, chars);

                if (text == " ") text = String.Empty;
            }

            Kernel32.VirtualFreeEx(
                hProcess,
                ipRemoteBuffer,
                0,
                AllocationType.Release);

            Kernel32.CloseHandle(hProcess);

            return true;
        }

        public static void ScanToolbarButtons()
        {
            IntPtr _ToolbarWindowHandle = GetSystemTrayHandle();
            UInt32 count = (UInt32)User32.SendMessage(_ToolbarWindowHandle.ToInt32(), (uint)TB.BUTTONCOUNT, 0, 0);

            for (int i = 0; i < count; i++)
            {
                TBBUTTON tbButton = new TBBUTTON();
                string text = String.Empty;
                IntPtr ipWindowHandle = IntPtr.Zero;

                bool b = GetTBButton(_ToolbarWindowHandle, i, ref tbButton, ref text, ref ipWindowHandle);

                Debug.Print(text);
            }
        }
    }
}

我不是Windows API编程方面的专家,也不知道是什么导致了这种情况。有人能看一下并告诉我你的想法吗?

原始代码有问题,所以这里有一个版本应该可以更好地工作。注意,它必须以与操作系统(资源管理器)相同的位(32位对64位)运行才能工作,否则,它将不会读取任何内容。此外,它不需要编译为
不安全

public static void ScanToolbarButtons()
{
    var handle = GetSystemTrayHandle();
    if (handle == IntPtr.Zero)
        return;

    var count = SendMessage(handle, TB_BUTTONCOUNT, IntPtr.Zero, IntPtr.Zero).ToInt32();
    if (count == 0)
        return;

    GetWindowThreadProcessId(handle, out var pid);
    var hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, pid);
    if (hProcess == IntPtr.Zero)
        throw new Win32Exception(Marshal.GetLastWin32Error());

    var size = (IntPtr)Marshal.SizeOf<TBBUTTONINFOW>();
    var buffer = VirtualAllocEx(hProcess, IntPtr.Zero, size, MEM_COMMIT, PAGE_READWRITE);
    if (buffer == IntPtr.Zero)
    {
        CloseHandle(hProcess);
        throw new Win32Exception(Marshal.GetLastWin32Error());
    }

    for (int i = 0; i < count; i++)
    {
        var btn = new TBBUTTONINFOW();
        btn.cbSize = size.ToInt32();
        btn.dwMask = TBIF_BYINDEX | TBIF_COMMAND;
        if (WriteProcessMemory(hProcess, buffer, ref btn, size, out var written))
        {
            // we want the identifier
            var res = SendMessage(handle, TB_GETBUTTONINFOW, (IntPtr)i, buffer);
            if (res.ToInt32() >= 0)
            {
                if (ReadProcessMemory(hProcess, buffer, ref btn, size, out var read))
                {
                    // now get display text using the identifier
                    // first pass we ask for size
                    var textSize = SendMessage(handle, TB_GETBUTTONTEXTW, (IntPtr)btn.idCommand, IntPtr.Zero);
                    if (textSize.ToInt32() != -1)
                    {
                        // we need to allocate for the terminating zero and unicode
                        var utextSize = (IntPtr)((1 + textSize.ToInt32()) * 2);
                        var textBuffer = VirtualAllocEx(hProcess, IntPtr.Zero, utextSize, MEM_COMMIT, PAGE_READWRITE);
                        if (textBuffer != IntPtr.Zero)
                        {
                            res = SendMessage(handle, TB_GETBUTTONTEXTW, (IntPtr)btn.idCommand, textBuffer);
                            if (res == textSize)
                            {
                                var localBuffer = Marshal.AllocHGlobal(utextSize.ToInt32());
                                if (ReadProcessMemory(hProcess, textBuffer, localBuffer, utextSize, out read))
                                {
                                    var text = Marshal.PtrToStringUni(localBuffer);
                                    Console.WriteLine(text);
                                }
                                Marshal.FreeHGlobal(localBuffer);
                            }
                            VirtualFreeEx(hProcess, textBuffer, IntPtr.Zero, MEM_RELEASE);
                        }
                    }
                }
            }
        }
    }

    VirtualFreeEx(hProcess, buffer, IntPtr.Zero, MEM_RELEASE);
    CloseHandle(hProcess);
}

private static IntPtr GetSystemTrayHandle()
{
    var hwnd = FindWindowEx(IntPtr.Zero, IntPtr.Zero, "Shell_TrayWnd", null);
    hwnd = FindWindowEx(hwnd, IntPtr.Zero, "TrayNotifyWnd", null);
    hwnd = FindWindowEx(hwnd, IntPtr.Zero, "SysPager", null);
    return FindWindowEx(hwnd, IntPtr.Zero, "ToolbarWindow32", null);
}

[DllImport("kernel32", SetLastError = true)]
private static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);

[DllImport("kernel32", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hObject);

[DllImport("kernel32", SetLastError = true)]
private static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, ref TBBUTTONINFOW lpBuffer, IntPtr nSize, out IntPtr lpNumberOfBytesWritten);

[DllImport("kernel32", SetLastError = true)]
private static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, ref TBBUTTONINFOW lpBuffer, IntPtr nSize, out IntPtr lpNumberOfBytesRead);

[DllImport("kernel32", SetLastError = true)]
private static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, IntPtr lpBuffer, IntPtr nSize, out IntPtr lpNumberOfBytesRead);

[DllImport("user32", SetLastError = true)]
private static extern int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);

[DllImport("kernel32", SetLastError = true)]
private static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, IntPtr dwSize, int flAllocationType, int flProtect);

[DllImport("kernel32", SetLastError = true)]
private static extern bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress, IntPtr dwSize, int dwFreeType);

[DllImport("user32")]
private static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);

[DllImport("user32", SetLastError = true)]
private static extern IntPtr FindWindowEx(IntPtr hWndParent, IntPtr hWndChildAfter, string lpClassName, string lpWindowName);

private const int TBIF_BYINDEX = unchecked((int)0x80000000); // this specifies that the wparam in Get/SetButtonInfo is an index, not id
private const int TBIF_COMMAND = 0x20;
private const int MEM_COMMIT = 0x1000;
private const int MEM_RELEASE = 0x8000;
private const int PAGE_READWRITE = 0x4;
private const int TB_GETBUTTONINFOW = 1087;
private const int TB_GETBUTTONTEXTW = 1099;
private const int TB_BUTTONCOUNT = 1048;

private static bool IsWindowsVistaOrAbove() => Environment.OSVersion.Platform == PlatformID.Win32NT && Environment.OSVersion.Version.Major >= 6;
private static int PROCESS_ALL_ACCESS => IsWindowsVistaOrAbove() ? 0x001FFFFF : 0x001F0FFF;

[StructLayout(LayoutKind.Sequential)]
private struct TBBUTTONINFOW
{
    public int cbSize;
    public int dwMask;
    public int idCommand;
    public int iImage;
    public byte fsState;
    public byte fsStyle;
    public short cx;
    public IntPtr lParam;
    public IntPtr pszText;
    public int cchText;
}
公共静态无效工具栏按钮()
{
var handle=GetSystemTrayHandle();
if(handle==IntPtr.Zero)
回来
var count=SendMessage(handle,TB_BUTTONCOUNT,IntPtr.Zero,IntPtr.Zero).ToInt32();
如果(计数=0)
回来
GetWindowThreadProcessId(句柄,out变量pid);
var hProcess=OpenProcess(PROCESS\u ALL\u ACCESS,false,pid);
if(hProcess==IntPtr.Zero)
抛出新的Win32Exception(Marshal.GetLastWin32Error());
var size=(IntPtr)Marshal.SizeOf();
var buffer=VirtualAllocEx(hProcess,IntPtr.Zero,size,MEM\u COMMIT,PAGE\u READWRITE);
if(buffer==IntPtr.Zero)
{
CloseHandle(hProcess);
抛出新的Win32Exception(Marshal.GetLastWin32Error());
}
for(int i=0;i=0)
{
if(ReadProcessMemory(hProcess,buffer,ref btn,size,out var read))
{
//现在使用标识符获取显示文本
//第一关我们要尺寸
var textSize=SendMessage(handle,TB_getbuttonextw,(IntPtr)btn.idCommand,IntPtr.Zero);
如果(textSize.ToInt32()!=-1)
{
//我们需要为终止零和unicode进行分配
var utextSize=(IntPtr)((1+textSize.ToInt32())*2);
var textBuffer=VirtualAllocEx(hproces、IntPtr.Zero、utextSize、MEM\u COMMIT、PAGE\u READWRITE);
if(textBuffer!=IntPtr.Zero)
{
res=SendMessage(handle,TB_getbuttonextw,(IntPtr)btn.idCommand,textBuffer);
if(res==textSize)
{
var localBuffer=Marshal.AllocHGlobal(utextSize.ToInt32());
if(ReadProcessMemory(hProcess、textBuffer、localBuffer、utextSize、out read))
{
var text=Marshal.PtrToStringUni(localBuffer);
控制台写入线(文本);
}
Marshal.FreeHGlobal(localBuffer);
}
VirtualFreeEx(hProcess、textBuffer、IntPtr.Zero、MEM_RELEASE);
}
}
}
}
}
}
VirtualFreeEx(hProcess、buffer、IntPtr.Zero、MEM_RELEASE);
CloseHandle(hProcess);
}
私有静态IntPtr GetSystemTrayHandle()
{
var hwnd=FindWindowEx(IntPtr.Zero,IntPtr.Zero,“Shell_TrayWnd”,null);
hwnd=FindWindowEx(hwnd,IntPtr.Zero,“TrayNotifyWnd”,null);
hwnd=FindWindowEx(hwnd,IntPtr.Zero,“SysPager”,null);
返回FindWindowEx(hwnd,IntPtr.Zero,“ToolbarWindow32”,null);
}
[DllImport(“内核32”,SetLastError=true)]
私有静态外部IntPtr OpenProcess(int-dwDesiredAccess、bool-bInheritHandle、int-dwProcessId);
[DllImport(“内核32”,SetLastError=true)]
专用静态外部布尔闭合手柄(IntPtr hObject);
[DllImport(“内核32”,SetLastError=true)]
专用静态外部bool WriteProcessMemory(IntPtr hProcess、IntPtr lpBaseAddress、ref TBBUTTONINFOW lpBuffer、IntPtr nSize、out IntPtr lpnumberofbytes writed);
[DllImport(“内核32”,SetLastError=true)]
专用静态外部bool ReadProcessMemory(IntPtr hProcess、IntPtr lpBaseAddress、ref TBBUTTONINFOW lpBuffer、IntPtr nSize、out IntPtr lpNumberOfBytesRead);
[DllImport(“内核32”,SetLastError=true)]
专用静态外部bool ReadProcessMemory(IntPtr hProcess、IntPtr lpBaseAddress、IntPtr lpBuffer、IntPtr nSize、out IntPtr lpNumberOfBytesRead);
[DllImport(“user32”,SetLastError=true)]
私有静态外部程序int GetWindowThreadProcessId(IntPtr hWnd,out int lpdwProcessId);
[DllImport(“内核32”,SetLastError=true)]
私有静态外部IntPtr VirtualAllocEx(IntPtr hProcess、IntPtr lpAddress、IntPtr dwSize、intflallocationtype、intflprotect);
[DllImport(“内核32”,SetLastError=true)]
私有静态外部bool VirtualFreeEx(IntPtr hProcess、IntPtr lpAddress、IntPtr dwSize、intdwfreetype);
[DllImport(“user32”)]
私有静态外部IntPtr SendMessage(IntPtr hWnd、int Msg、IntPtr wParam、IntPtr lParam);
[DllImport(“user32”,SetLastError=true)]
私有静态外部IntPtr FindWindowEx(IntPtr hWndParent、IntPtr hWndChildAfter、字符串lpClassName、字符串lpWindowName);
private const int TBIF_BYINDEX=unchecked((int)0x8000000);//这规定了
public static void ScanToolbarButtons()
{
    var handle = GetSystemTrayHandle();
    if (handle == IntPtr.Zero)
        return;

    var count = SendMessage(handle, TB_BUTTONCOUNT, IntPtr.Zero, IntPtr.Zero).ToInt32();
    if (count == 0)
        return;

    GetWindowThreadProcessId(handle, out var pid);
    var hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, pid);
    if (hProcess == IntPtr.Zero)
        throw new Win32Exception(Marshal.GetLastWin32Error());

    var size = (IntPtr)Marshal.SizeOf<TBBUTTONINFOW>();
    var buffer = VirtualAllocEx(hProcess, IntPtr.Zero, size, MEM_COMMIT, PAGE_READWRITE);
    if (buffer == IntPtr.Zero)
    {
        CloseHandle(hProcess);
        throw new Win32Exception(Marshal.GetLastWin32Error());
    }

    for (int i = 0; i < count; i++)
    {
        var btn = new TBBUTTONINFOW();
        btn.cbSize = size.ToInt32();
        btn.dwMask = TBIF_BYINDEX | TBIF_COMMAND;
        if (WriteProcessMemory(hProcess, buffer, ref btn, size, out var written))
        {
            // we want the identifier
            var res = SendMessage(handle, TB_GETBUTTONINFOW, (IntPtr)i, buffer);
            if (res.ToInt32() >= 0)
            {
                if (ReadProcessMemory(hProcess, buffer, ref btn, size, out var read))
                {
                    // now get display text using the identifier
                    // first pass we ask for size
                    var textSize = SendMessage(handle, TB_GETBUTTONTEXTW, (IntPtr)btn.idCommand, IntPtr.Zero);
                    if (textSize.ToInt32() != -1)
                    {
                        // we need to allocate for the terminating zero and unicode
                        var utextSize = (IntPtr)((1 + textSize.ToInt32()) * 2);
                        var textBuffer = VirtualAllocEx(hProcess, IntPtr.Zero, utextSize, MEM_COMMIT, PAGE_READWRITE);
                        if (textBuffer != IntPtr.Zero)
                        {
                            res = SendMessage(handle, TB_GETBUTTONTEXTW, (IntPtr)btn.idCommand, textBuffer);
                            if (res == textSize)
                            {
                                var localBuffer = Marshal.AllocHGlobal(utextSize.ToInt32());
                                if (ReadProcessMemory(hProcess, textBuffer, localBuffer, utextSize, out read))
                                {
                                    var text = Marshal.PtrToStringUni(localBuffer);
                                    Console.WriteLine(text);
                                }
                                Marshal.FreeHGlobal(localBuffer);
                            }
                            VirtualFreeEx(hProcess, textBuffer, IntPtr.Zero, MEM_RELEASE);
                        }
                    }
                }
            }
        }
    }

    VirtualFreeEx(hProcess, buffer, IntPtr.Zero, MEM_RELEASE);
    CloseHandle(hProcess);
}

private static IntPtr GetSystemTrayHandle()
{
    var hwnd = FindWindowEx(IntPtr.Zero, IntPtr.Zero, "Shell_TrayWnd", null);
    hwnd = FindWindowEx(hwnd, IntPtr.Zero, "TrayNotifyWnd", null);
    hwnd = FindWindowEx(hwnd, IntPtr.Zero, "SysPager", null);
    return FindWindowEx(hwnd, IntPtr.Zero, "ToolbarWindow32", null);
}

[DllImport("kernel32", SetLastError = true)]
private static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);

[DllImport("kernel32", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hObject);

[DllImport("kernel32", SetLastError = true)]
private static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, ref TBBUTTONINFOW lpBuffer, IntPtr nSize, out IntPtr lpNumberOfBytesWritten);

[DllImport("kernel32", SetLastError = true)]
private static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, ref TBBUTTONINFOW lpBuffer, IntPtr nSize, out IntPtr lpNumberOfBytesRead);

[DllImport("kernel32", SetLastError = true)]
private static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, IntPtr lpBuffer, IntPtr nSize, out IntPtr lpNumberOfBytesRead);

[DllImport("user32", SetLastError = true)]
private static extern int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);

[DllImport("kernel32", SetLastError = true)]
private static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, IntPtr dwSize, int flAllocationType, int flProtect);

[DllImport("kernel32", SetLastError = true)]
private static extern bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress, IntPtr dwSize, int dwFreeType);

[DllImport("user32")]
private static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);

[DllImport("user32", SetLastError = true)]
private static extern IntPtr FindWindowEx(IntPtr hWndParent, IntPtr hWndChildAfter, string lpClassName, string lpWindowName);

private const int TBIF_BYINDEX = unchecked((int)0x80000000); // this specifies that the wparam in Get/SetButtonInfo is an index, not id
private const int TBIF_COMMAND = 0x20;
private const int MEM_COMMIT = 0x1000;
private const int MEM_RELEASE = 0x8000;
private const int PAGE_READWRITE = 0x4;
private const int TB_GETBUTTONINFOW = 1087;
private const int TB_GETBUTTONTEXTW = 1099;
private const int TB_BUTTONCOUNT = 1048;

private static bool IsWindowsVistaOrAbove() => Environment.OSVersion.Platform == PlatformID.Win32NT && Environment.OSVersion.Version.Major >= 6;
private static int PROCESS_ALL_ACCESS => IsWindowsVistaOrAbove() ? 0x001FFFFF : 0x001F0FFF;

[StructLayout(LayoutKind.Sequential)]
private struct TBBUTTONINFOW
{
    public int cbSize;
    public int dwMask;
    public int idCommand;
    public int iImage;
    public byte fsState;
    public byte fsStyle;
    public short cx;
    public IntPtr lParam;
    public IntPtr pszText;
    public int cchText;
}