2015-12-04 9 views
3

Поэтому мне нужно создать приложение Metro GUI, в котором используется внешний считыватель отпечатков пальцев для идентификации пользователя.Обнаружение считывателя отпечатков пальцев Вне приложения консоли

Я дошел до того, что создал DLL на C++, поэтому я могу вызвать метод, который я сделал, с именем CaptureSample() в C#, ссылающийся на эту DLL. Метод CaptureSample() предоставляет мне массив байтов, который представляет отсканированный отпечаток пальца (изображение с оттенками серого от считывателя отпечатков пальцев). Так хорошо до сих пор. То, как это делается, заключается в том, чтобы использовать Microsoft Biometrics Framework для доступа к читателю, дождаться, пока читатель обнаружит, что на нем был нанесен палец, а затем отправить данные обратно от отсканированного отпечатка пальца.

Все это прекрасно работает. Однако есть один улов: У меня есть приложение, которое я делаю администратором, чтобы использовать фреймворк. Вот почему я не могу просто запустить фреймворк из Webservice, потому что Webservice запускается как пользователь сети или аналогичный, что делает библиотеку отрицательным при попытке ее использования. Поэтому я должен создать дополнительную консольную программу, которая сможет ее запустить.

Теперь вот где все становится волосатым. Чтобы программа чтения отпечатков пальцев работала с графическим приложением Windows Metro, вам нужно запустить приложение в качестве администратора. Это невозможно, так как все приложения Metro GUI работают в изолированной программной среде. Мне нужно создать внешнее консольное приложение, которое вызывает функциональность DLL для приложения, а затем отправить результат этого приложения.

Чтобы сделать вещи еще более сложными, я обнаружил, что подмножество Windows Phone .NET не имеет MSMQ, которое было бы неплохо использовать. Поэтому вместо этого я создал локальную службу WCF, которую нужно вызвать приложению Metro, затем служба WCF вызывает консольную программу через MSMQ и отправляет информацию обратно в приложение Metro GUI всякий раз, когда он получает его из приложения Console.

Пока все хорошо. В теории. Я столкнулся с проблемой в этом процессе, потому что консольное приложение запускается и готово к сканированию по запросу. Но когда я сканирую свой палец, ничего не происходит. Консоли нужно сосредоточиться, чтобы работать, и об этом не может быть и речи, поскольку приложение работает как киоск и никогда не должно покидать приложение Metro GUI вообще, кроме как для обслуживания.

Я рассмотрел различные решения для обнаружения ввода с клавиатуры вне приложения C#, но я не думаю, что это относится к считывателю отпечатков пальцев, или если это так, я не знаю, как бы это сделать. Какие-либо предложения?

Просто для хорошего служения Я включил диаграмму рабочего процесса, чтобы ее было легче понять. (Это P5100 Zvetcobiometrics считыватель отпечатков пальцев, если кому-то интересно):

enter image description here

+0

Я не понимаю, почему приложение консоли нужно будет сосредоточиться на работе , Можете ли вы объяснить немного больше? – nodots

+1

Также, сделав шаг назад, чтобы убедиться: вы заявляете, что «веб-сервис запускается как пользователь сети или аналогичный» - это неизменный факт из-за внешних ограничений? Как вы его принимаете? Например, можно ли использовать службу Windows под личным именем с повышенными привилегиями? – nodots

+0

@nodots Отличные вопросы! Сначала я отвечу на вопрос Webservice: если вы запустите службу WCF на планшете (что я и делаю), она будет запущена либо как NETWORK, либо другая учетная запись локальной службы, которая не является учетной записью пользователя. Это неизменно из-за Microsoft. Когда вы попытаетесь сделать это, Windows Biometrics Framework отклонит запрос на чтение и предоставит вам исключение с доступом. О консоли: поскольку Reader зарегистрирован как блок HID, он не будет инициировать никаких событий при попытке прочитать отпечаток пальца, если приложение, вызывающее чтение, не будет в фокусе. – OmniOwl

ответ

2

Итак, я наткнулся на вопрос, как это с Windows Forms, но точно не с вопросов, которые вы описали.

Я понимаю, что мое решение не является запрограммированным простым решением для вас, но я чувствую, что с некоторыми незначительными изменениями препятствие, которое я преодолел несколько лет назад, может вам пригодиться. Если вы считаете, что мое решение может вам помочь, я могу отправить вам любые недостающие части.

Я создал систему управления запасами, в которой использовались сканеры штрих-кода, и я решил реализовать возможность обработки ввода с устройства путем сортировки на некоторых классах C++, не требуя использования элемента управления ввода, такого как текстовое поле. Мне было необходимо, чтобы приложение могло обрабатывать ввод штрих-кода и принимать решения без каких-либо дополнительных взаимодействий или требований пользователя. Просто сканируйте и идите. Непосредственное использование ввода с клавиатуры/HID-устройства - вот почему я считаю, что это решение соответствует вашему вопросу.

При тестировании приложения, которое я написал, я смог быть внутри полноэкранной игры и все еще был в состоянии использовать сканер штрих-кода, как и ожидалось, в приложении инвентаризации окон. Эта же функциональность должна работать так же хорошо, как и консольная среда, поскольку консольные приложения не останавливаются, когда они находятся в фоновом режиме. Вы даже можете установить его как NT AUTHORITY и не показывать его на рабочем столе во время работы в качестве сервиса, и он все равно должен отключаться.

Что я сделал, так это то, что я использовал Win32API для отображения через устройства, соответствовал устройству с устройством, указанным приложением (выбранным пользователем), и в основном установил прослушиватель для этого конкретного устройства.

Вы можете использовать консольное приложение для запуска датчика отпечатков пальцев, запустив консольное приложение как локальную учетную запись службы или запрограммировав необходимую авторизацию (что позволит вам запускать ее с повышенными разрешениями, если материал UAC не будет на вашем пути) а затем используйте это внутри своего приложения метро, ​​чтобы прочитать ввод с устройства, когда устройство отправляет его.

Ниже приведены некоторые файлы кодов для описания того, что я описываю и которые были изменены, чтобы быть конкретными в отношении функций сканера штрих-кода.

Снова, пожалуйста, свяжитесь со мной конфиденциально, если вы хотите увидеть недостающие части.

PS: Это технически можно использовать в качестве взлома для перехвата ключей и сортировки, поэтому я поставлю отказ от ответственности по вашему усмотрению, и я не несу ответственности за все, что может сделать кто-то глупо с этим кодом.

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; 
    } 
} 
} 
+0

Хорошо спасибо. Я посмотрю на это :) – OmniOwl

+0

Итак, в конце концов, я и коллега решили перенести все это из приложения Metro в приложение WPF для рабочего стола, которое выглядит точно так же, но дает нам всю библиотеку .NET. Однако, просмотрев ваше решение и пытаясь применить его, я мог бы окончательно увидеть, что это решение проблемы, если вы просто достаточно модифицируете его, чтобы читать считыватель отпечатков пальцев вместо считывателя штрих-кодов. Спасибо, что поделились, и травы на баллы! – OmniOwl

+0

Удивительный! Это выдающиеся новости! Если вы хотели бы продолжить эти изменения и такие, я был бы более чем счастлив поделиться оставшимися частями и показать вам, как я его реализовал. –