dotnet 讀 WPF 源代碼筆記 了解 WPF 已知問題 用戶設備上不存在 Arial 字體將導致應用閃退


本文來告訴大家 WPF 已知問題,在用戶的設備上,如果不存在 Arial 字體,同時安裝了一些詭異的字體,那么也許就會讓應用在使用到詭異的字體的時候,軟件閃退

在 WPF 的 FontFamily.cs 字體類里面,有一個叫 FirstFontFamily 的屬性,這個屬性的邏輯代碼里面將包括在當前字體太過詭異時,自動 Fallback 到默認的字體,而默認的字體就是 Arial 字體。這個屬性將會在很多邏輯被調用,如獲取 FamilyNames 時

        public LanguageSpecificStringDictionary FamilyNames
        {
            get
            {
                CompositeFontFamily compositeFont = FirstFontFamily as CompositeFontFamily;
                if (compositeFont != null)
                {
                    // Return the read/write dictionary of family names.
                    return compositeFont.FamilyNames;
                }
                else
                {
                    // Return a wrapper for the cached family's read-only dictionary.
                    return new LanguageSpecificStringDictionary(FirstFontFamily.Names);
                }
            }
        }

在進入到尋找 Fallback 字體將會進入到 Invariant 的 Assert 判斷方法,在這里面找不到 Arial 字體時,將會進入 Environment.FailFast 讓應用程序閃退

以下是 FirstFontFamily 屬性的代碼,代碼我刪除了不關鍵部分

 if (family == null) 
 { 
     FontStyle style     = FontStyles.Normal; 
     FontWeight weight   = FontWeights.Normal; 
     FontStretch stretch = FontStretches.Normal; 
     family = FindFirstFontFamilyAndFace(ref style, ref weight, ref stretch); 
  
     if (family == null) 
     { 
     	 // 進入這里的邏輯將會去尋找 Fallback 字體
         // fall back to null font 
         family = LookupFontFamily(NullFontFamilyCanonicalName); 
         Invariant.Assert(family != null); 
     } 

在 LookupFontFamily 函數里面,將會嘗試去尋找 Arial 字體,上面代碼的 NullFontFamilyCanonicalName 默認就是使用 Arial 字體

        internal static readonly CanonicalFontFamilyReference NullFontFamilyCanonicalName = CanonicalFontFamilyReference.Create(null, "#ARIAL");

在 LookupFontFamily 函數里面將會調用 LookupFontFamilyAndFace 函數去尋找傳入的字體,尋找的方法是從 _defaultFamilyCollection 去尋找傳入的字體

這里的 _defaultFamilyCollection 是在靜態構造時獲取的,代碼如下

        private static volatile FamilyCollection _defaultFamilyCollection = PreCreateDefaultFamilyCollection();

以上的 PreCreateDefaultFamilyCollection 函數,實際就是讀取 WindowsFontsUriObject 列表,這里的 Windows 指的不是窗口,而是指 Windows 系統

        private static FamilyCollection PreCreateDefaultFamilyCollection()
        {
            FamilyCollection familyCollection = FamilyCollection.FromWindowsFonts(Util.WindowsFontsUriObject);
            return familyCollection;
        }

以上的 WindowsFontsUriObject 定義如下

        private const string WinDir = "windir";

            string s = Environment.GetEnvironmentVariable(WinDir) + @"\Fonts\";

            _windowsFontsLocalPath = s.ToUpperInvariant();

            _windowsFontsUriObject = new Uri(_windowsFontsLocalPath, UriKind.Absolute);

也就是說讀取的就是 windir 文件夾下的 Fonts 文件夾,也就是 C:\Windows\Fonts\ 文件夾

在 LookupFontFamilyAndFace 將會嘗試去從 C:\Windows\Fonts\ 文件夾尋找字體

                IFontFamily fontFamily = familyCollection.LookupFamily
                (
                    canonicalFamilyReference.FamilyName,
                    ref style,
                    ref weight,
                    ref stretch
                );

假定用戶從 C:\Windows\Fonts\ 文件夾刪除了 Arial 字體,那么將找不到字體,返回是空

也就是 LookupFontFamily 將返回空

        internal static IFontFamily LookupFontFamily(CanonicalFontFamilyReference canonicalName)
        {
            FontStyle style     = FontStyles.Normal;
            FontWeight weight   = FontWeights.Normal;
            FontStretch stretch = FontStretches.Normal;

            return LookupFontFamilyAndFace(canonicalName, ref style, ref weight, ref stretch);
        }

在 FirstFontFamily 屬性里面,判斷字體存在的代碼如下

 family = LookupFontFamily(NullFontFamilyCanonicalName);
 Invariant.Assert(family != null); 

在用戶刪除了 Arial 字體,將會讓 family 是空,而在 Invariant 的定義代碼如下

 internal static void Assert(bool condition) 
 { 
     if (!condition) 
     { 
         FailFast(null, null); 
     } 
 } 

以上的 FailFast 方法將會調用 Environment.FailFast 方法

 private // DO NOT MAKE PUBLIC OR INTERNAL -- See security note 
     static void FailFast(string message, string detailMessage) 
 { 
     if (Invariant.IsDialogOverrideEnabled) 
     { 
         // This is the override for stress and other automation. 
         // Automated systems can't handle a popup-dialog, so let 
         // them jump straight into the debugger. 
         Debugger.Break(); 
     } 
  
     Debug.Assert(false, "Invariant failure: " + message, detailMessage); 
  
     Environment.FailFast(SR.Get(SRID.InvariantFailure)); 
 } 

調用 Environment.FailFast 之后,應用程序就閃退了,只有在系統事件里面看到記錄

我認為這是一個不合理的設計,至少在框架層不應該有這樣的邏輯,作為一個十分成熟的 UI 框架,應該能兼容各個詭異的系統,我將這個問題報告給官方,請看 WPF known issues: Application will FailFast when not find the Arial font from system · Issue #4464 · dotnet/wpf


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM