【.Net Framework 体积大?】不安装.net framework 也能运行!?原理简介-2(补充)

看完点推荐,推荐数目超过100,打包脚本+工具+运行时 全部分享哈。。。。。(无公司争议,请放心)

 

接上一篇

【.Net Framework 体积大?】不安装.net framework 也能运行!?开篇叙述-1

 

下一篇

【.Net Framework 体积大?】不安装.net framework 也能运行!?原理补充-3

 

  昨天写了一个引子,还是有读者对这套“小把戏”感兴趣。那么不辜负大家的希望,争取博主不做太监........

  注意:笔者不想谈Link的方式,虽然很爽,但是不靠谱。毕竟解析翻译到原生的应用,微软到现在也就敢在Xaml系的应用做尝试,不知道是微软的策略,还是自身都信心不大......

  至于Mono项目,笔者就不说它了,BUG真的多的一个接一个的,甚至2.0都过去了,3.0还没有修复。虽然它可以支持Link ,mkbundle 工具,但是bug太多,不小心就是一个雷......

 

  开篇先谈下原生的应用和虚机应用。

  Native的程序,大多基于特定的平台硬件系统的,这里的系统 我们就说三大主流系统中的Win/Mac/Linux。硬件部分嘛,主要是CPU的类型...原生应用跟随操作系统的兼容性,可以类似插入内存条一样,直接跟系统集成,运行速度快,处理数据速度效率高。开发语言:C/C++  汇编?(呵呵,这都上个世纪的东东)  。基于C/C++ 衍生出来 主流的 QT VC .....VC 里有衍生出个MFC。。。。。。。

  虚机应用,这类应用大多是基于虚机运行时,也就是将运行时 Run time SDK 安装到适配的操作系统后,SDK将硬件 操作系统的不同 进行了自适应。应用开发者,只需要关注程序的运行效果。就好像坑坑哇哇的石头墙,摸了一层水泥,然后上面可以搭载各种形状的石头,而不是特定形状的石头(去适应石头缝大小形状)。特点是:平台适应强 开发速度快 迭代周期缩短...。开发语言:Python Java .net Framework node.js .......

  虚机应用,又各自有各自的特定。有的是基于脚本执行,有的是强类型的编译执行。不过大同小异。都离不开运行时!!!

  其他语言的运行时,我们不谈,有兴趣,自己研究。我们专门谈一下.net 的运行时 .net Framework. 

  特点:微软开发,大品牌。体积大,不能说大,简直是巨大!前面说了,从2.0 的几十兆 到3.5的几百兆 到4.0又回到几十兆(但是安装速度真慢的要命,因为要先自解压)。前面说了,十分佩服微软的 cab 压缩,愣是把一个几百兆的.net 4.0  压缩到了几十兆!!!4.5 4.6也都是4系列的。主版本号不变,改变次版本号,说明只是基于4的补丁,但是要亲命的是 4.5 抛弃了XP!!!!XP 啊,提到XP 就好像前端开发者对着IE6那个时代!!! 虽然痛苦恐怖,但是,XP在国内的使用群体依然大的离谱。

 

虽然 Win7普及的效果不错,但是那毕竟是XP 啊  XP 啊 ............泪奔。

所以,笔者认为 .net 4到目前为止 是兼容最全面的Win 系列的系统。对于开发者来说 4版本无疑是一个划时代的东东,各种应用框架都有,而且性能比3.5好,而且体积小了好多。但是仅仅是相对小了。笔者认为,它依然巨大。相比较 Python Ruby Node.js Lua的运行时,都小巧,安装速度快,运行速度也可以。凭啥.net 这个鬼 体积那么大!??

其实是有原因的。如图:

 C:\WINDOWS\Microsoft.NET

 

 

 这个基本就是.net 的运行时集中营了(虽然在System目录也打入了其他的dll,后面说)

看到这里,我们就需要回忆下,.net 的自身的架构

2.0

 

3.5

 

4.0

 

上图来自:http://www.cnblogs.com/xiaopin/archive/2011/01/07/1929467.html

