Jump to content

DllCall to GetFileSize


Go to solution Solved by Danp2,

Recommended Posts

Hello Autoit,

I'm very new to DllCall function, in fact never used it, but I'm just curious about it.

I found @toasterking's awesome beginner friendly dll GUI: 

I searched for some easier functions (less input / less output parameters), and I found GetFileSize function (I didn't modify his code, just using the GUI): 

https://learn.microsoft.com/en-gb/windows/win32/api/fileapi/nf-fileapi-getfilesize

So, this is what I did: 

image.thumb.png.c9387bd3fbc1752aceca9fdbe7787c83.png

 

The function itself is executing like this, but the output is 0 and -1

DllCall("kernel32.dll","int","GetFileSize","HANDLE","D:\DllCall GetFileSize Example.au3","DWORD*","$hello")

 

Then, I tried to execute my script (very simple) and put it into D drive: 

;DllCall("kernel32.dll", "int", "GetFileSize", "HANDLE", "D:\DllCall GetFileSize Example.au3")
$aDllCallReturn = DllCall("kernel32.dll","int","GetFileSize","HANDLE","D:\DllCall GetFileSize Example.au3","DWORD*","$hello")

For $i = 0 To UBound($aDllCallReturn) - 1
    ConsoleWrite($i & ": " & $aDllCallReturn[$i] & @CRLF)
    ;MsgBox(0, $i, $aDllCallReturn[$i])
Next

;ConsoleWrite($hello & @CRLF)

The result is as the following (same as the screenshot): 

0: -1
1: 0x00000000
2: 0

This file size is 403 bytes in my computer, it's very small, I expected to see 403 somewhere, but I don't know where I made it wrong. 

So, now I have some questions: 

1. Do I really need to use lpFileSizeHigh or can I use "hello"?

2. Do I always have to put $ in the beginning of the parameter? I tried without $ in his GUI, I got an error. So, I think $ is a must. 

3. But if I write as $hello (expecting this will store the file size), when I ConsoleWrite it, Autoit becomes angry because "undeclared global variable". 

4. If somehow $hello will be the output to my file size, why do I need to return an array $aDllCallReturn which contains 3 elements? So, I have one $hello + 3 items from the array = I have 4 outputs total. But Microsoft says this function has only one return "[out, optional] LPDWORD lpFileSizeHigh ". I'm confused. 

Please note that; the goal for me is to use the DllCall function, not to get the file size by using FileGetSize() or some other existing functions. 

TY.

Link to comment
Share on other sites

@taylansan I just tried  "toasterking's awesome beginner friendly dll GUI", as you named it :)

Maybe I tested it too quickly but I didn't find a way to modify an added line in the GUI, after the user typed something wrong concerning a parameter. Who likes to 'Clear All' and restart from the beginning ?
Also I couldn't find a way to add this kind of Parameter which will receive a value from the function, like Dword* when there's a star at its end etc...

12 hours ago, taylansan said:

Please note that; the goal for me is to use the DllCall function, not to get the file size by using FileGetSize() or some other existing functions. 

Below, I scripted a way to use the DllCall function concerning GetFileSize (it's not a part of AutoIt, maybe because GetFileSize isn't recommended and GetFileSizeEx should be used instead, as stated on msdn pages)

1st script, with a non-existing file on disk :

#include <Array.au3>
#include <WinAPIFiles.au3>
#include <WinAPIHObj.au3>

Opt( "MustDeclareVars", 1)
Local $sTempFile, $sText, $tBuffer, $hFile,  $nBytes, $iSize

; Create a temporary file and write data to it
$sTempFile = @TempDir & '\test.txt'
$sText = 'abcdefghijklmnopqrstuvwxyz' ; length is 26
$tBuffer = DllStructCreate("byte[" & StringLen($sText) & "]")
DllStructSetData($tBuffer, 1, $sText)
$hFile = _WinAPI_CreateFile($sTempFile, 1) ; 1 = Creates a new file. If a file exists, it is overwritten.
_WinAPI_WriteFile($hFile, $tBuffer, StringLen($sText), $nBytes) ; $nBytes receives the number of bytes written.

