1、需求描述
最近多个第三方开发厂商为了快速集成软件系统,希望直接从 Web 网页上启动客户端软件。类似的需求可以分以下几类:
- 仅仅是从 Web 网页上将 C++ 客户端软件启动起来,即将软件调起来就行了,没有后续自动操作。
- 从 Web 网页上将 C++ 客户端软件启动起来,并给软件传递服务器地址、用户名和密码,让软件自动发起登陆。
- 从 Web 网页上将 C++ 客户端软件启动起来,并且启动时传递一些信息,让软件执行指定的一些操作,比如加入指定的会议。
其实上述需求可以简单的归结为,将 C++ 客户端软件启动起来,并给 C++ 客户端软件传递一些命令行参数,C++ 客户端软件解析出参数,执行指定的操作。
以浏览器打开腾讯会议的会议链接为例,点击上面的会议链接,系统用系统中安装的浏览器打开链接,点击加入会议按钮,会弹出是否要打开本地安装的腾讯会议程序的提示框,点击打开腾讯会议,将本地安装的腾讯会议启动起来,并自动加入到指定的会议中。
2、选择 URI Scheme 实现
如果是在 C++ 程序中启动一个 C++ 软件,会比较简单,只要获取一下目标软件的安装路径(可以从注册表中读取),就能直接通过软件的全路径,直接将软件启动起来了。
现在越来越多的系统都转向 B/S 架构,Web 网页一般都是在浏览器中打开的,出于安全的原因,Web 浏览器既不能直接读写注册表,即无法通过访问注册表获取要启动软件的安装路径,所以无法像 C++ 程序那样直接启动二进制文件。使用 URI Scheme 技术与规范就能实现这样的需求。
3、何为 URI Scheme?
URI,全称是 Uniform Resource Identifier,统一资源标志符。在 Web 开发领域,其表示的是 Web 上每一种可用的资源,如 HTML 文档、图片、视频等。URI Scheme,我们称之为 URI 方案,是一种技术规范,其中的 URI 是个更宽泛的概念,它可以是一个本地的文件,也可以是一个网络上的视频。
从 Web 网页中启动本地应用程序的 URI Scheme 规范中,需要将本地应用程序的信息通过写注册表的方式注册到系统中,然后在网页中使用'SchemeName://'就可以只在启动本地程序了。具体的做法是,在注册表的 HKEY_CLASSES_ROOT 下创建一个自定义的 SchemeName 注册表节点,然后再在该节点下创建多个节点,并在给相关节点设置注册表键值。
以 QQ 内嵌的 QQGame 为例,添加注册表信息的步骤如下:
1)在 HKEY_CLASSES_ROOT 下创建 QQGameProtocol 节点
QQGameProtocol 就是对应的 Scheme 方案名称,也是 Web 页面上启动对应程序的 URL 的前缀名称,即 QQGameProtocol://。然后给该节点添加一个 URL Protocol 名称的键值,将其 Value 设置为本地应用程序的完整路径。
2)在 QQGameProtocol 根节点下创建 DefaultIcon 节点
给 DefaultIcon 节点设置默认的字符串键值(REG_SZ 类型),其 Value 的格式为'应用程序全路径,图标索引'的形式,该键值是用来指定该 URI 方案使用的图标。
3)在 QQGameProtocol 下创建 shell 节点
先在 QQGameProtocol 下创建 shell 节点,然后在 shell 节点下创建 open 节点,然后在 open 节点创建 command 节点。shell 节点和 open 节点不需要设置键值,command 节点需要设置键值,其键值用来指定启动目标应用程序时是否给目标程序传递命令行参数。
一般只需要设置传递一个参数即可,比如当前 Scheme 下的"C:\Users\Public\Documents\Tencent\QQGameMicro\QQGwp.exe" "%1"。如果要传递多个参数,可以自定义一个组合格式,命令行只用一个参数即可。
当在 Web 页面上点击'SchemeName://'链接时,就会到系统注册表的 HKEY_CLASSES_ROOT 节点下查找 SchemeName 节点项,找到后取出目标应用程序的全路径,并查找传递的命令行参数个数,这样就能把本地的目标应用程序启动起来了。
如果要给目标程序传递参数,则使用'SchemeName://参数'的形式。经测试发现,如果在 command 节点中设置了%1 传递参数的标识,则 Web 网页中设置的 URL 必须要带参数,即'SchemeName://参数'。如果使用不带参数的 URL:'SchemeName://',则无法启动目标程序。
那如何既要支持不传参数启动,也要支持传参数启动呢?其实不用这么麻烦,使用一个带参数的 SchemeName 节点就够了,对于直接启动目标程序不带启动参数的,也可以携带一个标识参数,在程序中约定不传参数的标识符,比如 noparam,当程序中解析出 noparam,则表示是不带参数启动的,直接启动程序即可,不用做后续的操作。
4、将自定义的 URL Scheme 信息写入注册表的 C++ 源码实现
下面给出将自定义的 URL Scheme 信息写入注册表的 C++ 源码实现:
BOOL {
CString strExePath = m_strInstallPath + _T();
CString strProtocolName = _T();
HKEY hRootKey = ;
DWORD dwKeyValue = ;
DWORD dwDisposition = ;
UCHAR szBuf[MAX_PATH] = { };
lRet = ::(HKEY_CLASSES_ROOT, ProtocalNodeName, , , , KEY_ALL_ACCESS, , &hRootKey, &dwDisposition);
(lRet != ERROR_SUCCESS) {
FALSE;
}
lRet = ::(hRootKey, , , REG_SZ, (LPBYTE)(LPCTSTR)strProtocolName, strProtocolName.() * (TCHAR));
(lRet != ERROR_SUCCESS) {
(hRootKey);
FALSE;
}
CString strKey = _T();
lRet = (hRootKey, strKey.(), , REG_SZ, (LPBYTE)(LPCTSTR)strExePath, strExePath.() * (TCHAR));
(lRet != ERROR_SUCCESS) {
(hRootKey);
FALSE;
}
strKey = _T();
HKEY hDefaultIconKey = ;
lRet = (hRootKey, strKey, , , , KEY_ALL_ACCESS, , &hDefaultIconKey, &dwDisposition);
(lRet != ERROR_SUCCESS) {
(hRootKey);
FALSE;
}
CString strExePathPlus = strExePath + _T();
lRet = (hDefaultIconKey, , , REG_SZ, (LPBYTE)(LPCTSTR)strExePathPlus, strExePathPlus.() * (TCHAR));
(lRet != ERROR_SUCCESS) {
(hDefaultIconKey);
(hRootKey);
FALSE;
}
strKey = _T();
HKEY hShellKey = ;
lRet = (hDefaultIconKey, strKey, , , , KEY_ALL_ACCESS, , &hShellKey, &dwDisposition);
(lRet != ERROR_SUCCESS) {
(hDefaultIconKey);
(hRootKey);
FALSE;
}
strKey = _T();
HKEY hOpenKey = ;
lRet = (hShellKey, strKey, , , , KEY_ALL_ACCESS, , &hOpenKey, &dwDisposition);
(lRet != ERROR_SUCCESS) {
(hDefaultIconKey);
(hRootKey);
FALSE;
}
strKey = _T();
HKEY hCommandKey = ;
lRet = (hOpenKey, strKey, , , , KEY_ALL_ACCESS, , &hCommandKey, &dwDisposition);
(lRet != ERROR_SUCCESS) {
(hOpenKey);
(hDefaultIconKey);
(hRootKey);
FALSE;
}
CString strCmdParam;
strCmdParam.(_T(), strExePath);
lRet = (hCommandKey, , , REG_SZ, (LPBYTE)(LPCTSTR)strCmdParam, strCmdParam.() * (TCHAR));
(lRet != ERROR_SUCCESS) {
(hCommandKey);
(hOpenKey);
(hDefaultIconKey);
(hRootKey);
FALSE;
}
(hCommandKey);
(hOpenKey);
(hDefaultIconKey);
(hRootKey);
TRUE;
}