是不是类似搭积木的方式,一块块的耦合上去的?确实,一个完整的.net framework安装包,需要把整个积木架构搭建起来。但是大多数情况下,我们只需要积木的台子,也就是到 第一个图中的程序集那一层就完事,有GAC  有运行时 ,就足够我们把程序集通过系统引导到 CLR 运行时解析 IL 中间语言到当前平台的原生代码,并执行原生代码。相当于,我们只需要雇佣一个翻译,会翻译其他语言即可,没必要长得高大威猛帅。。。。

  那么我们能把上面的枝枝叶叶修建掉么?笔者测试是可以的。

  我们只需要把GAC 的相关程序集,还有运行时即可。然后就是特定的引导目录+注册表。

  笔者没有找到.net 程序引导的顺序,不过从笔者实验的结果来看。微软打包的程序的时候,给程序集打上了特殊的标识。然后由系统注册表注册的特定应用去解析程序集。类似指定默认程序一样的效果(不知道表述是否正确,欢迎大家指正)。

  好,既然是指定的注册表来注册引导程序,那么哪个才是The One? 不卖关子,直接给答案!

  注册表:SOFTWARE\Microsoft\.NETFramework\

  这个注册表项,有一个安装路径的项,指定到.net的运行时目录。笔者的机器是64位机器,x86的机器就没有64.

 

 

 然后,注册完路径后,还有它下面的一个注册表键值对:policy

注意其中的键值对:

 

这个支持策略,类似设计模式中的策略模式。根据特定的场景,使用特定的策略支撑。我们注册4.0后,自然也就支持.net 4.0。

  好,注册表关键就这2个,接下来还有关键的一个步骤,在系统目录 

一个是箭头指向的dll  还有一个

 

100那个是4.0的 120那个是笔者安装了 VC 分布包的 VC++2013的。我们需要的是上面的那个哈。。。

 

好,把这两个程序集 在特定的机器上,也打入进去后,好,我们的精简版本的.net framework就完成了!!

然后,还可以在运行时程序集 目录下,裁剪不需要的程序集 比如什么 exe的小工具啊 WCF 啊 WPF啊什么的。最终就可以粗来一个体积小巧,性能不错,支持.net 4.0的运行时了。

也就是上面的3步走,有兴趣自己尝试,有疑问,请评论留言。笔者不会发布完整的小安装包。版权的问题很蛋痛 呵呵,Congratulations..............

  啰嗦一下,为什么添加注册表就能访问策略进行引导,跟.net的历史有关系吧,因为XP系统上面,就已经集成了1.X的.net framework.后续系统Vista win7 server03 08 等等也一样。所以,注册个版本号,也就可以识别粗来了。。。。。

   注册表献上:

 

 

Root: HKLM; Subkey: "SOFTWARE\Microsoft\.NETFramework";Permissions:admins-full; ValueType: string; ValueName: "InstallRoot"; ValueData: "{win}\Microsoft.NET\Framework\";Check:WebServerRuntimeNotInstall


Root: HKLM; Subkey: "SOFTWARE\Microsoft\.NETFramework\policy\v4.0";Permissions:admins-full;Check:WebServerRuntimeNotInstall


Root: HKLM; Subkey: "SOFTWARE\Microsoft\.NETFramework\policy\v4.0"; Permissions:admins-full; ValueType: string; ValueName: "30319"; ValueData: "30319-30319";Check:WebServerRuntimeHasNotV4SubKey

不要问我,上面是什么鬼,inno setup 又是什么鬼。尽情享用吧.........

 

 

 

 

  ---------------------延伸:mscoree.dll-----------------------------

 

.NET中的幕后英雄:MSCOREE.DLL

现在做.NET Framework的开发的朋友应该是越来越多了,但是可能并非人人都对MSCOREE.DLL非常了解。而事实上,毫不夸张地说,MSCOREE.DLL是.NET Framework中最为核心的DLL之一,没有这个DLL,托管程序根本无法开始执行起来,但是由于这个DLL藏在System32目录下,根本无人问津,可以说是有点委屈了这位.NET Framework中的幕后英雄。本文主要讨论MSCOREE.DLL的几大作用,以及MSCOREE.DLL的兼容性问题。

MSCOREE是托管程序的入口点
让我们来做一个小实验:

首先写一个最最简单的Hello World程序,用csc编译(当然你用VS我也没意见):

public class Program
{

       public static void Main(string[] args)

       {

              System.Console.WriteLine("Hello World!");

       }
}
 
然后,在命令行中键入:
 

