在控制台应用程序之外检测指纹阅读器输入

3
我需要制作一个使用外部指纹识别器进行用户识别的Metro GUI应用程序。
我已经制作了一个C ++ DLL,以便在C#中调用我所做的名为CaptureSample()的方法。CaptureSample()方法为我提供了表示扫描指纹的字节数组(来自指纹阅读器的灰度图像)。它使用Microsoft生物识别框架访问读取器,等待读取器检测到手指放在其上方,然后发送扫描指纹的数据。
这一切都很好。但有一个问题:我必须以管理员身份运行我的应用程序才能使用该框架。这也是为什么我不能从Web服务运行框架的原因,因为Web服务是作为网络用户或类似用户运行的,当尝试使用库时,库会拒绝任何访问。因此,我必须制作一个额外的控制台程序来代替运行它。
现在问题出现了。为了使指纹识别器与Windows Metro GUI应用程序配合使用,您需要以管理员身份运行该应用程序。但由于所有Metro GUI应用程序都在沙箱中运行,因此这是不可能的。我必须制作一个外部控制台应用程序,调用应用程序的DLL功能,然后将其结果发送回应用程序。
更加复杂的是,我发现.NET的Windows Phone子集没有MSMQ,这本来可以很好地使用。因此,我制作了一个本地WCF服务,Metro应用程序需要调用它,然后WCF服务通过MSMQ调用控制台程序,并在从控制台程序接收到信息时将信息发送回Metro GUI应用程序。
到目前为止,理论上还不错。在这个过程中,我遇到了一个问题,因为控制台应用程序确实运行并准备好扫描。但是当我扫描我的手指时,什么也没发生。控制台需要焦点才能工作,而这是不可能的,因为该应用程序像Kiosk一样运行,除非进行维护,否则永远不会离开Metro GUI应用程序。
我已经查看了各种解决方案,以便在C#应用程序之外检测键盘输入,但我认为那不适用于指纹读取器,或者如果适用,我不知道该怎么做。有什么建议吗?

为了更好地理解,我包含了一个工作流程图表。(如果有人感兴趣的话,它是一台P5100 Zvetcobiometrics指纹读取器):

enter image description here


我不明白为什么控制台应用需要焦点才能工作。你能再解释一下吗? - nodots
1
此外,退一步来确认一下:您说“Web服务是作为网络用户或类似身份运行的” - 这是由于外部限制而无法更改的事实吗?您如何进行主机托管?例如,您是否可以使用以具有提升权限的身份运行的Windows服务? - nodots
关于控制台焦点:啊,好的,现在我明白了。/ 关于服务:您是如何托管WCF服务的(IIS、Windows服务、自托管等)?我仍然不明白为什么您不能创建一个Windows服务作为主机,并在不同的身份下运行它(services.msc->右键单击->属性->登录)。 - nodots
也许我在这里漏掉了什么,但为什么你“必须以管理员身份运行我正在制作的任何应用程序才能使用框架”?对我来说,这似乎是事情开始变得最糟糕的地方,引入了大量可能不必要的复杂性。 - Randy
@Randy 当然,这是您需要的内容:“要成功调用此函数,您必须通过在WinBioOpenSession或WinBioAsyncOpenSession函数的Flags参数中指定WINBIO_FLAG_RAW来打开会话句柄。目前,只有在管理员和本地系统帐户下运行的应用程序才具有必要的特权。” https://msdn.microsoft.com/zh-cn/library/dd401603(v=vs.85).aspx 这是“备注”下的第一段。 - OmniOwl
显示剩余8条评论
1个回答

2
因此,我在Windows Forms上遇到了这样的问题,但并非与您描述的问题完全相同。
我知道我的解决方案不是一个为您提供预编码的易用性解决方案,但我认为通过一些小的修改,我几年前克服的障碍可能对您有所帮助。如果您认为我的解决方案可以帮助您,我可以发送给您任何缺失的部分。
我创建了一个利用条形码扫描器的库存管理系统,并决定通过引入一些C++类来实现处理设备输入的能力,而不需要使用诸如文本框之类的输入控件。我需要应用程序能够处理条形码输入并做出决策,而不需要任何额外的用户交互或要求。只要扫描和执行即可。从键盘/HID设备实时获取输入值是我认为该解决方案适合您问题的原因之一。
在测试我编写的应用程序时,我能够在全屏游戏中使用条形码扫描器,就像在Windows Forms库存应用程序中一样。这个功能应该在控制台环境中也同样有效,因为当它们在后台运行时,控制台应用程序不会停止。您甚至可以将其设置为NT AUTHORITY,防止其在作为服务运行时显示到桌面,它仍然会继续运行。
我的做法是使用Win32API反射设备,并通过与应用程序指定的设备匹配,为该特定设备建立监听器。
您可以使用控制台应用程序触发指纹传感器,方法是将控制台应用程序作为本地服务帐户运行或以编程方式获取所需的授权(这将允许您在没有UAC阻碍的情况下以提升的权限运行它),然后在您的Metro应用程序中使用此功能,以读取设备发送的输入。
以下是一些代码文件,用于描述我所描述的内容,并已修改为特定于我的条形码扫描器功能。
同样,请私下联系我,如果您想查看任何缺失的部分。
附注:从技术上讲,这可以用作拦截键等操作的黑客工具,因此我将放置一个免责声明,由您自行决定使用,我对任何愚蠢行为造成的后果不负责。
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;
    }
}
}

最终,我和一位同事决定将所有内容从Metro应用程序移植到桌面WPF应用程序中,它看起来完全相同,但为我们提供了整个.NET库。然而,在查看您的解决方案并尝试应用它之后,我可以明确地看到,如果您只是对其进行足够的修改以读取指纹阅读器而不是条形码阅读器,那么这将是解决问题的方法。感谢分享,并祝贺您获得了积分! - OmniOwl
太棒了!这是个非常好的消息!如果你想要继续进行那些修改之类的工作,我非常乐意分享剩余的部分并向你展示我是如何实现它的。 - Anthony Hart

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接