VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
  Persistable = 0  'NotPersistable
  DataBindingBehavior = 0  'vbNone
  DataSourceBehavior  = 0  'vbNone
  MTSTransactionMode  = 0  'NotAnMTSObject
END
Attribute VB_Name = "cAsyncFileRead"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = True
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit

'a small asyn-reader-class which is handy in such thumbnailing-scenarios,
'since we can decode the current Image - whilst the Filesytem already buffers
'the next file, without disturbing us in our own "quite busy" App-Process

Private Type OverLapped
  Internal As Long
  InternalHigh As Long
  Offset As Long
  OffsetHigh As Long
  hEvent As Long
End Type

Private Const INVALID_HANDLE_VALUE& = -1
Private Const OPEN_EXISTING& = 3, FILE_FLAG_OVERLAPPED& = &H40000000
Private Const GENERIC_READ& = &H80000000, FILE_SHARE_READ& = &H1, FILE_SHARE_Write& = &H2

Private Declare Function CreateFile& Lib "kernel32" Alias "CreateFileA" (ByVal lpFileName$, ByVal dwDesiredAccess&, ByVal dwShareMode&, ByVal lpSecurityAttributes&, ByVal dwCreationDisposition&, ByVal dwFlagsAndAttributes&, ByVal hTemplateFile&)
Private Declare Function CreateEvent& Lib "kernel32" Alias "CreateEventA" (ByVal lpEventAttributes&, ByVal bManualReset&, ByVal bInitialState&, ByVal lpName$)
Private Declare Function CloseHandle& Lib "kernel32" (ByVal hObject&)
Private Declare Function ReadFile& Lib "kernel32" (ByVal hFile&, lpBuffer As Any, ByVal nNumberOfBytesToRead&, lpNumberOfBytesRead&, lpOverlapped As Any)
Private Declare Function GetOverlappedResult& Lib "kernel32" (ByVal hFile&, lpOverlapped As Any, lpNumberOfBytesTransferred&, ByVal bWait&)
Private Declare Sub MemCopy Lib "kernel32" Alias "RtlMoveMemory" (pDst As Any, PSrc As Any, ByVal CB&)

Private OL As OverLapped, hFile As Long
Private AsyncFileName As String, mFileSize As Long, Data() As Byte, DataRead As Long


Public Function GetFileContent(FileName As String, B() As Byte, Optional NextFileName As String) As Boolean
  'in case the given ReadOut-Filename and our internal AsynFileName don't match, we treat the case as a synchronous one
  If LCase$(FileName) <> LCase$(AsyncFileName) Then
    StartAsyncReading FileName
  End If
    
  'in either case (no matter if the above condition happened or not), we need to make sure, we wait the last bit of time til it's really done
  If WaitForCompletion Then 'everything fine with the content in our Data-Array now (everything read completely)
    If DataRead = 0 Then
      B = vbNullString 'this is the ZeroLength-Filecase, we return with a (0 to -1) ByteArray
    Else
      ReDim B(0 To DataRead - 1)
      MemCopy B(0), Data(0), DataRead
    End If
    
  Else 'some error happened in retrieving the filecontent asyncronously
    B = vbNullString
    Err.Raise vbObjectError, , "Couldn't complete the async read-job"
  End If
    
  'now we start the next readjob-already (in case we got a NextFileName)
  If Len(NextFileName) Then
    StartAsyncReading NextFileName
  End If
End Function

Private Sub StartAsyncReading(FName As String)
  Cleanup
  mFileSize = FileLen(FName) 'this will already raise an error, if not found
  
  hFile = CreateFile(FName, GENERIC_READ, FILE_SHARE_READ Or FILE_SHARE_Write, 0, _
                            OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0)
  
  If hFile = INVALID_HANDLE_VALUE Then hFile = 0: Err.Raise vbObjectError, , "Async-open failed!"
  
  OL.hEvent = CreateEvent(0, 1, 0, vbNullString)
  If OL.hEvent = 0 Then Err.Raise vbObjectError, , "Async-Event-creation failed!"
  
  If mFileSize > UBound(Data) + 1 Then ReDim Preserve Data(0 To mFileSize - 1)
  
  AsyncFileName = FName
  If mFileSize Then ReadFile hFile, Data(0), mFileSize, DataRead, OL
End Sub

Private Function WaitForCompletion() As Boolean
  If hFile = 0 Or OL.hEvent = 0 Then Exit Function 'return false
  
  WaitForCompletion = GetOverlappedResult(hFile, OL, DataRead, 1)
End Function

Private Sub Cleanup()
  If hFile Then CloseHandle hFile: hFile = 0
  If OL.hEvent Then CloseHandle OL.hEvent: OL.hEvent = 0
  mFileSize = 0
  DataRead = 0
  AsyncFileName = vbNullString
End Sub

Private Sub Class_Initialize()
  ReDim Data(0 To 0) 'just ensure, that we start with a preinitialized Data-ByteArray that already has "some Bounds"
End Sub

Private Sub Class_Terminate()
  Cleanup
End Sub