C:/Windows/System32> ren mscoree.dll mscoree_.dll

 
请注意在Vista系统上需提升权限,否则重命名失败。
之后,运行刚才编译出来的EXE程序。Windows直接报错:

然后,再把mscoree.dll名字改回去,再次运行A.EXE,这次正确打印出了Hello World。

那么为什么一旦没有MSCOREE.DLL,就算是最简单的Hello World也无法运行呢?

有在Windows用C/C++编程的朋友们应该熟悉上面那个出错对话框的意思,这个对话框通常在程序找不到所需的DLL的时候出现。我们可以通过运行Visual Studio中自带的Depends.exe来查看A.EXE的对于DLL的依赖关系:

可以看到A.EXE只对一个DLL有依赖关系,也就是MSCOREE.DLL。并且A.EXE只用到了MSCOREE.DLL中的一个函数,即_CorExeMain。而MSCOREE.DLL本身却输出了137个函数之多。从这个函数的名字大家可以猜出,这个函数是EXE的一个入口点。为了证实这一点,我们可以用DumpBin看看内容:

Microsoft (R) COFF/PE Dumper Version 8.00.50727.762

Copyright (C) Microsoft Corporation. All rights reserved.

 
 
Dump of file a.exe
 
PE signature found
 
File Type: EXECUTABLE IMAGE
 
FILE HEADER VALUES
             14C machine (x86)
               3 number of sections

        46C83E12 time date stamp Sun Aug 19 20:56:50 2007

               0 file pointer to symbol table
               0 number of symbols
              E0 size of optional header
             10E characteristics
                   Executable
                   Line numbers stripped

                   Symbols stripped

                   32 bit word machine
 
OPTIONAL HEADER VALUES
             10B magic # (PE32)
            8.00 linker version
             400 size of code
             600 size of initialized data
               0 size of uninitialized data

            23DE entry point (004023DE)

            2000 base of code
            4000 base of data
          400000 image base (00400000 to 00407FFF)
 
 
注意这个EXE的入口点的RVA是23DE。
再使用Windbg加载A.EXE,使用lmm (list module match)命令查看A.EXE加载的首地址:
 
0:000> lmm a

start    end        module name

00de0000 00de8000   a          (deferred) 

 
 
可以看到是0x00de0000,那么再加上23DE这个RVA值,就是程序的入口点。用u命令反汇编看一下:
 
0:000> u de23de
a+0x23de:

00de23de ff250020de00    jmp     dword ptr [a+0x2000 (00de2000)]

 
可以看到这就是一个简单的JUMP指令,跳转到0x00de2000所指向的位置,那么这个位置是什么函数呢?我们可以通过dds命令查看:
 
0:000> dds 2000+de0000
00de2000 5b034e50 mscoree!_CorExeMain
 
 
到此事实就非常清楚了,任何托管的EXE程序中的入口点都是一条JMP指令直接跳转到MSCOREE.DLL的_CorExeMain函数执行。而这个_CorExeMain的地址(也就是00de2000所保存的0x5b034e50)则是由OS Loader填入,因为这个位置正是Import Table的位置。
对于一个托管的DLL而言,情况也非常类似,入口点则是_CorDllMain:

可以想象的到,假如你的系统中没有安装.NET Framework,那么执行托管程序也会立刻同样的对话框。显然根据这个信息用户本身很难推断出发生了什么问题。一个可行的方案是Windows总是自带一个MSCOREE.DLL,这个MSCOREE.DLL如果发现没有.NET Framework则会给出比较清晰的报错信息。不过由于从Windows 2003之后(包括Vista)的所有Windows版本都会自带.Net Framework,那么这个问题基本上不会出现了。

MSCOREE负责选择.NET Framework版本

MSCOREE.DLL有个非常特殊的地方,也就是它位于C:/Windows/System32目录下,换句话说,不管你的系统上面安装了多少个不同的.NET Framework版本,这个DLL都最多只可能有2份(32位/64位各一份),而在C:/Windows/Microsoft.NET/Framework 或者C:/Windows/Microsoft.NET/Framework64下面,则会有多份不同的.NET Framework同时存在。那么,这个MSCOREE.DLL又是如何对应不同版本的.NET Framework呢?答案很简单:MSCOREE.DLL通过注册表信息确定系统上面安装的.NET Framework版本号码,然后根据应用程序本身的所要求的版本来选择一个合适的.NET Framework版本来执行。真正的工作则是交给实际的某个版本的 .NET的DLL执行,在通常情况下,这个DLL是Work Station版本的CLR,名为MSCORWKS.DLL,而服务器版本的CLR则对应MSCORSVR.DLL

