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

我在这里引用了以下线程来解答我的问题:

从系统托盘中的图标获取工具提示文本

我基本上想要做与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); } } } } 

代码编译好了,但是当我尝试执行ScanToolbarButtons()函数时,当它在GetTBButton函数中到达此行时,它会崩溃我的Windows资源管理器:

 int b = (int)User32.SendMessage(hToolbar, (int)TB.GETBUTTON, i, ref ipRemoteBuffer); 

我不是Windows API编程方面的专家,也不知道是什么造成了这种情况。 有人可以看看,让我知道你的想法吗?

原始代码存在问题,因此这是一个应该更好的版本。 请注意,它必须以与操作系统(资源管理器)相同的位(32位与64位)运行,否则,它将无法读取任何内容。 此外,它不需要编译为unsafe

 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(); 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; }