在使用默认的 WPF 项目开发的时候,咱是不需要自己编写 Main 函数的,在 WPF 中的 Main 函数是存放在 App.g.cs 里面,看起来这个 Main 函数是生成的函数,本文将介绍在 WPF 框架中是如何创建这个入口函数
阅读本文你将了解 WPF 框架中,默认在 App.g.cs 生成入口 Main 函数的详细过程。阅读本文之前,你需要了解一些编译过程的知识以及代码生成的知识
默认的 Application 继承类命名是 App.xaml 如果在你的项目中,依然使用默认的命名,那么在 .NET 5 的 SDK 下,将会自动加上以下默认的代码
上面代码是将 App.xaml 识别作为 ApplicationDefinition 的特殊内容,这个内容将被作为创建入口函数的出发点文件。也就是 App.g.cs 文件里面存放入口函数就由此决定
在 src\Microsoft.DotNet.Wpf\src\PresentationBuildTasks\MS\Internal\MarkupCompiler\MarkupCompiler.cs
文件里面的 GenerateAppEntryPoint 函数,如此函数命名所示,这就是创建应用入口点的方法,大概逻辑如下
在 WPF 中不是拼接字符串的方式完成代码生成的,而是需要用上代码生成逻辑进行生成。在上面代码中有各个注释来告诉大家生成代码的作用,阅读方便
调用链关系上,通过 Roslyn 如何了解某个项目在 msbuild 中所有用到的属性以及构建过程 的方法,可以看到在构建过程中,将会先使用 UsingTask 加载 MarkupCompilePass2 任务
接着有一个专门的 Target 用来执行,如下面代码
因此 GenerateAppEntryPoint
函数的调用是放在 WPF 项目构建过程中执行,细节请看 WPF 程序的编译过程 - walterlv
在 MarkupCompilePass2 里面,将会经过层层调用,使用 GenerateAppEntryPoint
函数创建出 App.g.cs 的入口函数。在 GenerateAppEntryPoint 函数包含如下步骤
- 使用 GenerateEntryPointMethod 创建空的 Main 函数本身
- 使用 GenerateSplashScreenInstance 创建 SplashScreen 的调用,如果开发者有设置的话才会调用
- 通过 GenerateAppInstance 创建 App 对象,在接下来逻辑调用 InitializeComponent 和 Run 方法
下面是详细的步骤
在 GenerateAppEntryPoint
函数使用 GenerateEntryPointMethod
函数即可创建 Main 函数本身,里面不包含任何的逻辑
调用以上代码,就可以生成如下的逻辑,可以看到这是一个空白的 Main 方法
在接下来的逻辑就是生成 SplashScreen 的代码了
这里的判断核心就是用户有在 csproj 中设置某个图片作为 SplashScreen 图片,如下面代码
如果有做这个设置,那么 _splashImage
字段将存在值。这里稍微吐槽 WPF 的 MarkupCompiler\MarkupCompiler.cs 的诡异设计,在这个类里面有以下的定义
这是一个只开放给外面设置的属性,只有在 CompilerWrapper.cs 类里面设置,而 CompilerWrapper 是通过一个只设置的属性进行设置
而 CompilerWrapper 的 SplashImage 属性仅在 MarkupCompilePass1.cs 的 DoMarkupCompilation 函数进行设置,核心获取的值是在 MarkupCompilePass1 的 SplashScreen 属性获取
通过这个定义可以了解到这是在编译过程中获取的
回到入口函数的创建,在 GenerateSplashScreenInstance 函数里面将会在用户有设置 SplashScreen 时加上 SplashScreen 对象的创建逻辑,如下面代码
上面代码的 SPLASHCLASSNAME 的含义是 SplashClassName 定义如下面代码
而 SPLASHVAR 的含义是 Splash var
也就是 SplashScreen 对象的变量名,定义如下面代码
接下来就是调用 GenerateAppInstance
函数以来创建 App 对象
在 GenerateAppInstance 的逻辑如下
执行到上面的代码,大概在入口函数就有以下代码
可以看到用代码生成的逻辑会比拼接字符串需要更多的代码,如果用拼接字符串就不需要几行代码。上面代码的 SplashScreen 创建逻辑是可选的
在创建 App 完成之后,将会尝试判断是否存在 InitializeComponent 函数,如果存在就调用一下
上面代码的 INITIALIZE_COMPONENT
定义如下面代码
最后还需要加上 Run 方法执行,如下面代码
这样就完成了 Main 函数的创建,创建完成需要将 Main 函数加入到类中
这就是在 WPF 中创建入口函数的所有逻辑。如果大家不熟悉代码创建的编写方式,就假装 WPF 是通过拼接字符串的形式创建的就可以
当前的 WPF 在 https://github.com/dotnet/wpf 完全开源,使用友好的 MIT 协议,意味着允许任何人任何组织和企业任意处置,包括使用,复制,修改,合并,发表,分发,再授权,或者销售。在仓库里面包含了完全的构建逻辑,只需要本地的网络足够好(因为需要下载一堆构建工具),即可进行本地构建
更多请看 WPF 程序的编译过程 - walterlv
更多关于 App.xaml 请看 WPF教程七:通过App.xaml来了解Application类都能干什么 - 杜文龙 - 博客园
原文链接: http://blog.lindexi.com/post/dotnet-%E8%AF%BB-WPF-%E6%BA%90%E4%BB%A3%E7%A0%81%E7%AC%94%E8%AE%B0-%E9%BB%98%E8%AE%A4%E7%9A%84-Main-%E5%87%BD%E6%95%B0%E6%98%AF%E5%9C%A8%E5%93%AA%E5%88%9B%E5%BB%BA%E7%9A%84
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。
欢迎转载、使用、重新发布,但务必保留文章署名 林德熙 (包含链接: https://blog.lindexi.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我 联系。