在傳統(tǒng)的桌面應(yīng)用中使用 WinRT 組件
????在實(shí)際開發(fā)中,我們的 UWP 需要通過(guò) RPC 和系統(tǒng)服務(wù)通信,UWP 使用的 RPC 客戶端模塊是用 C++/CX 開發(fā)的 WinRT 組件,它可以直接被 UWP 工程引用。但是有時(shí)候我們想寫一些諸如 Console 類的測(cè)試程序,如果也能復(fù)用該 WinRT 組件就最好不過(guò)了。本文根據(jù)這個(gè)背景展開。
????標(biāo)題中提到的兩個(gè)名詞涉及范圍如下,
傳統(tǒng)的桌面應(yīng)用表示,
基于 C/C++ 的 Console,Win32 應(yīng)用
基于 C# 的 WinForm, WPF 應(yīng)用
WinRT 組件表示
基于 C++ 或者 C++/CX 的 WinRT 組件
????WinRT 組件一般由兩部分組成,WinMD 和 DLL,?大致來(lái)講 winmd 包含了接口定義信息,DLL 包含具體的實(shí)現(xiàn)。
????WinRT 組件包含的 DLL 是基于 COM 技術(shù),使用之前需要注冊(cè)。一般在安裝 UWP 的時(shí)候,安裝包會(huì)負(fù)責(zé) COM 注冊(cè),但是傳統(tǒng)的桌面應(yīng)用并不知道如何注冊(cè)。雖然不注冊(cè)這些 DLL并不影響編譯,但是在運(yùn)行時(shí)如果調(diào)用到相關(guān)的接口,會(huì)有異常拋出。所以需要一種免注冊(cè)的技術(shù)。通過(guò)添加 manifest 文件,并在其中指定本地的 COM 服務(wù)器,也可以通過(guò) COM 技術(shù)訪問(wèn)這些 DLL。
????WinRT 組件中的 winmd 文件實(shí)際和 .Net DLL 一樣,可以直接被 .Net 工程引用并使用里面定義的各種接口。但是在 C/C++ 工程里面,我們只能使用頭文件,借助?CppWinRT,可以基于 winmd 文件生成對(duì)應(yīng)的頭文件供我們調(diào)用,并且我們不需要關(guān)心這些頭文件內(nèi)部的實(shí)現(xiàn)細(xì)節(jié)。它內(nèi)部實(shí)現(xiàn)了調(diào)用 COM 接口相關(guān)的代碼,我們只需要關(guān)注接口即可。
下面是具體實(shí)踐演示,在演示中,我使用 CppWinRT 模板創(chuàng)建一個(gè)?WinRT 組件工程,命名為WinRTComponet,主要接口定義如下:
該工程生成兩個(gè)文件
WinRTComponent.winmd
WinRTComponent.dll
演示一,在 C++ console 應(yīng)用中使用 WinRT組件
1.創(chuàng)建一個(gè)空的 C++ Console 工程?Comsumer_CppConsole
2.引入 WinRTComponent.winmd
不能直接添加對(duì)工程 WinRTComponent 的引用,VS 會(huì)報(bào)目標(biāo)平臺(tái)不兼容的錯(cuò)誤。
在 Nuget Manager 中,對(duì)該工程安裝 Microsoft.Windows.CppWinRT,安裝完成后,可以在添加引用面板中,找到生成的文件 WinRTComponent.winmd, 然后把它添加到工程中
以上操作對(duì)應(yīng)到工程配置文件,實(shí)際添加了如下片段
3. 生成頭文件
編譯一次該工程,因?yàn)橹鞍惭b了 Nuget 包 Microsoft.Windows.CppWinRT,它會(huì)生成WinRTComponent.winmd 對(duì)應(yīng)的頭文件,這些文件位于工程目錄下面,代碼片段如下
4. 編寫客戶代碼
如下代碼片段中引用了生成的頭文件,該頭文件正是第3步中生成的,生成的頭文件名一般是<winrt/命名空間.h>
如果現(xiàn)在我們就編譯執(zhí)行這段代碼,會(huì)遇到如下類似錯(cuò)誤
Microsoft C++ exception: winrt::hresult_class_not_registered at memory location 0x000000BF422FF2F8
因?yàn)樗也坏紺OM服務(wù)器
5.?引入?WinRTComponent.dll,
首先需要把 WinRTComponent.dll 復(fù)制到客戶程序Comsumer_CppConsole.exe的目錄,可以手動(dòng),也可以通過(guò)腳本
在工程中創(chuàng)建一個(gè) manifest 清單文件,Comsumer_CppConsole.manifest,一定保證該文件的在VS 屬性對(duì)話框中的文件類型是?Manifest Tool
添加完該文件后,實(shí)際在 VS 工程配置文件中多了如下配置
現(xiàn)在可以嘗試編譯運(yùn)行該 console app,但是依然會(huì)有如下錯(cuò)誤
WinRT originate error - 0x8007007E : 'The specified module could not be found.'
這是因?yàn)樯傻?WinRT組件本身還有一些依賴的 DLL。
6. 安裝 Nuget 包?Microsoft.VCRTForwarders
因?yàn)?WinRT 組件本身要用到很多的 VC 庫(kù),比如 vcruntime140_app.dll,他們和傳統(tǒng)的桌面應(yīng)用用到的庫(kù)不同之處是有一個(gè)“_app”后綴,表明他們是應(yīng)用商店專用版本,這種 VC 庫(kù)并不會(huì)安裝在用戶機(jī)器,所以我們的 WinRT 組件當(dāng)然找不到對(duì)應(yīng)的庫(kù),所以會(huì)報(bào)錯(cuò)。
VCRTForwarders 的作用就是把所有用到商店版本的 VC 庫(kù)接口的調(diào)用,轉(zhuǎn)發(fā)到隨 Windows分發(fā)的桌面版本的VC 庫(kù)中,這樣就解決了依賴的問(wèn)題。
安裝完成 VCRTForwarders 后,可以發(fā)現(xiàn)我們成功的在 Console 中調(diào)用到 WinRT 組件里的接口!
演示二,在 基于.Net Framework的 WinForm或者 WPF 應(yīng)用中使用 WinRT組件
1.直接添加對(duì) WinRTComponent 的工程引用;
2.如同演示一,創(chuàng)建 manifest 文件
需要手動(dòng)將該 manifest 文件加入到 VS 工程配置文件中,添加如下代碼配置即可
3.安裝 Nuget 包 Microsoft.VCRTForwarders
值得注意的是,針對(duì)基于.Net Framework 的 WinForm和WPF程序,流程比較簡(jiǎn)單,因?yàn)樗麄兛梢灾苯右?WinRT 組件工程。但如果是基于.Net Core,則情況有所不同,演示二不適用。
以上兩個(gè)演示主要參考了如下文章
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