浅谈 .NET 程序的编译时间

我们在编写 .NET 程序时,经常会在该程序的“关于本软件”对话框中给出这个程序的编译时间,如下图所示:

上图中的编译时间是如果得到的呢?其实是在其 C# 源程序中有这么一句:

[assembly: AssemblyVersion("1.3.*")]

上述语句使用了 System.Reflection.AssemblyVersionAttribute 类,该类用于指定正在特性化的程序集的版本。在 MSDN 文档中有以下描述:

程序集版本号是程序集标识的一部分,在绑定到程序集时以及在版本策略中扮演着关键的角色。

版本号包含以下四部分:<主版本(Major)>.<次版本(Minor)>.<内部版本号(Build)>.<修订号(Revision)>

您可以指定所有这些值,也可使用星号 (*) 表示接受默认的内部版本号、修订号,或者接受二者。 例如,

[assembly:AssemblyVersion("2.3.25.1")] 指示主版本为 2,次版本为 3,内部版本号为 25,修订号为 1。 版本号 [assembly:AssemblyVersion("1.2.*")] 指定主版本为 1,次版本为 2,并接受默认的内部版本号和修订号。 版本号 [assembly:AssemblyVersion("1.2.15.*")] 指定主版本为 1,次版本为 2,内部版本号为 15,并接受默认的修订号。

默认的内部版本号每日增加。 默认修订号是随机的。

具体来说,默认的内部版本号表示自2000年1月1日以来的天数,而默认修订号也不是随机的,表示自该天午夜零时以来的秒数的一半。于是就可以使用下面的表达式获得 .NET 程序的编译时间:

new DateTime(2000, 1, 1).AddDays(version.Build).AddSeconds(version.Revision * 2)

但是,还有很多 .NET 程序的程序集版本号没有使用星号来接受默认的内部版本号、修订号,就不能使用这个方法了。我们知道,.NET 程序也是一个标准的32位或64位的 Microsoft Windows 可执行体(PE32,Portable Executable)文件。而PE文件头中包含一个时间标记来指出文件的生成时间,请参见:“Microsoft可移植可执行文件和通用目标文件格式文件规范v8.1修订版”和“Microsoft
Portable Executable and Common Object File Format Specification
”。具体来说就是:

PE文件头由Microsoft MS-DOS?占位程序、PE文件签名、COFF文件头以及可选文件头组成,COFF目标文件头由COFF文件头和可选文件头组成。在这两种情况下,文件头后面紧跟着的都是节头。

MS-DOS占位程序是一个运行于MS?DOS下的合法应用程序,它被放在EXE映像的最前面。在位置0x3C处,这个占位程序包含PE文件签名的偏移地址。

在MS-DOS占位程序后面、在偏移0x3C指定的文件偏移处,是一个4字节的签名,它用来标识文件为一个PE格式的映像文件。这个签名是“PE\0\0”(字母“P”和“E”后跟着两个空字节)。

紧跟着映像文件签名之后,是一个标准COFF文件头。在这个COFF文件头的偏移为4开始的4个字节就是从UTC时间1970年1月1日00:00起的总秒数(一个C运行时time_t类型的值)的低32位,它指出文件何时被创建。

一个PE文件的例子如下图所示:

使用 DumpBin.exe 可得到如下信息:

Microsoft (R) COFF/PE Dumper Version 10.00.30319.01
Copyright (C) Microsoft Corporation.  All rights reserved.

Dump of file PowerWord2Snb.exe

PE signature found

File Type: EXECUTABLE IMAGE

FILE HEADER VALUES
             14C machine (x86)
               3 number of sections
        4D0165D7 time date stamp Fri Dec 10 07:27:19 2010
               0 file pointer to symbol table
               0 number of symbols
              E0 size of optional header
             102 characteristics
                   Executable
                   32 bit word machine

此外,在操作系统的文件系统中,也记录了每个文件(不要求是PE文件)的创建和修改时间,如下图所示:

我们写一个 C# 程序来获取这三个时间吧:

