为何DotNetCore的exe能双击运行_dotnet.exe 进程名-程序员宅基地

技术标签: c#  DotNetCore  

我们用DotNetCore开发的Winform和控制台程序按理编译的是中间代码。既然不是机器码,为何双击生成的.exe文件可以运行。DotNetCore和FrameWork不同,FrameWork有操作系统黑科技,来让双击的.Net程序运行。DotNetCore完全可以用独立部署,这种情况就是普通进程运行,必须得有可直接运行的机器码来加载运行时和托管程序集才能运行。

通过看DotNetCore源码发现dotnet.runtime\src\native\corehost\corehost.cpp有这么一段说明

/**
 * Detect if the apphost executable is allowed to load and execute a managed assembly.
 *
 *    - The exe is built with a known hash string at some offset in the image
 *    - The exe is useless as is with the built-in hash value, and will fail with an error message
 *    - The hash value should be replaced with the managed DLL filename with optional relative path
 *    - The optional path is relative to the location of the apphost executable
 *    - The relative path plus filename are verified to reference a valid file
 *    - The filename should be "NUL terminated UTF-8" by "dotnet build"
 *    - The managed DLL filename does not have to be the same name as the apphost executable name
 *    - The exe may be signed at this point by the app publisher
 *    - Note: the maximum size of the filename and relative path is 1024 bytes in UTF-8 (not including NUL)
 *        o https://en.wikipedia.org/wiki/Comparison_of_file_systems
 *          has more details on maximum file name sizes.
 */

再看github的代码
https://github.com/dnSpy/dnSpy/blob/2fa5c978b1a9fb8d1979c8aa4cfa6d177bf5aa9c/Build/AppHostPatcher/Program.cs的实现代码

/*
    Copyright (C) 2014-2019 [email protected]
    This file is part of dnSpy
    dnSpy is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    dnSpy is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    You should have received a copy of the GNU General Public License
    along with dnSpy.  If not, see <http://www.gnu.org/licenses/>.
*/

using System;
using System.Diagnostics;
using System.IO;
using System.Text;

namespace AppHostPatcher {
    
	class Program {
    
		static void Usage() {
    
			Console.WriteLine("apphostpatcher <apphostexe> <origdllpath> <newdllpath>");
			Console.WriteLine("apphostpatcher <apphostexe> <newdllpath>");
			Console.WriteLine("apphostpatcher <apphostexe> -d <newsubdir>");
			Console.WriteLine("example: apphostpatcher my.exe -d bin");
		}

		const int maxPathBytes = 1024;

		static string ChangeExecutableExtension(string apphostExe) =>
			// Windows apphosts have an .exe extension. Don't call Path.ChangeExtension() unless it's guaranteed
			// to have an .exe extension, eg. 'some.file' => 'some.file.dll', not 'some.dll'
			apphostExe.EndsWith(".exe", StringComparison.OrdinalIgnoreCase) ? Path.ChangeExtension(apphostExe, ".dll") : apphostExe + ".dll";

		static string GetPathSeparator(string apphostExe) =>
			apphostExe.EndsWith(".exe", StringComparison.OrdinalIgnoreCase) ? @"\" : "/";

