So I need to make a Metro GUI App that uses an external fingerprint reader for user identification.
I've come so far as to make a DLL in C++ so I can call a method I made called CaptureSample()
in C# referencing that DLL. The CaptureSample()
method provides me with a byte array that represents the scanned fingerprint (a greyscale image from the fingerprint reader). So good so far. The way it does this, is to use the Microsoft Biometrics Framework to access the reader, wait for the reader to detect that a finger was laid on top of it, then send the data back of the scanned fingerprint.
This all works great. There is one catch however: I HAVE to run whatever application I am making as Administrator to make use of the framework. This is also why I can't just run the framework from the Webservice because the Webservice is run as a Network User or similar, making the library deny any access when tried to using it. So I have to make an extra console program that can run it instead.
Now here is where things get hairy. In order to make the fingerprint reader work with a Windows Metro GUI App, you'd have to run the app as admin. This is not possible as all Metro GUI apps run in a sandbox. I have to make an external console application that calls the DLL functionality for the app and then send the result of that back to the app.
To make things even more complicated I've found out that the Windows Phone subset of .NET does not have MSMQ which would have been nice to use. So instead, I made a local WCF Service that the Metro app needs to call, then the WCF Service calls the console program via MSMQ and sends the info back to the Metro GUI App whenever it receives it from the Console app.
So far so good. In theory. I've run into a problem in this process because the Console Application does run and is ready to scan when asked. But when I scan my finger nothing happens. The console needs focus in order to work, and that's out of the question as the app runs like a Kiosk and should never leave the Metro GUI App at all unless for maintenance.
I've looked at various solutions for detecting keyboard input outside a C# application but I don't think that applies to a Fingerprint Reader or if it does, I don't know how I'd do that. Any suggestions?
Just for good meassure I included a workflow chart so it's easier to understand. (It's a P5100 Zvetcobiometrics Fingerprint Reader, if anyone is curious):
So, I ran across an issue like this with Windows Forms, but not exactly with the issues that you described.
I understand that my solution isn't a pre-coded easy-day solution for you but I feel that with some minor modifications, an obstacle I overcame a few years ago may be of use to you. If you think my solution may help you, I can send you any missing pieces.
I created an inventory management system that utilized barcode scanners and I decided to implement the capability of being able to handle the input from a device by marshalling in some C++ classes without requiring the use of an input control such as a textbox. I needed the application to be able to process barcode input and make decisions without any additional user interactions or requirements. Just scan and go. The immediate use of the input from a keyboard/HID device is why I feel that this solution fits your question.
While testing the application I wrote, I was able to be inside of a full-screen game and still was able to utilize the barcode scanner as expected in the windows forms inventory application. This same functionality should work just as well in a console environment as console applications don't stop when they're in the background. You can even set it as NT AUTHORITY and prevent it from displaying to the desktop while running as a service and it should still chug away.
What I did was I used the Win32API to reflect through the devices, matched the device with the device specified via the application (User-chosen) and basically established a listener for that specific device.
You could use your console application to trigger the fingerprint sensor by running the console application as the local service account or programmatically obtaining necessary authorization (which would allow you to run it in elevated permissions without the UAC stuff getting in your way) and then use this inside of your metro app to read the input from the device as the device sends it.
Below are some of the code files for going about what I describe and have been modified to be specific towards my barcode scanner functionality.
Again, please contact me privately if you'd like to see any missing pieces.
PS: This technically could be used as a hack for intercepting keys and the sort so I will put a disclaimer for you to use at your own discretion and I am not responsible for anything that someone stupid may do with this code.
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;
}
}
}