使用VS2019新建的项目生成后无法获取程序集正确的编译时间
版本记录
时间 | 说明 |
---|---|
2020-06-16 | 初稿 |
零、前言
一直以来,我都习惯将主程序的编译时间作为整个软件的发布时间,这样做的好处是,省去了每发布一个新版本就去关于
界面中修改发布时间的操作。
关于获取程序集的编译时间,原理是读取可执行文件的PE结构中的TimeDateStamp
这个属性,上网一搜就能找到现成的代码。之前我也是没动脑子,抄来就用了,倒是也没出现什么问题。
一、无法读取C++程序集的PE结构
直到有一天在读取一个使用C++编写的dll的编译时间时抛了异常,我才花了些时间了解了一下Windows可执行文件的头部结构,也就是PE32文件结构
。
初步了解PE文件结构后,很容易就找到了报错的原因,原本offset
只读取了一个字节,普通的.NET程序集似乎都没问题,恰好这个C++程序集的偏移地址有两个字节,因此被识别为非法的程序集,改成ReadUInt16
就好了。
由于本人也是新手,对PE32文件结构也是一知半解,不展开讨论了。
下面附上我改进过的代码:
/// <summary>
/// 获取PE程序集中的编译时间
/// </summary>
/// <param name="fileName"></param>
/// <returns></returns>
public static DateTime GetPE32Time(string fileName)
{
long seconds;
using (var br = new BinaryReader(new FileStream(fileName, FileMode.Open, FileAccess.Read)))
{
var bs = br.ReadBytes(2);
var msg = "非法的PE32文件";
if (bs.Length != 2) { throw new Exception(msg); }
if (bs[0] != 'M' || bs[1] != 'Z') { throw new Exception(msg); }
br.BaseStream.Seek(0x3c, SeekOrigin.Begin);
var offset = br.ReadUInt16();
br.BaseStream.Seek(offset, SeekOrigin.Begin);
bs = br.ReadBytes(4);
if (bs.Length != 4) { throw new Exception(msg); }
if (bs[0] != 'P' || bs[1] != 'E' || bs[2] != 0 || bs[3] != 0) { throw new Exception(msg); }
bs = br.ReadBytes(4);
if (bs.Length != 4) { throw new Exception(msg); }
seconds = br.ReadUInt32();
}
return DateTime.SpecifyKind(new DateTime(1970, 1, 1),
DateTimeKind.Utc).AddSeconds(seconds).ToLocalTime();
}
二、VS2019新建的项目读取到的PE时间戳错误
上面的问题是解决了,但别忘了,本文的主题是使用VS2019新建的项目生成后无法获取程序集正确的编译时间
,换句话说,那个问题纯粹是因为我偷懒才引入的。
今天聊的这个问题是在无意间发现的,困扰了我很久,也很诡异。简单的说,就是使用VS2019新建的项目,读取到的PE结构中的TimeDateStamp
是一个很大的数字,换算成时间,基本都是2100年以后。以前能正确读取时间的程序都是用2015或者更早版本的VS建的工程。
2.1 使用VS2012新建工程,读取时间戳
为了查找原因,我在虚拟机中装了VS2012,新建了一个控制台程序,编译后用EmEditor打开,得到的时间戳为0x5E 0xE8 0x5C 0x49。如下:
换算成十进制为1592286281,这是个Unix时间戳,换算成日期就是今天。
因此,使用VS2012新建的项目能正确读取到PE时间戳。
###2.2 使用VS2019新建工程,读取时间戳
同样,用VS2019新建一个控制台程序,编译后用EmEditor打开,得到的时间戳为0x82 0x6B 0x62 0x08。如下:
换算成十进制为2188075528,这个值明显偏大,用它算出来的日期就不对了。
2.3 比较两个csproj
根据我多年的经验,问题十有八九出在csproj
这个文件身上。用工具对比两个版本的VS生成的工程文件,左边是VS2012,右边是VS2019。
果不其然,VS2019生成的工程文件里这么一段:
<Deterministic>true</Deterministic>
直觉告诉我,问题就出在这里。
依靠万能的度娘,很快找到了这篇文章Roslyn 的确定性构建。原来这是C#编译器的一个特性,加上这个标记之后,每次生成的Hash值都是一样的,去掉后才会把时间编译进去。
如此,解决办法就简单了,把这行删掉即可。