分享用Delphi实现动态获取版本信息
文件版本信息的存在使得应用程序正确的安装文件变得简单,并且使安装程序能够分析文件的当前安装状态。通常,版本信息应该包括文件的版本号、文件功能描述以及文件作者等多项重要内容。   在应用层面上,程序员可以通过保存在应用程序文件或动态链接库文件中的版本信息判断一个文件是否应该被安装,并确定当前安装文件的冲突。在文件有了版本信息这个属性后,我们编写的程序就能够实现以下功能:  1. 避免在新版本的组件上安装旧版本的相同组件;  2. 在多语言系统环境中,操作系统根据文件版本信息里提供的语言信息在启动程序时决定使用的正确语言;  3. 可以防止在不同的路径下安装多个文件的拷贝;  4. 应用程序在运行时,便能判断文件的版本是否正确;  5. 在应用程序的关于对话框中显示可执行文件的版本号;  6. 在线升级程序可以判断一个文件是否因为版本过旧,从而进行必要的文件升级。  因为Windows系统在设计上的先天缺陷,其使用的FSO并不支持文件的版本信息功能。为了能对文件的版本信息进行管理,微软在Platform SDK(平台软件开发工具包)中通过API(可编程接口)的方式为软件开发人员提供了管理版本信息的方法。这些API是GetFileVersionInfo、GetFileVersionInfo、VerFindFile、VerInstallFile和VerQueryValue,它们被存放在Windows系统目录下一个被命名为VERSION.DLL的动态链接库文件中。  在Windows系统中任何可以包含Windows资源的文件都可以包含版本信息,比如动态链接库文件、可执行文件、字体文件等。版本信息被包装成一个VERSIONINFO结构的资源,通过编译器打包进这些文件中。  本文试图通过分别讲解这些API的使用方法,向读者介绍在Delphi中通过编程的方法,怎样实现在运行时动态获取程序文件版本信息的功能。  一、几个重点版本信息函数的使用方法  在Windows操作系统中采用语言标识符来区别不同的自然语言,语言标识符是一个32位的无符号整数,一个数值唯一对应一种自然语言。因此,除了前面列出的五个函数外,在我们编写程序的时候还会经常使用到VerLanguageName函数,通过调用这个函数,我们的程序便能够自由的在用整数表示的语言标识符和用字符串表示的语言名称之间进行转换。  以下便是用于处理版本信息函数的完整列表。须要注意的是:1. 在Windows NT 3.51或更早版本的操作系统中,版本信息函数不能操作16位的Windows文件像(File Image);2. 在Windows 95/98/Me/NT4/2000操作系统中,这些函数同时可以操作16位和32位两种文件像;3. 在Windows XP操作系统中,这些函数同时可以操作16位、32位和64位共三种文件像。[插入表格1]  在这篇文章中,我们将着重介绍GetFileVersionInfo、GetFileVersionInfoSize、VerLanguageName和VerQueryValue这四个函数的使用方法。在实际应用时,其余的两个函数很少使用,因此不作为重点内容进行介绍。  1.1. GetFileVersionInfo函数  GetFileVersionInfo函数被用来获取包含在指定文件中的版本信息。其Delphi函数声明如下:function GetFileVersionInfo(
lptstrFilename: PChar; // 文件名
dwHandle: DWORD; // 忽略
dwLen: DWORD; // 缓冲区大小
lpData: Pointer // 版本信息缓冲区
): BOOL; stdcall;   参数说明:  lptstrFilename,一个以NULL结束字符串,它指定了期望从中获取版本详细的文件名。如果文件名不包含完整路径,函数将使用LoadLibrary函数的默认搜索次序进行搜索。在Windows 95/98/Me操作系统中路径名不能超过126个字符。  dwHandle,这个参数没有使用,将被忽略。  dwLen,请先调用GetFileVersionInfoSize函数确定文件版本信息的字节数大小。dwLen必须等于或大于这个值。如果lpDate指向的缓冲区空间不够,函数将根据实际大小裁减出文件的版本信息。  lpData,指向一个用于保存函数调用后返回的文件版本信息的缓冲区。  如果函数调用成功,它将返回True;否则返回False。可通过GetLastError函数得到扩展的错误信息。  在调用GetFileVersionInfo函数前必须先调用GetFileVersionFileSize。为了从文件版本信息中获取有用信息,必须使用VerQueryValue函数。  1.2. GetFileVersionInfoSize函数  GetFileVersionInfoSize函数被用来判断操作系统是否能够从指定文件中获取版本信息。如果存在版本信息,便返回以字节为单位的这些信息所占用空间的大小。其Delphi函数声明如下:function GetFileVersionInfoSize(
lptstrFilename: PChar; // 文件名
var lpdwHandle: DWORD // set to zero
): DWORD; stdcall;   参数说明:  lptstrFilename,一个以NULL结束字符串,它指明期望从哪个文件中获取版本信息的文件名。  lpdwHandle,一个指向将被函数设置为0的变量的指针。  如果函数调用成功,它将返回文件版本信息的字节大小;否则返回0,可通过GetLastError函数得到扩展的错误信息。  在调用GetFileVersionInfo函数前应先调用GetFileVersionInfoSize函数。GetFileVersionInfoSize函数的返回值确定了GetFileVersionInfo函数所使用的版本信息缓冲区的大小。  1.3. VerLanguageName函数  VerLanguagename函数被用来获取与指定的二进制微软语言标示相关联的语言描述字符串。其Delphi函数声明如下:function VerLanguageName(
wLang: DWORD; // 微软语言标识符
szLang: PChar; // 语言描述缓冲区
nSize: DWORD // 缓冲区大小
): DWORD; stdcall;   参数说明:  wLang,语言标识符,是一个二进制数字。指定二进制语言标识符。如果向得到完整的语言标识符列表,请参见语言标识符部分的内容。举个例子,与语言标识符0x040A相关联的描述字符串就是“卡斯蒂利亚西班牙语”。如果是一个未知的标识符,那么szLang参数就会指向一个缺省字符串--“Language Neutral”。  szLang,这个参数指向一个缓冲区。这个缓冲区用于存储由wLang参数所确定的、用来描述语言的、以NULL结尾的字符串。  nSize 指定缓冲区的大小,单位是字符数量。  函数将返回存储在缓冲区中字符串的以字符为单位的大小。返回值不包含结束NULL字符。如果描述字符串小于或等于缓冲区的大小,那么整个描述字符串将保存在这个缓冲区中;否则,缓冲区中将之保留描述字符串的前面大小等于缓冲区大小的部分。  如果发生错误,返回值将等于0。未知的语言标识符不会产生错误。  通常,安装程序通过这个函数来翻译从VarQuery函数返回的语言标识符。当出现语言冲突的时候,这个得到的文本字符串便可以用在一个向用户询问怎样处理的对话框中,提示用户进行处理。  1.4. VerQueryValue函数  VerQueryValue函数被用来从指定的版本信息资源中获取指定版本信息。最常用的获取版本信息的逻辑流程是:先调用GetFileVersionInfoSize函数,紧接着再调用GetFileVersionInfo函数,最后再调用VerQueryValue函数。其Delphi函数声明如下:function VerQueryValue(
pBlock: Pointer; // 存放版本资源的缓冲区
lpSubBlock: PChar; // 期望获取的值
var lplpBuffer: Pointer; // 指向存放版本值缓冲区的指针
var puLen: UINT // 版本信息长度
): BOOL; stdcall;    参数说明:  pBlock,一个指向用于存储版本信息资源的缓冲区的指针,这个版本信息资源是从GetFileVersionInfo函数返回的。  lpSubBlock,指向一个零结尾的字符串,指定到底获得哪个版本信息值。这个字符串必须由被反斜线符号(\)分开的名字组成如下格式之一:  →“\”,指定根区域。函数将返回一个指向VS_FIXEDFIELDFILEINFO结构的版本信息资源。  →“\VarFileInfo\Translation”,指定一个保存在可变类型变量信息的结构中的转换阵列。函数返回一个指向语言和代码页标识符数组的指针。应用程序可以使用这些标识符来访问存储在版本信息资源中的特定语言字符串表结构。  →“\StringFileInfo\lang-codepage\string-name”,指定存储在特定语言字符串表中结构的值。其中,lang-codepage的书写格式是:用双字(DWORD)表示的、保存在资源中的转换阵列的语言与代码页标识符对,并且需要书写成十六进制形式的字符串;string-name必须是在后面注释中预定义的字符串之一。函数根据指定的语言与代码页,返回一个与之相关的字符串。  lplpBuffer,一个指向用于保存指向被请求的版本信息缓冲区的变量的指针。简单的说,就是一个指向指针的指针。  puLen,指向一个保存版本信息长度的缓冲区。  如果指定的版本信息结构存在并且有效,函数将返回一个非0值。如果长度缓冲区的地址等于0,指定的版本信息名称将无效。  并且,在指定的名称不存在或指定的资源无效时,函数的返回值将等于0。  以下列表是预定义的版本信息统一字符编码标准字符串:Comments、InternalName、ProductName、CompanyName、LegalCopyright、ProductVersion、FileDescription、LegalTrades、PrivateBuild、FileVersion、OriginalFilename、SpecialBuild

