项目中需要C#程序给浏览器窗口发送CTRL+CLICK消息。网查了若干文章(包括AI写的):单个发送键盘消息(使用win32的SendInput或PostMessage和SendMessage)和鼠标消息(使用win32的PostMessage和SendMessage)均没有问题,但组合发送时多数未能成功。在参考了某篇发送控制键盘消息的文章中发现,键盘抬起消息虚拟键(VK_KEYUP)编码为0x0002,而不是许多网文(包括AI)中的0x0102。编程测试,0x0002是可行的,并且SendInput与PostMessage可以组合发送键盘与鼠标消息。如下是主要的C#代码。
代码1:TWin32类,PostMessage函数及鼠标消息码。
public class TWin32
{
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
public const uint WM_LBUTTONDOWN = 0x0201; // 左键按下消息
public const uint WM_LBUTTONUP = 0x0202; // 左键释放消息
}
代码2:TKeyInput类,SendInput函数及相关结构体和键盘虚拟码。
public class TKeyInput {
[StructLayout(LayoutKind.Sequential)]
public struct TInput {
public int type;
public TUnion union;
}
[StructLayout(LayoutKind.Explicit)]
public struct TUnion {
[FieldOffset(0)] public TMouse mi;
[FieldOffset(0)] public TKeybd ki;
[FieldOffset(0)] public THware hi;
}
[StructLayout(LayoutKind.Sequential)]
public struct TMouse {
public int dx;
public int dy;
public int mouseData;
public int dwFlags;
public int time;
public IntPtr dwExtraInfo;
}
[StructLayout(LayoutKind.Sequential)]
public struct TKeybd {
public short wVk;
public short wScan;
public int dwFlags;
public int time;
public IntPtr dwExtraInfo;
}
[StructLayout(LayoutKind.Sequential)]
public struct THware {
public int uMsg;
public short wParamL;
public short wParamH;
}
[Flags]
public enum TInputType {
MOUSE = 0,
KEYBOARD = 1,
HARDWARE = 2
}
public const int VK_SHIFT = 0x0010; //虚拟键键常量
public const int VK_CONTROL = 0x0011;
public const int VK_KEYDOWN = 0x0100; //事件常量
public const int VK_KEYUP = 0x0002; //0x0101;
[DllImport("User32.dll", EntryPoint = "SendInput")]
public static extern uint SendInput(uint cInputs, TInput[] pInputs, int cbSize);
public static uint DownKey(short key) {
TInput[] inputs = new TInput[1];
inputs[0] = GetKeyInput(key, VK_KEYDOWN);
var result = SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(TInput)));
return result; // 0表示失败
}
public static uint UpKey(short key) {
TInput[] inputs = new TInput[1];
inputs[0] = GetKeyInput(key, VK_KEYUP);
var result = SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(TInput)));
return result;
}
private static TInput GetKeyInput(short key, int option) {
TInput input = new TInput {
type = (int)TInputType.KEYBOARD,
union = new TUnion {
ki = new TKeybd {
wVk = key,
dwFlags = option,
}
}
};
return input;
}
}
代码3:发送Key+Mouse组合消息(CTRL+CLICK)的函数。
public void PostDownUpMouseWithCtrlMessage(IntPtr wHandle, int screenX, int screenY) {
IntPtr lParam = new IntPtr(screenX + (screenY << 16));
TKeyInput.DownKey(TKeyInput.VK_CONTROL);
System.Threading.Thread.Sleep(50); // 延后一点点
TWin32.PostMessage(wHandle, TWin32.WM_LBUTTONDOWN, (new IntPtr(0)), lParam);
System.Threading.Thread.Sleep(50); // 延后一点点
TWin32.PostMessage(wHandle, TWin32.WM_LBUTTONUP, (new IntPtr(0)), lParam);
System.Threading.Thread.Sleep(50); // 延后一点点
TKeyInput.UpKey(TKeyInput.VK_CONTROL);
}
其它说明:
- SendInput发送的消息,将被前台Windows窗口捕获,PostMessage发送的则被指定wHandle句柄的窗口捕获(注意,不能是mini化的窗口),但鼠标消息事件参数的ctrlKey属性总是true(可以用网页程序测试),表明是CTRL+CLICK消息。
- C#程序模拟的键盘和鼠标消息,其消息事件参数的isTrusted=true,表明消息是“手工”按键或点击鼠标产生的。而js模拟的键盘鼠标消息,其isTrusted=false,且无法更改。一些视频播放网站中,需要手工点击鼠标网页才能激活,ji模拟的鼠标或键盘消息无效。
- PostMessage和SendInput发送消息的方式,均是将消息送入Windows的消息队列中,因此不能确保发送成功。还有一个Win32的SendMessage函数,发送的消息不是送入队列,因此不能与SendInput组合使用。
- 技术资料表明,SendInput可以发送硬件设备消息,包括鼠标、键盘、触摸屏、触摸笔等,但笔者编程用SendInput发送鼠标CLICK消息没有成功,待以后完善。