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,终于满足了那颗不安分的好奇心。

  • 相关阅读:
    ssh 命令
    mtr 命令
    ping 命令
    curl 命令
    echo 命令
    cp 命令
    sftp服务器配置
    tomcat性能优化
    消息队列
    深度学习
  • 原文地址:https://www.cnblogs.com/cmt/p/5271505.html
Copyright © 2011-2022 走看看