专栏首页林德熙的博客dotnet 读 WPF 源代码笔记 了解 WPF 已知问题 用户设备上不存在 Arial 字体将导致应用闪退

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


本文会经常更新,请阅读原文: https://blog.lindexi.com/post/dotnet-%E8%AF%BB-WPF-%E6%BA%90%E4%BB%A3%E7%A0%81%E7%AC%94%E8%AE%B0-%E4%BA%86%E8%A7%A3-WPF-%E5%B7%B2%E7%9F%A5%E9%97%AE%E9%A2%98-%E7%94%A8%E6%88%B7%E8%AE%BE%E5%A4%87%E4%B8%8A%E4%B8%8D%E5%AD%98%E5%9C%A8-Arial-%E5%AD%97%E4%BD%93%E5%B0%86%E5%AF%BC%E8%87%B4%E5%BA%94%E7%94%A8%E9%97%AA%E9%80%80.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名林德熙(包含链接: https://blog.lindexi.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系

无盈利,不卖课,做纯粹的技术博客

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • dotnet 从入门到放弃的 500 篇文章合集

    博客包括 C#、WPF、UWP、dotnet core 、git 和 VisualStudio 和一些算法,所有博客使用 docx 保存

    林德熙
  • 通过解读 WPF 触摸源码,分析 WPF 插拔设备触摸失效的问题(问题篇)

    发布于 2018-08-15 07:42 更新于 2018-08...

    walterlv
  • dotnet 在 Windows 系统上使用 stakx 的 WIC 库

    在 Windows 系统上,有一个很重要的概念是 Windows Imaging Component 也就是 WIC 层,这是专门用来处理多媒体相关的系统组件,...

    林德熙
  • SourceYard 制作源代码包 控制台项目WPF 程序调试

    本文带大家走进SourceYard开发之旅 在项目开发中,将一个大的项目拆为多个小项目解耦,减少模块之间的耦合。因为如果将代码放在一起,即使有团队的约束,但只要...

    林德熙
  • win10 支持默认把触摸提升 Pointer 消息

    在 WPF 经常需要重写一套触摸事件,没有UWP的Pointer那么好用。 如果一直都觉得 WPF 的触摸做的不好,或想解决 WPF 的触摸问题,但是没有方法,...

    林德熙
  • 制作的 dotnet tool 运行失败提示依赖缺失

    小伙伴做了一个很好用的 dotnet tool 工具,但是这个工具仅在他的设备上能运行,在我的设备上运行就会退出提示 An assembly specified...

    林德熙
  • dotnet 读 WPF 源代码笔记 WIC 多媒体图片处理通过 WindowsCodecs.dll 实现功能

    在 WPF 中,作为一个现代化的 UI 框架,自然有很多多媒体相关的事情需要处理,在 WPF 中有特别的一层是 WIC 层,这一层将包揽了大部分的多媒体图片的处...

    林德熙
  • WPF 从 dotnet core 3 到 dotnet 5 的变更

    本文收藏我所了解的从 dotnet core 3 到 2020.11.10 发布的 dotnet 5 的 WPF 的变更

    林德熙
  • WPF 最简方法使用自己定制的 WPF 框架

    本文提供了一个最简的方法,可以用到整个 WPF 框架里面所有 internal 内部权限的成员的方法。这是一个我自己定制的 WPF 框架,可以在此基础上构建属于...

    林德熙

扫码关注云+社区

领取腾讯云代金券