6

How can i get the version of my running application?


i have been using 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);

The problem with this technique is that it requires the Windows loader to load the image before the resources can be read. i build my applications with the IMAGE_FILE_NET_RUN_FROM_SWAP image flag (in order to avoid in-page exceptions on a fiddly network).

This causes the Windows loader to load the entire image across the network again, rather than just looking at "me". Since i check, and save, my own version at startup, a 6 second application startup turns into a 10 second application startup.

How can i read the version of me, my running application?


i would assume Windows has no API to read the version of a running process, only the file that i loaded from (and if the file no longer exists, then it cannot read any version info).

But i also assume that it might be possible to read version resources out of my processes own memory (without being a member of the Administrators or Debuggers group of course).

Can i read the version of my process?


Associated Bonus Question: How can i load PE Image resources from me rather than across the network?

Community
  • 1
  • 1
Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219

4 Answers4

11

Found it, right here on Stackoverflow:

How to determine Delphi Application Version

i already knew how to determine an application version, but @StijnSanders suggested the "better" way, for exactly the reasons i was hitting:

I most strongly recommend not to use GetFileVersion when you want to know the version of the executable that is currently running! I have two pretty good reasons to do this:

  • The executable may be unaccessible (disconnected drive/share), or changed (.exe renamed to .bak and replaced by a new .exe without the running process being stopped).
  • The version data you're trying to read has actually already been loaded into memory, and is available to you by loading this resource, which is always better than to perform extra (relatively slow) disk operations.

Which i adapted into:

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;

Warning: The above code crashes sometimes due to a bug in Delphi:

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

If there is no version information, Delphi tries to raise an exception:

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;

The bug, of course, is that PChar is not always a pointer to an ansi char. With non-named resources they are integer constants, cast to a PChar. In this case:

Name: PChar = PChar(1);

When Delphi tries to build the exception string, and dereferences the pointer 0x00000001 it triggers and access violation.

The fix is to manually call FindResource(Instance, 1, RT_VERSION) first:

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;
    ...

Note: Any code is released into the public domain. No attribution required.

Daniel Marschall
  • 3,739
  • 2
  • 28
  • 67
Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
  • 1
    Indeed this is the same as I thought of: looking up the `VERSIONINFO` (`RT_VERSION` type) resource, then feeding it to the `VerQueryValue` API. And lucky you it is already Delphi code snippet as opposed to C++ on MSDN. – Roman R. Jun 01 '12 at 19:14
  • 1
    So doesn't this make your question an exact duplicate? – Warren P Jun 01 '12 at 19:39
  • @WarrenP The answers are the same; even when the questions are different. This question deals with "my own" process, which is not necessarily what someone wanting the version of a Delphi application might want. And who knows, Microsoft may add an API to get the version of "me". – Ian Boyd Jun 01 '12 at 20:30
  • So your two word difference in question (my own) makes no difference in the answer (no surprise), ergo, duplicate. I could add the words blue, purple, and green to the question, and they would be equally have no effect on the answer, thus, they are the same questions. – Warren P Jun 01 '12 at 21:22
  • @Warren, read the 2 questions... The fact that 1 of the possible answers to the other question is THE answer to THIS question does not mean the questions are the same. This one is much more specific and there is no guarantee that a simple search for THIS problem would fetch the other question, not to mention that none of the other answers are applicable here. – Francesca Jun 01 '12 at 22:02
  • RE: Duplicate or not... Keep in mind that all SO users do not necessarily master the English language enough to find the best keywords when doing a search. So if the answers could be the same but the questions are slightly different (at least in their phrasing), I don't mind keeping pseudo-duplicates. – Francesca Jun 01 '12 at 22:08
  • Different questions very well may have the same answer. But my question is not [*how to get the version of a file*](http://stackoverflow.com/questions/940707/how-do-i-programatically-get-the-version-of-a-dll-or-exe). In my case it's likely there *is* no file. And i think this is an important enough distinction that the question should remain simply as a pointer on how it can be done. It is the limitation of a system that allows multiple answers, but not multiple questions. – Ian Boyd Jun 03 '12 at 04:32
  • @Warren: I agree with Ian.The two questions are very different - although the answer is the same. They are not duplicate. – Fabricio Araujo Jun 06 '12 at 17:52
  • There is still two bugs in the "updated" answer: 1) A cast to LPCWSTR is missing in FindResource for the second argument 2) There is no result value assigned when FindResource returns 0. Add a Result := FALSE; just before Exit. The comment of Remy Lebeau is perfectly correct: Stream m can be completely removed. – fpiette Aug 19 '15 at 08:36
  • @fpiette The code already does contain `if (resource=0) then Result := False;`. – Ian Boyd Aug 20 '15 at 02:01
8

You might want to try FindResource/LockResource API in order to access VERSIONINFO resource of your running module. MSDN article links an example in Examples section, and there is also a community comment with C++ sample code which does exactly this. This starts from already loaded module, not from file name (which is supposedly loaded separately with flag indicating "load resources only", and thus possibly ignoring the fact that image is already mapped into process).

Note that - per provided code snippet - you can find resource of your module and then reuse standard VerQueryValue API to continue resource parsing from there.

Roman R.
  • 68,205
  • 6
  • 94
  • 158
  • 1
    the example seems to have been incorporated, in the section Examples, you'll find *"For an example, see [Updating Resources](https://msdn.microsoft.com/en-us/library/windows/desktop/ms648008(v=vs.85).aspx#_win32_Updating_Resources)."* – Wolf Oct 10 '17 at 09:50
2

I suggest you to read the code JclFileUtils.pas, from JCL. The "TJclFileVersionInfo" class has some overloaded constructors that allows work from file and from the module handle to get version information.

You can use the JCL class, or at least read it for check how it works in details.

jfoliveira
  • 2,138
  • 14
  • 25
  • That doesn't help at all: both constructors (`Window: HWND` and `Module: HMODULE` first retrieve the file name from this handle and then call the `FileName: string` constructor. – maf-soft Nov 05 '18 at 08:35
1

Delphi 10.3.3 provides a reasonable method to read ProductVersion from running exe. Set this value in [Options] [ProductVersion] of project exe.

set variables as:

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

set code as:

// [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();
Joseph Poirier
  • 386
  • 2
  • 17
  • I'm guessing there's no way to just grab this as a variable from protect options > Application > Version Info > version name ??? – Greg T Mar 25 '23 at 15:17