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

作者: 题库在线资料查找 | 来源:发表于2017-07-04 12:46 被阅读67次

    先说一下我们想要达到的效果吧:

    想要使用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;//获取可执行文件路径,如:src\coreclr\hosts\corerun\Debug\CoreRun.exewchar_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.修改程序集路径的获取方式

    ```cpp

    //声明程序集路径变量

    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('\0');

    appPath.CloseBuffer(DWORD(filePart - appPathPtr));

    //打印完整的dll路径

    log << W("Loading: ") << managedAssemblyFullName.GetUnicode() << Logger::endl;

    ```

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

    原文来自:技术之家

    相关文章

      网友评论

        本文标题:通过修改CoreCLR中的ClrHost实现自托管程序

        本文链接:https://www.haomeiwen.com/subject/omothxtx.html