检测指纹 Reader 在控制台应用程序外部输入

Detect Fingerprint Reader Input outside of Console App

所以我需要制作一个使用外部指纹 reader 进行用户识别的 Metro GUI 应用程序。

到目前为止,我已经用 C++ 创建了一个 DLL,因此我可以调用我在 C# 中创建的名为 CaptureSample() 的方法来引用该 DLL。 CaptureSample() 方法为我提供了一个表示扫描指纹的字节数组(来自指纹 reader 的灰度图像)。到目前为止还不错。它这样做的方法是使用 Microsoft Biometrics Framework 访问 reader,等待 reader 检测手指放在它上面,然后将扫描的数据发回指纹.

这一切都很好。但是有一个问题:我必须 运行 我作为管理员制作的任何应用程序才能使用该框架。 这也是我不能 [=44] 的原因=] 来自 Web 服务的框架,因为 Web 服务是 运行 作为网络用户或类似用户,使得库在尝试使用它时拒绝任何访问。所以我必须制作一个额外的控制台程序来 运行 它。

现在事情变得棘手了。为了使指纹 reader 与 Windows Metro GUI 应用程序一起使用,您必须 运行 该应用程序作为管理员。这是不可能的,因为沙盒中的所有 Metro GUI 应用程序 运行。我必须创建一个外部控制台应用程序来调用该应用程序的 DLL 功能,然后将其结果发送回该应用程序。

让事情变得更加复杂的是,我发现 .NET 的 Windows Phone 子集没有 MSMQ,它本来可以很好用。因此,我创建了 Metro 应用程序需要调用的本地 WCF 服务,然后 WCF 服务通过 MSMQ 调用控制台程序,并在从控制台应用程序接收到信息时将信息发送回 Metro GUI 应用程序。

到目前为止一切顺利。理论上。我在这个过程中 运行 遇到了一个问题,因为控制台应用程序确实 运行 并且在被询问时已准备好进行扫描。但是当我扫描我的手指时,什么也没有发生。控制台需要焦点才能工作,这是不可能的,因为应用程序 运行 就像一个信息亭,除非进行维护,否则根本不应该离开 Metro GUI 应用程序。

我查看了各种用于检测 C# 应用程序外的键盘输入的解决方案,但我认为这不适用于指纹 Reader 或者即使适用,我也不知道该怎么做去做。有什么建议吗?

为了确保安全,我附上了工作流程图,这样更容易理解。 (这是 P5100 Zvetcobiometrics 指纹 Reader,如果有人好奇):

所以,我 运行 使用 Windows 表单遇到了这样的问题,但与您描述的问题不完全相同。

我知道我的解决方案对您来说不是预编码的简单日解决方案,但我觉得经过一些小的修改,我几年前克服的一个障碍可能对您有用。如果您认为我的解决方案对您有帮助,我可以将任何遗漏的部分发送给您。

我创建了一个使用条形码扫描仪的库存管理系统,我决定通过编组某些 C++ 类 来实现能够处理来自设备的输入的功能,而无需使用输入控件比如文本框。我需要应用程序能够处理条形码输入并做出决定,而无需任何额外的用户交互或要求。只需扫描即可。立即使用来自 keyboard/HID 设备的输入是我认为此解决方案符合您的问题的原因。

在测试我编写的应用程序时,我能够进入全屏游戏,并且仍然能够按预期在 windows 表格库存应用程序中使用条形码扫描仪。同样的功能在控制台环境中应该也能正常工作,因为控制台应用程序在后台运行时不会停止。您甚至可以将其设置为 NT AUTHORITY 并阻止它在 运行 作为服务显示到桌面时它仍然会突然消失。

我所做的是使用 Win32API 通过设备进行反映,将设备与通过应用程序指定的设备(用户选择)进行匹配,并基本上为该特定设备建立一个侦听器。

您可以使用您的控制台应用程序来触发指纹传感器,方法是 运行将控制台应用程序设置为本地服务帐户或以编程方式获取必要的授权(这将允许您 运行 它在提升没有 UAC 东西妨碍你的权限),然后在你的 Metro 应用程序中使用它来读取设备发送的输入。

