.NET Core 和 .NET Framework 启动可执行文件的差别

在 Windows 下,使用 .NET Framework 构建出来的应用,可以只有一个可执行文件,在可执行文件里面包含了 IL 代码。使用 .NET Core 构建出来的应用,将会包含一个 Exe 可执行文件,和对应的 Dll 文件,而 IL 代码将放在 Dll 文件里面。那么使用 .NET Framework 和使用 .NET Core 所输出的 Exe 可执行文件有什么差别,本文将从文件格式以及启动过程两个方面给大家聊聊这两个的不同

与封闭的 dotnet framework 不相同的是,咱可以从 GitHub 上获取完全的 dotnet core 整个的源代码。也因此咱能了解到的 dotnet core 的细节将会比 dotnet framework 多得多。在本文开始之前,必须要说明的是,本文非入门向,而且本文的内容是需要在很多的限制条件下才成立。在经过了这么多年的发展,无论是 dotnet framework 还是 dotnet core 都有着各自的不同的多个版本甚至的多个分支,每个之间都有着很多差别。而限于我的技术和考古能力,我仅仅能聊的只有最通用的部分

当前有很多书籍在讲 dotnet core 的底层,我在这里将这些书籍推荐给期望了解更多底层细节的伙伴:农夫的 《.NET Core底层入门》 以及伟民哥翻译的 《.NET内存管理宝典 - 提高代码质量、性能和可扩展性》 这两本书。本文的很多细节出处都来自于这两本书

本文所指的 dotnet core 包括了 dotnet core 以及 dotnet 5 等多个版本,不讨论加入 Mono 以及加入 .NET Native 和单文件发布等科技。本文的 dotnet framework 指的是 dotnet framework 4.0 到 4.8 的版本,其他版本不在本文范围内,根据我的考古,更古老的 dotnet framework 有不同的行为,但我缺乏足够的依据,因此也不在放在本文

先从文件的格式开始聊起。在咱写一个默认的 .NET Framework 控制台应用的时候,在 VisualStudio 上进行 Debug 调试构建输出,此时将可以从输出文件路径上看到仅仅只有一个 EXE 可执行文件,而没有 DLL 动态链接库文件。这个输出的 Exe 可执行文件是一个符合标准的 PE 格式的文件。而 PE (Portable Executable)格式文件是微软 Win32 环境可执行文件的标准文件格式。也就是说使用 .NET Framework 输出的可执行文件和其他 Win32 可执行文件的文件格式是相同的。尽管格式上是相同的,微软在 Windows 下依然对 .NET Framework 应用做了特别的处理,因为在 .NET Framework 输出的可执行文件里面,包含了从元数据和 MSIL 代码,换句话说就是真正的逻辑是包含在 MSIL 代码里面,而不是作为本机代码的存在。这就需要 Windows 系统在用户执行 .NET Framework 可执行文件的时候进行一些特殊的处理

那既然 .NET Framework 的可执行文件在执行时需要 Windows 做特殊的处理,那么 Windows 如何了解到这是一个需要处理的 .NET Framework 应用?根据 Managed Execution Process 官方文档 可以了解到,在 .NET Framework 的输出可执行文件里面,在 PE 文件的 COFF 头内容添加了特殊的内容,用来标识这是一个 .NET Framework 应用

在运行 .NET Framework 的可执行文件的时候,首先进入的 operating system loader 将会判断 PE 文件的 COFF 头内容,通过 COFF 头识别这个可执行文件是否 .NET Framework 可执行文件。对于 .NET Framework 可执行文件而言,将会加载 mscoree.dll 进行执行,通过 _CorValidateImage 和 _CorImageUnloading 分别用来通知 operating system loader 托管模块的映像的加载和卸载。其中在 _CorValidateImage 中将执行确保该代码是有效的托管代码以及将映像中的入口点更改为运行时中的入口点。而在 x64 中,还会在 _CorValidateImage 中通过在内存中修改映像的 PE32 为 PE32+ 格式。也因为 .NET Framework 应用是依靠系统的特殊处理,因此 .NET Framework 又有一个原因耦合了系统环境,这和 .NET Core 的启动有着本质的差别

回到 .NET Core 下,依然是通过 VisualStudio 在 Debug 下构建输出一个控制台的应用。此时可以看到构建输出将会包含一个 Exe 和对应的 Dll 文件。通过 dotnet core 应用是如何跑起来的 通过AppHost理解运行过程 可以了解到,在 .NET Core 的默认输出的 Exe 可执行文件是 AppHost 文件,这是一个纯 Win32 可执行文件,里面不包含 IL 代码。而业务逻辑的 IL 代码是存放在 DLL 里面

