VBA 8バイト符号なし整数

タグの編集
投稿者 てつ  (社会人) 投稿日時 2023/11/12 20:57:00
32bit版EXCEL VBAで8バイト符号なし整数を扱うには、どうすればいいですか?

Win32APIのFindFirstFileでファイル検索した時、第2引数のWIN32_FIND_DATAに
ファイルサイズがDWORDで「nFileSizeHigh」と「nFileSizeLow」で返ります。
このファイルサイズをVBAで取得したいです。
https://learn.microsoft.com/ja-jp/windows/win32/api/minwinbase/ns-minwinbase-win32_find_dataw

10GBのテストファイルを作って試したところ、以下の値が返りました。
「nFileSizeLow」-2147483648
「nFileSizeHigh」2

(Highを32bit左シフト)+ Low でいいと思うのですが、VBAでのコーディングがわかりません。
&H200000000+&H80000000=8,589,934,592+2,147,483,648=10,737,418,240/1024
希望結果はExplorerと同じ「10,485,760」KBです。
投稿者 るきお  (社会人) 投稿日時 2023/11/12 22:54:12
Excelの32ビット版と64ビット版でVBAにどういう差異があるのか理解していませんが、

> (Highを32bit左シフト)+ Low でいいと思うのですが、VBAでのコーディングがわかりません。
私はこういうビット演算が苦手なので、こういう系はByte型にして、地道にしたからかけ算して足していきます。
この考え方は32ビットでも64ビットでも同じです。

なので、fileSizeの部分はこのようにByteの配列にしてしまって、
fileSizeHigh(3) As Byte
fileSizeLow(3) As Byte


取得したあとで、これをこんな感じで計算します。
fileSizeB = CDec(data.fileSizeLow(0))
fileSizeB = fileSizeB + data.fileSizeLow(1) * 256
fileSizeB = fileSizeB + data.fileSizeLow(2) * 256 ^ 2
fileSizeB = fileSizeB + data.fileSizeLow(3) * 256 ^ 3



かっこよくビット演算すればもっとスマートに行けるとは思いますが…。

参考に64ビット版ではありますが、動作確認したコードを載せておきます。約60GBのファイルでサイズを取得できることを確認しました。
ひさしぶりにExcel VBAを書きました。知らない間に LongLong などという型ができていたんですね。が、これは32ビット版にはないようだったので、一応ここはVariant(Decimal)にしておきました。

Private Const MAX_PATH As Long = 260

Private Declare PtrSafe Function FindFirstFile Lib "kernel32" Alias "FindFirstFileW" _
    (ByVal lpFileName As LongPtr, _
     ByRef FindData As WIN32_FIND_DATAW) As LongPtr

Private Type FILETIME
    dwLowDateTime As Long
    dwHighDateTime As Long
End Type

Private Type WIN32_FIND_DATAW
    dwFileAttributes As Long
    ftCreationTime As FILETIME
    ftLastAccessTime As FILETIME
    ftLastWriteTime As FILETIME
    
    fileSizeHigh(3) As Byte
    fileSizeLow(3) As Byte
    
    dwReserved0 As Long
    dwReserved1 As Long
    cFileName(MAX_PATH * 2 - 1) As Byte
    cAlternateFileName(14 * 2 - 1) As Byte
    dwFileType As Long
    dwCreatorType As Long
    wFinderFlags As Integer
End Type

Public Sub FindFirstFileTest()

    Dim data As WIN32_FIND_DATAW
   
    Dim fileName As String
    fileName = "C:\VM\Windows7.vhd"
    
    Call FindFirstFile(StrPtr(fileName), data)
    
    Dim fileSizeB As Variant
    
    fileSizeB = CDec(data.fileSizeLow(0))
    fileSizeB = fileSizeB + data.fileSizeLow(1) * 256
    fileSizeB = fileSizeB + data.fileSizeLow(2) * 256 ^ 2
    fileSizeB = fileSizeB + data.fileSizeLow(3) * 256 ^ 3
    fileSizeB = fileSizeB + data.fileSizeHigh(0) * 256 ^ 4
    fileSizeB = fileSizeB + data.fileSizeHigh(1) * 256 ^ 5
    fileSizeB = fileSizeB + data.fileSizeHigh(2) * 256 ^ 6
    fileSizeB = fileSizeB + data.fileSizeHigh(3) * 256 ^ 7
    
    Dim fileSizeKB As Variant
    fileSizeKB = CDec(fileSizeB) / 1024
    
    Debug.Print fileSizeKB

End Sub
投稿者 KOZ  (社会人) 投稿日時 2023/11/12 23:21:24
るきおさんの方法ってわかりやすくていいですね。
nFileSizeHigh As Long
nFileSizeLow As Long
の部分を Currency (8ビット2進数)でとって 10000 倍するという方法もあります。
投稿者 KOZ  (社会人) 投稿日時 2023/11/12 23:22:57
まちがえた!8ビットでなくて8バイト2進数です。
投稿者 KOZ  (社会人) 投稿日時 2023/11/12 23:28:48
正攻法なら
Sub main()
    Debug.Print GetFileSize(2, -2147483648@) / 1024; "KB"
End Sub

Function GetFileSize(ByVal nHigh As LongByVal nLow As LongAs Variant
    GetFileSize = CDec(ToUint32(nHigh)) * (2 ^ 32) + ToUint32(nLow)
End Function

Function ToUint32(ByVal value As LongAs Currency
    Dim tmp As Currency
    If value > 0 Then
        ToUint32 = value
    Else
        ToUint32 = 4294967295@ + value + 1
    End If
End Function

かなー
投稿者 てつ  (社会人) 投稿日時 2023/11/13 00:30:11
るきお様、KOZ様、こんなに早く返信頂き、ありがとうございます。
どちらの方法でも、希望結果を取得できました。
「Decimal、Currencyあたりを使うのかな」程度の知識しかなく、VBAでのコーディングが解らなかったので助かりました。
どうもありがとうございました。