摘要:信息系统开发中难免会有要操作摄像头、高拍仪、指纹仪等硬件外设,异或诸如获取机器签名、硬件授权保护(加密锁)检测等情况。受限于Web本身运行机制,就不得不使用Active、浏览器插件进行能力扩展了。本文主要向大分享一种基于URL Scheme的与Windows扩展应用进程通信的方案,供大家参考。

一、方案对比

1.1 ActiveX

早期的IE浏览器扩展方法,可以使用VB6、C++、.Net等编写,曾经使用VB6编写过指纹仪操控,IE6时代还好配置,后面IE7、IE8出来后兼容性就实在是头大了,其它浏览器出来后就更难于解决了,便再没有使用过此方案了。缺点是对浏览器限制太多、兼容性太差,难于部署及调用,且只支持IE。

1.2 Chrome扩展插件

Chrome系浏览器的插件扩展方法,由于对此不熟,没有实际使用过,在此不作介绍。明显的确点便是只支持Chrome系列。

1.3 自定义URL Scheme方案

此方案便是本文介绍的方案,方案过程如下,Web页面使用自定义URL协议驱动起协议进程 , 再由协议进程拉起扩展功能WinForm应用进程,Web页通过HTTP与扩展应用通信,操控扩展功能,如下图所示:

二、方案实现

2.1 协议进程

