一種啟動和檢測 UWP 應用的方法 背景 我們發佈過多款 UWP 平臺的同類型 App ,最近有一個需求:用傳統 Win32 程式啟動我們的 UWP 程式。因為我們的每一個UWP App在客戶機器上都是互斥的,也就是同時只能存在一個,並且我們的win32程式也只有一個版本,所以啟動 UWP App ...
一種啟動和檢測 UWP 應用的方法
背景
我們發佈過多款 UWP 平臺的同類型 App ,最近有一個需求:用傳統 Win32 程式啟動我們的 UWP 程式。因為我們的每一個UWP App在客戶機器上都是互斥的,也就是同時只能存在一個,並且我們的win32程式也只有一個版本,所以啟動 UWP App 時,需要先檢測,再啟動。
我們大概有4個辦法,前3個比較扯,第4個目前可行,也是我們採用的。這4個方法的主要關註點是:如何檢測客戶機器上是否有我們的 UWP App。至於調用,方法比較簡單。
Solution 1
Win32 和 UWP 交互,首先想到的就是微軟的 Desktop Bridge 相關的內容,找了一圈,倒是發現了 Win32 調用 UWP Api 的方法,不過可以調用的 Api 有限,而且文檔比較殘缺,最麻煩的就是要對 Win32 Project 配置修改,引入一堆 WinRT 的東西。嘗試了半天,終於不報錯了,但是運行時會奔潰,原因未知,有待繼續探索。而且比較存疑的是官方文檔有矛盾,我們用到的 Windows.System.Launcher Api 是否被這種調用方式支持不明確,因為報錯我們也無法驗證。
有興趣的小伙伴可以參考以下鏈接:
Desktop Bridge
Detect UWP App
- UWP Quick Tip - Detect any installed app on Windows 10 這也是 Solution 1 的核心,一個小 trick
Solution 2
簡單粗暴,直接檢測 UWP 的安裝目錄。一般 UWP 的預設安裝路徑就是 "C:\Program Files\WindowsApps"。這種方法真的很簡單粗暴,但是有幾個缺點:
- 可能有強迫症用戶修改了 UWP 的安裝路徑。這種情況下,需要自行去查註冊表,當然註冊表鍵值是什麼就需要baidu了;
- 如果直接枚舉 "C:\Program Files\WindowsApps"的子目錄,會有許可權問題(System),普通用戶許可權只能訪問類似 "C:\Program Files\WindowsApps\microsoft.windowscommunicationsapps_17.9126.21695.0_x64__8wekyb3d8bbwe"的特定 UWP App 目錄,這就需要我們提前確定要查找的 UWP App的 pfn (package family name,UWP App 的特定標識,全球唯一)和版本,但是版本因為經常變化,比較不好確定。
Solution 3 (Solution 1和這個差不多)
微軟為我們提供了許多啟動 UWP 的方式,比如什麼協議啟動,命令行啟動等,但是這些方法的使用前提是:我們的UWP app需要修改現有的 App Manifest,這對於已經發佈出去的UWP App,顯然是不可能的。(在我們的場景下,因為我們的 UWP App 和驅動綁定,一般隨驅動升級,比較穩定,所以此方法不可用)
Solution 4 (Best solution)
隱約記得以前使用 Fiddler 的時候,有一個 WinConfig 功能,可以列出當前電腦上所有的 UWP 程式(實際上是 沙箱類程式,從 Windows 8 開始, UWP 也包含其中),然後可以進行 web 調試。所以就想能不能借鑒 Fiddler 的做法。然後理所應當的發現 Fiddler 安裝目錄下麵有一個名為 EnableLoopback.exe 的程式,沒有為什麼,我就把它丟到了ILSpy裡面,完美的反編譯出了C#代碼,然後經過一番探索,發現了AppContainer類,無論看類名還是類的定義,都很明確,這就是我們要找的東西,然後順著這個類看下去,找到了它獲取所有 UWP 程式的方法:通過 FirewallAPI.dll 裡面的介面 NetworkIsolationEnumAppContainers 來枚舉。
有了瞭解,開始Coding!
BTW,如果想省事兒的話,直接把這個類相關的內容導出,是可以直接用的。不過我們的 Win32 是用C++寫的,所以要稍稍轉換一下。
C++代碼如下:
#include <Netfw.h>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
namespace Launcher
{
typedef DWORD(*pNetworkIsolationEnumAppContainers)(
_In_ DWORD Flags,
_Out_ DWORD *pdwNumPublicAppCs,
_Out_ PINET_FIREWALL_APP_CONTAINER *ppPublicAppCs
);
typedef DWORD(*pNetworkIsolationFreeAppContainers)(
_In_ PINET_FIREWALL_APP_CONTAINER pPublicAppCs
);
void LaunchSpecifcApp(wstring *pfn)
{
TCHAR szCommandLine[1024];
wsprintf(szCommandLine, L"explorer.exe shell:AppsFolder\\%ws!App", (*pfn).c_str());
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = TRUE;
BOOL bRet = ::CreateProcess(NULL, szCommandLine, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);
}
void LaunchUWPApp()
{
vector<wstring> uwpApps;
uwpApps.push_back(L"microsoft.windowscommunicationsapps_8wekyb3d8bbwe");
HMODULE FirewallAPIModule;
FirewallAPIModule = (LoadLibrary(L"FirewallAPI.dll"));
auto EnumAppContainersProc = pNetworkIsolationEnumAppContainers(GetProcAddress(FirewallAPIModule, "NetworkIsolationEnumAppContainers"));
auto FreeAppContainersProc = pNetworkIsolationFreeAppContainers(GetProcAddress(FirewallAPIModule, "NetworkIsolationFreeAppContainers"));
DWORD pdwNumPublicAppCs = 0;
PINET_FIREWALL_APP_CONTAINER ppPublicAppCs = NULL;
HRESULT re = EnumAppContainersProc(0, &pdwNumPublicAppCs, &ppPublicAppCs);
for (int i = 0; i < pdwNumPublicAppCs; i++)
{
auto appContainer = ppPublicAppCs[i];
for (int j = 0; j < uwpApps.size(); j++)
{
auto app = uwpApps.at(j);
transform(app.begin(), app.end(), app.begin(), tolower);
if (app == appContainer.appContainerName)
{
//launch it;
auto temp = uwpApps.at(j);
LaunchSpecifcApp(&temp);
}
}
}
FreeAppContainersProc(ppPublicAppCs);
FreeLibrary(FirewallAPIModule);
vector<wstring>().swap(uwpApps);
}
}
代碼很直白,裡裡面就兩個函數,一個用來查找,一個用來啟動,額外用到的就是 Win32 Dll 調用相關的內容了。
最後
可以看到,我們查找 UWP 比較麻煩,但是調用卻很簡單,核心就是:
"explorer.exe shell:AppsFolder\\{pfn}!App"
很直白,赤裸裸的一個快捷方式呀!但是有坑,如果傳遞的參數有任何問題(要麼拼錯了,要麼不存在),explorer 會直接忽略參數,把自己啟動。這種行為,對於不明真相的用戶,會很莫名其妙,垃圾軟體。所以我們在啟動我們的 UWP App 時,要確保這個我們的 App 一定存在於用戶的電腦上面,所以才有了上面檢測 UWP App 的邏輯。如果參數錯誤,explorer 啥也不敢的話,我們就不這麼麻煩了,可以直接把我們所有的 UWP app 挨個啟動一遍,簡單粗暴!
最後的最後
我們用到了 Fillder 裡面所使用的方法,但對於 Fiddler 版權的各種問題,個人不瞭解。好在我們直接用 C++ 實現,沒有任何影響。 權當學習學習!
之前網上有 Fiddler 2.x版本的源碼,但不清楚這軟體是不是開源。
致敬 Fiddler !