下面是一些代码文件,用于执行我所描述的内容,并已针对我的条码扫描器功能进行了修改。

再说一次,如果你想看到任何遗漏的部分,请私下联系我。

PS:从技术上讲,这可以用作拦截密钥和此类的 hack,因此我将声明一个免责声明供您自行决定使用,我不对任何愚蠢的人可能造成的后果负责使用此代码。

BarcodeScannerListenerInteropHelper.h:

#include <winuser.h>

BEGIN_INTEROP_NAMESPACE

using namespace System;
using namespace System::Collections::Generic;
using namespace HFSLIB::Barcode;
using namespace HFSLIB::Barcode::Interop;
using namespace HFSLIB::Barcode::Infrastructure::BarcodeScannerListener;

/// <summary>
/// Provides some helper methods that help the BarcodeScannerListener use native
/// Windows APIs without resorting to P/Invoking from C#.
/// </summary>
public ref class BarcodeScannerListenerInteropHelper
{
    public:
        /// <summary>
        /// Returns a dictionary of barcode device handles to information about
        /// the device.
        /// </summary>
        /// <param name="hardwareIds">The enumerable of hardware IDs to filter by.</param>
        /// <returns>The device handle-to-information mapping of the filtered hardware IDs.</returns>
        Dictionary<IntPtr, BarcodeScannerDeviceInfo^>^ InitializeBarcodeScannerDeviceHandles(
            IEnumerable<String^>^ hardwareIds);

        /// <summary>
        /// Registers ourselves to listen to raw input from keyboard-like devices.
        /// </summary>
        /// <param name="hwnd">the handle of the form that will receive the raw
        /// input messages</param>
        /// <exception cref="InvalidOperationException">if the call to register with the
        /// raw input API fails for some reason</exception>
        void HookRawInput(IntPtr hwnd);

        /// <summary>
        /// Gets information from a WM_INPUT message.
        /// </summary>
        /// <param name="rawInputHeader">The LParam from the WM_INPUT message.</param>
        /// <param name="deviceHandle">[Out] The device handle that the message came from.</param>
        /// <param name="handled">[Out] True if the message represents a keystroke from that device.</param>
        /// <param name="buffer">[Out] If handled is true, this contains the characters that the keystroke represents.</param>
        void GetRawInputInfo(
            IntPtr rawInputHeader, 
            IntPtr% deviceHandle, 
            bool% handled,
            String^% buffer);
    private:
        /// <summary>
        /// Converts a native raw input type into our version.
        /// </summary>
        /// <param name="rawInputType">The raw input type.</param>
        /// <returns>Our version of the type.</returns>
        static BarcodeScannerDeviceType GetBarcodeScannerDeviceType(DWORD rawInputType);
};

END_INTEROP_NAMESPACE

BarcodeScannerListenerInteropHelper.cpp:

#include "BarcodeScannerListenerInteropHelper.h"
using namespace System::ComponentModel;

BEGIN_INTEROP_NAMESPACE