		static int Main(string[] args) {
    
			try {
    
				string apphostExe, origPath, newPath;
				if (args.Length == 3) {
    
					if (args[1] == "-d") {
    
						apphostExe = args[0];
						origPath = Path.GetFileName(ChangeExecutableExtension(apphostExe));
						newPath = args[2] + GetPathSeparator(apphostExe) + origPath;
					}
					else {
    
						apphostExe = args[0];
						origPath = args[1];
						newPath = args[2];
					}
				}
				else if (args.Length == 2) {
    
					apphostExe = args[0];
					origPath = Path.GetFileName(ChangeExecutableExtension(apphostExe));
					newPath = args[1];
				}
				else {
    
					Usage();
					return 1;
				}
				if (!File.Exists(apphostExe)) {
    
					Console.WriteLine($"Apphost '{
      apphostExe}' does not exist");
					return 1;
				}
				if (origPath == string.Empty) {
    
					Console.WriteLine("Original path is empty");
					return 1;
				}
				var origPathBytes = Encoding.UTF8.GetBytes(origPath + "\0");
				Debug.Assert(origPathBytes.Length > 0);
				var newPathBytes = Encoding.UTF8.GetBytes(newPath + "\0");
				if (origPathBytes.Length > maxPathBytes) {
    
					Console.WriteLine($"Original path is too long");
					return 1;
				}
				if (newPathBytes.Length > maxPathBytes) {
    
					Console.WriteLine($"New path is too long");
					return 1;
				}

				var apphostExeBytes = File.ReadAllBytes(apphostExe);
				int offset = GetOffset(apphostExeBytes, origPathBytes);
				if (offset < 0) {
    
					Console.WriteLine($"Could not find original path '{
      origPath}'");
					return 1;
				}
				if (offset + newPathBytes.Length > apphostExeBytes.Length) {
    
					Console.WriteLine($"New path is too long: {
      newPath}");
					return 1;
				}
				for (int i = 0; i < newPathBytes.Length; i++)
					apphostExeBytes[offset + i] = newPathBytes[i];
				File.WriteAllBytes(apphostExe, apphostExeBytes);
				return 0;
			}
			catch (Exception ex) {
    
				Console.WriteLine(ex.ToString());
				return 1;
			}
		}

		static int GetOffset(byte[] bytes, byte[] pattern) {
    
			int si = 0;
			var b = pattern[0];
			while (si < bytes.Length) {
    
				si = Array.IndexOf(bytes, b, si);
				if (si < 0)
					break;
				if (Match(bytes, si, pattern))
					return si;
				si++;
			}
			return -1;
		}

		static bool Match(byte[] bytes, int index, byte[] pattern) {
    
			if (index + pattern.Length > bytes.Length)
				return false;
			for (int i = 0; i < pattern.Length; i++) {
    
				if (bytes[index + i] != pattern[i])
					return false;
			}
			return true;
		}
	}
}

发现原来DotNetCore编译生成的.exe并不是中间代码,.dll参数托管语言的中间代码。该exe是由dotnet.runtime\src\native\corehost的C++代码生成的启动模板exe(直接是机器码),该exe负责寻找clr和托管程序集来加载运行。安装DotNetSDK后在C:\Program Files\dotnet\sdk\5.0.402\AppHostTemplate\apphost.exe。

在vs编译DotNetCore工程时候先把apphost.exe拷贝到项目的obj下
在这里插入图片描述

然后采用填坑替换的方式把dll路径替换apphost.exe的占位部分就得的项目的运行exe。

