c# - 在控制台应用程序之外检测指纹读取器输入

标签 c# wcf windows-8 msmq biometrics

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

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

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

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

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

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

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

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

enter image description here

最佳答案

因此,我在使用 Windows Forms 时遇到过这样的问题,但不完全是您描述的问题。

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

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

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

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

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

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

再次强调,如果您想查看任何遗漏的部分,请私下联系我。

PS:从技术上讲,这可以用作拦截 key 和类型的 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;
    }
}
}

关于c# - 在控制台应用程序之外检测指纹读取器输入,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34091691/

相关文章:

c# - 我可以使用脚本管理器为 WCF 服务库生成代理对象吗?

azure - Windows 8/RT + Phone 8 应用程序 - 同步数据

windows-8 - 适用于 Windows 8 Metro 应用程序的免费 PDF 编写器

c# - 如何根据当前对象的 bool 属性隐藏 RadGridView 中的单元格?

c# - DateTime 解析 WinRT Windows 8 RTM 中的错误?

json - 将 JSON 日期发送到 WCF 服务

c# - 如何在 XAML 页面之间传递值(参数)?

c# - 使用 C# 将 XML 转换为 BSON

c# - 支持或反对 .NET(野兽)的案例

c# - RESTful 服务的好教程