我在问以下问题:
Get ToolTip Text from Icon in System Tray
我基本上想做与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编程专家,也不知道是什么原因造成的。有人可以看一下让我知道您的想法吗?
答案 0 :(得分:2)
原始代码存在问题,因此这是一个效果更好的版本。请注意,它必须以与操作系统(资源管理器)相同的位(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<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;
}