这些是 VK_MEDIA_NEXT_TRACK 和 VK_MEDIA_PREV_TRACK 虚拟键。它们的处理非常复杂:
- 任何拥有前台窗口的程序都会在调用 GetMessage() 时从其消息队列中检索他的击键
- 该程序的消息循环中的 TranslateMessage() 调用将击键转换为WM_APPCOMMAND 消息、APPCOMMAND_MEDIA_NEXTTRACK 或 APPCOMMAND_MEDIA_PREVIOUSTRACK 并将其发送到具有焦点的子窗口
- 子窗口不会使用它并将消息传递给 DefWindowProc()
- 它将消息传递给子窗口的父级。这会随着子窗口的嵌套而重复,最终到达顶层窗口
- 当它调用 DefWindowProc 时,Windows 会调用一个 shell 挂钩,以在任何已为 WH_SHELL 挂钩 HSHELL_APPCOMMAND 通知调用 SetWindowsHookEx() 的程序中触发回调。像 Windows Media Player 这样的程序将使用它
- 如果没有钩子拦截它,它最终会在 Explorer 中作为最后一个钩子最终执行操作。
这里的解决方案是不发送击键,而是向上移动之前列出的处理链。如果可以直接调用 shell 钩子但该功能没有公开,那就太好了。下一步是改为发送 WM_APPCOMMAND 消息。
这需要选择一个窗口来发送消息。由于 UAC,现在这很困难,一种称为 UIPI(用户界面特权隔离)的功能可以防止非提升的程序向提升的程序发送消息。所以你不能只使用前台的窗口,它很可能是一个以管理员权限运行的应用程序。与 Visual Studio 一样,PostMessage 和 keybd_event() 在您尝试时失败的可能原因。
诀窍是将它发送到您拥有的窗口。您遇到困难,您正在使用控制台模式项目。这可以解决。项目 + 添加引用,选择 System.Windows.Forms。向您的项目添加一个新类并粘贴如下所示的代码。将您的 keybd_event() 调用替换为
AppCommand.Send(AppCommands.MediaNext);
我把你可以发送的所有命令的整套工具包都扔进去了。我提供了一个 Cleanup() 方法来释放资源,不需要调用它。该代码与 .NET 版本 2.0 到 4.5.1 兼容
using System;
using System.Threading;
using System.Windows.Forms;
using System.Runtime.InteropServices;
public enum AppCommands {
BrowserBack = 1,
BrowserForward = 2,
BrowserRefresh = 3,
BrowserStop = 4,
BrowserSearch = 5,
BrowserFavorite = 6,
BrowserHome = 7,
VolumeMute = 8,
VolumeDown = 9,
VolumeUp = 10,
MediaNext = 11,
MediaPrevious = 12,
MediaStop = 13,
MediaPlayPause = 14,
LaunchMail = 15,
LaunchMediaSelect = 16,
LaunchApp1 = 17,
LaunchApp2 = 18,
BassDown = 19,
BassBoost = 20,
BassUp = 21,
TrebleUp = 22,
TrebleDown = 23,
MicrophoneMute = 24,
MicrophoneVolumeUp = 25,
MicrophoneVolumeDown = 26,
Help = 27,
Find = 28,
New = 29,
Open = 30,
Close = 31,
Save = 32,
Print = 33,
Undo = 34,
Redo = 35,
Copy = 36,
Cut = 37,
Paste = 38,
ReplyToMail = 39,
ForwardMail = 40,
SendMail = 41,
SpellCheck = 42,
Dictate = 43,
MicrophoneOnOff = 44,
CorrectionList = 45,
MediaPlay = 46,
MediaPause = 47,
MediaRecord = 48,
MediaFastForward = 49,
MediaRewind = 50,
MediaChannelUp = 51,
MediaChannelDown = 52,
Delete = 53,
Flip3D = 54
}
public static class AppCommand {
public static void Send(AppCommands cmd) {
if (frm == null) Initialize();
frm.Invoke(new MethodInvoker(() => SendMessage(frm.Handle, WM_APPCOMMAND, frm.Handle, (IntPtr)((int)cmd << 16))));
}
private static void Initialize() {
// Run the message loop on another thread so we're compatible with a console mode app
var t = new Thread(() => {
frm = new Form();
var dummy = frm.Handle;
frm.BeginInvoke(new MethodInvoker(() => mre.Set()));
Application.Run();
});
t.SetApartmentState(ApartmentState.STA);
t.IsBackground = true;
t.Start();
mre.WaitOne();
}
public static void Cleanup() {
if (frm != null) {
frm.BeginInvoke(new MethodInvoker(() => {
frm.Close();
Application.ExitThread();
mre.Set();
}));
mre.WaitOne();
frm = null;
}
}
private static ManualResetEvent mre = new ManualResetEvent(false);
private static Form frm;
// Pinvoke
private const int WM_APPCOMMAND = 0x319;
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
}