那么自己也可以参照微软代码定制一个填坑程序,来改变exe和dll的相对路径,在有的时候让exe脱离出去,有更清晰的目录

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace NetCoreAppHostTemplateEdit
{
    
    /// <summary>
	/// 在 dotnet.runtime\src\native\corehost\将会构建出 apphost.exe 文件
	/// 在安装dotnet sdk 的时候,将会输出到 C:\Program Files\dotnet\sdk\5.0.402\AppHostTemplate 
	/// apphost.exe将会在构建程序的时候,输出到项目obj文件夹里面
	/// 然后被替换执行的dll路径,根据dotnet.runtime\src\native\corehost\corehost.cpp 的注释
    /// 及https://github.com/dnSpy/dnSpy/blob/2fa5c978b1a9fb8d1979c8aa4cfa6d177bf5aa9c/Build/AppHostPatcher/Program.cs的代码,可以了解到,替换这个路径就可以自己定制执行的路径
	/// </summary>
    public partial class FrmMian : Form
    {
    
        /**
        * Detect if the apphost executable is allowed to load and execute a managed assembly.
        *
        *    - The exe is built with a known hash string at some offset in the image
        *    - The exe is useless as is with the built-in hash value, and will fail with an error message
        *    - The hash value should be replaced with the managed DLL filename with optional relative path
        *    - The optional path is relative to the location of the apphost executable
        *    - The relative path plus filename are verified to reference a valid file
        *    - The filename should be "NUL terminated UTF-8" by "dotnet build"
        *    - The managed DLL filename does not have to be the same name as the apphost executable name
        *    - The exe may be signed at this point by the app publisher
        *    - Note: the maximum size of the filename and relative path is 1024 bytes in UTF-8 (not including NUL)
        *        o https://en.wikipedia.org/wiki/Comparison_of_file_systems
        *          has more details on maximum file name sizes.
        */

        /// <summary>
        /// 最长路径长度,占位长度
        /// </summary>
        private const int MaxPathBytes = 1024;

        public FrmMian()
        {
    
            InitializeComponent();
        }


        /// <summary>
        /// 生成新程序
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnOk_Click(object sender, EventArgs e)
        {
    
            try
            {
    
                //老exe路径
                string oldExePath = txtOld.Text;
                FileInfo fiOld = new FileInfo(oldExePath);
                //老dll路径
                string oldDllPath = oldExePath.Replace(".exe", ".dll");
                //新exe路径
                string newExePath = Path.Combine(txtNew.Text, fiOld.Name);
                FileInfo fiNew = new FileInfo(oldExePath);
                //dll名字
                string oldDllName = fiOld.Name.Replace(".exe", ".dll");
                Uri url = new Uri(newExePath);
                //算新exe和dll的相对路径
                Uri relativeUrl = url.MakeRelativeUri(new Uri(oldDllPath));
                //得到路径的比特
                var origPathBytes = Encoding.UTF8.GetBytes(oldDllName + "\0");
                //得到新路径的比特
                var newPathBytes = Encoding.UTF8.GetBytes(relativeUrl + "\0");
                //不能超过最大限度
                if (newPathBytes.Length > MaxPathBytes)
                {
    
                    MessageBox.Show("新路径太长!", "提示");
                    return;
                }
                //拷贝exe到新目录
                File.Copy(oldExePath, newExePath);
                //读取exe到比特数组
                byte[] apphostExeBytes = File.ReadAllBytes(newExePath);
                //找到原路径偏移量
                int offset = GetOffset(apphostExeBytes, origPathBytes);
                if (offset < 0)
                {
    
                    MessageBox.Show("不能找到地址:" + oldDllName, "提示");
                    return;
                }
                if (offset + newPathBytes.Length > apphostExeBytes.Length)
                {
    
                    MessageBox.Show("新地址太长:" + relativeUrl, "提示");
                    return;
                }
                //替换比特
                for (int i = 0; i < newPathBytes.Length; i++)
                {
    
                    apphostExeBytes[offset + i] = newPathBytes[i];
                }
                //写回文件
                File.WriteAllBytes(newExePath, apphostExeBytes);
                MessageBox.Show("新exe和dll的相对路径:" + relativeUrl, "提示");
                MessageBox.Show(newExePath + "成功生成新文件!", "成功");
            }
            catch (Exception ex)
            {
    
                MessageBox.Show(ex.Message, "错误");
                return;
            }
        }

        /// <summary>
        /// 选择老地址
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnOld_Click(object sender, EventArgs e)
        {
    
            OpenFileDialog of = new OpenFileDialog();
            of.Filter = "(exe文件)|*.exe";
            if (of.ShowDialog() == DialogResult.OK)
            {
    
                txtOld.Text = of.FileName;
            }
        }

        /// <summary>
        /// 输出目录
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnNew_Click(object sender, EventArgs e)
        {
    
            FolderBrowserDialog fi = new FolderBrowserDialog();
            if (fi.ShowDialog() == DialogResult.OK)
            {
    
                txtNew.Text = fi.SelectedPath;
            }
        }

        /// <summary>
        /// 得到偏移
        /// </summary>
        /// <param name="bytes"></param>
        /// <param name="pattern"></param>
        /// <returns></returns>
        private int GetOffset(byte[] bytes, byte[] pattern)
        {
    
            int si = 0;
            var b = pattern[0];
            while (si < bytes.Length)
            {
    
                si = Array.IndexOf(bytes, b, si);
                if (si < 0)
                    break;
                if (Match(bytes, si, pattern))
                    return si;
                si++;
            }
            return -1;
        }

        /// <summary>
        /// 匹配
        /// </summary>
        /// <param name="bytes"></param>
        /// <param name="index"></param>
        /// <param name="pattern"></param>
        /// <returns></returns>
        private bool Match(byte[] bytes, int index, byte[] pattern)
        {
    
            if (index + pattern.Length > bytes.Length)
                return false;
            for (int i = 0; i < pattern.Length; i++)
            {
    
                if (bytes[index + i] != pattern[i])
                    return false;
            }
            return true;
        }

    }
}