在 .NET Core 下,输出的 Exe 可执行文件其实是通过预先已经构建完毕的模板文件,进行二进制修改替换生成的文件。在构建的过程中,将会从 C:\Program Files\dotnet\sdk\5.0.100\AppHostTemplate\ 文件夹里面将 apphost.exe 文件拷贝到项目的 obj 文件夹下。然后执行 HostWriter 构建过程命令,将 AppHost 中的特定二进制内容替换为具体项目的信息,接着拷贝到最终输出文件路径。当然,上文的 AppHostTemplate 文件夹路径会根据你所安装的 dotnet sdk 版本而变化。那么这个输出的 AppHost 文件是谁构建的,作用又是什么?细节部分如下

通过开源的 .NET Core 仓库,可以了解到在 dotnet runtime\src\installer\corehost\ 文件夹里面,其实就是 AppHost 文件的核心逻辑。通过阅读 dotnet runtime\src\installer\corehost\corehost.cpp 文件可以了解到在 AppHost 文件在执行过程中所执行的过程。在 dotnet runtime\src\installer\corehost\corehost.cpp 文件的 exe_start 大概就是整个可执行文件的入口方法了,在这里实现的功能将包含使用 hostfxr 和 hostpolicy 来托管执行整个 dotnet 进程,以及主函数的调起。而在使用托管之前,需要先寻找 dotnet_root 也就是 dotnet 框架用来承载整个 dotnet 进程。而不同的项目之间有着不同的项目信息,需要在执行过程中进行动态配置。这就是在构建过程中 HostWriter 构建过程所执行的逻辑,将预先构建完成的 AppHost 中的部分内容替换为具体项目的信息,同时给 AppHost 嵌入 Win32 清单,如图标等内容

在 .NET Core 中,在 SDK 里面已经包含了将 dotnet runtime\src\installer\corehost\ 文件夹里面的 AppHost 构建输出的 AppHost.exe 文件。在此文件里面由以下代码定义了部分模板替换内容

#define EMBED_HASH_HI_PART_UTF8 "c3ab8ff13720e8ad9047dd39466b3c89" // SHA-256 of "foobar" in UTF-8
#define EMBED_HASH_LO_PART_UTF8 "74e592c2fa383d4a3960714caef0c4f2" // 这两句代码就是 foobar 的 UTF-8 二进制的 SHA-256 字符串
#define EMBED_HASH_FULL_UTF8    (EMBED_HASH_HI_PART_UTF8 EMBED_HASH_LO_PART_UTF8) // NUL terminatedbool is_exe_enabled_for_execution(pal::string_t* app_dll)
{constexpr int EMBED_SZ = sizeof(EMBED_HASH_FULL_UTF8) / sizeof(EMBED_HASH_FULL_UTF8[0]);// 这里给的是就是最长 1024 个 byte 的 dll 名,加上一个 \0 一共是 1025 个字符constexpr int EMBED_MAX = (EMBED_SZ > 1025 ? EMBED_SZ : 1025); // 1024 DLL name length, 1 NUL// 这就是定义在 AppHost.exe 二进制文件里面的一段空间了,长度就是 EMBED_MAX 长度,内容就是 c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2 这段字符串static char embed[EMBED_MAX] = EMBED_HASH_FULL_UTF8;     // series of NULs followed by embed hash stringstatic const char hi_part[] = EMBED_HASH_HI_PART_UTF8;static const char lo_part[] = EMBED_HASH_LO_PART_UTF8;// 将 embed 的内容复制到 app_dll 变量里面pal::clr_palstring(embed, app_dll);
}int exe_start(const int argc, const pal::char_t* argv[])
{// 读取嵌入到二进制文件的 App 名,也就是 dotnet 的入口 dll 路径,可以是相对也可以是绝对路径pal::string_t embedded_app_name;if (!is_exe_enabled_for_execution(&embedded_app_name)){trace::error(_X("A fatal error was encountered. This executable was not bound to load a managed DLL."));return StatusCode::AppHostExeNotBoundFailure;}// 将 embedded_app_name 的内容赋值给 app_path 变量,这个变量的定义代码我没有写append_path(&app_path, embedded_app_name.c_str());const pal::char_t* app_path_cstr = app_path.empty() ? nullptr : app_path.c_str();// 跑起来 dotnet 应用rc = hostfxr_main_bundle_startupinfo(argc, argv, host_path_cstr, dotnet_root_cstr, app_path_cstr, bundle_header_offset);
}