01:  using System;
02:  using System.IO;
03:  using System.Reflection;
04:
05:  [assembly: AssemblyVersion("1.0.*")]
06:
07:  namespace Skyiv.BuildTime
08:  {
09:    sealed class Program
10:    {
11:      delegate DateTime GetTime(string fileName);
12:
13:      TextWriter writer;
14:
15:      Program(TextWriter writer)
16:      {
17:        this.writer = writer;
18:      }
19:
20:      static void Main(string[] args)
21:      {
22:        Console.WriteLine("OS  Version: " + Environment.OSVersion);
23:        Console.WriteLine("CLR Version: " + Environment.Version);
24:        Console.WriteLine();
25:        var fileName = (args.Length > 0) ? args[0] : Assembly.GetExecutingAssembly().Location;
26:        new Program(Console.Out).Write(fileName);
27:      }
28:
29:      void Write(string fileName)
30:      {
31:        writer.WriteLine(fileName);
32:        Write("文件系统  ", GetFileCreationTime, fileName);
33:        Write("PE32      ", GetPe32Time, fileName);
34:        Write("装配件版本", GetAssemblyVersionTime, fileName);
35:      }
36:
37:      void Write(string msg, GetTime getTime, string fileName)
38:      {
39:        string time;
40:        try
41:        {
42:          time = getTime(fileName).ToString("yyyy-MM-dd HH:mm:ss");
43:        }
44:        catch (Exception ex)
45:        {
46:          time = ex.Message;
47:        }
48:        writer.WriteLine("{0}: {1}", msg, time);
49:      }
50:
51:      DateTime GetFileCreationTime(string fileName)
52:      {
53:        return new FileInfo(fileName).CreationTime;
54:      }
55:
56:      DateTime GetAssemblyVersionTime(string fileName)
57:      {
58:        var version = Assembly.LoadFrom(fileName).GetName().Version;
59:        return new DateTime(2000, 1, 1).AddDays(version.Build).AddSeconds(version.Revision * 2);
60:      }
61:
62:      DateTime GetPe32Time(string fileName)
63:      {
64:        int seconds;
65:        using (var br = new BinaryReader(new FileStream(fileName, FileMode.Open, FileAccess.Read)))
66:        {
67:          var bs = br.ReadBytes(2);
68:          var msg = "非法的PE32文件";
69:          if (bs.Length != 2) throw new Exception(msg);
70:          if (bs[0] != ‘M‘ || bs[1] != ‘Z‘) throw new Exception(msg);
71:          br.BaseStream.Seek(0x3c, SeekOrigin.Begin);
72:          var offset = br.ReadByte();
73:          br.BaseStream.Seek(offset, SeekOrigin.Begin);
74:          bs = br.ReadBytes(4);
75:          if (bs.Length != 4) throw new Exception(msg);
76:          if (bs[0] != ‘P‘ || bs[1] != ‘E‘ || bs[2] != 0 || bs[3] != 0) throw new Exception(msg);
77:          bs = br.ReadBytes(4);
78:          if (bs.Length != 4) throw new Exception(msg);
79:          seconds = br.ReadInt32();
80:        }
81:        return DateTime.SpecifyKind(new DateTime(1970, 1, 1), DateTimeKind.Utc).
82:          AddSeconds(seconds).ToLocalTime();
83:      }
84:    }
85:  }

这个程序的运行结果如下所示:

E:\work> BuildTime PowerWord2Snb.exe
OS  Version: Microsoft Windows NT 6.0.6002 Service Pack 2
CLR Version: 2.0.50727.4206

PowerWord2Snb.exe
文件系统  : 2010-12-10 07:32:14
PE32      : 2010-12-10 07:27:19
装配件版本: 2010-12-10 07:27:18

E:\work> BuildTime
OS  Version: Microsoft Windows NT 6.0.6002 Service Pack 2
CLR Version: 2.0.50727.4206

E:\work\BuildTime.exe
文件系统  : 2010-12-18 20:26:24
PE32      : 2010-12-18 19:56:41
装配件版本: 2010-12-18 19:56:40

我们来看看著名的 .NET Reflector 的信息吧:

E:\work> BuildTime d:\bin\reflector\reflector.exe
OS  Version: Microsoft Windows NT 6.0.6002 Service Pack 2
CLR Version: 2.0.50727.4206

d:\bin\reflector\reflector.exe
文件系统  : 2008-07-10 19:36:45
PE32      : 2010-07-15 00:42:36
装配件版本: 2000-01-01 00:00:00