/// <summary>
/// Gets information from a WM_INPUT message.
/// </summary>
/// <param name="rawInputHeader">The LParam from the WM_INPUT message.</param>
/// <param name="deviceHandle">[Out] The device handle that the message came from.</param>
/// <param name="handled">[Out] True if the message represents a keystroke from that device.</param>
/// <param name="buffer">[Out] If handled is true, this contains the characters that the keystroke represents.</param>
void BarcodeScannerListenerInteropHelper::GetRawInputInfo(
    IntPtr rawInputHeader,
    IntPtr% deviceHandle, 
    bool% handled,
    String^% buffer)
{
    UINT cbSize;
    HRAWINPUT hRawInput;

    hRawInput = (HRAWINPUT)rawInputHeader.ToPointer();
    if (GetRawInputData(hRawInput, RID_INPUT, NULL, &cbSize, sizeof(RAWINPUTHEADER)) == 0)
    {
        RAWINPUT* raw;

        raw = (RAWINPUT*)malloc(cbSize);

        if (GetRawInputData(hRawInput, RID_INPUT, raw, &cbSize, sizeof(RAWINPUTHEADER)) == cbSize)
        {
            deviceHandle = IntPtr(raw->header.hDevice);
            handled = raw->header.dwType == RIM_TYPEKEYBOARD &&
                raw->data.keyboard.Message == WM_KEYDOWN;

            if (handled)
            {
                BYTE state[256];

                // Force the keyboard status cache to update
                GetKeyState(0);

                // Note: GetKeyboardState only returns valid state when
                // the application has focus -- this is why we weren't
                // getting shift keys when the application was not focused
                if (GetKeyboardState(state))
                {
                    WCHAR unmanagedBuffer[64];

                    if (ToUnicode(raw->data.keyboard.VKey,
                            raw->data.keyboard.MakeCode,
                            state,
                            unmanagedBuffer,
                            64,
                            0) > 0)
                    {
                        buffer = gcnew String(unmanagedBuffer);
                    }
                }
            }
        }

        free(raw);
    }
}

/// <summary>
/// Registers ourselves to listen to raw input from keyboard-like devices.
/// </summary>
/// <param name="hwnd">the handle of the form that will receive the raw
/// input messages</param>
/// <exception cref="InvalidOperationException">if the call to register with the
/// raw input API fails for some reason</exception>
void BarcodeScannerListenerInteropHelper::HookRawInput(IntPtr hwnd)
{
    RAWINPUTDEVICE rid[1];

    rid[0].dwFlags = 0;
    rid[0].hwndTarget = (HWND)hwnd.ToPointer();
    rid[0].usUsage = 0x06;     // Keyboard Usage ID
    rid[0].usUsagePage = 0x01; // USB HID Generic Desktop Page

    if (!RegisterRawInputDevices(rid, 1, sizeof(RAWINPUTDEVICE)))
    {
        InvalidOperationException^ e;

        e = gcnew InvalidOperationException(
            "The barcode scanner listener could not register for raw input devices.",
            gcnew Win32Exception());
        throw e;
    }
}

/// <summary>
/// Returns a dictionary of barcode device handles to information about
/// the device.
/// </summary>
Dictionary<IntPtr, BarcodeScannerDeviceInfo^>^ BarcodeScannerListenerInteropHelper::InitializeBarcodeScannerDeviceHandles(IEnumerable<String^>^ hardwareIds)
{
    Dictionary<IntPtr, BarcodeScannerDeviceInfo^>^ devices;
    UINT uiNumDevices;
    UINT cbSize;

    devices = gcnew Dictionary<IntPtr, BarcodeScannerDeviceInfo^>();
    uiNumDevices = 0;
    cbSize = sizeof(RAWINPUTDEVICELIST);

    if (GetRawInputDeviceList(NULL, &uiNumDevices, cbSize) != -1)
    {
        PRAWINPUTDEVICELIST pRawInputDeviceList;

        if (pRawInputDeviceList = (PRAWINPUTDEVICELIST)malloc(cbSize * uiNumDevices))
        {
            if (GetRawInputDeviceList(pRawInputDeviceList, &uiNumDevices, cbSize) != -1)
            {
                for (UINT i = 0; i < uiNumDevices; ++i)
                {
                    UINT pcbSize;
                    RAWINPUTDEVICELIST rid;

                    rid = pRawInputDeviceList[i];

                    if (GetRawInputDeviceInfo(rid.hDevice, RIDI_DEVICENAME, NULL, &pcbSize) >= 0 &&
                        pcbSize > 0)
                    {
                        WCHAR* deviceName;

                        deviceName = (WCHAR*)malloc(sizeof(WCHAR) * (pcbSize + 1));
                        if (GetRawInputDeviceInfo(rid.hDevice, RIDI_DEVICENAME, deviceName, &pcbSize) >= 0)
                        {
                            bool add;
                            IntPtr deviceHandle;
                            BarcodeScannerDeviceInfo^ info;
                            String^ managedDeviceName;

                            add = false;
                            deviceHandle = IntPtr(rid.hDevice);
                            managedDeviceName = gcnew String(deviceName);

                            for each (String^ hardwareId in hardwareIds)
                            {
                                if (managedDeviceName->IndexOf(hardwareId, StringComparison::OrdinalIgnoreCase) >= 0)
                                {
                                    add = true;
                                    break;
                                }
                            }

                            if (add)
                            {
                                info = gcnew BarcodeScannerDeviceInfo(
                                    managedDeviceName,
                                    BarcodeScannerListenerInteropHelper::GetBarcodeScannerDeviceType(rid.dwType),
                                    deviceHandle);

                                devices->Add(deviceHandle, info);
                            }
                        }

                        free(deviceName);
                    }
                }
            }

            free(pRawInputDeviceList);
        }
    }

    return devices;
}