也如上面代码所述,在 AppHost 文件里面将会通过 hostfxr_main_bundle_startupinfo 跑起来 dotnet 应用,加载 CLR 执行引擎,执行 DLL 里面的 IL 逻辑

在具体项目构建过程中,将通过 HostWriter 构建过程,执行如下逻辑,将如上面代码的 foobar 的 UTF-8 二进制的 SHA-256 字符串替换为具体项目的路径

    /// <summary>/// Embeds the App Name into the AppHost.exe/// If an apphost is a single-file bundle, updates the location of the bundle headers./// </summary>public static class HostWriter{/// <summary>/// 这就是 AppHost 的 foobar 的 UTF-8 二进制的 SHA-256 字符串/// </summary>private const string AppBinaryPathPlaceholder = "c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2";private static readonly byte[] AppBinaryPathPlaceholderSearchValue = Encoding.UTF8.GetBytes(AppBinaryPathPlaceholder);/// <summary>/// Create an AppHost with embedded configuration of app binary location/// </summary>/// <param name="appHostSourceFilePath">The path of Apphost template, which has the place holder</param>/// <param name="appHostDestinationFilePath">The destination path for desired location to place, including the file name</param>/// <param name="appBinaryFilePath">Full path to app binary or relative path to the result apphost file</param>/// <param name="windowsGraphicalUserInterface">Specify whether to set the subsystem to GUI. Only valid for PE apphosts.</param>/// <param name="assemblyToCopyResorcesFrom">Path to the intermediate assembly, used for copying resources to PE apphosts.</param>public static void CreateAppHost(string appHostSourceFilePath,string appHostDestinationFilePath,string appBinaryFilePath,bool windowsGraphicalUserInterface = false,string assemblyToCopyResorcesFrom = null){var bytesToWrite = Encoding.UTF8.GetBytes(appBinaryFilePath);if (bytesToWrite.Length > 1024){throw new AppNameTooLongException(appBinaryFilePath);}void RewriteAppHost(){// Re-write the destination apphost with the proper contents.using (var memoryMappedFile = MemoryMappedFile.CreateFromFile(appHostDestinationFilePath)){using (MemoryMappedViewAccessor accessor = memoryMappedFile.CreateViewAccessor()){BinaryUtils.SearchAndReplace(accessor, AppBinaryPathPlaceholderSearchValue, bytesToWrite);appHostIsPEImage = PEUtils.IsPEImage(accessor);if (windowsGraphicalUserInterface){if (!appHostIsPEImage){throw new AppHostNotPEFileException();}PEUtils.SetWindowsGraphicalUserInterfaceBit(accessor);}}}}// 忽略代码}}

那为什么在 dotnet 里面选择的是预先构建出来 AppHost 文件,将 AppHost 文件放在 SDK 里面。在构建代码的时候通过构建过程将 AppHost 文件的部分二进制替换而输出为最终可执行文件?原因就是 dotnet core 期望能做到让构建过程尽可能简单,同时又期望能支持更多的平台。通过预先构建出来可执行二进制文件,可以在构建出来的二进制文件尽可能使用 Native 的逻辑,而一旦使用了 Native 的逻辑就意味着构建环境有足够的要求。但是在开发者端,其实很没有必要去安装那么多的构建环境,因此就预先构建出来二进制文件,这样开发者端就能专注于构建 dotnet 的逻辑,而不需要为了构建入口的可执行文件而安装大量的构建环境

在 dotnet core 里面,所有的 IL 逻辑存放在独立的 DLL 里面,在 Windows 下的可执行文件仅仅是 AppHost 文件,是一个没有被系统特殊处理的 Win32 可执行文件。在运行过程中,将在一开始执行 AppHost 的本机逻辑,在 AppHost 的 Native 逻辑里面将跑起来 dotnet 引擎,加载 DLL 里面的 IL 逻辑,然后将舞台交给咱的业务逻辑

回顾一下 dotnet core 和 dotnet framework 的可执行文件的差别

文件内容的差别是:

  • .NET Core: 纯 Win32 的 PE 格式文件,不包含 IL 逻辑。包含 IL 逻辑的放在额外的 Dll 文件

  • .NET Framework: 稍微特殊的 Win32 的 PE 格式文件,包含了特殊 COFF 头内容用来标识这是 .NET Framework 文件。在 PE 格式文件里面包含了 IL 逻辑

