• 通过修改CoreCLR中的ClrHost实现自托管程序


    上一篇我们讲了如何在windows和Linux上编译CoreClr的问题 虽然文章使用的是windows 10 (Bash)环境,但是也可以做为ubuntu环境的参考。

    成功编译CoreCLR的源代码之后,会在coreclrinProductWindows_NT.x64.{*}目录生成对应的二进制文件,这里包含了基本的CLR运行时文件。其中就有我们这次想要修改的CoreRun.exe文件,它就是CLRHost的入口可执行程序,等同于dotnet命令。

    当然本篇文章主要是以windows环境为例,通过修改Windowst版本的CoreRun为例来介绍,如何实现一个自己的自托管程序入口。

    要想编辑Windows环境的源代码首先也是同样的需要编译CoreCLR源代码的。成功编译后会在coreclrinobjWindows_NT.x64.Debug 目录下看到VC++的项目和解决方案。打开CoreCLR.sln解决方案,可以看到其中的CoreRun项目。

    首先它是一个Win32项目,我在这里只简单的讲几处关键的代码段,有兴趣的同学可以到Github上去看看CoreRun源代码

    先说一下我们想要达到的效果吧:
    想要使用CoreRun启动一个dotnet程序集只需要如下命令:

    corerun  demo.dll
    

    当然想真正执行起来,还需要在系统环境变量里添加CORE_ROOT来指定已经安装的CoreCLR目录。

    但这次想达到的目标是不需要指定Runtime目录也不需要指定dll文件名,如下:

    demo.exe
    

    这样是不是写发布一个自托管程序是一样的?接下来,我们来通过修改代码来实现这一目标。

    首先找到HostEnvironment类,看下它的代码段第112行

    StackSString coreRoot;
    m_coreCLRModule = NULL; // Initialize this here since we don't call TryLoadCoreCLR if CORE_ROOT is unset.
    if (WszGetEnvironmentVariable(W("CORE_ROOT"), coreRoot) > 0 && coreRoot.GetCount() > 0)
    {
        coreRoot.Append(W('\'));
        m_coreCLRModule = TryLoadCoreCLR(coreRoot);
    }
    

    它通过获取系统环境变量CORE_ROOT的值来定位CoreCLR目录,并传递给TryLoadCoreCLR函数,来加载CoreCLR.dll文件。

    下面来到主函数TryRun:

    //获取命令行参数数组的指针
    const wchar_t* exeName = argc > 0 ? argv[0] : nullptr;
    if(exeName == nullptr)
    {
        log << W("No exename specified.") << Logger::endl;
        return false;
    }
    
    StackSString appPath;
    StackSString appNiPath;
    StackSString managedAssemblyFullName;
    StackSString appLocalWinmetadata;
    
    wchar_t* filePart = NULL;
    
    COUNT_T size = MAX_LONGPATH;
    //获取可执行文件路径,如:srccoreclrhostscorerunDebugCoreRun.exe
    wchar_t* appPathPtr = appPath.OpenUnicodeBuffer(size - 1);
    DWORD length = WszGetFullPathName(exeName, size, appPathPtr, &filePart);
    if (length >= size)
    {
        appPath.CloseBuffer();
        size = length;
        //获取程序集名称,如:Demo.dll
        appPathPtr = appPath.OpenUnicodeBuffer(size - 1);
        length = WszGetFullPathName(exeName, size, appPathPtr, &filePart);
    }
    if (length == 0 || length >= size) {
        log << W("Failed to get full path: ") << exeName << Logger::endl;
        log << W("Error code: ") << GetLastError() << Logger::endl;
        return false;
    } 
    //设置程序集名称变量
    managedAssemblyFullName.Set(appPathPtr);
    
    

    中间的代码就省略了,无非是创建ICLRRuntimeHost2接口,加载参数如gc_server等之后就是创建AppDomain生成domainId。

    //这里启动的就是上面设置的程序集的全路径
    hr = host->ExecuteAssembly(domainId, managedAssemblyFullName, argc-1, (argc-1)?&(argv[1]):NULL, &exitCode);
    if (FAILED(hr)) {
        log << W("Failed call to ExecuteAssembly. ERRORCODE: ") << Logger::hresult << hr << Logger::endl;
        return false;
    }
    

    ExecuteAssembly函数会真正的通过domainId执行这个程序集。

    其实讲到这里有的朋友应该已经明白了,想要达到我们的目标,只需要做两件事儿。

    • 1.修改CORE_ROOT的加载方式
      首先修改HostEnvironment类,将获取环境CORE_ROOT的代码去掉,然后修改构造函数将路径作为参数(coreRoot)传入。
        HostEnvironment(StackSString coreRoot, Logger *logger)
            : m_log(logger), m_CLRRuntimeHost(nullptr) {
            //......省略代码
             //
             m_coreCLRModule = TryLoadCoreCLR(coreRoot);
    
    这里我使用的方式为不加载环境变量,而是指向加载目录(也就是程序执行目录appPath或是指向子目录),我使用的是后者指向了一个名为**Runtimes**的子目录。
    
    • 2.修改程序集路径的获取方式
    //声明程序集路径变量
    StackSString assemblyPath;
    //获取可执行文件路径
    assemblyPath.Set(appPathPtr);
    SString::CIterator lastBackslash = assemblyPath.End();
    assemblyPath.FindBack(lastBackslash, W('\'));
    //分离路径与文件名,如 ../corerun/bin/debug/  和  corerun.exe
    managedAssemblyFullName.Set(assemblyPath, assemblyPath.Begin(), lastBackslash + 1);
    //声明临时变量计算程序集文件名
    StackSString tempName;
    StackSString assemblyName;
    tempName.Set(filePart);
    auto endofName = tempName.End();
    //查找到扩展名标志"."位置
    tempName.FindBack(endofName, W('.'));
    assemblyName.Set(tempName, tempName.Begin(), endofName + 1);
    //替换exe为dll
    assemblyName.Append(W("dll"));
    managedAssemblyFullName.Append(assemblyName);
    
    *(filePart) = W('');
    appPath.CloseBuffer(DWORD(filePart - appPathPtr));
    //打印完整的dll路径
    log << W("Loading: ") << managedAssemblyFullName.GetUnicode() << Logger::endl;
    

    想实现自托管的方式,就可以参考dotnet publish的生成文件,它生成是将可执行文件.exe与程序集文件同名如: demo.exe 、 demo.dll 这样的文件组织方式。其实解决方案就是得到exeName后,获取当前执行文件的全路径,提取出路径和文件名两个部分,并将文件名进行替换,这样可执行文件在加载时就会默认加载与它同名的程序集文件,来做为ExecuteAssembly的参数来执行些程序集。

    Demo和修改的源代码,已经上传到QQ群文件中(DemosCoreCLRDemo.zip),仅供参考。

    GitHub:https://github.com/maxzhang1985/YOYOFx 如果觉还可以请Star下, 欢迎一起交流。

    .NET Core 开源学习群:214741894

  • 相关阅读:
    bitmap解码
    好用的dos命令
    Navicat Premium 12.0.18 / 12.0.24安装与激活
    Linux基本概念与常用命令
    Hbuilder+MUI(一)
    VS2015 运行项目报错“无可用源”,无法加载“C:WindowsMicrosoft.NETassemblyGAC_MSILSystem.Netv4.0_4.0.0.0__b03f5f7f11d50a3aSystem.Net.dll”这类型错误
    报错:找到了与该请求匹配的多个操作
    ACCESS迁移到SQLSERVE的两种方法
    C# 调用HTTP接口两种方式Demo
    转:WebApi(二)
  • 原文地址:https://www.cnblogs.com/maxzhang1985/p/6720855.html
Copyright © 2020-2023  润新知