我正在尝试从 .NET 8 / C# 后台服务中具有特定供应商 ID、产品 ID 的 HID 读卡器进行读取。我可以成功注册设备,但刷卡时无法读取读卡器。看起来
ProcessRawInput
从来没有被叫过。
读卡器模拟键盘,所以我可以刷卡并在记事本中读取卡号。读卡器和卡正在工作。
这是我的代码:
namespace HIDReaderService
{
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
private IntPtr _messageOnlyWindowHandle = IntPtr.Zero;
private const ushort VENDOR_ID = 0xFFFF; //input vendor id of attached keyboard/reader
private const ushort PRODUCT_ID = 0xFF; //input product id of attached keyboard/reader
private delegate IntPtr WindowProcDelegate(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
private WindowProcDelegate _windowProcDelegate;
public Worker(ILogger<Worker> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Starting HID reader service...");
_windowProcDelegate = new WindowProcDelegate(WindowProc);
_messageOnlyWindowHandle = CreateMessageOnlyWindow();
if (_messageOnlyWindowHandle == IntPtr.Zero)
{
_logger.LogError("Failed to create message-only window.");
return;
}
else
{
_logger.LogInformation("Message-only window created successfully.");
}
// Register the HID devices
if (!RegisterHIDDevice(_messageOnlyWindowHandle))
{
_logger.LogError("Failed to register HID device.");
return;
}
_logger.LogInformation("Listening for HID device input...");
var messageLoopThread = new Thread(MessageLoop);
messageLoopThread.Start();
while (!stoppingToken.IsCancellationRequested)
{
await Task.Delay(100);
}
}
// Define a delegate matching the signature of the window procedure
private delegate IntPtr WndProcDelegate(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
// Update CreateMessageOnlyWindow method
private IntPtr CreateMessageOnlyWindow()
{
// Create a delegate instance for the Window Procedure
WndProcDelegate wndProcDelegate = new WndProcDelegate(WindowProc);
// Define and register the window class
var wndClass = new WNDCLASS
{
// Convert delegate to function pointer
lpfnWndProc = Marshal.GetFunctionPointerForDelegate(wndProcDelegate),
hInstance = Marshal.GetHINSTANCE(typeof(Program).Module),
lpszClassName = "MessageOnlyWindow"
};
ushort classAtom = RegisterClass(ref wndClass);
if (classAtom == 0)
{
int error = Marshal.GetLastWin32Error();
_logger.LogError($"Failed to register window class. Error: {error}");
return IntPtr.Zero;
}
// Create the message-only window
IntPtr hWnd = CreateWindowEx(0, "MessageOnlyWindow", "Message Only Window",
0, 0, 0, 0, 0, HWND_MESSAGE, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
if (hWnd == IntPtr.Zero)
{
int error = Marshal.GetLastWin32Error();
_logger.LogError($"Failed to create message-only window. Error: {error}");
return IntPtr.Zero;
}
_logger.LogInformation("Message-only window created successfully.");
RegisterHIDDevice(hWnd);
return hWnd;
}
// Window Procedure method
private IntPtr WindowProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
// Log every message that arrives in WindowProc to confirm message reception
_logger.LogInformation($"WindowProc received message: {msg}");
// Check if we received the expected WM_INPUT message
if (msg == WM_INPUT)
{
_logger.LogInformation("WM_INPUT message received.");
ProcessRawInput(lParam);
}
return DefWindowProc(hWnd, msg, wParam, lParam);
}
private bool RegisterHIDDevice(IntPtr hWnd)
{
var rid = new RAWINPUTDEVICE[1];
rid[0].UsagePage = 0x01; // Generic Desktop Controls
rid[0].Usage = 0x06; // Keyboard
rid[0].Flags = RIDEV_INPUTSINK;
rid[0].Target = hWnd;
bool result = RegisterRawInputDevices(rid, (uint)rid.Length, (uint)Marshal.SizeOf(typeof(RAWINPUTDEVICE)));
if (!result)
{
int errorCode = Marshal.GetLastWin32Error();
_logger.LogError($"RegisterRawInputDevices failed. Error Code: {errorCode}");
}
else
{
_logger.LogInformation("HID device registered successfully.");
}
return result;
}
private void MessageLoop()
{
MSG msg;
while (GetMessage(out msg, IntPtr.Zero, 0, 0))
{
_logger.LogInformation($"Received message: {msg.message}");
TranslateMessage(ref msg);
DispatchMessage(ref msg);
}
}
private void ProcessRawInput(IntPtr lParam)
{
uint dwSize = 0;
GetRawInputData(lParam, RID_INPUT, IntPtr.Zero, ref dwSize, (uint)Marshal.SizeOf(typeof(RAWINPUTHEADER)));
IntPtr buffer = Marshal.AllocHGlobal((int)dwSize);
try
{
if (GetRawInputData(lParam, RID_INPUT, buffer, ref dwSize, (uint)Marshal.SizeOf(typeof(RAWINPUTHEADER))) == dwSize)
{
var raw = (RAWINPUT)Marshal.PtrToStructure(buffer, typeof(RAWINPUT));
if (raw.header.dwType == RIM_TYPEKEYBOARD)
{
_logger.LogInformation($"RawInput Keyboard Event: VKey={raw.keyboard.VKey}, Message={raw.keyboard.Message}");
if (raw.keyboard.Message == WM_KEYDOWN && raw.keyboard.VKey == VK_RETURN)
{
_logger.LogInformation("Enter key pressed!");
// Handle enter key, e.g., launch a browser
Process.Start("notepad");
}
}
}
}
finally
{
Marshal.FreeHGlobal(buffer);
}
}
private void GetDeviceInfo(IntPtr hDevice, out ushort vendorId, out ushort productId)
{
uint size = 0;
GetRawInputDeviceInfo(hDevice, RIDI_DEVICENAME, IntPtr.Zero, ref size);
if (size == 0)
{
vendorId = productId = 0;
return;
}
var nameBuffer = Marshal.AllocHGlobal((int)size);
try
{
if (GetRawInputDeviceInfo(hDevice, RIDI_DEVICENAME, nameBuffer, ref size) > 0)
{
var deviceName = Marshal.PtrToStringAnsi(nameBuffer);
if (deviceName != null)
{
_logger.LogInformation($"Device name: {deviceName}");
var vidIndex = deviceName.IndexOf("VID_") + 4;
var pidIndex = deviceName.IndexOf("PID_") + 4;
vendorId = Convert.ToUInt16(deviceName.Substring(vidIndex, 4), 16);
productId = Convert.ToUInt16(deviceName.Substring(pidIndex, 4), 16);
return;
}
}
}
finally
{
Marshal.FreeHGlobal(nameBuffer);
}
vendorId = productId = 0;
}
private const int WM_KEYDOWN = 0x100;
private const int WM_INPUT = 0x00FF;
private const uint RID_INPUT = 0x10000003;
private const uint RIM_TYPEKEYBOARD = 1;
private const uint VK_RETURN = 0x0D;
private const int RIDEV_INPUTSINK = 0x00000100;
private const uint RIDI_DEVICENAME = 0x20000007;
private static readonly IntPtr HWND_MESSAGE = new IntPtr(-3);
[DllImport("user32.dll")]
private static extern bool RegisterRawInputDevices(RAWINPUTDEVICE[] pRawInputDevices, uint uiNumDevices, uint cbSize);
[DllImport("user32.dll")]
private static extern uint GetRawInputData(IntPtr hRawInput, uint uiCommand, IntPtr pData, ref uint pcbSize, uint cbSizeHeader);
[DllImport("user32.dll")]
private static extern uint GetRawInputDeviceInfo(IntPtr hDevice, uint uiCommand, IntPtr pData, ref uint pcbSize);
[DllImport("user32.dll")]
private static extern IntPtr CreateWindowEx(int exStyle, string lpClassName, string lpWindowName, int dwStyle, int x, int y, int nWidth, int nHeight, IntPtr hWndParent, IntPtr hMenu, IntPtr hInstance, IntPtr lpParam);
[DllImport("user32.dll", SetLastError = true)]
private static extern ushort RegisterClass(ref WNDCLASS lpWndClass);
[DllImport("user32.dll")]
private static extern bool GetMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax);
[DllImport("user32.dll")]
private static extern bool TranslateMessage(ref MSG lpMsg);
[DllImport("user32.dll")]
private static extern IntPtr DispatchMessage(ref MSG lpMsg);
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr DefWindowProc(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam);
[StructLayout(LayoutKind.Sequential)]
private struct WNDCLASS
{
public int style;
public IntPtr lpfnWndProc;
public int cbClsExtra;
public int cbWndExtra;
public IntPtr hInstance;
public IntPtr hIcon;
public IntPtr hCursor;
public IntPtr hbrBackground;
public string lpszMenuName;
public string lpszClassName;
}
[StructLayout(LayoutKind.Sequential)]
private struct MSG
{
public IntPtr hWnd;
public uint message;
public IntPtr wParam;
public IntPtr lParam;
public uint time;
public POINT pt;
}
[StructLayout(LayoutKind.Sequential)]
private struct POINT
{
public int x;
public int y;
}
[StructLayout(LayoutKind.Sequential)]
private struct RAWINPUTHEADER
{
public uint dwType;
public uint dwSize;
public IntPtr hDevice;
public IntPtr wParam;
}
[StructLayout(LayoutKind.Explicit)]
private struct RAWINPUT
{
[FieldOffset(0)]
public RAWINPUTHEADER header;
[FieldOffset(16)]
public RAWKEYBOARD keyboard;
}
[StructLayout(LayoutKind.Sequential)]
private struct RAWKEYBOARD
{
public ushort MakeCode;
public ushort Flags;
public ushort Reserved;
public ushort VKey;
public uint Message;
public uint ExtraInformation;
}
[StructLayout(LayoutKind.Sequential)]
public struct RAWINPUTDEVICE
{
public ushort UsagePage;
public ushort Usage;
public int Flags;
public IntPtr Target;
}
}
}
出于测试目的,可以使用任何附加的键盘供应商、产品 ID。
正如 Selvin 所指出的,我正在为 MessageLoop 启动一个新线程。在 ExecuteAsync 中调用 MessageLoop 解决了该问题。谢谢你塞尔文。