程序可以通过MSCOREE调用CLR的提供的功能或者定制CLR

MSCOREE.DLL导出了大量的函数,那么这些函数是否公开并且可以调用呢?答案是肯定的。几乎所有这些函数在MSDN中都可以查到对应的文档,并且在.NET Framework SDK的Include目录中有一个对应的mscoree.h,提供了这些函数的Prototype。应用程序通过这些函数,可以访问CLR提供的各项功能,比如:

函数名
用途
GetCORSystemDirectory
获得进程中加载的CLR的安装目录
GetCORVersion
获得进程中加载的CLR的版本西nxi 
GetFileVersion
获得指定文件的CLR版本信息
GetRequestedRuntimeInfo
获得指定版本CLR的相关信息
GetRequestedRuntimeVersion
获得应用程序运行所需要的CLR版本信息
ClrCreateManagedInstance
创建一个.NET对象并返回指定的接口,使用此函数可以访问大量的.NET Framework的已有功能
CorBindToRuntime
加载指定版本CLR
CorBindToRuntimeHost
在Host中加载指定版本CLR,Hosting时候使用
CreateDebuggingInterfaceFromVersion
获得对应版本CLR的ICorDebug接口,用于编写调试器(比如Visual Studio)
CorLaunchApplication
以指定参数启动托管程序

除此之外还有很多,这里只是列了一些比较常用的功能而已。可以看到这些功能都非常有用,特别值得提出的是CorBindToRuntimeHost和CreateDebuggingInterfaceFromVersion。前者提供了对CLR各个方面的定制功能,功能非常强大,有兴趣的朋友可以参考MSDN或者Customizing the Common Language Runtime一书。而后者则提供了对托管程序的调试支持,通过ICorDebug接口。

MSCOREE提供对COM支持

非托管代码可以通过COM直接调用.NET的Assembly中的托管对象。以下面这个对象为例,该对象CLSID为{0029598F-26Fa-46F7-953B-86E2947AB19F},类型为Microsoft.SqlServer.Replication.ComErrorRecord,线程模型为Both,Assembly名称为Microsoft.SqlServer.Replication, Version=9.0.242.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91,所需CLR版本为v2.0.50727 (2.0 RTM)。最值得注意的是,入口点为mscoree.dll。

 

对于COM来说,COM只知道CLSID=0029598F-26Fa-46F7-953B-86E2947AB19F}的COM对象位于MSCOREE.DLL中。而事实上,这个类型位于Microsoft.SqlServer.Replication.dll之中,但是COM并不知道,COM只需要知道从MSCOREE.DLL中可以获得对应的ClassFactory就可以了,然后COM会通过IClassFactory接口创建这个托管对象的实例,并返回对应的接口。假定一个非托管程序尝试通过COM创建这样一个对象,那么会发生下面这些事情:
1.     程序调用CoCreateInstance通知COM需要创建这样一个对象,CLSID=0029598F-26Fa-46F7-953B-86E2947AB19F,需要返回IDispatch接口
2.     COM调用CoGetClassObject函数查找对应的入口DLL
3.     COM找到对应的注册表,发现对应的DLL为MSCOREE.DLL
4.     COM加载MSCOREE.DLL,调用DllGetClassObject函数,传入CLSID
5.     MSCOREE.DLL中的DllGetClassObject函数读入CLSID,找到对应的注册表,获取对象的类型名称,Assembly名称,CLR版本等信息
6.     DllGetClassObject加载对应版本的CLR
7.     DllGetClassObject返回一个临时Class Factory对象的IClassFactory接口
8.     COM调用这个Class Factory对象的IClassFactory::CreateInstance方法
9.     这个Class Factory加载对应的CLR,并创建对象,返回一个对象的CCW (Com Callable Wrapper),并该CCW的返回指定的IDispatch接口
可以看到,这个情况下起到最关键作用的是MSCOREE.DLL所提供的DllGetClassObject函数。

