如何确定Delphi应用程序的版本

40
想要获取Delphi应用程序的构建编号并将其发布到标题栏中。

4
我看到大多数提出的答案都使用了GetFileVersion。这个选项有些问题,我在自己的回答中发表了详细内容。 - Stijn Sanders
7个回答

33

这是我的做法。我在几乎所有小型实用程序中都会使用:

procedure GetBuildInfo(var V1, V2, V3, V4: word);
var
  VerInfoSize, VerValueSize, Dummy: DWORD;
  VerInfo: Pointer;
  VerValue: PVSFixedFileInfo;
begin
  VerInfoSize := GetFileVersionInfoSize(PChar(ParamStr(0)), Dummy);
  if VerInfoSize > 0 then
  begin
      GetMem(VerInfo, VerInfoSize);
      try
        if GetFileVersionInfo(PChar(ParamStr(0)), 0, VerInfoSize, VerInfo) then
        begin
          VerQueryValue(VerInfo, '\', Pointer(VerValue), VerValueSize);
          with VerValue^ do
          begin
            V1 := dwFileVersionMS shr 16;
            V2 := dwFileVersionMS and $FFFF;
            V3 := dwFileVersionLS shr 16;
            V4 := dwFileVersionLS and $FFFF;
          end;
        end;
      finally
        FreeMem(VerInfo, VerInfoSize);
      end;
  end;
end;

function GetBuildInfoAsString: string;
var
  V1, V2, V3, V4: word;
begin
  GetBuildInfo(V1, V2, V3, V4);
  Result := IntToStr(V1) + '.' + IntToStr(V2) + '.' +
    IntToStr(V3) + '.' + IntToStr(V4);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Form1.Caption := Form1.Caption + ' - v' + GetBuildInfoAsString;
end;

1
Mick,你应该像Joseph一样检查返回的VerInfoSize,仅在其大于0时才继续进行,因为它可能没有文件版本信息。 - Francesca
1
对于GetMem/FreeMem,使用try...finally也不错。 - Francesca
我刚刚添加了 try...finally。 - Mick
Mick,我发现您的代码无法编译,因为片段中没有对sFileName变量进行声明。 - Konstantin

22
感谢上面的帖子,我为此目的制作了自己的库。
我相信这比这里的所有其他解决方案都更正确,所以我分享它——请随意重复使用...
unit KkVersion;

interface

function FileDescription: String;
function LegalCopyright: String;
function DateOfRelease: String; // Proprietary
function ProductVersion: String;
function FileVersion: String;

implementation

uses
  Winapi.Windows, System.SysUtils, System.Classes, Math;

(*
  function GetHeader(out AHdr: TVSFixedFileInfo): Boolean;

  var
  BFixedFileInfo: PVSFixedFileInfo;
  RM: TMemoryStream;
  RS: TResourceStream;
  BL: Cardinal;

  begin
  Result := False;
  RM := TMemoryStream.Create;
  try
  RS := TResourceStream.CreateFromID(HInstance, 1, RT_VERSION);
  try
  RM.CopyFrom(RS, RS.Size);
  finally
  FreeAndNil(RS);
  end;

  // Extract header
  if not VerQueryValue(RM.Memory, '\\', Pointer(BFixedFileInfo), BL) then
  Exit;

  // Prepare result
  CopyMemory(@AHdr, BFixedFileInfo, Math.Min(sizeof(AHdr), BL));
  Result := True;
  finally
  FreeAndNil(RM);
  end;
  end;
*)

function GetVersionInfo(AIdent: String): String;

type
  TLang = packed record
    Lng, Page: WORD;
  end;

  TLangs = array [0 .. 10000] of TLang;

  PLangs = ^TLangs;

var
  BLngs: PLangs;
  BLngsCnt: Cardinal;
  BLangId: String;
  RM: TMemoryStream;
  RS: TResourceStream;
  BP: PChar;
  BL: Cardinal;
  BId: String;

begin
  // Assume error
  Result := '';

  RM := TMemoryStream.Create;
  try
    // Load the version resource into memory
    RS := TResourceStream.CreateFromID(HInstance, 1, RT_VERSION);
    try
      RM.CopyFrom(RS, RS.Size);
    finally
      FreeAndNil(RS);
    end;

    // Extract the translations list
    if not VerQueryValue(RM.Memory, '\\VarFileInfo\\Translation', Pointer(BLngs), BL) then
      Exit; // Failed to parse the translations table
    BLngsCnt := BL div sizeof(TLang);
    if BLngsCnt <= 0 then
      Exit; // No translations available

    // Use the first translation from the table (in most cases will be OK)
    with BLngs[0] do
      BLangId := IntToHex(Lng, 4) + IntToHex(Page, 4);

    // Extract field by parameter
    BId := '\\StringFileInfo\\' + BLangId + '\\' + AIdent;
    if not VerQueryValue(RM.Memory, PChar(BId), Pointer(BP), BL) then
      Exit; // No such field

    // Prepare result
    Result := BP;
  finally
    FreeAndNil(RM);
  end;
end;

function FileDescription: String;
begin
  Result := GetVersionInfo('FileDescription');
end;

function LegalCopyright: String;
begin
  Result := GetVersionInfo('LegalCopyright');
end;

function DateOfRelease: String;
begin
  Result := GetVersionInfo('DateOfRelease');
end;

function ProductVersion: String;
begin
  Result := GetVersionInfo('ProductVersion');
end;

function FileVersion: String;
begin
  Result := GetVersionInfo('FileVersion');
end;

end.

3
谢谢您 - 我已为您点赞,因为您没有陷入假设语言环境为$0409(美国英语)的陷阱。在我的Delphi XE7上,并不是这个值,我相信对大多数Delphi开发人员也是如此。 - Reversed Engineer
@DaveBoltman 是的,我的 Delphi 项目中始终设置为南非英语。 - Shaun Roselt
1
@ShaunRoselt - “南非英语” - 是啊,我的也是!!来自克鲁格斯多普的问候。 - Reversed Engineer

22

我强烈建议不要在想要知道当前正在运行的可执行文件版本时使用GetFileVersion!我有两个很好的理由:

  1. 可执行文件可能无法访问(断开连接的驱动器/共享),或已更改(.exe重命名为.bak,并替换为新的.exe,而运行的进程没有停止)。
  2. 你尝试读取的版本数据实际上已经加载到内存中,并且可以通过加载此资源来获取,这总是比执行额外(相对较慢)的磁盘操作更好。

在Delphi中加载版本资源,我使用类似于以下的代码:

uses Windows,Classes,SysUtils;
var
  verblock:PVSFIXEDFILEINFO;
  versionMS,versionLS:cardinal;
  verlen:cardinal;
  rs:TResourceStream;
  m:TMemoryStream;
  p:pointer;
  s:cardinal;
begin
  m:=TMemoryStream.Create;
  try
    rs:=TResourceStream.CreateFromID(HInstance,1,RT_VERSION);
    try
      m.CopyFrom(rs,rs.Size);
    finally
      rs.Free;
    end;
    m.Position:=0;
    if VerQueryValue(m.Memory,'\',pointer(verblock),verlen) then
      begin
        VersionMS:=verblock.dwFileVersionMS;
        VersionLS:=verblock.dwFileVersionLS;
        AppVersionString:=Application.Title+' '+
          IntToStr(versionMS shr 16)+'.'+
          IntToStr(versionMS and $FFFF)+'.'+
          IntToStr(VersionLS shr 16)+'.'+
          IntToStr(VersionLS and $FFFF);
      end;
    if VerQueryValue(m.Memory,PChar('\\StringFileInfo\\'+
      IntToHex(GetThreadLocale,4)+IntToHex(GetACP,4)+'\\FileDescription'),p,s) or
        VerQueryValue(m.Memory,'\\StringFileInfo\\040904E4\\FileDescription',p,s) then //en-us
          AppVersionString:=PChar(p)+' '+AppVersionString;
  finally
    m.Free;
  end;
end;

1
我已经添加了try-finally,应该可以解决问题。如果你想知道TMemoryStream的作用:VerQueryValue在读取rs.Memory目录时出了问题... - Stijn Sanders
2
重命名正在运行的可执行文件并没有什么不寻常的,这是Windows无法覆盖它的已接受解决方法。更新程序通常会将正在运行的可执行文件重命名,以便能够将新版本复制到原始位置。 - mghie
2
Delphi中有一个错误在代码中被暴露,你所请求的资源的名称是“PChar(1)”。如果未找到该资源(即没有版本信息),Delphi将尝试抛出EResNotFound找不到资源%s)。当它尝试使用PChar 0x00000001构建字符串时,它将触发访问冲突,因为在地址$00000001处没有ansi字符可读。 - Ian Boyd
1
除非语言环境为$0409(美式英语),否则该方法也无法工作——在Delphi开发人员的全球人口中并不是一种可靠的假设——不过,答案还是挺好的。 - Reversed Engineer
@DaveBoltman,Delphi项目属性对话框有一个区域选择下拉菜单,用于添加版本数据。确实需要在那里选择“英语(美国)”,或者在上面的代码中使用所选的区域ID。 - Stijn Sanders
显示剩余4条评论

13

将你的EXE文件的完整文件名传递给此函数,它将返回一个字符串,例如:2.1.5.9,或者任何你的版本号。

function GetFileVersion(exeName : string): string;
const
  c_StringInfo = 'StringFileInfo\040904E4\FileVersion';
var
  n, Len : cardinal;
  Buf, Value : PChar;
begin
  Result := '';
  n := GetFileVersionInfoSize(PChar(exeName),n);
  if n > 0 then begin
    Buf := AllocMem(n);
    try
      GetFileVersionInfo(PChar(exeName),0,n,Buf);
      if VerQueryValue(Buf,PChar(c_StringInfo),Pointer(Value),Len) then begin
        Result := Trim(Value);
      end;
    finally
      FreeMem(Buf,n);
    end;
  end;
end;

定义了这个之后,您可以按照以下方式使用它来设置表单的标题:

procedure TForm1.FormShow(Sender: TObject);
begin 
  //ParamStr(0) is the full path and file name of the current application
  Form1.Caption := Form1.Caption + ' version ' + GetFileVersion(ParamStr(0));
end;

2
Joseph,你应该用try...finally来保护你的AllocMem。 - Francesca
@Wodzu:在我的D2007中可以工作。你的项目在Project->Options->Version Info下是否勾选了“在项目中包含版本信息”选项?Windows资源管理器显示的文件版本是什么? - JosephStyons
2
这个不起作用,除非你的区域设置是$0409(美国英语)- 这不是一个可靠的假设。 - Reversed Engineer
与 Delphi 10.1(Berlin)非常良好地配合工作。 :) - Paulo França Lacerda
@DaveBoltman,你说得对,感谢指出。用户Jiri Krivanek在下面提供了另一个版本,声称可以避免区域设置问题。我没有尝试过,但它可能更适合你。或者,你可以将我的代码中的“040904E4”更改为你需要的区域设置。 - JosephStyons