$iSize = _WinAPI_GetFileSize($hFile)
; $iSize = _WinAPI_GetFileSizeEx($hFile)

_WinAPI_CloseHandle($hFile)
ConsoleWrite($iSize & ' ' & FileRead($sTempFile) & @CRLF)
FileDelete($sTempFile)

;===============================
Func _WinAPI_GetFileSize($hFile)
    Local $aRet = DllCall("kernel32.dll", "dword", "GetFileSize", "handle", $hFile, "dword*", 0)
    If @error Or ($aRet[0] = 4294967295) Then Return SetError(@error, @extended, 0)

    ; Note : if GetFileSize returns INVALID_FILE_SIZE, then $aResult[0] = 0xFFFFFFFF = 4 294 967 295
    ; _ArrayDisplay($aRet)

    Return $aRet[0]
EndFunc   ;==>_WinAPI_GetFileSize

Console Output :

26 abcdefghijklmnopqrstuvwxyz

2nd script, with an already existing file on disk (your case) :

#include <Array.au3>
#include <WinAPIFiles.au3>
#include <WinAPIHObj.au3>

Opt( "MustDeclareVars", 1)
Local $sFile, $hFile, $iSize

; Get the handle of an already existing file on disk
$sFile = @ScriptDir & '\Test for GetFileSize.txt' ; length is 10, as it contains 1234567890 without @CRLF at the end.
$hFile = _WinAPI_CreateFile($sFile, 2) ; 2 = Opens a file. The function fails if the file does not exist.
If $hFile = 0 Then Exit MsgBox(0, "Error : file not found", $sFile)

$iSize = _WinAPI_GetFileSize($hFile)
; $iSize = _WinAPI_GetFileSizeEx($hFile)

_WinAPI_CloseHandle($hFile)
ConsoleWrite($iSize & ' ' & FileRead($sFile) & @CRLF)
; ConsoleWrite($iSize & @CRLF) ; don't read the existing file in case it's several hundred of MB

;===============================
Func _WinAPI_GetFileSize($hFile)
    Local $aRet = DllCall("kernel32.dll", "dword", "GetFileSize", "handle", $hFile, "dword*", 0)
    If @error Or ($aRet[0] = 4294967295) Then Return SetError(@error, @extended, 0)

    ; Note : if GetFileSize returns INVALID_FILE_SIZE, then $aResult[0] = 0xFFFFFFFF = 4 294 967 295
    ; _ArrayDisplay($aRet)

    Return $aRet[0]
EndFunc   ;==>_WinAPI_GetFileSize

Console Output :

10 1234567890

You can uncomment _ArrayDisplay found within the function, if you'd like to have a look at the array returned by DllCall

If toasterking's script allows to output the types and values as found in the  _WinAPI_GetFileSize() above, then you should be able to achieve your goal. If not (because of his $ checking, * checking etc...) then maybe someone could rework his original script to complete it, so it will become even "more friendlier" :D

Good luck

Edited by pixelsearch
typo
Link to comment
Share on other sites

Edit (placed on purpose in a new post) :

I think the function _WinAPI_GetFileSize() as scripted just above would return a correct result only if the file size is < 4GB (maximum of Dword) so $aRet[0] will contain the correct file size.

If the file size is > 4GB, then the high-order doubleword of the file size will be returned to the 2nd parameter of the function, e.g $aRet[2] which was scripted like this :

"dword*", 0

Which means, if the file size is > 4GB, then we should take care of $aRet[2] AND $aRet[0] to calculate the correct size (!?)

Reminder: Msdn notes when GetFileSize function succeeds :

GetFileSize function

Return value :
If the function succeeds, the return value is the low-order doubleword of the file size, and, if lpFileSizeHigh is non-NULL, the function puts the high-order doubleword of the file size into the variable pointed to by that parameter.