协议进程主要功能是:注册、反注册URL Scheme;协议调用时负责检查扩展功能WinForm应用是否已启动,如果没有启动则拉起扩展应用,否则直接退出不动作。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Web;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Reflection;
using System.Windows.Forms;
using Microsoft.Win32; namespace UrlHook
{
/// <summary>
/// 协议入口程序
/// </summary>
class Program
{
#region 私有成员
private const string PROTOCOL = "Hdc";
private const string URL_FILE = "origin.set";
#endregion #region 入口点
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main(params string[] args)
{
if (args.Length < 1)
return; var first = args[0].Trim().ToLower();
var second = false;
if (args.Length >= 2)
second = args[1].Trim().ToLower() == "-q"; switch (first)
{
case "-i":
RegistrUrl(second);
return;
case "-u":
UnregistrUrl(second);
return;
} try
{ if (Process.GetProcessesByName("HdcClient").Any())
{
return;
} //启动进程
Process p = new Process();
p.StartInfo.FileName = Assembly.GetExecutingAssembly().Location;
p.StartInfo.FileName = p.StartInfo.FileName.Substring(0, p.StartInfo.FileName.LastIndexOf("\\"));
p.StartInfo.WorkingDirectory = p.StartInfo.FileName;
p.StartInfo.FileName += "\\HdcClient.exe";
p.Start();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString(), "提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
#endregion #region 私有方法
/// <summary>
/// 向注册表注册URL地址
/// </summary>
private static void RegistrUrl(bool quiet = false)
{
var exePath = Assembly.GetExecutingAssembly().Location; RegistryKey root = Registry.ClassesRoot.CreateSubKey(PROTOCOL);
root.SetValue(null, "Url:" + PROTOCOL);
root.SetValue("URL Protocol", exePath); var deficon = root.CreateSubKey("DefaultIcon");
deficon.SetValue(null, exePath + ",1", RegistryValueKind.String); var shell = root.CreateSubKey("shell")
.CreateSubKey("open")
.CreateSubKey("command"); shell.SetValue(null, "\"" + exePath + "\" \"%1\""); shell.Close();
deficon.Close();
root.Close(); if (!quiet)
{
MessageBox.Show("恭喜,协义注册成功;如果仍不生效,请偿试重启浏览器...", "提示"
, MessageBoxButtons.OK, MessageBoxIcon.Information);
}
} /// <summary>
/// 解除协议注册
/// </summary>
private static void UnregistrUrl(bool quiet = false)
{
RegistryKey root = Registry.ClassesRoot.OpenSubKey(PROTOCOL, true);
if (root != null)
{
root.DeleteSubKeyTree("shell", false);
Registry.ClassesRoot.DeleteSubKeyTree(PROTOCOL, false);
root.Close();
} Registry.ClassesRoot.Close(); if (!quiet)
{
MessageBox.Show("协议解除成功,客户端已经失效。", "提示"
, MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
#endregion
}
}

2.2 助手集成Http Server

Web页是通过HTTP协议与扩展WinForm应用通信的,所以我们要在扩展应用中集成一个HTTP Server,这里我们采用的是.Net的OWIN库中的Kestrel,轻量、使用简单,虽然在WinForm中只支持Web API,但已经够用了。这里我们的监听的是http://localhost:27089,端口选择尽量选5位数的端口,以免与客户机的其它应用冲突。

//main.cs
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.IO;
using Microsoft.Owin.Hosting;
using System.Reflection; namespace HdcClient
{
static class Program
{
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main()
{
//不能同时启动多个
var query = from p in Process.GetProcesses()
where p.ProcessName == "HdcClient"
&& p.Id != Process.GetCurrentProcess().Id
select p; if (query.Any())
{
IntPtr winHan = query.First().MainWindowHandle;
if (winHan.ToInt32() == 0 && File.Exists("win.hwd"))
{
winHan = (IntPtr)Convert.ToInt64(File.ReadAllText("win.hwd"), 16);
} ShowWindow(winHan, 4);
SetForegroundWindow(winHan);
return;
} //重定向路径
var path = Assembly.GetExecutingAssembly().Location;
path = Path.GetDirectoryName(path);
Directory.SetCurrentDirectory(path); //启动服务通信
WebApp.Start<Startup>("http://localhost:27089"); Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new FrmMain());
} }
}
//Startup.cs
using System.IO;
using System.Web.Http; using Microsoft.Owin;
using Microsoft.Owin.FileSystems;
using Microsoft.Owin.StaticFiles;
using Owin; namespace HdcClient
{
/// <summary>
///Web启动程序
/// </summary>
public class Startup
{
/// <summary>
/// 配置各中间件
/// </summary>
/// <param name="appBuilder"></param>
public void Configuration(IAppBuilder appBuilder)
{
//配置API路由
HttpConfiguration config = new HttpConfiguration(); config.Routes.MapHttpRoute(
name: "MediaApi",
routeTemplate: "api/media/{action}/{key}",
defaults: new
{
controller = "media"
}
); config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new
{
id = RouteParameter.Optional
}
); appBuilder.Use<Api.CorsOptionsMiddleware>();
appBuilder.UseWebApi(config);
}
}
}

2.3 关键问题跨域访问

助手虽然使用localhost本地地址与Web页通信,但是像chrome这样严格检测跨域访问的浏览器,仍然会有跨域无法访问扩展应用的问题。因此,HTTP Server要开启允许跨域访问,我们定义一个中间件来处理跨域,代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Owin; namespace HdcClient.Api
{
/// <summary>
/// CORS时OPTIONS提交响应
/// </summary>
public class CorsOptionsMiddleware : OwinMiddleware
{
#region 构造方法
/// <summary>
/// 初始化中间件
/// </summary>
/// <param name="next"></param>
public CorsOptionsMiddleware(OwinMiddleware next)
:base(next)
{ }
#endregion #region 重写方法
/// <summary>
/// 响应跨域请求
/// </summary>
/// <param name="context">请求上下文</param>
/// <returns></returns>
public override Task Invoke(IOwinContext context)
{
if (context.Request.Method.ToUpper() != "OPTIONS")
return this.Next.Invoke(context); var response = context.Response;
response.Headers.Append("Access-Control-Allow-Origin", "*");
response.Headers.Append("Access-Control-Allow-Methods", "*");
response.Headers.Append("Access-Control-Allow-Headers", "x-requested-with");
response.Headers.Append("Access-Control-Allow-Headers", "content-type");
response.Headers.Append("Access-Control-Allow-Headers", "content-length"); return Task.FromResult<string>("OK");
}
#endregion
}
}

三、Web页如何调用?

3.1 调起助手


/*
* 硬件设备控制访问控制
*
* @Alphaair
* 20151114 create.
* 20190723 移值,更换AJAX库。
**/ import http from "axios"; const hdc = {
VERSION: '2.0.0',
CLIENT_URL: 'http://localhost:27089/',
getRootUrl: function () {
///<summary>获取当前访问地址根URL</summary> var url = location.protocol + '//';
url += location.host; return url;
},
openClient: function (callback, count) {
///<summary>开启客户端组件</summary> let url = `${this.CLIENT_URL}api/pipe/test`;
http.get(url, {
responseType: 'text'
}).then(rsp => {
//错误
if (rsp.stack)
throw rsp; try {
if (callback)
callback();
}
catch (err) {
alert(err.message);
console.error(err);
}
}).catch(err => {
console.error(err);
if (count >= 10) {
alert("客户端组件启动失败,请确认是否已经正常安装或者偿试手工启动!");
return;
} count = count || 1;
if (count < 3) {
let origin = this.getRootUrl();
origin = encodeURIComponent(origin);
window.open(`Hdc://startup/${origin}`);
} //递归
setTimeout(function () {
count = count || 1;
count++;
hdc.openClient(callback, count);
}, 5000);
});
},
/**
* 启动身份证读取
*
* @param {Function} callback 读取回调
*
*/
readIdCard: function (callback) { const self = this;
let url = `${self.CLIENT_URL}/api/IdReader/Reading`;
http.get(url, {
params: {
_ds: (new Date()).getTime()
}
}).then(rsp => {
let fkb = rsp.data;
if (fkb.Success) {
callback(fkb);
}
else {
alert(fkb.Message);
}
}).catch(err => {
console.error('身份证阅读器启动失败,可能客户端未打开.', err);
callback(false);
});
},
/**
* 获取身份证号码扫描结果
*
* @param {Function} callback 读取回调
* @param {Boolean} isLoop 是否循环读取
*/
getIdCard: function (callback, isLoop) {
//获取身份证扫描结果 var self = this;
if (!isLoop)
self._cancelGetIdCard = false;
else
self._cancelGetIdCard = true; let url = `${self.CLIENT_URL}/api/IdReader/GetIdCard`;
http.get(url, {
params: {
_ds: (new Date()).getTime()
}
}).then(rsp => {
let fkb = rsp.data;
if (fkb.Success) {
callback(fkb);
return;
} //一秒后重新发起监听
if (self._cancelGetIdCard) {
setTimeout(function () {
self.getIdCard(callback, true);
}, 1000);
}
}).catch(err => {
console.error('获取身份证识别结果失败,请确认客户端正常.', err);
callback(false);
});
},
cancelIdCard: function () {
this._cancelGetIdCard = false;
}
}; export default hdc;

3.2 操作反馈结果获取

像高拍仪拍摄这样的操控需要一定时长,或者无法确认什么时候完成,如果使用发起连接等待操作完成,势必有可能引起因为HTTP超时而失败。所以本方案采用操控发起与结果连接分离的方法,控制请求只负责调起相应的功能,不返回操控结果,结果采用轮询的方式获取,如下面代码:

http.get(url, {
params: {
_ds: (new Date()).getTime()
}
}).then(rsp => {
...
//一秒后重新发起监听
if (self._cancelGetIdCard) {
setTimeout(function () {
self.getIdCard(callback, true);
}, 1000);
}
}).catch(err => {
...
});

四、实现效果

受篇幅限制,这里仅展示部分关键代码,如果有需要了解方案更详细信息,请按下面方式联系我们。

Web操作摄像头、高拍仪、指纹仪等设备的功能扩展方案的更多相关文章

  1. 如何在Web页面里使用高拍仪扫描上传图像

    如何在Web页面里使用高拍仪扫描上传图像 市场上所有的高拍仪都支持扫描图片并保存到本地,一般公司都会提供控件.开发人员只需要在页面集成就可以进行拍照和扫描.只不过一般扫描的图片是保存在本地固定的文件夹 ...

  2. web高拍仪图片上传

    公司引进高拍仪,想拍完照片点上传按钮直接上传图片.高拍仪接口能提供照片的本地路径,现在的问题是不用file控件选择,只有路径,不知道如何上传到服务器,求解决方案. 方法: 使用泽优Web图片上传控件( ...

  3. 高拍仪拍照SDK开发(良田影像S300L|S500L)

    高拍仪拍照SDK开发下载地址:点击下载 本SDK适用于:良田影像S300L|S500L 高拍仪如图: SDN开发包安装之后找到安装目录,如图: 大家找到各自需要的版本即可,需要注意的是如果需要上传图片 ...

  4. 良田高拍仪集成vue项目

    一.硬件及开发包说明: 产品型号为良田高拍仪S1800A3,集成b/s系统,适用现代浏览器,图片使用BASE64数据.开发包的bin文件下的video.flt文件需要和高拍仪型号的硬件id对应,这个可 ...

  5. 捷宇高拍仪XY530 网页集成总结

    应甲方要求,需要把高拍仪集成到B/S系统中来,在集成过程中遇到的几点问题做为总结,以备查找. 1.甲方送来的高拍仪是淘宝上买来的,型号是XY530,功能非常简单,成像效果也很一般.如果没有其它要求,可 ...

  6. 转:亿级Web系统的高容错性实践(好博文)

    亿级Web系统的高容错性实践 亿级Web系统的高容错性实践 背景介绍 大概三年前,我在腾讯负责的活动运营系统,因为业务流量规模的数倍增长,系统出现了各种各样的异常,当时,作为开发的我,7*24小时地没 ...

  7. 亿级Web系统的高容错性实践

    亿级Web系统的高容错性实践 背景介绍 大概三年前,我在腾讯负责的活动运营系统,因为业务流量规模的数倍增长,系统出现了各种各样的异常,当时,作为开发的我,7*24小时地没日没夜处理告警,周末和凌晨也经 ...

  8. C#操作摄像头 实现拍照功能

    从正式工作以来一直做的都是基于B/S的Web开发,已经很长时间不研究C/S的东西了,但是受朋友的委托,帮他做一下拍照的这么个小功能.其实类似的代码网上有很多,但是真的能够拿来运行的估计也没几个.本来是 ...

  9. 学习heartbeat-03t实现web服务的高可用案例及维护要点

    8.Heartbeat实现web服务的高可用案例 8.1部署准备 通过web服务高可用案例来熟悉heatbeat软件的使用,用上面的两台服务器机器名分别为heartbeat-1-130和heartbe ...

随机推荐

  1. mysql锁——innodb的行级锁

    [前言]数据库锁定机制简单来说,就是数据库为了保证数据的一致性,而使各种共享资源在被并发访问变得有序所设计的一种规则.MySQL数据库由于其自身架构的特点,存在多种数据存储引擎,每种存储引擎所针对的应 ...

  2. abp中多种登陆用户的设计

    项目地址:https://gitee.com/bxjg1987/abp 场景 在<学校管理系统>中,学生.家长.教师.教务都可能登陆,做一些属于他们自己的操作.这些用户需要的属性各不相同, ...

  3. 使用Maven新建SpringBoot工程

    最近用IDEA插件创建Springboot项目,总是403,估计被墙了! 那么这里在提供两种方法 1.从官网下载模板,导入IDEA内 2.使用Maven创建 方法一:打开 https://start. ...

  4. 基于QT的全自动超声波焊接机上位机追溯系统(已经在设备上应用)

    应用说明: 本上位机程序是我在做锂电池产线项目的时候开发的,用于采集设备数据以及实现设备自动控制,下位机采用基恩士PLC,超声波机采用上海一家的超声波焊接机,实现电芯极耳的自动焊接,上位在设备焊接过程 ...

  5. 剑指 Offer 21. 调整数组顺序使奇数位于偶数前面

    剑指 Offer 21. 调整数组顺序使奇数位于偶数前面 Offer 21 这题的解法其实是考察快慢指针和头尾指针. package com.walegarrett.offer; /** * @Aut ...

  6. Go中定时器实现原理及源码解析

    转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com 本文使用的go的源码15.7,需要注意的是由于timer是1.14版本进行改版,但是1. ...

  7. 聊一聊和Nacos 2.0.0对接那些事

    前言 nacos 2.0.0 已经发布了 alpha1, alpha2 和 beta 三个版本了,部分测试报告也已经出来了. Nacos2.0.0-ALPHA2 服务发现性能测试报告 Nacos 2. ...

  8. shell脚本,mysql数据库的备份-2[mysqldump]

    # 数据库IPIP=127.0.0.1# 数据库端口PORT=3306# 数据库用户USER=root# 数据库密码PASSWORD=****# 要备份的数据库TARGET_DB=database_n ...

  9. FreeBSD 如何让csh像zsh那样具有命令错误修正呢

    比如,,你用 emacs写c ,但你输完emacs ma按tab回车是,他会匹配所有ma开头的文件,而这个是忽略掉,也就是按tab时不会在有你忽略的东西,对编程之类的友好,不用再匹配到二进制..o之类 ...

  10. Linux普通用户安装配置mysql(非root权限)

    Linux普通用户安装配置mysql(非root权限) 说明:在实际工作中,公司内网的机器我们一般没有root权限,也没有连网,最近参考网上的资料使用一般的账户成功安装mysql,记录如下 Linux ...