2
我们针对所有应用程序都进行这样的操作,但我们使用了一个Raize组件RzVersioninfo。只需要使用以下代码即可。
在表单创建时:
Caption := RzVersioninfo1.filedescripion + ': ' + RzVersionInfo1.FileVersion;
显然,如果您不想使用来自Raize的其他组件,请使用上述选项之一,因为Raize组件是需要花费的。

1
我喜欢这种方法,因为它非常简单和简短。 - Shaun Roselt

0

我的代码:

uses unit Winapi.Windows;

function GetModuleVersion(Instance: THandle; out iMajor, iMinor, iRelease, iBuild: Integer): Boolean;
var
    fileInformation: PVSFIXEDFILEINFO;
    verlen: Cardinal;
    rs: TResourceStream;
    m: TMemoryStream;
begin

   result := false;

    m := TMemoryStream.Create;
    try
        try
          rs := TResourceStream.CreateFromID(Instance, 1, RT_VERSION);
          try
              m.CopyFrom(rs, rs.Size);
          finally
              rs.Free;
          end;
        except
          exit;
        end;

        m.Position:=0;
        if not VerQueryValue(m.Memory, '\', Pointer(fileInformation), verlen) then
        begin
          iMajor := 0;
          iMinor := 0;
          iRelease := 0;
          iBuild := 0;
          Exit;
        end;

        iMajor := fileInformation.dwFileVersionMS shr 16;
        iMinor := fileInformation.dwFileVersionMS and $FFFF;
        iRelease := fileInformation.dwFileVersionLS shr 16;
        iBuild := fileInformation.dwFileVersionLS and $FFFF;
    finally
        m.Free;
    end;

    Result := True;
end;

使用方法:

  if GetModuleVersion(HInstance, iMajor, iMinor, iRelease, iBuild) then
    ProgramVersion := inttostr(iMajor)+'.'+inttostr(iMinor)+'.'+inttostr(iRelease)+'.'+inttostr(iBuild);

0

来自http://www.martinstoeckli.ch/delphi/delphi.html#AppVersion

使用此函数,您可以获取包含版本资源的文件的版本。这样,您就可以在信息对话框中显示应用程序的版本号。要将版本资源包含到Delphi应用程序中,请在项目选项中设置“Versioninfo”。


10
好的链接,但是回答很差。请总结链接的内容:我们应该通过跟随链接找到什么样的解决方案,以及需要注意哪些重要功能? - Rob Kennedy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接