/// <summary>
/// Converts a native raw input type into our version.
/// </summary>
/// <param name="rawInputType">The raw input type.</param>
/// <returns>Our version of the type.</returns>
BarcodeScannerDeviceType BarcodeScannerListenerInteropHelper::GetBarcodeScannerDeviceType(DWORD rawInputType)
{
    BarcodeScannerDeviceType type;

    switch (rawInputType)
    {
        case RIM_TYPEHID:
            type = BarcodeScannerDeviceType::HumanInterfaceDevice;
            break;
        case RIM_TYPEKEYBOARD:
            type = BarcodeScannerDeviceType::Keyboard;
            break;
        default:
            type = BarcodeScannerDeviceType::Unknown;
            break;
    }

    return type;
}

END_INTEROP_NAMESPACE

BarcodeScannerListener.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Permissions;
using System.Text;
using System.Windows.Forms;
using HFSLIB.Barcode.Infrastructure.BarcodeScannerListener;
using HFSLIB.Barcode.Interop;

namespace HFSLIB.Barcode
{
/// <summary>
/// This class uses Windows's native Raw Input API to listen for input from
/// a certain set of barcode scanners and devices. This way, the application
/// can receive input from a barcode scanner without the user having to
/// worry about whether or not a certain text field has focus, which was a
/// big problem
/// </summary>
public class BarcodeScannerListener : NativeWindow
{
    /// <summary>
    /// A mapping of device handles to information about the barcode scanner
    /// devices.
    /// </summary>
    private Dictionary<IntPtr, BarcodeScannerDeviceInfo> devices;

    /// <summary>
    /// The WM_KEYDOWN filter.
    /// </summary>
    private BarcodeScannerKeyDownMessageFilter filter;

    /// <summary>
    /// The barcode currently being read.
    /// </summary>
    private StringBuilder keystrokeBuffer;

    /// <summary>
    /// The interop helper.
    /// </summary>
    private BarcodeScannerListenerInteropHelper interopHelper =
        new BarcodeScannerListenerInteropHelper();

    /// <summary>
    /// Event fired when a barcode is scanned.
    /// </summary>
    public event EventHandler BarcodeScanned;

    /// <summary>
    /// Attaches the listener to the given form.
    /// </summary>
    /// <param name="form">The form to attach to.</param>
    public void Attach(Form form)
    {
        IntPtr hwnd;

        if (form == null)
        {
            throw new ArgumentNullException("form");
        }

        hwnd = form.Handle;

        this.keystrokeBuffer = new StringBuilder();

        this.InitializeBarcodeScannerDeviceHandles();
        this.interopHelper.HookRawInput(hwnd);
        this.HookHandleEvents(form);

        this.AssignHandle(hwnd);

        this.filter = new BarcodeScannerKeyDownMessageFilter();
        Application.AddMessageFilter(this.filter);
    }