E:\work>

再来更多的例子:

E:\work> BuildTime C:\windows\regedit.exe
OS  Version: Microsoft Windows NT 6.0.6002 Service Pack 2
CLR Version: 2.0.50727.4206

C:\windows\regedit.exe
文件系统  : 2008-04-23 19:35:29
PE32      : 2008-01-19 13:30:16
装配件版本: 未能加载文件或程序集“file:///C:\windows\regedit.exe”
或它的某一个依赖项。该模块应包含一个程序集清单。

E:\work> BuildTime BuildTime.cs
OS  Version: Microsoft Windows NT 6.0.6002 Service Pack 2
CLR Version: 2.0.50727.4206

BuildTime.cs
文件系统  : 2010-12-18 20:50:03
PE32      : 非法的PE32文件
装配件版本: 未能加载文件或程序集“file:///E:\work\BuildTime.cs”
或它的某一个依赖项。该模块应包含一个程序集清单。

E:\work>

我们再看看在 Linux 操作系统中的情况(openSUSE 11.3, mono 2.8.1):

[email protected]:~/work> dmcs BuildTime.cs
[email protected]:~/work> mono BuildTime.exe
OS  Version: Unix 2.6.34.7
CLR Version: 4.0.30319.1

/home/ben/work/BuildTime.exe
文件系统  : 2010-12-18 21:29:14
PE32      : 2010-12-18 21:29:14
装配件版本: 2000-01-01 00:00:00
[email protected]:~/work> mono BuildTime.exe /usr/lib/mono/4.0/dmcs.exe
OS  Version: Unix 2.6.34.7
CLR Version: 4.0.30319.1

/usr/lib/mono/4.0/dmcs.exe
文件系统  : 2010-11-12 22:52:48
PE32      : 2010-11-12 22:44:24
装配件版本: 2000-01-02 00:00:00
[email protected]:~/work> mono BuildTime.exe /usr/bin/gcc-4.5
OS  Version: Unix 2.6.34.7
CLR Version: 4.0.30319.1

/usr/bin/gcc-4.5
文件系统  : 2010-07-02 02:29:53
PE32      : 非法的PE32文件
装配件版本: Could not load file or assembly ‘/usr/bin/gcc-4.5‘ or one of its dependencies.
 An attempt was made to load a program with an incorrect format.
[email protected]:~/work>

由上可见,在 Linux 操作系统中 mono 对 System.Reflection.AssemblyVersionAttribute 类的支持很成问题。在 Microsoft
的 MSDN 文档中说可以用星号表示接受默认的内部版本号、修订号,默认的内部版本号每日增加。 默认修订号是随机的。而 mono 的 C# 编译器直接将默认的内部版本号和默认的修订号都设置为零了。

综上所述,要得到 .NET 程序的编译时间,还是去读该程序的PE文件头中的文件创建时间最靠谱。

版权声明:本文为博主http://www.zuiniusn.com原创文章,未经博主允许不得转载。

时间: 2024-08-24 04:13:36

浅谈 .NET 程序的编译时间的相关文章

浅谈 Python 程序和 C 程序的整合

源地址:http://www.ibm.com/developerworks/cn/linux/l-cn-pythonandc/ 概览 Python 是一种用于快速开发软件的编程语言,它的语法比较简单,易于掌握,但存在执行速度慢的问题,并且在处理某些问题时存在不足,如对计算机硬件系统的访问,对媒体文件的访问等.而作为软件开发的传统编程语言—— C 语言,却能在这些问题上很好地弥补 Python 语言的不足.因此,本文通过实例研究如何在 Python 程序中整合既有的 C 语言模块,包括用 C 语言

浅谈mapreduce程序部署

尽管我们在虚拟机client上能非常快通过shell命令,进行运行一些已经封装好实例程序,可是在应用中还是是自己敲代码,然后部署到server中去,以下,我通过程序进行浅谈一个程序的部署过程. 在启动Hadoop之后,然后把程序达成可运行的jar包,并把对应的第三方jar包 包括进去.运行hadoop    jar   XXX. +驱动名称. package com.mapred; import java.io.IOException; import java.io.PrintStream; i

