通过修改CoreCLR中的ClrHost实现自托管程序
上一篇我们讲了如何在windows和Linux上编译CoreClr的问题 虽然文章使用的是windows 10 (Bash)环境,但是也可以做为ubuntu环境的参考。
成功编译CoreCLR的源代码之后,会在\coreclr\bin\Product\Windows_NT.x64.{*}目录生成对应的二进制文件,这里包含了基本的CLR运行时文件。其中就有我们这次想要修改的CoreRun.exe文件,它就是CLRHost的入口可执行程序,等同于dotnet命令。
当然本篇文章主要是以windows环境为例,通过修改Windowst版本的CoreRun为例来介绍,如何实现一个自己的自托管程序入口。
要想编辑Windows环境的源代码首先也是同样的需要编译CoreCLR源代码的。成功编译后会在coreclr\bin\obj\Windows_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;
//获取可执行文件路径,如: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执行这个程序集。
其实讲到这里有的朋友应该已经明白了,想要达到我们的目标,只需要做两件事儿。
- 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('\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
通过修改CoreCLR中的ClrHost实现自托管程序的更多相关文章
- 修改Unity中Lua文件的默认打开程序
项目中引用了XLua,而Lua文件又是以txt文件结尾的,当修改系统的扩展脚本编辑器为vs后双击lua文件(xx.txt)默认也使用vs打开了,无提示的黑白文本编辑 昨办? -. 后来看到网上有写Un ...
- Map java中的map 如何修改Map中的对应元素
Map java中的map 如何修改Map中的对应元素 Map以按键/数值对的形式存储数据,和数组非常相似,在数组中存在的索引,它们本身也是对象. Map的接口 Map ...
- Java反射机制可以动态修改实例中final修饰的成员变量吗?
问题:Java反射机制可以动态修改实例中final修饰的成员变量吗? 回答是分两种情况的. 1. 当final修饰的成员变量在定义的时候就初始化了值,那么java反射机制就已经不能动态修改它的值了. ...
- 何修改WAMP中mysql默认空密码--转
何修改WAMP中mysql默认空密码 http://www.cnblogs.com/hooray/archive/2011/07/23/2114792.html WAMP安装好后,mysql密码是为 ...
- 使用jquery修改css中带有!important的样式属性
当CSS中含有!important的样式属性时,普通的修改方式是会出现失败的.如下: <div class="test">使用jquery修改css中带有!import ...
- 以NameValueCollection 修改URL中的查询参数
以NameValueCollection 修改URL中的查询参数 本文参考于:http://www.c-sharpcorner.com/Blogs/9421/add-remove-or-modify- ...
- 修改数据库中group_concat的返回结果的长度限制
修改数据库中group_concat的返回结果的长度限制 我们可以使用Mysql的客户端管理工具,Sqlyog 新建一个查询编辑器 显示 SHOW VARIABLES LIKE "grou ...
- WCF中修改接口或步骤名称而不影响客户端程序
WCF中修改接口或方法名称而不影响客户端程序 本篇接着"从Web Service和Remoting Service引出WCF服务"中有关WCF的部分. 运行宿主应用程序. 运行We ...
- 修改Android中strings.xml文件, 动态改变数据
有些朋友可能会动态的修改Android中strings.xml文件中的值,在这里给大家推荐一种简单的方法.strings.xml中节点是支持占位符的,如下所示: <string name=&qu ...
随机推荐
- VUE2.0实现购物车和地址选配功能学习第二节
第二节 创建VUE实例 购物车项目计划: 1.创建一个vue实例 2.通过v-for指令渲染产品数据 3.使用filter对金额和图片进行格式化 4.使用v-on实现产品金额动态计算 5.综合演示 ① ...
- 阿里云开发之OSS数据迁移
最近由于项目需求,需要将一个aliyun账号下的oss数据导入到两一个aliyun账号下的oss,经过一番坎坷,最终搞定. 1.查看oss数据迁移官方文档,我是在本地windows电脑上进行操作的,先 ...
- java标签(label)求16进制字符串的整数和 把一个整数转为4个16进制字符表示
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 18.0px Menlo; color: #4f76cb } p.p2 { margin: 0.0px 0. ...
- 《深入理解Java虚拟机》学习笔记之最后总结
编译器 Java是编译型语言,按照编译的时期不同,编译器可分为: 前端编译器:其实叫编译器的前端更合适些,它把*.java文件转变成*.class文件,如Sun的Javac.Eclipse JDT中的 ...
- 可扩展标记语言XML
XML简述 XML用于描述数据,是当前处理结构化文档信息的有力工具.与操作系统编程语言的开发平台无关,可以实现不同系统之间的数据交互. 结构 <?xml version="1.0&qu ...
- Bzoj超级经验大放送题集(好评如潮哦~~~)
其实这些是因为没有数据才形成的...唯一可惜的是这些都是需要300软妹币才能打开的萌萌哒权限题*^_^* 好啦,吾来教你如何快速AC么么哒 pascal: 1 begin end. //Pascal ...
- ORM-Dapper学习<一>
ORM 框架简介 对象-关系映射(Object/Relation Mapping,简称ORM),是随着面向对象的软件开发方法发展而产生的.面向对象的开发方法是当今企业级应用开发环境中的主流开发方法,关 ...
- mybatis源码跟踪
第一步:从web.xml进入dispatcherServlet进入前端控制器 第二步:使用handlerMapping 获得handlerChain 第三步:使用适配器执行handler获取model ...
- SpringMVC中重定向底层原理
只要将数据放入model中, 也能取到值,原因是model临时放入session域中,当从定向到另一个url时,底层把数据拼接在url地址后面(重定向一定是get请求方式),同时将session域 ...
- C++ Primer 5 CH5 语句
5.1 简单语句 空语句: ; 5.2 语句作用域 5.3 条件语句 switch 语句:表达式与某个 case 匹配成功,执行 case 之后的语句直到 break 或者 switch 结尾,cas ...