Итак, я наткнулся на вопрос, как это с 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;
}
}
}
Я не понимаю, почему приложение консоли нужно будет сосредоточиться на работе , Можете ли вы объяснить немного больше? – nodots
Также, сделав шаг назад, чтобы убедиться: вы заявляете, что «веб-сервис запускается как пользователь сети или аналогичный» - это неизменный факт из-за внешних ограничений? Как вы его принимаете? Например, можно ли использовать службу Windows под личным именем с повышенными привилегиями? – nodots
@nodots Отличные вопросы! Сначала я отвечу на вопрос Webservice: если вы запустите службу WCF на планшете (что я и делаю), она будет запущена либо как NETWORK, либо другая учетная запись локальной службы, которая не является учетной записью пользователя. Это неизменно из-за Microsoft. Когда вы попытаетесь сделать это, Windows Biometrics Framework отклонит запрос на чтение и предоставит вам исключение с доступом. О консоли: поскольку Reader зарегистрирован как блок HID, он не будет инициировать никаких событий при попытке прочитать отпечаток пальца, если приложение, вызывающее чтение, не будет в фокусе. – OmniOwl