zoukankan      html  css  js  c++  java
  • .NET跨平台之旅:探秘 dotnet run 如何运行 .NET Core 应用程序团队

    自从用 dotnet run 成功运行第一个 "Hello world" .NET Core 应用程序后,一直有个好奇心:dotnet run 究竟是如何运行一个 .NET Core 应用程序的?

    从 ASP.NET 5 RC1 升级至 ASP.NET Core 1.0在Linux上以本地机器码运行 ASP.NET Core 站点 之后,这个好奇心被进一步激发,于是“探秘 dotnet run”顺理成章地成为.NET跨平台之旅的下一站。

    首先我们了解一下 dotnet 命令是什么东东?dotnet 命令实际就是一个C#写的简单的.NET控制台程序(详见Program.cs),但为什么在不同操作系统平台上安装 dotnet cli 后,dotnet 命令是一个本地可执行文件?功臣依然是前一篇博文中见识过其威力的 .NET Native,dotnet 命令背后的.NET控制台程序被编译为针对不同操作系统的本地机器码,dotnet 命令本身就是 .NET Native 的一个实际应用。

    接下来,我们沿着 dotnet 命令的 Program.cs 探寻 dotnet run 运行 .NET Core 应用程序的秘密。

    dotnet Program.cs 的C#代码不超过200行,而与我们的探秘之旅最相关的是下面这一段代码:

    var builtIns = new Dictionary<string, Func<string[], int>>
    {
        //...
        ["run"] = RunCommand.Run,
        //...
    };
    
    Func<string[], int> builtIn;
    if (builtIns.TryGetValue(command, out builtIn))
    {
        return builtIn(appArgs.ToArray());
    }

    从上面的代码可以看出,dotnet run 命令实际执行的是 RunCommand 的 Run() 方法,沿着 Run() 方法往前走,从 Start() 方法 来到 RunExecutable() 方法,此处的风景吸引了我们。

    在 RunExecutable() 方法中,先执行了 BuildCommand.Run() 方法 —— 对 .NET Core 应用程序进行 build :

    var result = Build.BuildCommand.Run(new[]
    {
        $"--framework",
        $"{_context.TargetFramework}",
        $"--configuration",
        Configuration,
        $"{_context.ProjectFile.ProjectDirectory}"
    });

    如果 build 成功,会在 .NET Core 应用程序的bin文件夹中生成相应的程序集(.dll文件)。如何 build,不是我们这次旅程所关心的,我们关心的是 build 出来的程序集是如何被运行的。

    所以略过此处风景,继续向前,发现了下面的代码:

    result = Command.Create(outputName, _args)
        .ForwardStdOut()
        .ForwardStdErr()
        .Execute()
        .ExitCode;

    从上面的代码可以分析出,dotnet run 最终执行的是一个命令行,而这个命令行是由 Command.Create() 根据 outputName 生成的,outputName 就是 BuildCommand 生成的应用程序的程序集名称。显然,秘密一定藏在 Command.Create() 中。

    目标 Command.Create() ,跑步前进 。。。在 Command.cs 中看到了 Command.Create() 的庐山真面目:

    public static Command Create(
        string commandName, 
        IEnumerable<string> args, 
        NuGetFramework framework = null, 
        string configuration = Constants.DefaultConfiguration)
    {
        var commandSpec = CommandResolver.TryResolveCommandSpec(commandName, 
            args, 
            framework, 
            configuration: configuration);
    
        if (commandSpec == null)
        {
            throw new CommandUnknownException(commandName);
        }
    
        var command = new Command(commandSpec);
    
        return command;
    }

    发现 CommandResolver,快步迈入 CommandResolver.TryResolveCommandSpec() ,看看其中又是怎样的风景:

    public static CommandSpec TryResolveCommandSpec(
        string commandName, 
        IEnumerable<string> args, 
        NuGetFramework framework = null, 
        string configuration=Constants.DefaultConfiguration, 
        string outputPath=null)
    {
        var commandResolverArgs = new CommandResolverArguments
        {
            CommandName = commandName,
            CommandArguments = args,
            Framework = framework,
            ProjectDirectory = Directory.GetCurrentDirectory(),
            Configuration = configuration,
            OutputPath = outputPath
        };
        
        var defaultCommandResolver = DefaultCommandResolverPolicy.Create();
        
        return defaultCommandResolver.Resolve(commandResolverArgs);
    }

    发现 DefaultCommandResolverPolicy ,一个箭步,置身其 Create() 方法中:

    public static CompositeCommandResolver Create()
    {
        var environment = new EnvironmentProvider();
        var packagedCommandSpecFactory = new PackagedCommandSpecFactory();
    
        var platformCommandSpecFactory = default(IPlatformCommandSpecFactory);
        if (PlatformServices.Default.Runtime.OperatingSystemPlatform == Platform.Windows)
        {
            platformCommandSpecFactory = new WindowsExePreferredCommandSpecFactory();
        }
        else
        {
            platformCommandSpecFactory = new GenericPlatformCommandSpecFactory();
        }
    
        return CreateDefaultCommandResolver(environment, packagedCommandSpecFactory, platformCommandSpecFactory);
    }

    出现两道风景 —— packagedCommandSpecFactory 与 platformCommandSpecFactory,它们都被作为参数传给了 CreateDefaultCommandResolver() 方法。

    一心不可二用,先看其中一道风景 —— packagedCommandSpecFactory ,急不可待地奔向 CreateDefaultCommandResolver() 方法 。

    public static CompositeCommandResolver CreateDefaultCommandResolver(
        IEnvironmentProvider environment,
        IPackagedCommandSpecFactory packagedCommandSpecFactory,
        IPlatformCommandSpecFactory platformCommandSpecFactory)
    {
        var compositeCommandResolver = new CompositeCommandResolver();
        //..
        compositeCommandResolver.AddCommandResolver(
            new ProjectToolsCommandResolver(packagedCommandSpecFactory));
        //..            
        return compositeCommandResolver;
    }

    packagedCommandSpecFactory 将我们引向新的风景 —— ProjectToolsCommandResolver 。飞奔过去之后,立即被 Resolve() 方法吸引(在之前的 DefaultCommandResolverPolicy.Create() 执行之后,执行 defaultCommandResolver.Resolve(commandResolverArgs) 时,该方法被调用)。

    这里的风景十八弯。在 ProjectToolsCommandResolver 中七绕八绕,从 ResolveFromProjectTools() -> ResolveCommandSpecFromAllToolLibraries() -> ResolveCommandSpecFromToolLibrary() 。。。又回到了 PackagedCommandSpecFactory ,进入 CreateCommandSpecFromLibrary() 方法。

    在 PackagedCommandSpecFactory 中继续转悠,在从 CreateCommandSpecWrappingWithCorehostfDll() 到 CreatePackageCommandSpecUsingCorehost() 时,发现一个新东东从天而降 —— corehost :

    private CommandSpec CreatePackageCommandSpecUsingCorehost(
        string commandPath, 
        IEnumerable<string> commandArguments, 
        string depsFilePath,
        CommandResolutionStrategy commandResolutionStrategy)
    {
        var corehost = CoreHost.HostExePath;
    
        var arguments = new List<string>();
        arguments.Add(commandPath);
    
        if (depsFilePath != null)
        {
            arguments.Add($"--depsfile:{depsFilePath}");
        }
    
        arguments.AddRange(commandArguments);
    
        return CreateCommandSpec(corehost, arguments, commandResolutionStrategy);
    }

    这里的 corehost 变量是干嘛的?心中产生了一个大大的问号。

    遥望 corehost 的身后,发现 CreateCommandSpec() 方法(corehost 是它的一个参数),一路狂奔过去:

    private CommandSpec CreateCommandSpec(
        string commandPath, 
        IEnumerable<string> commandArguments,
        CommandResolutionStrategy commandResolutionStrategy)
    {
        var escapedArgs = ArgumentEscaper.EscapeAndConcatenateArgArrayForProcessStart(commandArguments);
    
        return new CommandSpec(commandPath, escapedArgs, commandResolutionStrategy);
    }

    原来 corehost 是作为 commandPath 的值,也就是说 Command.Create() 创建的 dotnet run 所对应的命令行是以 corehost 开头的,秘密一定就在 corehost 的前方不远处。

    corehost 的值来自 CoreHost.HostExePath ,HostExePath 的值来自 Constants.HostExecutableName ,HostExecutableName 的值是:

    public static readonly string HostExecutableName = "corehost" + ExeSuffix;

    corehost 命令!原来 dotnet run 命令最终执行的 corehost 命令,corehost 才是背后真正的主角,.NET Core 应用程序是由它运行的。

    去 dotnet cli 的安装目录一看,果然有一个 corehost 可执行文件。

    -rwxr-xr-x 1 root root 31208 Mar  2 03:59 /usr/share/dotnet-nightly/bin/corehost

    既然 corehost 是主角,那么不通过 dotnet run ,直接用 corehost 应该也可以运行 .NET Core 程序,我们来试一试。

    进入示例站点 about.cnblogs.com 的 build 输出文件夹:

    cd /git/AboutUs/bin/Debug/netstandardapp1.3/ubuntu.14.04-x64

    然后直接用 corehost 命令运行程序集:

    /usr/share/dotnet-nightly/bin/corehost AboutUs.dll

    运行成功!事实证明 corehost 是运行 .NET Core 程序的主角。

    dbug: Microsoft.AspNetCore.Hosting.Internal.WebHost[3]
          Hosting starting
    dbug: Microsoft.AspNetCore.Hosting.Internal.WebHost[4]
          Hosting started
    Hosting environment: Production
    Application base path: /git/AboutUs/bin/Debug/netstandardapp1.3/ubuntu.14.04-x64
    Now listening on: http://*:8001
    Application started. Press Ctrl+C to shut down.

    去 dotnet cli 的源代码中看一下 corehost 的实现代码,是用 C++ 写的,这是 dotnet cli 中唯一用 C++ 实现的部分,它也不得不用 C++ ,因为它有一个重要职责 —— 加载 coreclr ,再次证实 corehost 是主角。

    探秘 dotnet run , 踏破铁鞋,走过千山万水,终于找到了你 —— corehost,终于满足了那颗不安分的好奇心。

  • 相关阅读:
    Spring基础知识
    Hibernate基础知识
    Struts2基础知识
    在eclipse里头用checkstyle检查项目出现 File contains tab characters (this is the first instance)原因
    java后台获取cookie里面值得方法
    ckplayer 中的style.swf 中的 style.xml 中的修改方法
    java hql case when 的用法
    Windows下Mongodb安装及配置
    Mongodb中经常出现的错误(汇总)child process failed, exited with error number
    Mac 安装mongodb
  • 原文地址:https://www.cnblogs.com/cmt/p/5271505.html
Copyright © 2011-2022 走看看