Maybe that's the reason why GetFileSize shouldn't be used and GetFileSizeEx should be preferred, as GetFileSizeEx deals in a simpler way when files have a size > 4GB . Also in Msdn words :

GetFileSize function

Retrieves the size of the specified file, in bytes.
It is recommended that you use GetFileSizeEx.
[...]

Could a DllCall guru share his comments about this, especially the part  $aRet[2] AND $aRet[0]  discussed just above ?
Thanks :)
 

Link to comment
Share on other sites

@Danp2 Yeah, FileGetSize will get the file size. But I want to use DllCall to get the size. So, maybe we can try _WinAPI_GetFileSizeEx

@pixelsearch In the help documentation, you are creating a new file to be used in _WinAPI_GetFileSizeEx. 

 

Let's say, I want to get the size of "autoit3.exe" which is around 900kb. This is stored (in my computer) in "C:\Program Files (x86)\AutoIt3\autoit3.exe" and we have a shortcut to reach this easily: @AutoItExe

So, I tried this: 1 + 2 is working as expected because they are using FileGetSize. But I wanted to make it work for 3 + 4 + 5. So, I tried several random options: 

#include <WinAPIFiles.au3>

;Example 1 = OK
Local $iFileSize = FileGetSize(@AutoItExe)
ConsoleWrite("Example 1: " & $iFileSize & @CRLF)

;Example 2 = OK
$iFileSize = 0
$iFileSize = FileGetSize("C:\Program Files (x86)\AutoIt3\autoit3.exe")
ConsoleWrite("Example 2: " & $iFileSize & @CRLF)

;Example 3 = NOT OK
Local $hFile = FileOpen(@AutoItExe) ;OR Local $hFile = FileOpen("C:\Program Files (x86)\AutoIt3\autoit3.exe")
$iFileSize = 0
$iFileSize = FileGetSize($hFile)
ConsoleWrite("Example 3: " & $iFileSize & @CRLF)

;Example 4 = NOT OK
$iFileSize = 0
$iFileSize = _WinAPI_GetFileSizeEx($hFile)
ConsoleWrite("Example 4: " & $iFileSize & @CRLF)

;Example 5 = NOT OK
$iFileSize = 0
Local $aCall = DllCall("kernel32.dll", "bool", "GetFileSizeEx", "handle", $hFile, "int64*", 0)
ConsoleWrite("Example 5: " & $aCall[2] & @CRLF)

FileClose($hFile)

My output is as the following: 

Example 1: 946776
Example 2: 946776
Example 3: 0
Example 4: -1
Example 5: 0

So, I think I couldn't send handle $hFile correctly. So: 

  1. How can I send "C:\Program Files (x86)\AutoIt3\autoit3.exe" or @AutoItExe as the correct handle? 
  2. I used FileOpen() to get the handle, is this the place where I did wrong? Should I use a different function to open and get the file? Which one is it?
  3. What if the file I'm looking for is a big file, like few GB? Do I still need to open them to assign as a handle? Because FileGetSize() doesn't use a handle. 

 

Please note the same as before; the goal for me is to use the DllCall function, not to get the file size by using FileGetSize(). 

TY.

Link to comment
Share on other sites

  • Solution

@taylansanWhat's your obsession with using DLLCall? Also, how to do you that FileGetSize isn't calling the same API behind the scenes?

  • Example 3 isn't valid because you can't use a file handle with FileGetSize
  • Examples 4 & 5 will work if adjust as follows --
    ;Example 4
    $hFile = _WinAPI_CreateFile(@AutoItExe, 2, 2, 2)
    ;### Debug CONSOLE ↓↓↓
    ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : $hFile = ' & $hFile & @CRLF & '>Error code: ' & @error & @CRLF)
    $iFileSize = 0
    $iFileSize = _WinAPI_GetFileSizeEx($hFile)
    ConsoleWrite("Example 4: " & $iFileSize & @CRLF)
    
    ;Example 5
    $iFileSize = 0
    Local $aCall = DllCall("kernel32.dll", "bool", "GetFileSizeEx", "handle", $hFile, "int64*", 0)
    ConsoleWrite("Example 5: " & $aCall[2] & @CRLF)
    
    _WinAPI_CloseHandle($hFile)

     

