inno-setuppascalscript

Inno Setup Get size of a file over 2GB limit opened in another application


I am trying to return the size of a file using the Public Domain code from ISXKB at vincenzo.net.

function CloseHandle (hHandle: THandle): Boolean;
  external 'CloseHandle@kernel32.dll stdcall';

const
    // Some constants for CreateFile ().
    GENERIC_READ           = $80000000;
    GENERIC_WRITE          = $40000000;
    GENERIC_EXECUTE        = $20000000;
    GENERIC_ALL            = $10000000;
    FILE_SHARE_READ        = 1;
    FILE_SHARE_WRITE       = 2;
    FILE_SHARE_DELETE      = 4;
    CREATE_NEW             = 1;
    CREATE_ALWAYS          = 2;
    OPEN_EXISTING          = 3;
    OPEN_ALWAYS            = 4;
    TRUNCATE_EXISTING      = 5;
    FILE_READ_ATTRIBUTES   = $80;
    FILE_WRITE_ATTRIBUTES  = $100;

    // General Win32.
    INVALID_HANDLE_VALUE   = -1;

function CreateFile (
    lpFileName             : String;
    dwDesiredAccess        : Cardinal;
    dwShareMode            : Cardinal;
    lpSecurityAttributes   : Cardinal;
    dwCreationDisposition  : Cardinal;
    dwFlagsAndAttributes   : Cardinal;
    hTemplateFile          : Integer
): Integer;
 external 'CreateFileA@kernel32.dll stdcall';

function GetFileSize (hFile: THandle; var lpFileSizeHigh: Integer): Integer;
  external 'GetFileSize@kernel32.dll stdcall';

function GetTheFileSize (FileName: String): Integer;
var
    hFile:  THandle;
    iSize:  Integer;
    hSize:  Integer;
begin
    hFile := CreateFile (FileName,
        GENERIC_READ,// Desired access.
        FILE_SHARE_READ + FILE_SHARE_WRITE,
        0,                // Security attributes.
        OPEN_EXISTING,
        FILE_ATTRIBUTE_TEMPORARY,
        0);
    if (INVALID_HANDLE_VALUE = hFile) then
    begin
        Result := 0;
        Exit;
    end;
    iSize := GetFileSize (hFile, hSize);
    CloseHandle (hFile);
    Result := iSize;
end;

However, this does not appear to work as intended and is returning 0, which I believe is because it is exiting at if (INVALID_HANDLE_VALUE = hFile) then Result := 0. The file I am passing to it exists and is accessible. Can anyone shed some light on why this is failing or suggest an alternate method? Note that I cannot use the built in FileSize function as this has a 2GB limit, which is not enough for my purposes.


Solution

  • I assume you are using an Unicode version of Inno Setup.

    So you must use an Unicode version of CreateFile, the CreateFileW, not CreateFileA:

    external 'CreateFileW@kernel32.dll stdcall';
    

    Anyway, the GetTheFileSize implementation (from now defunct ISXKB) has 2 GB limit too:

    This declaration works with files up to 2 GB.
    ...
    ... retrieves its low 32 bit part of the file size as an integer, and closes the file again.


    To support 64-bit sizes, change it like:

    function GetTheFileSize (FileName: String): Int64;
    ...
    begin
      ...
      Result := Int64(Cardinal(iSize)) + (Int64(Cardinal(hSize)) shl 32);
    end;
    

    Anyway, it's somewhat overkill. And as you have found it does not work if another application has the file opened without allowing others applications to at least read the file (it has not specified FILE_SHARE_READ in its call to CreateFile).

    Note that the FileSize won't work either in this case, as it has basically the same implementation as ISXKB's GetTheFileSize.


    There's an easy solution using FindFirst support function:

    function GetTheFileSize(FileName: String): Int64;
    var
      FindRec: TFindRec;
    begin
      if FindFirst(FileName, FindRec) then
      begin
        Result := Int64(FindRec.SizeHigh) shl 32 + FindRec.SizeLow;
        FindClose(FindRec);
      end
        else
      begin
        Result := -1;
      end;
    end;