在这里插入图片描述

在这里插入图片描述

看完DotNetCore的一些实现不得不说佩服。字符串二进制占坑替换还能这么玩。

下一步可编译dotnet.runtime\src\coreclr\hosts来生成corerun程序,让corerun指定clr路径和托管程序集运行程序(类似dotnet run命令)。会C/C++的可以具体看DotNetCore加载clr和托管程序集的具体过程。也可以自己定制DotNetCore的启动程序。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/zhanglianzhu_91/article/details/121189615

智能推荐

Linux 离线安装 python3.7.4_离线安装linux环境的python3.7.4-程序员宅基地

文章浏览阅读1.8k次。1.首先到官网下载最新py源码包 地址:https://www.python.org2.将python3.7.4.tgz上传至目标服务器。3. 解压 并./configure --prefix=/usr/local/python3.7.4 执行脚本对该目录进行配置4. make && make install插曲:1.zipimport.ZipImportError..._离线安装linux环境的python3.7.4

matlab读取excel表作图,读取Excel表格数据进行绘图-如何将excel表格中大量数据导入matlab中并作图...-程序员宅基地

文章浏览阅读5k次。matlab 读取excel时间数据并绘图没看懂你的格式是什么意思,不过我一般读取excel都用xlsread函数,你也可以试一试[a,b,c]=xlsresd('data.xls');%r如果是office2007及以上。那就是data.xlsxa b c 是读取不同的数据类型在matlab上如何导入excel表格然后画图工具:MATLAB、office excel步骤将待导入的矩阵结构的数据录..._matlab批量读取excel表格数据并处理画图

微软多模态ChatGPT来了?搞定看图答题、智商测验等任务!-程序员宅基地

文章浏览阅读1k次。点击下方卡片,关注“CVer”公众号AI/CV重磅干货,第一时间送达点击进入—>【计算机视觉】微信技术交流群转载自:机器之心 | 编辑:杜伟、陈萍从大型语言模型(LLM)到多模态大型语言模型(MLLM),微软又迈出了重要一步。在 NLP 领域,大型语言模型(LLM)已经成功地在各种自然语言任务中充当通用接口。只要我们能够将输入和输出转换为文本,就能使得基于 LLM 的接口适应一个任务。举例而..._chatgpt回答iq题

强化学习连续动作,离散动作算法选择_强化学习离散空间适合用什么算法-程序员宅基地

文章浏览阅读7.6k次,点赞3次,收藏16次。连续动作:Policygradient、DDPG、A3C、PPO离散动作:Q-learning、DQN、A3C、PPO_强化学习离散空间适合用什么算法

我国工业互联网 技术路线与发展趋势研究-程序员宅基地