Link to comment
Share on other sites

@taylansan If you read carefully @Danp2 last script, then you should have the answers to your 3 questions.

9 hours ago, taylansan said:

1. How can I send "C:\Program Files (x86)\AutoIt3\autoit3.exe" or @AutoItExe as the correct handle? 

By using the  _WinAPI_CreateFile function that returns the correct handle.

9 hours ago, taylansan said:

In the help documentation, you are creating a new file to be used in _WinAPI_GetFileSizeEx. 

If you look carefully, I also created a new file only in Script 1, but not in script 2, where I wrote just before the script :

2nd script, with an already existing file on disk (your case)

By the way, it's not because the function is called _WinAPI_CreateFile that it always creates a new file. I know it sounds strange but this function works also with existing files stored on disk. Maybe Microsoft could have given a better name to the function, avoiding the word "Create" in it. Look at the explanations found on msdn :

CreateFile function :
Creates OR opens a file or I/O device [...]

 

9 hours ago, taylansan said:

2. I used FileOpen() to get the handle, is this the place where I did wrong? Should I use a different function to open and get the file? Which one is it?

Danp2 answered to that concerning your example 3, in his own words : "you can't use a file handle with FileGetSize" . Always look carefully at the help file to make sure when you need a file string name, or a file handle

9 hours ago, taylansan said:

3a. What if the file I'm looking for is a big file, like few GB? Do I still need to open them to assign as a handle?

Yes you do need to open it but don't be scared of this, because _WinAPI_CreateFile opens it immediately, even if it's several GB.

9 hours ago, taylansan said:

3b. Because FileGetSize() doesn't use a handle. 

Danp2 answered to that, in his own words : "Also, how do you know that FileGetSize isn't calling the same API behind the scenes?"  (e.g. CreateFile)

Now let's go back to my last post (the "Edit post") where I asked at the end :

20 hours ago, pixelsearch said:

Could a DllCall guru share his comments about this, especially the part  $aRet[2] AND $aRet[0]  discussed just above ?