【菜鸟学php】小菜鸟由帝国备份王在Wamp环境下打开500错误浅谈PHP程序员

===================问题情况描述=================== 小弟一直在玩discuz论坛开源程序,这个论坛程序经常涉及到论坛搬家的问题. 今天我在本地Wamp环境下,用开源软件帝国备份王2010进行数据库备份数据,结果打开发现报错500! 这真是坑爹了,回想下以前自己使用这个开源程序进行备份也不下于十几次了,大部分都正常成功, 但也不乏出现这种情况的,小弟之前一般遇到这种问题, 都是直接忽略,换其他办法来进行备份,但是用惯了帝国备份王,换其他的方法备份数据,总感觉难

浅谈优化程序性能(下)

前言 在上一篇随笔中,我们谈到最小化一个计算中的操作数量不一定会提高它的性能.现在,就让我们来解开为什么会出现这种情况的原因吧. 处理器体系结构 在计算机的处理器中,处理一条指令包括很多操作,可以分为取指(fetch).译码(decode).执行(execute).访存(memory).写回(write back)和更新程序计数器(PC update)等几个阶段.这些阶段可以在流水线上同时进行,如下图所示: 上图中,F.D.E.M 和 W 分别代表上述五个阶段.当然,现代的处理器比这个示例要复杂

浅谈小程序生命周期

马上要做小程序的开发啦,看了一波文档,总结一下. 小程序开发框架小程序的框架分为视图层和逻辑层.逻辑层由js 编写,视图层由WXML和WXSS编写.WXML 用来描述页面结构,相当于HTML;WXSS用来用来描述页面样式,相当于CSS. 小程序启动后,每个页面的数据放在data(这个data是在页面的js文件里)里面,初始化页面, 当用户触发页面的事件,逻辑层接受页面的事件反馈,通过setData发送新数据到视图层(异步),页面更新,同时改变对应的this.data的值(同步).小程序里面的se

浅谈VC++中预编译的头文件放那里的问题分析

用C++写程序,肯定要用预编译头文件,就是那个stdafx.h.不过我一直以为只要在.cpp文件中包含stdafx.h 就使用了预编译头文件,其实不对.在VC++中,预编译头文件是指放到stdafx.h中的头文件才会有效果.如下: file: stdafx.h // stdafx.h : include file for standard system include files, // or project specific include files that are used freque

浅谈css的预编译---less语言

正如各位所知道的一样,css是一门标记性语言,语法相对简单,对使用者的要求也比较低 .不过可乐不知道友友们有没有发现,在使用css的时候需要书写大量看似没有逻辑的代码,不方便维护及扩展,不利于复用,尤其对于一些缺乏css编写经验的猿猿来讲,写出组织良好且易于维护的 CSS 代码是相当困难的一件事情.这个时候呢,可乐悄悄地告诉你们,咱们的程序员蜀黍是无所不能的,针对这个调皮的css,专门开发了less语言.那么,就由可乐来细细为你们介绍这一个新朋友吧~~~ 一.less简介: 1.less语言是在

如何获取程序的编译时间

有两个宏可以获取获取编译的时间 __DATA__:编译的日期 __TIME__:编译的时间 可以通过打印来看编译的日期和时间: printf("data: %s     time: %s",__DATA__,__TIME__); 有时需要将编译的时间放入版本号中,方法如下: #include "stdlib.h" #define BUILD_YEAR_CH0 (__DATE__[ 7])#define BUILD_YEAR_CH1 (__DATE__[ 8])#de

分享8年开发经验,浅谈java程序员职业规划

在中国有很多人都认为IT行为是吃青春饭的,如果过了30岁就很难有机会再发展下去!其实现实并不是这样子的,在下从事.NET及JAVA方面的开发的也有8年的时间了,在这里在下想凭借自己的亲身经历,与大家一起探讨一下. 明确入行的目的 很多人干IT这一行都冲着"收入高"这一点的,因为只要学会一点HTML.DIV+CSS,要做一个页面开发人员并不是一件难事,而且做一个页面开发人员更容易找到工作,收入比普通的工作还要高一些,所以成为了很多高校毕业生的选择.如果您只是抱着这样一个心态来入行的话,那