WinUI 剪裁釋出中的一個小坑

Scighost發表於2023-02-21

WinUI 3 (以下簡稱 WinUI)框架釋出後的二進位制檔案過大的問題存在了很長時間,我在這篇文章中有過詳細的討論,好在 Windows App SDK v1.2 就已經支援剪裁釋出,但是我卻一直沒有成功實現,直到最近才發現了問題所在。

坑在哪

在 WinUI 上使用雲母或亞克力材質的時候,一般會照抄微軟文件中提供的方法。問題就出在這,這一頁最後更新於 2022-09-24,那時 WinUI 還不支援剪裁,文件中的方法自然不會有問題。

// 僅包含關鍵程式碼
class WindowsSystemDispatcherQueueHelper
{
    // ...

    object m_dispatcherQueueController = null;

    [DllImport("CoreMessaging.dll")]
    private static extern int CreateDispatcherQueueController([In] DispatcherQueueOptions options, [In, Out, MarshalAs(UnmanagedType.IUnknown)] ref object dispatcherQueueController);

    // 此方法在設定雲母或亞克力前呼叫
    public void EnsureWindowsSystemDispatcherQueueController()
    {
        // ...

        if (m_dispatcherQueueController == null)
        {
            DispatcherQueueOptions options;
            // ...
            CreateDispatcherQueueController(options, ref m_dispatcherQueueController);
        }
    }
}

在這裡透過 P/Invoke 呼叫了一個外部 COM 函式,而開啟剪裁後,編譯器會警告:Trim analysis warning IL2050: P/invoke method 'WindowsSystemDispatcherQueueHelper.CreateDispatcherQueueController' declares a parameter with COM marshalling. Correctness of COM interop cannot be guaranteed after trimming. Interfaces and interface members might be removed. 剪裁後無法保證 COM 互操作的正確性,可能會刪除介面和介面成員。事實證明剪裁後確實出錯了。

檢視函式 CreateDispatcherQueueController 的定義,第二個引數返回的是一個指標,同時我們也不關心 COM 物件內容的細節,那解決方法就很簡單,使用 IntPtr 替換 object 即可。

HRESULT CreateDispatcherQueueController(
    [in]  DispatcherQueueOptions     options,
    [out] PDISPATCHERQUEUECONTROLLER *dispatcherQueueController
);

修改後的程式碼如下:

class WindowsSystemDispatcherQueueHelper
{
    // 改為 nint
    nint m_dispatcherQueueController;
    
    [DllImport("CoreMessaging.dll")]
    // 刪除互操作相關的特性
    private static extern int CreateDispatcherQueueController(in DispatcherQueueOptions options, out nint dispatcherQueueController);

    public void EnsureWindowsSystemDispatcherQueueController()
    {
        if (m_dispatcherQueueController == 0)
        {
            DispatcherQueueOptions options;
            
            _ = CreateDispatcherQueueController(options, out m_dispatcherQueueController);
        }
    }
}

在最新的 Windows App SDK v1.3 實驗版 1 中引入了新的 API,大大簡化了設定背景材質的流程,並且這個新 API 不存在上述問題。

釋出設定

WinUI 支援了剪裁,這為我們釋出小體積應用創造了條件,不過這裡的坑也很多。

需要注意的是,.NET 7 修改了剪裁粒度的預設值,預設剪裁全部程式集,所以釋出時一定要把 TrimMode 設定為 partial。這是一個比較粗糙的解決辦法,可能有更優的方案,但是我沒有深入研究。

WinUI 執行依賴 Windows App Runtime,這個元件沒有內建在系統中,並且更新還很頻繁。當釋出為 Msix 包時,無論是否上架微軟商店,安裝應用的過程中商店服務都會自動下載並安裝 Runtime(可能會因為網路問題安裝失敗)。但是不上架商店的 Msix 包安裝起來就很麻煩,釋出為非打包的二進位制程式更為合適。

釋出為非打包的二進位制程式時,可以選擇是否包含 Windows App Runtime,如果不包含並且按照以下設定釋出:

<!--  不打包  -->
<WindowsPackageType>None</WindowsPackageType>
<!--  啟用剪裁  -->
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>partial</TrimMode>
<!--  自包含 .NET 執行時  -->
<SelfContained>true</SelfContained>
<!--  單檔案  -->
<PublishSingleFile>true</PublishSingleFile>
<!--  關閉 ReadyToRun  -->
<PublishReadyToRun>false</PublishReadyToRun>
<!--  不包含 Windows App Runtime  -->
<WindowsAppSDKSelfContained>false</WindowsAppSDKSelfContained>
<!--  壓縮釋出的檔案  -->
<EnableCompressionInSingleFile>true</EnableCompressionInSingleFile>
<!--  單檔案包含原生庫  -->
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>

空模板專案最終生成三個檔案,“小而美”,但是執行後會彈窗提醒下載安裝 Windows App Runtime,在這個網頁中沒有引導真的很難找到正確的下載連結。

不包含 WindowsAppRuntime 釋出

WindowsAppRuntime 下載提醒

如果選擇釋出時包含 Windows App Runtime,就不能在釋出的單檔案中包含原生庫,否則會出現無法開啟的情況,相關討論參考 issue。結果就是釋出後的檔案很繁雜,整個 Runtime 都在釋出資料夾中,並且還存在著非常多的語言相關資料夾。經過我的測試只需要保留 en-usMicrosoft.UI.Xaml 這兩個資料夾,刪除其他資料夾不影響使用。這樣一個空模板專案的大小最終是 58 MB。

包含 WindowsAppRuntime 釋出

總結

沒有總結。

相關文章