I just did interesting tests concerning the _WinAPI_GetFileSize (using my Script #2 found in a post above) and the results are crystal clear. First the resulting pic :

1862755949_Realtestson_WinAPI_GetFileSizewhen4GB.png.c1c48a634e24b186ccb24af5cf9062e9.png

As you can see, the assumption was correct : $aRet[2] must be checked in case the file size is >= 4GB
If you don't check it, then you'll have correct results for a file whose size is < 4GB - 2 bytes, but as soon as a file size is >= 4GB, then $aRet[2] is required to retrieve the  high-order Dword (remember it was scripted dword* with a star, e.g. to retrieve in the resulting array a value returned by the system. Without the star it won't have retrieved anything and $aRet[2] would have been = 0 in all pics !)

In 3 out of 4 pics, $aRet[2] = 1 means that 4GB need to be added to $aRet[0] . If $aRet[2] was equal to 2, then 8GB would have needed to be added to $aRet[0] (which contains bytes), so the calculation of an accurate result isn't that hard to do. I won't do it here and keep the precedent script #2 "as-is" because of all explanations that followed the post, also because noone should use this non AutoIt  _WinAPI_GetFileSize function when there are better, easier and safer ways to retrieve a file size. But it was interesting to dig in it and understand better how the function works. Thanks to you for creating the thread !

For the record, to create  all these big files in a snap, I typed fsutil at the command prompt... but that's another story :)

Link to comment
Share on other sites

15 minutes ago, pixelsearch said:

As you can see, the assumption was correct : $aRet[2] must be checked in case the file size is >= 4GB

well... if you always want the real size of the file you always should check $aRet[2]:

$result =  $aRet[2] * highDword + $aRet[1]  ; highDword is that big value constant: 4 294 967 296

 

If you use FAT32  you have 32 bits, so the max value you can get is 2^32 = 4 294 967 296, that's why a file can not be greater than this on FAT32.  Obviously there is a  close relationship between 16 bits hardware registers, 16 bits OS, 32 bits OS, FAT16 partition table, FAT32, etc.

if you have a 32 bits Operating System, it only uses $aRet[1] to handle files smaller than 4GB (or maybe you have a FAT16 formatted partition), then, when 64 bits was technically posible (hardware, OS, hard disk size grew up from 4GB), they just added a HighDword to include more values, in fact, you needed to check if the value was overloaded and then do a different api call to get the highword value... I remember those days.... 😉

Link to comment
Share on other sites

Might be superfluous, but I can add a WINAPI function that checks the size with a single Function:

Global $iFileSize, $iFilePath = @AutoItExe ; "C:\Program Files (x86)\AutoIt3\autoit3.exe"
ConsoleWrite(">File to Check: " & $iFilePath & " | FileExists=" & FileExists($iFilePath) & " " & @CRLF)

;Example 1 = Built-in functions
$iFileSize = FileGetSize($iFilePath)
ConsoleWrite("+ FileSize: " & $iFileSize & @CRLF)

;Example 2 = WinAPI DllCall
$iFileSize = _WinAPI_GetFileSize($iFilePath)
ConsoleWrite("- FileSize: " & $iFileSize & @CRLF)

Func _WinAPI_GetFileSize($sFileNameOrHandle)
    Local $hFile = 0, $aCall = 0
    If VarGetType($sFileNameOrHandle) = 'Ptr' And (Not FileExists($sFileNameOrHandle)) Then
        $hFile = $sFileNameOrHandle
    Else
        Local Const $iOpenExist = 3, $iShareRead = 0x00000001, $iAccessRead = 0x80000000, $iAttributes = 0, $tSecurity = 0
        $aCall = DllCall("kernel32.dll", "handle", "CreateFileW", "wstr", $sFileNameOrHandle, "dword", $iAccessRead, "dword", $iShareRead, "struct*", $tSecurity, "dword", $iOpenExist, "dword", $iAttributes, "ptr", 0)
        If @error Or ($aCall[0] = Ptr(-1)) Then Return SetError(1, @extended, 0)
        $hFile = $aCall[0]
    EndIf
    $aCall = DllCall("kernel32.dll", "bool", "GetFileSizeEx", "handle", $hFile, "int64*", 0)
    If @error Or Not $aCall[0] Then Return SetError(2, @extended, 0)
    Return $aCall[2]
EndFunc   ;==>_WinAPI_GetFileSize

 

Regards,
 

Link to comment
Share on other sites

I think I didn't explained myself very good... Please, let's me try again.

You have 1 digit and you start counting from 0 to 9, what happend if you need more?
You just add a "1" to the left, and start again counting from zero, that is, a new range from 10..99; What happen if you need more?
Exactly the same, you add a "1" to the left and two zeros, a new range from 100..999

They did exactly the same with computers:
if you have 8 bits (called 1 byte), you can handle 2^8 = 256 differents values. Do you need more? just put another 8 bits to the left of the previous one:
Now you have 16 bits (called 1 word) and you can handle 2^16 = 2 GB of differents values. Do you need more? Repeat again, this time add 16 bits to the left
Now you have 32 bits (called Double word, because it is 16 doubled), you can handle 2^32 = 4 GB of differents values. More? 32 more bits at the left
Now you have 64 bits (called quadword)

So, whenever you upgrade you need to keep compatibility with previous Harddisk sizes, old software, etc, so you create a new function GetFileSizeEX(tended) that makes easy to obtain the new value, of course you can use the pre-existing, deprecated, GetFileSize to do the same with more work.

pre-existing functions only handles 32 bits (it won't be updated for backwards compability, old softwares already uses it on the old way), The new GetFileSizeEx handles the new big type "int64" instead of the old "DWord"

 

@VIP  I think It is not superfluous, you nailed! you are using the "new function added to the new OS" to get the real file size

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...