解决方案 »

  1.   

    二、一个关于版本信息函数使用的实例  下面开始,我们给出一个实例。通过这个实例,大家可以学习到在我们自己编写的应用程序中怎么使用这些函数来达到获取文件版本信息的目的。  2.1. 建立实例窗体样板  首先,我们需要建立两个Delphi窗体,它们分别是程序主窗体和关于对话框窗体。用于这个程序只作演示用途,所以没有必要在主窗体中实现过多的功能。事实上,在我们这个实例中,主窗体上只有“关于”和“退出”两个菜单项。  稍微复杂一点的要算是关于对话框窗体了。在这个窗体中,将包含“产品名称”、“产品版本号”、“文件说明”、“产品合法商标”、“运行文件版本号”、“公司名称”、“合法商标”、“产品内部名称”、“原文件名”等几个重要且基本的版本信息。当然,正如你所看到的,在这个窗体中还包含了操作系统信息版本和系统内存信息。但由于篇幅的关系,两条信息的获取方法在本文中不会加以说明。我们会在下一次的学习中讨论这两个话题。关于对话框的布局如下图,大家可以对比这个图片自己用Delphi制作一个对话框处出来。  2.2. 填写应用程序的运行文件版本信息  在Delphi的集成编辑器中按下Shift+Ctrl+F11键,在弹出的对话框中按照下表的内容,在相应键值的未知输入内容:
     2.3. 实现获取版本信息功能的代码  下面的代码是这个关于对话框创建时的事件响应代码片断。大家把它们拷贝+粘贴到窗体的OnCreate事件中就可以了。
    const
    InfoNum = 9;
    InfoStr: array[1..InfoNum] of string = (
    'ProductName',
    'ProductVersion',
    'FileDescription',
    'LegalCopyright',
    'FileVersion',
    'CompanyName',
    'LegalTradeMarks',
    'InternalName',
    'OriginalFileName'
    );
    var
    S: string;
    BufSize, Len: DWORD;
    Buf: PChar;
    Value: PChar;
    begin
    S := Application.ExeName;
    BufSize := GetFileVersionInfoSize(PChar(S), BufSize);
    if BufSize > 0 then beginBuf := AllocMem(BufSize);
    GetFileVersionInfo(PChar(S), 0, BufSize, Buf);
    if VerQueryValue(Buf, PChar('StringFileInfo\080403A8\' + InfoStr[1]), Pointer(Value), Len) then
    ProductName.Caption := Value;
    if VerQueryValue(Buf, PChar('StringFileInfo\080403A8\' + InfoStr[2]), Pointer(Value), Len) then
    ProductVersion.Caption := '产品版本: ' + Value;
    if VerQueryValue(Buf, PChar('StringFileInfo\080403A8\' + InfoStr[3]), Pointer(Value), Len) then
    FileDescription.Caption := '文件说明: ' + Value;
    if VerQueryValue(Buf, PChar('StringFileInfo\080403A8\' + InfoStr[4]), Pointer(Value), Len) then
    LegalCopyright.Caption := '合法版权: ' + Value;
    if VerQueryValue(Buf, PChar('StringFileInfo\080403A8\' + InfoStr[5]), Pointer(Value), Len) then
    FileVersion.Caption := '文件版本: ' + Value;
    if VerQueryValue(Buf, PChar('StringFileInfo\080403A8\' + InfoStr[6]), Pointer(Value), Len) then
    CompanyName.Caption := '公司名称: ' + Value;
    if VerQueryValue(Buf, PChar('StringFileInfo\080403A8\' + InfoStr[7]), Pointer(Value), Len) then
    LegalTrades.Caption := '合法商标: ' + Value;
    if VerQueryValue(Buf, PChar('StringFileInfo\080403A8\' + InfoStr[8]), Pointer(Value), Len) then
    InternalName.Caption := '内部名称: ' + Value;
    if VerQueryValue(Buf, PChar('StringFileInfo\080403A8\' + InfoStr[9]), Pointer(Value), Len) then
    OriginalFilename.Caption := '原文件名: ' + Value;
    FreeMem(Buf, BufSize);// OperatingSystem.Caption := GetOSVerInfo;// SystemMemory.Caption := GetMemStat;
    end
    else begin
    Application.MessageBox('获取产品信息时遇到致命错误,请尝试重新启动软件。'+ #13 + '若仍未能解决问题,请联系产品服务人员。','错误',MB_OK + MB_ICONSTOP);
    Application.Terminate;
    end;   在这段代码中,首先通过调用GetFileVersionInfoSize()函数获得在内存中存储版本信息资源所需要的最小内存空间大小。如果函数的返回值小于等于0,则函数调用失败,继而程序转入错误处理部分。在这部分的错误处理代码中,程序会提示用户程序运行实现错误,并要求用户重新启动软件。  如果函数调用成功,程序将通过AllocMem()函数向操作系统申请一块内容空间,用于保存从文件中获得的版本信息资源。  此后,利用GetFileVersionInfo()函数从文件中得到版本信息。但这些信息对我们来说是不可读的,因为它们都是一些二进制的编码。在随后的程序中,通过一连串对VerQueryValue()函数的调用,我们得到了文件的版本信息,并把它们显示在这个关于对话框窗体中正确的位置上。  在使用完这些信息后,我们一定要记住用FreeMem()函数释放前面用AllocMem()函数分配的内存,否则就会出现内存泄漏。
    // OperatingSystem.Caption := GetOSVerInfo;   和// SystemMemory.Caption := GetMemStat;   这两行代码调用的两个函数是我自己编制的,分别用于获取操作系统版本信息和内存信息。前面已经说过,由于篇幅的关系,我们在这里暂时跳过这两行代码,不去管它。反正程序已经可以正常的运行了。  三、总结  通过本文中对文件版本信息处理方法的简单介绍,相信读者已经对这种技术有了一个初步的认识。这种实用且非常重要的技术是不是很简单呢?答案当然是肯定的!正如上面所讲述的内容,其实就是对那六个函数的交替调用。在这里唯一要提醒读者的是,在使用这些函数的时候,一定要注意调用次序,不能搞错,否则很有可能怎么也不能正常访问这些函数(函数返回值始终是调用失败)。
      

  2.   

    试了一下,还不错。
    但怎样获取这个值呢 080403A8 ,不同的语言这个值不想同?
    我打开某个程序的资源文件其Translation值是这个,怎样读取它呢。
        BLOCK "VarFileInfo"
        BEGIN
            VALUE "Translation", 0x804, 0x4b0
        END
      

  3.   

    Getversioninfo不是万能的,比如用着函数读取windows7版本,结果会是6.1,6.1是微软内部命名版本……所以很多时候很尴尬
      

  4.   

    尴尬
    我现在的问题是用DELPHI编程用这个Getversioninfo能读取DELPHI所有编写的EXE/DLL文件
    的版本信息(必须EXE/DLL还有版本信息),但是再读取C#编写的EXE/DLL就没有反应了