5

如何获取正在运行的应用程序的版本?


我一直在使用GetFileVersionInfo(ParamStr(0), ...)

filename := PChar(ExtractShortPathName(ParamStr(0)));

//Get the number of bytes he have to allocate for the file information structure
dwInfoLength := GetFileVersionInfoSize(lptstrFilename, {var}dwHandle);

//Get version info
GetMem(pInfoData, dwInfoLength);
GetFileVersionInfo(lptstrFilename, dwHandle, dwInfoLength, pInfoData);

//Set what information we want to extract from pInfoData
lpSubBlock := PChar(Chr(92)+Chr(0));

//Extract the desired data from pInfoData into the FileInformation structure
VerQueryValue(pInfoData, lpSubBlock, PFileInformation, LengthOfReturned);

这种技术的问题在于它需要 Windows 加载程序在读取资源之前加载图像IMAGE_FILE_NET_RUN_FROM_SWAP我使用图像标志构建我的应用程序(以避免在繁琐的网络上出现页面内异常)。

这会导致 Windows 加载程序再次通过网络加载整个图像,而不仅仅是查看“我”。由于我在启动时检查并保存了我自己的版本,因此 6 秒的应用程序启动变成了 10 秒的应用程序启动。

我如何阅读我的版本,我正在运行的应用程序?


我会假设 Windows 没有 API 来读取正在运行的进程的版本,只有我从中加载的文件(如果文件不再存在,则它无法读取任何版本信息)。

但我也假设有可能从我的进程自己的内存中读取版本资源(当然不是管理员或调试器组的成员)。

我可以阅读的流程版本吗?


相关奖励问题:如何从这里而不是通过网络加载PE Image资源?

4

4 回答 4

10

在 Stackoverflow 上找到它:

如何确定 Delphi 应用程序版本

我已经知道如何确定应用程序版本,但@StijnSanders 提出了“更好”的方法,这正是我遇到的原因:

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

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

我适应了:

function GetModuleVersion(Instance: THandle; out iMajor, iMinor, iRelease, iBuild: Integer): Boolean;
var
    fileInformation: PVSFIXEDFILEINFO;
    verlen: Cardinal;
    rs: TResourceStream;
    m: TMemoryStream;
    resource: HRSRC;
begin
    //You said zero, but you mean "us"
    if Instance = 0 then
        Instance := HInstance;

    //UPDATE: Workaround bug in Delphi if resource doesn't exist    
    resource := FindResource(Instance, 1, RT_VERSION);
    if resource = 0 then
    begin
       iMajor := 0;
       iMinor := 0;
       iRelease := 0;
       iBuild := 0;
       Result := False;
       Exit;
    end;

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

        m.Position:=0;
        if not VerQueryValue(m.Memory, '\', (*var*)Pointer(fileInformation), (*var*)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;

警告:由于 Delphi 中的错误,上述代码有时会崩溃:

rs := TResourceStream.CreateFromID(Instance, 1, RT_VERSION);

如果没有版本信息,Delphi 会尝试引发异常:

procedure TResourceStream.Initialize(Instance: THandle; Name, ResType: PChar);
   procedure Error;
   begin
      raise EResNotFound.CreateFmt(SResNotFound, [Name]);
   end;
begin
   HResInfo := FindResource(Instance, Name, ResType);
   if HResInfo = 0 then Error;
   ...
end;

当然,错误在于PChar它并不总是指向 ansi char的指针。对于未命名的资源,它们是整数常量,转换为PChar. 在这种情况下:

Name: PChar = PChar(1);

当 Delphi 尝试构建异常字符串并取消引用0x00000001它触发和访问冲突的指针时。

解决方法是先手动调用FindResource(Instance, 1, RT_VERSION)

var
    ...
    resource: HRSRC;
begin
    ...
    resource := FindResource(Instance, 1, RT_VERSION);
    if (resource = 0) 
    begin
       iMajor := 0;
       iMinor := 0;
       iRelease := 0;
       iBuild := 0;
       Result := False;
       Exit;
    end;

    m := TMemoryStream.Create;
    ...

注意:任何代码都会发布到公共领域。无需归属。

于 2012-06-01T19:08:03.197 回答
8

您可能想尝试FindResource/ LockResourceAPI 以访问VERSIONINFO正在运行的模块的资源。MSDN 文章在示例部分链接了一个示例,还有一个带有C++ 示例代码的社区评论正是这样做的。这从已经加载的模块开始,而不是从文件名开始(据说是单独加载的,带有指示“仅加载资源”的标志,因此可能忽略图像已经映射到进程中的事实)。

请注意 - 根据提供的代码片段 - 您可以找到模块的资源,然后重用标准VerQueryValueAPI 以从那里继续资源解析。

于 2012-06-01T18:03:44.113 回答
2

我建议您阅读 JCL 中的代码 JclFileUtils.pas。“TJclFileVersionInfo”类有一些重载的构造函数,允许从文件和模块句柄获取版本信息。

您可以使用 JCL 类,或者至少阅读它以详细了解它是如何工作的。

于 2012-06-01T19:00:55.177 回答
0

Delphi 10.3.3 提供了一种合理的方法来从运行的 exe 中读取 ProductVersion。在项目 exe 的 [Options] [ProductVersion] 中设置此值。

将变量设置为:

var
  major, minor, build: Cardinal;
  Version: String;    

设置代码为:

// [GetProductVersion] uses 1st 3 values separated by decimal (.)
// as: major, minor, build. 4th optional parameter is ignored.
//
// Gui may display -- Major, Minor, Release, Build
// but reports as -- Major, Minor, Build

if GetProductVersion(Application.ExeName, major, minor, build) then
  Version := 'v' + major.ToString() + '.' + minor.ToString()
    + ' Beta Build ' + build.ToString();
于 2020-01-10T16:37:26.997 回答