在传统的桌面应用中使用 WinRT 组件
在实际开发中,我们的 UWP 需要通过 RPC 和系统服务通信,UWP 使用的 RPC 客户端模块是用 C++/CX 开发的 WinRT 组件,它可以直接被 UWP 工程引用。但是有时候我们想写一些诸如 Console 类的测试程序,如果也能复用该 WinRT 组件就最好不过了。本文根据这个背景展开。
标题中提到的两个名词涉及范围如下,
传统的桌面应用表示,
基于 C/C++ 的 Console,Win32 应用
基于 C# 的 WinForm, WPF 应用
WinRT 组件表示
基于 C++ 或者 C++/CX 的 WinRT 组件
WinRT 组件一般由两部分组成,WinMD 和 DLL, 大致来讲 winmd 包含了接口定义信息,DLL 包含具体的实现。
WinRT 组件包含的 DLL 是基于 COM 技术,使用之前需要注册。一般在安装 UWP 的时候,安装包会负责 COM 注册,但是传统的桌面应用并不知道如何注册。虽然不注册这些 DLL并不影响编译,但是在运行时如果调用到相关的接口,会有异常抛出。所以需要一种免注册的技术。通过添加 manifest 文件,并在其中指定本地的 COM 服务器,也可以通过 COM 技术访问这些 DLL。
WinRT 组件中的 winmd 文件实际和 .Net DLL 一样,可以直接被 .Net 工程引用并使用里面定义的各种接口。但是在 C/C++ 工程里面,我们只能使用头文件,借助 CppWinRT,可以基于 winmd 文件生成对应的头文件供我们调用,并且我们不需要关心这些头文件内部的实现细节。它内部实现了调用 COM 接口相关的代码,我们只需要关注接口即可。
下面是具体实践演示,在演示中,我使用 CppWinRT 模板创建一个 WinRT 组件工程,命名为WinRTComponet,主要接口定义如下:
该工程生成两个文件
WinRTComponent.winmd
WinRTComponent.dll
演示一,在 C++ console 应用中使用 WinRT组件
1.创建一个空的 C++ Console 工程 Comsumer_CppConsole
2.引入 WinRTComponent.winmd
不能直接添加对工程 WinRTComponent 的引用,VS 会报目标平台不兼容的错误。
在 Nuget Manager 中,对该工程安装 Microsoft.Windows.CppWinRT,安装完成后,可以在添加引用面板中,找到生成的文件 WinRTComponent.winmd, 然后把它添加到工程中
以上操作对应到工程配置文件,实际添加了如下片段
3. 生成头文件
编译一次该工程,因为之前安装了 Nuget 包 Microsoft.Windows.CppWinRT,它会生成WinRTComponent.winmd 对应的头文件,这些文件位于工程目录下面,代码片段如下
4. 编写客户代码
如下代码片段中引用了生成的头文件,该头文件正是第3步中生成的,生成的头文件名一般是<winrt/命名空间.h>
如果现在我们就编译执行这段代码,会遇到如下类似错误
Microsoft C++ exception: winrt::hresult_class_not_registered at memory location 0x000000BF422FF2F8
因为它找不到COM服务器
5. 引入 WinRTComponent.dll,
首先需要把 WinRTComponent.dll 复制到客户程序Comsumer_CppConsole.exe的目录,可以手动,也可以通过脚本
在工程中创建一个 manifest 清单文件,Comsumer_CppConsole.manifest,一定保证该文件的在VS 属性对话框中的文件类型是 Manifest Tool
添加完该文件后,实际在 VS 工程配置文件中多了如下配置
现在可以尝试编译运行该 console app,但是依然会有如下错误
WinRT originate error - 0x8007007E : 'The specified module could not be found.'
这是因为生成的 WinRT组件本身还有一些依赖的 DLL。
6. 安装 Nuget 包 Microsoft.VCRTForwarders
因为 WinRT 组件本身要用到很多的 VC 库,比如 vcruntime140_app.dll,他们和传统的桌面应用用到的库不同之处是有一个“_app”后缀,表明他们是应用商店专用版本,这种 VC 库并不会安装在用户机器,所以我们的 WinRT 组件当然找不到对应的库,所以会报错。
VCRTForwarders 的作用就是把所有用到商店版本的 VC 库接口的调用,转发到随 Windows分发的桌面版本的VC 库中,这样就解决了依赖的问题。
安装完成 VCRTForwarders 后,可以发现我们成功的在 Console 中调用到 WinRT 组件里的接口!
演示二,在 基于.Net Framework的 WinForm或者 WPF 应用中使用 WinRT组件
1.直接添加对 WinRTComponent 的工程引用;
2.如同演示一,创建 manifest 文件
需要手动将该 manifest 文件加入到 VS 工程配置文件中,添加如下代码配置即可
3.安装 Nuget 包 Microsoft.VCRTForwarders
值得注意的是,针对基于.Net Framework 的 WinForm和WPF程序,流程比较简单,因为他们可以直接引用 WinRT 组件工程。但如果是基于.Net Core,则情况有所不同,演示二不适用。
以上两个演示主要参考了如下文章
https://blogs.windows.com/windowsdeveloper/2019/04/30/enhancing-non-packaged-desktop-apps-using-windows-runtime-components/
https://github.com/microsoft/RegFree_WinRT
https://github.com/Microsoft/vcrt-forwarders