    /// <summary>
    /// Hook into the form's WndProc message. We listen for WM_INPUT and do
    /// special processing on the raw data.
    /// </summary>
    /// <param name="m">the message</param>
    [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
    protected override void WndProc(ref Message m)
    {
        switch (m.Msg)
        {
            case NativeMethods.WM_INPUT:
                if (this.ProcessRawInputMessage(m.LParam))
                {
                    this.filter.FilterNext = true;
                }

                break;
        }

        base.WndProc(ref m);
    }

    /// <summary>
    /// Fires the barcode scanned event.
    /// </summary>
    /// <param name="deviceInfo">information about the device that generated
    /// the barcode</param>
    private void FireBarcodeScanned(BarcodeScannerDeviceInfo deviceInfo)
    {
        string barcode;
        EventHandler handler;

        barcode = this.keystrokeBuffer.ToString();

        if (barcode != null && barcode.Length > 0)
        {
            handler = this.BarcodeScanned;

            this.keystrokeBuffer = new StringBuilder();

            if (handler != null)
            {
                handler(this, new BarcodeScannedEventArgs(barcode, deviceInfo));
            }
        }
    }

    /// <summary>
    /// Hooks into the form's HandleCreated and HandleDestoryed events
    /// to ensure that we start and stop listening at appropriate times.
    /// </summary>
    /// <param name="form">the form to listen to</param>
    private void HookHandleEvents(Form form)
    {
        form.HandleCreated += this.OnHandleCreated;
        form.HandleDestroyed += this.OnHandleDestroyed;
    }

    /// <summary>
    /// Initializes the barcode scanner device handles.
    /// </summary>
    private void InitializeBarcodeScannerDeviceHandles()
    {
        BarcodeScannerListenerConfigurationSection config;
        BarcodeScannerListenerConfigurationElementCollection hardwareIdsConfig;
        IEnumerable<string> hardwareIds;

        config = BarcodeScannerListenerConfigurationSection.GetConfiguration();
        hardwareIdsConfig = config.HardwareIds;
        hardwareIds = from hardwareIdConfig in hardwareIdsConfig.Cast<BarcodeScannerListenerConfigurationElement>()
                      select hardwareIdConfig.Id;

        this.devices = this.interopHelper.InitializeBarcodeScannerDeviceHandles(hardwareIds);
    }

    /// <summary>
    /// When the form's handle is created, let's hook into it so we can see
    /// the WM_INPUT event.
    /// </summary>
    /// <param name="sender">the form whose handle was created</param>
    /// <param name="e">the event arguments</param>
    private void OnHandleCreated(object sender, EventArgs e)
    {
        this.AssignHandle(((Form)sender).Handle);
    }

    /// <summary>
    /// When the form's handle is destroyed, let's unhook from it so we stop
    /// listening and allow the OS to free up its resources.
    /// </summary>
    /// <param name="sender">the form whose handle was destroyed</param>
    /// <param name="e">the event arguments</param>
    private void OnHandleDestroyed(object sender, EventArgs e)
    {
        this.ReleaseHandle();
    }

    /// <summary>
    /// Process the given WM_INPUT message.
    /// </summary>
    /// <param name="rawInputHeader">the rawInputHeader of the message</param>
    /// <returns>whether or not the keystroke was handled</returns>
    private bool ProcessRawInputMessage(IntPtr rawInputHeader)
    {
        BarcodeScannerDeviceInfo deviceInfo;
        bool handled;
        bool keystroke;
        string localBuffer;
        IntPtr rawInputDeviceHandle;

        handled = false;
        keystroke = false;
        localBuffer = string.Empty;
        rawInputDeviceHandle = IntPtr.Zero;

        this.interopHelper.GetRawInputInfo(
            rawInputHeader,
            ref rawInputDeviceHandle,
            ref keystroke,
            ref localBuffer);

        if (this.devices.TryGetValue(rawInputDeviceHandle, out deviceInfo) && keystroke)
        {
            handled = true;

            if (localBuffer.Length == 1 && localBuffer[0] == 0xA)
            {
                this.FireBarcodeScanned(deviceInfo);
            }
            else
            {
                this.keystrokeBuffer.Append(localBuffer);
            }
        }

        return handled;
    }
}
}