上一篇咱们讲了如何在windows和Linux上编译CoreClr的问题 虽然文章使用的是windows 10 (Bash)环境,可是也能够作为ubuntu环境的参考。git
成功编译CoreCLR的源代码以后,会在**\coreclr\bin\Product\Windows_NT.x64.{*}**目录生成对应的二进制文件,这里包含了基本的CLR运行时文件。其中就有咱们此次想要修改的CoreRun.exe文件,它就是CLRHost的入口可执行程序,等同于dotnet命令。github
固然本篇文章主要是以windows环境为例,经过修改Windowst版本的CoreRun为例来介绍,如何实现一个本身的自托管程序入口。shell
要想编辑Windows环境的源代码首先也是一样的须要编译CoreCLR源代码的。成功编译后会在coreclr\bin\obj\Windows_NT.x64.Debug 目录下看到VC++的项目和解决方案。打开CoreCLR.sln解决方案,能够看到其中的CoreRun项目。ubuntu
首先它是一个Win32项目,我在这里只简单的讲几处关键的代码段,有兴趣的同窗能够到Github上去看看CoreRun源代码 。windows
先说一下咱们想要达到的效果吧:
想要使用CoreRun启动一个dotnet程序集只须要以下命令:数组
corerun demo.dll
固然想真正执行起来,还须要在系统环境变量里添加CORE_ROOT来指定已经安装的CoreCLR目录。app
但此次想达到的目标是不须要指定Runtime目录也不须要指定dll文件名,以下:dom
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.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执行这个程序集。
其实讲到这里有的朋友应该已经明白了,想要达到咱们的目标,只须要作两件事儿。
HostEnvironment(StackSString coreRoot, Logger *logger) : m_log(logger), m_CLRRuntimeHost(nullptr) { //......省略代码 // m_coreCLRModule = TryLoadCoreCLR(coreRoot);
这里我使用的方式为不加载环境变量,而是指向加载目录(也就是程序执行目录appPath或是指向子目录),我使用的是后者指向了一个名为**Runtimes**的子目录。
//声明程序集路径变量 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的参数来执行些程序集。
Demo和修改的源代码,已经上传到QQ群文件中(Demos\CoreCLRDemo.zip),仅供参考。
GitHub:https://github.com/maxzhang1985/YOYOFx 若是觉还能够请Star下, 欢迎一块儿交流。
.NET Core 开源学习群:214741894