启动的时候的差别是:

  • .NET Core: 作为传统的 Win32 应用启动,在启动过程中加载 CLR 引擎,然后通过 CLR 引擎执行 IL 逻辑

  • .NET Framework: 由系统根据 COFF 头判断这是 .NET Framework 应用,通过特殊手段启动,使用系统的 mscoree.dll 进行初始化

这就是 .NET Framework 和 .NET Core 启动的可执行文件的差别,以及执行的差别

现在的 .NET Framework 的运行时大部分逻辑都没有开源(我即使能通过MVP权限拿到我也不敢在这里吹)因此只能通过官方公开的文档了解到细节,而 .NET Core 是完全开源的,因此我对 .NET Core 里面的逻辑相对来说更了解。好在后续将会统一使用为完全开源的 .NET 5 以及后续版本,所以即使对 .NET Framework 的执行细节不了解,问题也许不大

关于 .NET Core 底层接地气的书籍,我推荐农夫的 《.NET Core底层入门》 这本书。而关于内存相关,我推荐伟民哥翻译的 .NET内存管理宝典 - 提高代码质量、性能和可扩展性 这本书。

参考

dotnet core 应用是如何跑起来的 通过AppHost理解运行过程

dotnet core 应用是如何跑起来的 通过自己写一个 dotnet host 理解运行过程

Managed Execution Process

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/305310.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

firefox 3.0 在 windows 下的编译

&#xff08;1&#xff09;下载firefox 3.0源代码。下载并安装 mozilla-build &#xff08;2&#xff09;运行 start-msvc9.bat&#xff0c;进入shell界面&#xff0c; 查看环境变量&#xff1a; echo $PATH echo $LIB echo $INCLUDE &#xff08;3&#xff09;确保: windo…

常见的几种最优化方法

阅读目录1. 梯度下降法&#xff08;Gradient Descent&#xff09;2. 牛顿法和拟牛顿法&#xff08;Newtons method & Quasi-Newton Methods&#xff09;3. 共轭梯度法&#xff08;Conjugate Gradient&#xff09;4. 启发式优化方法我们每个人都会在我们的生活或者工作中遇到…

安卓平板运行python_使用Python进行手机平板移动开发 | 学步园

过去&#xff0c;Android和iOS上的移动应用程序开发不是Python的强项&#xff0c;但情况可能会发生变化……使用Python进行移动应用开发怎么样&#xff1f;从历史上看&#xff0c;在编写移动GUI应用程序时&#xff0c;Python并没有很强的故事。实际上&#xff0c;仅使用纯Pytho…

专业的软件安装包可以这样做!

C/S客户端开发完成&#xff0c;需要将程序交付给用户&#xff0c;直接压缩发给用户是可以的&#xff08;只是有点不专业&#xff09;&#xff0c;如果能有一个比较好看的安装界面&#xff0c;那档次就不一样了。本文介绍怎么使用Adanced Installer 17.9 制作专业的Windows 客户…

jsp需要多少java基础_Java基础——JSP(一)

注意&#xff1a;访问JSP的过程如果是第一次访问服务器&#xff0c;则翻译成一个对应的java文件(Servlet)。然后&#xff0c;再被编成 .class 文件并加载到内存中。如果是以后访问&#xff0c;则直接调用内存中的jsp实例&#xff0c;所以第一次访问慢,以后访问会更加快。四、3种…

宝贝,对不起

宝贝&#xff0c;对不起 题记:读在地震中用生命保护三个月大的婴儿的伟大母亲的遗言 “亲爱的宝贝&#xff0c;如果你能活着&#xff0c;请一定要记得我爱你”有感 谨以此文献给那逝去的伟大母亲和幸存下来坚强的宝贝 ——代腾飞 2008年5月21日 于成都 面对这突如其来的空前灾…

IT人喝酒,不同岗位不同姿势

这是Boss们的常用套路&#xff0c;频频举杯&#xff0c;给大家鼓劲加油&#xff0c;但是自己不喝。有的销售&#xff0c;业绩好&#xff0c;酒品也好&#xff0c;不管和自己人喝酒&#xff0c;还是和客户喝酒&#xff0c;都是一副舍我其谁的霸气&#xff01;这是某些销售的写照…

国产CPU群雄逐鹿谁主沉浮