文章浏览阅读678次。来源:中国工业和信息化本文发表于《中国工业和信息化》杂志2021年4月刊总第33期作者:许雪荷 中国工业互联网研究院自2017年《国务院关于深化“互联网+先进制造业”发展工业互联网的指..._工业互联网系统 技术方法和线路

西北大学本科毕业论文答辩PPT模板_西北大学毕业答辩ppt-程序员宅基地

文章浏览阅读333次。模板介绍精美PPT模板设计,西北大学本科毕业论文答辩PPT模板。一套高校PPT幻灯片模板,内含灰色多种配色,精美风格设计,动态播放效果,精美实用。一份设计精美的PPT模板,可以让你在汇报演讲时脱颖而出。希望下面这份精美的PPT模板能给你带来帮助,温馨提示:本资源使用PPT或PPTX等格式,请安装并使用Office或WPS软件打开。模板信息模板编号:P63474用途:高校PPT。模板格式:pptx格式(可随意下载编辑)页数:27页大小:10MB比例:16:9编辑软件:wps/p_西北大学毕业答辩ppt

随便推点

If you already have a 64-bit JDK installed 解决方法-程序员宅基地

文章浏览阅读8.2k次。跟着方法做,重启ide后报错:If you already have a 64-bit JDK installed ,defined a JAVA_HOME variable in Computer>System Properties>System Settings>Environment Variables Failed to create JVM .JVM Path:问题:我是因为根据文档中3.将 - javaagent: C: \ jetbrains-agent.jar _if you already have a 64-bit jdk

hihocoder1586-2017acm北京网络赛9&线段树&思维&板子- Minimum_acm专题9线段树-程序员宅基地

文章浏览阅读327次。http://hihocoder.com/problemset/problem/1586 线段树,单点更新,区间查询极值。 给你一个数组, 有下面操作 输入 a b c 若为1,那么再b-c中找两个数,让他们的乘积最小。 若为2,那么把 a[b]=c; 乘积最小这个。。 因为有正负数。所以需要 min*min1 max1*max1 max1*min1 的最小值。。。#includ_acm专题9线段树

ERR_PNPM_FETCH_404 GET https://registry.npm.taobao.org/@qiaoqiaoyun/drag-free/-/drag-free-1.-程序员宅基地

文章浏览阅读2.2k次。ERR_PNPM_FETCH_404  GET https://registry.npm.taobao.org/@qiaoqiaoyun/drag-free/-/drag-free-1.0.52.tgz: Not Found - 404_err_pnpm_fetch_404

Scala入门_数据类型与运算符_scala 中运算符==可以比较数值类型和引用类型对吗-程序员宅基地

文章浏览阅读4.1k次。数据类型与操作符scala中的类型以及操作符绝大多数和Java一样,我们主要来学习与Java不一样的一些用法scala类型的继承体系数据类型基础类型类型说明Byte8位带符号整数Short16位带符号整数Int32位带符号整数Long64位带符号整数Char16位无符号Unicode字符StringChar类型的序列..._scala 中运算符==可以比较数值类型和引用类型对吗

VS2010-MFC(对话框:向导对话框的创建及显示)-程序员宅基地

文章浏览阅读120次。转自:http://www.jizhuomi.com/software/166.html 上一节讲了属性页对话框和相关的两个类CPropertyPage类和CPropertySheet类,对使用属性页对话框做准备。本节将为大家演示如何创建向导对话框。 仍然以前面的“加法计算器”的例子为基础,在其中加入向导对话框,我们可以用它来说明加法..._在solution explorer视图中的根节点“addition”上点右键,

SOPC问答-程序员宅基地

文章浏览阅读5.2k次。hyh808问:如何获得完整的SOPC Builder和GNUPro Bill Yuan答复:如果您购买了我们的开发板,我们随板提供这些软件,包括Quartus II sdfwx1问:完全版的NIOS需要多少钱? Horace答复:You can buy the Cyclone-Nios Kit or Stratix-Nios Kit, now has a special offe..._0755-83867431