[Solved] Exception in TFileVersionInfo.ReadFileInfo lazarus / 64-FPC development ---------------------------------------------------------------------- OLLI_S · 2018-07-11 19:45 Hello, I get some exceptions when I read the file info from some applications and the method I am using is provided by Lazarus. I use Lazarus 1.8.4 in Windows 10 Pro German. My programming skills I would estimate between beginner and intermediate. I programmed a little tool that reads all information (like copyright, version info, etc) from applications. I used the following code example: http://wiki.freepascal.org/Show_App lication_Title,_Version,_and_Company I created a new project in Lazarus, added a button and for the Button1Click event I pasted some code from the WIKI article above. Here is the complete source of my unit: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i unit Unit1; {$mode objfpc}{$H+} interface uses  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,   fileinfo,  winpeimagereader; {need this for reading exe info} type   { TForm1 }   TForm1 = class(TForm)    Button1: TButton;    procedure Button1Click(Sender: TObject);  private   public   end; var  Form1: TForm1; implementation {$R *.lfm} { TForm1 } procedure TForm1.Button1Click(Sender: TObject); var  FileVerInfo: TFileVersionInfo; begin   FileVerInfo := TFileVersionInfo.Create(nil);   try     FileVerInfo.FileName := 'C:\lazarus\lazarus.exe';    FileVerInfo.ReadFileInfo;    ShowMessage(FileVerInfo.VersionStrings.Text);   finally    FileVerInfo.Free;  end; end; end. This works perfectly for most applications. But some applications raise exceptions at the line FileVerInfo.ReadFileInfo; (line 47). Examples: C:\Program Files\FreeCommander XE\FreeCommander.exe Exception Message: Cannot find resource: Type = 1, Name = 17, Lang ID = 0800) C:\Program Files (x86)\System Explorer\SystemExplorer.exe Exception Message: Cannot find resource: Type = 1, Name = 12, Lang ID = 0000) D:\PortableApps\PortableApps\FirefoxPortable\App\Firefox\firefox.exe Exception Message: Duplicate key '0' D:\PortableApps\PortableApps\_ToDoList\ToDoList.exe Exception Message: Duplicate key 'oList' As I wrote above I am not a good programmer. But in FileVerInfo.ReadFileInfo I see no exception handling (the block "except"). Could that be the problem? Is this a problem that can be fixed from your side (Lazarus Developers) or do I have to do anything? I also can not set a breakpoint in FileVerInfo.ReadFileInfo to see what line causes the problems (the debugger does not stop, no matter where I set the breakpoint). If you have any tips how I can make the breakpoint working, this would be fantastic. I have in Lazarus the two additional packages anchordockingdsgn and sparta_DockedFormEditor. I hope that this is not the reason for my problems (but I think not). Thank you for your help! Best regards OLLI ---------------------------------------------------------------------- ASerge · 2018-07-12 04:34 Quote from: OLLI_S on July 11, 2018, 09:45:08 pmI get some exceptions when I read the file info from some applications and the method I am using is provided by Lazarus. Please, give a link to the "some applications" exe file (without setup), for testing. ---------------------------------------------------------------------- OLLI_S · 2018-07-12 09:04 Hello ASerge, Quote from: ASerge on July 12, 2018, 06:34:19 amPlease, give a link to the "some applications" exe file (without setup), for testing. I posted the names of some of these applications (Firefox, System Explorer, etc) in my first posting. I suppose you have Linux because you asked for the EXE without installers? So I will upload the EXE files to OneDrive tonight and send the links. OLLI ---------------------------------------------------------------------- ASerge · 2018-07-12 14:29 Quote from: OLLI_S on July 12, 2018, 11:04:18 amI posted the names of some of these applications (Firefox, System Explorer, etc) in my first posting. Oops, missed FireFox. Yeah, it looks like a bug. If you need only under Windows, here's a quick made module (attached), it seems to work. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i uses VerInfo; procedure TForm1.Button1Click(Sender: TObject);var  VerInfo: TVersionInfo;   procedure AppendInfo(const Value: string);  begin    Memo1.Append(Value + '=' + VerInfo[Value]);  end; var  LV: TLongVersion;begin  VerInfo := TVersionInfo.Create('c:\Program Files (x86)\Mozilla Firefox\firefox.exe');  try    Memo1.Clear;    AppendInfo(CviFileVersion);    AppendInfo(CviFileDescription);    AppendInfo(CviLegalCopyright);    AppendInfo(CviOriginalFilename);    AppendInfo(CviProductName);    AppendInfo('BuildID');    LV := VerInfo.FileLongVersion;    Memo1.Append(Format('FileLongVersion=%d.%d.%d.%d', [LV.Words[3], LV.Words[2], LV.Words[1], LV.Words[0]]));  finally    VerInfo.Free;  end;end; ---------------------------------------------------------------------- OLLI_S · 2018-07-12 15:47 Hello, I copied your VerInfo.pp into the folder of my project and use it like you did in your source code. It is working, all apps are now shown without any exceptions! Fantastic work! Is this a fix that will be included in Lazarus in the future? So I don't need your code in my project folder? Thank you again and best regards OLLI ---------------------------------------------------------------------- ASerge · 2018-07-12 16:02 Quote from: OLLI_S on July 12, 2018, 05:47:33 pmIs this a fix that will be included in Lazarus in the future? Given the cross-platform nature of the FPC, it is very doubtful. Just use it in your project. ---------------------------------------------------------------------- OLLI_S · 2018-07-12 17:47 Quote from: ASerge on July 12, 2018, 06:02:10 pmGiven the cross- platform nature of the FPC, it is very doubtful. Just use it in your project. OK, but thank you for providing the code, I will use it in my project. ---------------------------------------------------------------------- OLLI_S · 2018-07-15 21:40 @ASerge I added your code and it is working perfectly (I tested many apps). But one user reported that he gets no information when he tries to read them from the application Acronis True Image Home. I downloaded and installed this app (in a VM) and I also get no information from this app (the memo is empty, just the following lines get a result: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i     LV := VerInfo.FileLongVersion;    Memo1.Append(Format('FileLongVersion=%d.%d.%d.%d', [LV.Words[3], LV.Words[2], LV.Words[1], LV.Words[0]]));     LV := VerInfo.ProductLongVersion;     Memo1.Append(Format('FileLongVersion=%d.%d.%d.%d', [LV.Words[3], LV.Words[2], LV.Words[1], LV.Words[0]])); A trial version of Acronis True Image Home can be downloaded here: https://www.acronis.com/en-us/personal/computer-backup/ I uploaded the EXE to my OneDrive (https://1drv.ms/f/s!AsiLVok82IpQg_97o977pmEQx4gi9A) I have protected the folder with a password (because I fear legal issues when I upload and share an application) and send you the password via a PM. Can you please try your code with this EXE and tell me where the problem might be? Thank you! Best regards OLLI ---------------------------------------------------------------------- ASerge · 2018-07-16 05:20 Quote from: OLLI_S on July 15, 2018, 11:40:00 pm@ASerge But one user reported that he gets no information when he tries to read them from the application Acronis True Image Home. Can you please try your code with this EXE and tell me where the problem might be? Problem in Acronis True Image. There is it's VersionInfo block: Code: C  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i 1 VERSIONINFOFILEVERSION 22,5,1,12510PRODUCTVERSION 22,5,1,12510FILEOS 0x4FILETYPE 0x1{BLOCK "StringFileInfo"{        BLOCK "040904b0"      {                VALUE "Comments", "http://www.acronis.com/"              VALUE "CompanyName", "Acronis International GmbH"                VALUE "FileDescription", "Acronis True Image 2018"                VALUE "FileVersion", "22,5,1,12510"                VALUE "InternalName", "ti_console"                VALUE "LegalCopyright", "Copyright (C) Acronis International GmbH, 2002-2017."                VALUE "LegalTrademarks", "Acronis International GmbH. All rights reserved."                VALUE "OriginalFilename", "ti_console.exe"              VALUE "ProductName", "Acronis True Image 2018"              VALUE "ProductVersion", "22,5,1,12510"        }} BLOCK "VarFileInfo"{        VALUE "Translation", 0x0000 0x04B0}}As you can see VarFileInfo=0x0000 0x04B0, but StringFileInfo contains only "040904b0", and the block "000004b0" does not. This is a mistake of developers of acronis. You can tweak the programs to account for this particular error so: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i function TVersionInfo.GetVerValue(const Name: string): string;type  TTranslateInfo = packed record    LangId, CodePage: Word;  end;const  Default: TTranslateInfo = (LangId:$0409; CodePage:1252);var  PLang: ^TTranslateInfo;  PValue: PChar;  BlockName: string;begin  Result := '';  PLang := VerGetValue(FBuffer, '\VarFileInfo\Translation');  if PLang = nil then    PLang := @Default;  FmtStr(BlockName, '\StringFileInfo\%.4x%.4x\%s',    [PLang^.LangId, PLang^.CodePage, Name]);  PValue := VerGetValue(FBuffer, BlockName);  if PValue = nil then  begin    // Error in resource, like TrueImage.exe, where no LangId.    // Lets try with default langId    FmtStr(BlockName, '\StringFileInfo\%.4x%.4x\%s',      [Default.LangId, PLang^.CodePage, Name]);    PValue := VerGetValue(FBuffer, BlockName);  end;  if PValue <> nil then    Result := UTF8Encode(WideString(PWideChar(PValue)));end;Added line [20..27]. Although of course more correct to correct the error. But it is necessary to write to acronis developers. ---------------------------------------------------------------------- OLLI_S · 2018-07-16 08:36 Hello ASerge, Quote from: ASerge on July 16, 2018, 07:20:03 amProblem in Acronis True Image. There is it's VersionInfo block: Code: C  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i 1 VERSIONINFOFILEVERSION 22,5,1,12510PRODUCTVERSION 22,5,1,12510FILEOS 0x4FILETYPE 0x1{BLOCK "StringFileInfo"{        BLOCK "040904b0"      {                VALUE "Comments", "http://www.acronis.com/"              VALUE "CompanyName", "Acronis International GmbH"                VALUE "FileDescription", "Acronis True Image 2018"                VALUE "FileVersion", "22,5,1,12510"                VALUE "InternalName", "ti_console"                VALUE "LegalCopyright", "Copyright (C) Acronis International GmbH, 2002-2017."                VALUE "LegalTrademarks", "Acronis International GmbH. All rights reserved."                VALUE "OriginalFilename", "ti_console.exe"              VALUE "ProductName", "Acronis True Image 2018"              VALUE "ProductVersion", "22,5,1,12510"        }} BLOCK "VarFileInfo"{        VALUE "Translation", 0x0000 0x04B0}}As you can see VarFileInfo=0x0000 0x04B0, but StringFileInfo contains only "040904b0", and the block "000004b0" does not. This is a mistake of developers of acronis. I am really a noob (beginner) about this topic. Where can this version-information that you postet be found? What do thoese Hex-Values (like 0x0000) mean? I will check the code tonight. But is this a universal working code (for all applications that have the same issues in the version info)? Or is this just a fix for Acronis True Image? Thank you for helping me! Best regards OLLI ---------------------------------------------------------------------- ASerge · 2018-07-16 14:02 Quote from: OLLI_S on July 16, 2018, 10:36:59 amWhere can this version-information that you postet be found? What do thoese Hex-Values (like 0x0000) mean? From documentation https://docs.microsoft.com/en- us/windows/desktop/menurc/versioninfo-resource QuoteBut is this a universal working code (for all applications that have the same issues in the version info)? Or is this just a fix for Acronis True Image? This is a fix for errors such as those in Acronis True Image. In normal cases, this is not necessary. Applications are usually developed without taking into account other developers errors, but if the percentage of such errors is large, then add code that takes this into account. ---------------------------------------------------------------------- OLLI_S · 2018-07-16 20:45 Thank you ASerge for the source code, it is working perfectly. And also thank you for providing the link with additional info. ---------------------------------------------------------------------- OLLI_S · 2018-07-17 19:04 Hello ASerge, I have one more question about the File Version and the Product Version: With the following lines I get the File Version and Product Version stings: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i     AppendInfo(CviFileVersion);    AppendInfo(CviProductVersion); But there are also the following lines: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i     LV := VerInfo.FileLongVersion;    Memo1.Append(Format('FileLongVersion=%d.%d.%d.%d', [LV.Words[3], LV.Words[2], LV.Words[1], LV.Words[0]]));     LV := VerInfo.ProductLongVersion;    Memo1.Append(Format('FileLongVersion=%d.%d.%d.%d', [LV.Words[3], LV.Words[2], LV.Words[1], LV.Words[0]])); There are sometimes differences between File Version (from first code) and File Version (from second code). Is it correct that the first file info is the File Version String from the version info and the second is the file version from the application (outside the version info block)? I am asking because I am looking for a perfect label (caption) for those fields. The text I write before the values. Maybe "File Version" and "File Version String" (but I need something better). Best regards OLLI ---------------------------------------------------------------------- ASerge · 2018-07-17 22:07 Quote from: OLLI_S on July 17, 2018, 09:04:26 pmThere are sometimes differences between File Version (from first code) and File Version (from second code). The link above already contains information. These fields are different. A string is a general way of storing data for a version resource, and a numeric one is a dedicated way of storing data for quick access (For example the SysUtils.GetFileVersion reads it). Usually developers make them the same. Example of *.rc file: Code: Text  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i #define FileVersionInt 2018,7,18,1#define FileVersionStr "2018.07.18.1\0" #pragma code_page(1252)LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US1 VERSIONINFOFILEVERSION FileVersionIntFILEOS VOS_NT_WINDOWS32FILETYPE VFT_APPBEGIN  BLOCK "StringFileInfo"  BEGIN  BLOCK "040904E4"    BEGIN      VALUE "FileVersion", FileVersionStr#... ---------------------------------------------------------------------- OLLI_S · 2018-11-12 15:10 Hello @ASerge, a user sent me some additional files where the current version of VerInfo crashes: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TVersionInfo.ReadVersionInfo;var  Size: DWORD;  Dummy: DWORD;  FileNameW: UnicodeString;begin  FileNameW := UTF8Decode(FFileName);  Size := GetFileVersionInfoSizeW(Pointer(FileNameW), @Dummy);  if Size = 0 then    RaiseLastOSError; // <------- Here the application crashes  GetMem(FBuffer, Size);  if not GetFileVersionInfoW(Pointer(FileNameW), 0, Size, FBuffer) then    RaiseLastOSError;end; The variable "Size" has the value 0 for these files. So I am not sure if these EXE-File that users try to analyze simply have no version info stored or if something is missing in the code. I have the EXE files, so I can send them by PM. Best regards OLLI ---------------------------------------------------------------------- Thaddy · 2018-11-12 15:36 Indeed, a filesize of 0 is a legal filesize and will not immediately set an Os Error. This is actually a bit undefined I guess. ---------------------------------------------------------------------- lucamar · 2018-11-12 15:57 According to Microsoft's doc: QuoteIf the function fails, the return value is zero. To get extended error information, call GetLastError. The most probable cause for the function to fail is that the executable has no version info but there are others: You should check the error info and act in consequence. ---------------------------------------------------------------------- Thaddy · 2018-11-12 16:05 Quote from: lucamar on November 12, 2018, 04:57:57 pmThe most probable cause for the function to fail is that the executable has no version info but there are others: You should check the error info and act in consequence. Which it doesn't properly. Right? It should only raise if there is actually an error. ---------------------------------------------------------------------- OLLI_S · 2018-11-17 16:30 Hello, Quote from: lucamar on November 12, 2018, 04:57:57 pmAccording to Microsoft's doc: QuoteIf the function fails, the return value is zero. To get extended error information, call GetLastError. The most probable cause for the function to fail is that the executable has no version info but there are others: You should check the error info and act in consequence. I am not a good programmer, what must be changed in ReadVersionInfo? Best regards OLLI ---------------------------------------------------------------------- ASerge · 2018-11-17 16:57 Quote from: OLLI_S on November 17, 2018, 05:30:45 pmI am not a good programmer, what must be changed in ReadVersionInfo? Nothing. There may be several reasons why version information is not available. Simply does not exist, or no access, etc. So that generation of errors and transfer to a higher code - the right thing. If you do not want to show this to the user - catch, output to the log, and show the user that there is no data. ---------------------------------------------------------------------- OLLI_S · 2018-11-17 17:30 Hello Aserge, I added a try ... except block: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i     try    // Create the Version Information      VerInfo := TVersionInfo.Create(AppFileName);    except      // will only be executed in case of an exception      on E: Exception do begin        ShowMessage( 'Error: '+ E.ClassName + #13#10 + E.Message );        VerInfo.Free;        exit;      end;    end; When I select an application that causes problems I see the error message (as expected). But I also get an Access-Violation message. So maybe I have to add the try block in TVersionInfo.ReadVersionInfo ? Or should I modify TVersionInfo.ReadVersionInfo so when the size is zero, that I don't call RaiseLastOSError but show a message and exit? OLLI ---------------------------------------------------------------------- lucamar · 2018-11-17 18:29 Quote from: OLLI_S on November 17, 2018, 06:30:48 pmWhen I select an application that causes problems I see the error message (as expected). But I also get an Access-Violation message. So maybe I have to add the try block in TVersionInfo.ReadVersionInfo ? Or should I modify TVersionInfo.ReadVersionInfo so when the size is zero, that I don't call RaiseLastOSError but show a message and exit? The access violation probably comes from the fact that since, indirectly, ReadVersionInfo is called from the constructor, an exception will prevent the constructor from finalizing, so your call to VerInfo.Free catchs the object at an ... "incovenient" moment To avoid it, you have some options but the best, IMHO, is to modify how TVersionInfo works. The needed changes are rather few and small: Change TVersionInfo.Create() to also set FBuffer to Nil.Delete the call to ReadVersionInfo from TVersionInfo.SetFileName.At the beginning of methods that use FBuffer (GetFixedFileInfo and GetVerValue) add this guard: if FBuffer = Nil then ReadVersionInfo; The basic effect of the changes is to defer doing anything until it is actually needed, instead of proactively doing it as soon as possible; which incidentally prevents raising exceptions until we are actually ready to catch them. Just a question of tastes ETA Incidentally, if I were you I wouldn't call that class TVersionInfo but TAltVersionInfo or something like that, since there is already a class TVersionInfo in the Fileinfo unit. ---------------------------------------------------------------------- ASerge · 2018-11-17 19:39 Quote from: OLLI_S on November 17, 2018, 06:30:48 pmI added a try ... except block: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i     try    // Create the Version Information      VerInfo := TVersionInfo.Create(AppFileName);    except      // will only be executed in case of an exception      on E: Exception do begin        ShowMessage( 'Error: '+ E.ClassName + #13#10 + E.Message );        VerInfo.Free;        exit;      end;    end; When I select an application that causes problems I see the error message (as expected). But I also get an Access-Violation message. Bad code: release object not in finally, but in the except block. When an exception occurs, the constructor calls the destructor and then you use Free again. Use right construction: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i try  VerInfo := TVersionInfo.Create(AppFileName);  try    // Do something with version  finally    VerInfo.Free;  end;except  on E: Exception do    ShowMessage( 'Error: '+ E.Message);end; ---------------------------------------------------------------------- OLLI_S · 2019-02-10 13:37 Hello ASerge, Quote from: ASerge on November 17, 2018, 08:39:16 pmBad code: release object not in finally, but in the except block. When an exception occurs, the constructor calls the destructor and then you use Free again. Use right construction: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i try  VerInfo := TVersionInfo.Create(AppFileName);  try    // Do something with version  finally    VerInfo.Free;  end;except  on E: Exception do    ShowMessage( 'Error: '+ E.Message);end; Thank you for your correction of the code. I now catch the exception and show an Information-Message. So the problem is solved. Best regards OLLI ---------------------------------------------------------------------- d.oertel · 2026-01-20 17:18 Question to ASerge: What is the license of the unit verinfo ? I would like to integrate it in a AGPL open source project (opsi.org). d.oertel