当下&#xff0c;国内&#xff08;桌面、服务器&#xff09;CPU与外商有较大差距&#xff0c;除了海光在性能上可能具有一拼之力外&#xff0c;其它国产CPU在商业市场上面对英特尔、AMD基本不具备竞争力&#xff0c;因而只能在篱笆墙内的市场角逐。而为了能够进入篱笆墙内的市场…

让网站性能最佳的34条黄金守则

Yahoo!的Exceptional Performance团队为改善Web性能带来最佳实践。他们为此进行了一系列的实验、开发了各种工具、写了大量的文章和博客并在各种会议上参与探讨。最佳实践的核心就是旨在提高网站性能。Excetional Performance团队总结出了一系列可以提高网站速度的方法。可以分…

蒙特卡罗方法入门

本文通过五个例子&#xff0c;介绍蒙特卡罗方法&#xff08;Monte Carlo Method&#xff09;。一、概述蒙特卡罗方法是一种计算方法。原理是通过大量随机样本&#xff0c;去了解一个系统&#xff0c;进而得到所要计算的值。它非常强大和灵活&#xff0c;又相当简单易懂&#xf…

面向业务的微服务消息总线

源宝导读&#xff1a;移动PaaS项目的异步场景中&#xff0c;随着订阅主题数的增加&#xff0c;会出现开发维护成本高、管理难度大等问题&#xff0c;本文将分享如何通过构建面向业务的微服务消息总线应对这些问题。一、背景面向业务的消息总线本质上是对消息队列进行二次封装&a…

java locale.us_JAVA实现国际化

## 1 Java国际化的思路Java程序的国际化的思路是将程序中的标签、提示等信息放在资源文件中&#xff0c;程序需要支持哪些国家、语言环境&#xff0c;就对应提供相应的资源文件。资源文件是key-value对&#xff0c;每个资源文件中的key是不变的&#xff0c;但value则随不同国家…

模拟时钟中断的产生及设计一个对时钟中断事件进行处理的模拟程序_操作系统基础6-支持操作系统的最基本的硬件-中断...

无论是桌面PC操作系统还是嵌入式都是多任务的操作系统&#xff0c;而很遗憾&#xff0c;处理器往往是单个的&#xff0c;即便在硬件成本逐渐下降&#xff0c;而硬件配置直线上升的今天&#xff0c;PC机的核心可能已经达到&#xff14;核心&#xff0c;&#xff18;核心&#xf…

XMLhttp学习应用

Client.htm页面代码如下: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns"http://www.w3.org/1999/xhtml"><head><title>客…

机器学习没有想象中的那么难

文末彩蛋&#xff0c;不容错过&#xff01;背景从去年的AlphaGo到今年人工智能首次写进政府工作报告&#xff0c;人工智能正在席卷全球&#xff0c;引发第4次工业革命&#xff0c;而AI的核心技术是机器学习和深度学习。目前&#xff0c;机器学习已广泛应用于数据挖掘、计算机视…

json.net java_java解析JSON (使用net.sf.json)

例如JSON字符串str如下&#xff1a;{"data": [{"basic_title": "运筹帷幄因特网","basic_creator": "马跃&#xff0c;余南阳编著","basic_publisher": "成都市&#xff1a;西南交通大学出版社","…

Docker查看应用的实际内存

前言 我们把应用部署到Docker里面之后&#xff0c;有什么办法查看这个应用占用了多少内存呢&#xff1f;docker本身提供了一个命令让我们可以直接看到当前时间所有容易占用的情况。docker stats --no-stream从上面来看&#xff0c;这几个应用用的内存加起来已经是将近12G了。但…

如何动态的生成某种类型的集合呢_知乎画报」的移动端动态化工程实践

本文基于移动端动态化方案在知乎原生推广落地页「知乎画报」上的实践经验&#xff0c;对该方案技术升级过程中的思考以及技术关键细节做了详尽的解读。商业化是互联网公司发展的重要阶段&#xff0c;App 端的商业广告业务对移动端动态化能力的需求很强烈&#xff0c;一方面需要…

asp.net 得到上一页地址

if(!IsPostBack) ViewState["retu"]Request.UrlReferrer.ToString();

任正非致歉华为前程序员:回来吧,公司错了

近几日&#xff0c;因着任正非连续签发邮件&#xff0c;无处不在热议华为&#xff0c;其中与所有的技术人可谓密切相关。除了一员工因说真话&#xff0c;被晋升两级&#xff0c;根据其自愿选择工作岗位及地点&#xff0c;并由无线网络产品线总裁邓泰华保护其不受打击报复之外&a…