除了对用户自定义的托管对象提供COM支持之外,MSCOREE.DLL自己也支持少量COM对象,因此MSCOREE.dll支持DllGetClassObject, DllRegisterServer, DllUnregisterServer, DllCanUnloadNow这些COM DLL所需要支持的标准函数。

MSCOREE.DLL的兼容性问题

我们可以考虑一下,假如我们现在有了.NET Framework 1.0,然后安装了.NET Framework 2.0,那么MSCOREE.DLL会发生变化吗。答案是可能会,如果到2.0到1.0发生了改变需要修改MSCOREE.DLL(比如多加了一个函数),那么肯定要更新MSCOREE.DLL,但是这个MSCOREE.DLL必须完全支持所有.NET Framework 1.0中的MSCOREE.DLL中的函数,否则可以想象所有依赖于这些变化的MSCOREE.DLL中的函数程序都会出错。因此MSCOREE.DLL必须要做到完全向前兼容。

再考虑卸载的情况,假如这个时候.NET Framework 2.0被卸载,那么MSCOREE.DLL会被还原成.NET Framework 1.0的吗?这次答案则是不会。因为2.0的MSCOREE.DLL本身支持.NET Framework 1.0,因此无须替换。这样最为简单。

事实上是,CLR Team一般很少改动MSCOREE.DLL,避免出现兼容性问题,因此可以预见,MSCOREE.DLL将会是.NET Framework/CLR中变化最少的DLL。换句话说,这篇文章的内容,在可以预见的未来几个版本基本上不会过时。OK,对于MSCOREE.DLL的讨论到这里就告一段落,有兴趣的朋友可以自己动手写一些小程序,实际体会一下MSCOREE.DLL所提供的各项功能。

 

https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/hosting/deprecated-clr-hosting-functions

https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/hosting/cordllmain-function

_CorDllMain Function

 

 

Initializes the common language runtime (CLR), locates the managed entry point in the DLL assembly's CLR header, and begins execution.

Syntax

BOOL STDMETHODCALLTYPE _CorDllMain (  
   [in] HINSTANCE hInst,  
   [in] DWORD     dwReason,  
   [in] LPVOID    lpReserved  
);  

Parameters

hInst
[in] The instance handle of the loaded module.

dwReason
[in]Indicates why the DLL entry-point function is being called. This parameter can be one of the following values: DLL_PROCESS_ATTACH, DLL_THREAD_ATTACH, DLL_THREAD_ATTACH, or DLL_PROCESS_DETACH. For descriptions of these values, see the DllMain documentation in the Platform SDK.

lpReserved
[in] Unused.

Return Value

This method returns true for success and false if an error occurs.

Remarks

This function is called by the operating system loader for DLL assemblies. For executable assemblies, the loader calls the _CorExeMain function instead.

The operating system loader calls this method regardless of the entry point specified in the DLL file.

In Windows 98, Windows ME, Windows NT, and Windows 2000, the _CorDllMain function is called indirectly through a fixupin the operating system loader. In all other versions of Windows, it is called directly by the operating system loader.

For additional information, see the Remarks section in the _CorValidateImage topic.

Requirements

Platforms: See System Requirements.

Header: Cor.h

Library: Included as a resource in MsCorEE.dll

.NET Framework Versions: Available since 1.0

See Also

Metadata Global Static Functions

Feedback

 

_CorExeMain Function

Initializes the common language runtime (CLR), locates the managed entry point in the executable assembly's CLR header, and begins execution.

Syntax

__int32 STDMETHODCALLTYPE _CorExeMain ();  

Remarks

This function is called by the loader in processes created from managed executable assemblies. For DLL assemblies, the loader calls the _CorDllMain function instead.

The operating system loader calls this method regardless of the entry point specified in the image file.

In Windows 98, Windows ME, Windows NT, and Windows 2000, the _CorExeMain function is called indirectly through a fixup in the operating system loader. In all other versions of Windows, it is called directly by the operating system loader.

For additional information, see the Remarks section in the _CorValidateImage topic.

Requirements

Platforms: See System Requirements.

Header: Cor.h

Library: Included as a resource in MsCorEE.dll

.NET Framework Versions: Available since 1.0

See Also

Metadata Global Static Functions

 

 

 

 

posted @ 2017-02-15 10:47  特洛伊-Micro  阅读(1282)  评论(2编辑  收藏  举报