Tutorial
Windows Drag und Drop Tutorial
von stephanbrunker | Seite 11 von 12 |
Das IStream-Interface
Nachdem wir ja vererbte Interfaces verwenden, haben wir es bei dem IStream-Interface eigentlich mit zwei Interfaces zu tun: Von IUnknown erbt erst einmal ISequentialStream mit den Methoden Read und Write, und von diesem erbt dann IStream mit den Methoden Seek und ein paar anderen, die wir aber nicht implementieren müssen.
Nachdem wir das Prinzip ja schon hatten, braucht der Typ eigentlich kaum Erklärung:
Type ISequentialStream EXTENDS IUnknown 'Custom IISequentialStream Interface, requires Buxfix for IsEqualIID!
Declare Constructor ()
Declare Constructor (ByRef filepath As String)
Declare Destructor ()
'Methods:
'IUnknown Interface:
Declare Virtual Function QueryInterface ( ByVal iid As REFIID, ByVal ppvObject As PVOID Ptr) As HRESULT
'ISequential Stream Interface:
Declare Virtual Function Readx Alias "Read" ( ByVal pv As Any Ptr, ByVal cb As ULong, ByVal pcbread As ULong Ptr) As HRESULT
Declare Virtual Function Writex Alias "Write" ( ByVal pv As Any Ptr, ByVal cb As ULong, ByVal pdbWritten As ULong Ptr) As HRESULT
'member variables:
As String m_Filepath
As String m_Filecontent
As ULongInt m_Seekpointer
End Type
Constructor ISequentialStream
Print "ISequentialStream::Constructor()"
'initalize member variables
m_Seekpointer = 0
'data buffer, because it's read in heaps
m_Filecontent = ""
End Constructor
Constructor ISequentialStream (ByRef filepath As String)
Print "ISequentialStream::Constructor"
'initalize member variables
m_Seekpointer = 0
'data source:
m_Filepath = filepath
'data buffer, because it's read in heaps
m_Filecontent = ""
End Constructor
Destructor ISequentialStream()
Print "ISequentialStream::Destructor"
End Destructor
'IUnknown::QueryInterface
Function ISequentialStream.QueryInterface (ByVal iid As REFIID, ByVal ppvObject As Any Ptr Ptr) As HRESULT
'if this interface is what you want, then return a pointer
Print "ISequentialStream::QueryInterface"
If IsEqualIID ( iid, @IID_ISequentialStream) Or IsEqualIID ( iid, @IID_IUnknown) Then
AddRef()
*ppvObject = @This
Return S_OK
Else
*ppvObject = NULL
Return E_NOINTERFACE
End If
End Function
Bei AddRef und Release habe ich einfach die Elternfunktionen belassen, denn in den vorherigen Interfaces ging es mir darum, die Referenzvergabe zu dokumentieren und da brauchte ich die passende Print-Anweisung. QueryInterface muss natürlich überschrieben werden um auf die Anfrage richtig antworten zu können. Constructor und Destructor brauchen wir auch unbedingt sowie einen Parameterlosen Default-Constructor für die Vererbung zum nächsten Interface. Den Destructor bräuchten wir zwar theoretisch nicht, aber ein Virtueller Destructor bei IUnknown macht erst mal Probleme, also definieren wir ihn einfach, steht eh nix drin ...
Write ist ebenso nicht implementiert, interessant wird dann Read, das ist dann tatsächlich wo unsere Daten gelesen werden wollen:
Function ISequentialStream.Readx Alias "Read" ( ByVal pv As Any Ptr, ByVal cb As ULong, ByVal pcbread As ULong Ptr) As HRESULT
Print "ISequentialStream::Read [";cb;" Bytes @ Seekpointer "; m_Seekpointer;" ]"
'Caller requests cb Bytes of Data put at memory *pv, control value is pcbread (copied bytes)
'Copy Filecontent into buffer (Replace with helper function to implement in actual program)
If m_Filecontent = "" Then
Open m_Filepath For Binary As #1
m_Filecontent = Space(Lof(1))
Get #1,,m_Filecontent
Close #1
End If
Dim bytesleft As LongInt
Dim toread As ULong
'calculate remaining data from seekpointer to eof
bytesleft = FileLen(m_Filepath) - m_Seekpointer
If bytesleft <= 0 Then 'no more data
*pcbRead = 0
Return S_FALSE
ElseIf bytesleft < cb Then 'as many as requested
toread = bytesleft
Else
toread = cb 'less than requested
EndIf
Dim i As ULong
Dim pp As UByte Ptr = pv
'copy data to pointer recieved from caller
For i = 0 To toread -1
pp[i] = m_Filecontent[i + m_Seekpointer]
Next i
'update Seekpointer
m_Seekpointer += i
'give feedback if as many data is copied than requested
If cb > toread Then
*pcbRead = toread
Return S_FALSE
Else
*pcbRead = cb
Return S_OK
EndIf
End Function
'ISequentialStream::Write
Function ISequentialStream.Writex ( ByVal pv As Any Ptr, ByVal cb As ULong, ByVal pcbWritten As ULong Ptr) As HRESULT
Print "ISequentialStream::Write"
'request for writing data *pv in the stream
Return E_NOTIMPL
End Function
Was ich jetzt hier gemacht habe ist zwar eigentlich Blödsinn, aber es war die beste Möglichkeit der Demonstration. Ich lese nämlich den Dateiinhalt beim Aufruf komplett in einen String ein, was zwar funktioniert, aber eigentlich dient das Interface ja dazu, eben die Datei nur häppchenweise zu lesen und Speicher zu belegen. Jedenfalls hat das Interface einen Seekpointer genauso wie das beim Dateizugriff in Freebasic mit GET und PUT auch ist. Man übergibt der Read-Methode jetzt die Anweisung, cb Bytes ab dem Seekpointer zu lesen und den Inhalt am Pointer pv zu speichern. Zur Kontrolle gibt es den Wert pcbread, der angibt, wie viele Bytes tatsächlich gelesen wurden, wenn zum Beispiel über das Dateiende hinaus zu lesen versucht wurde. In ISequentialStream kann der Seekpointer nicht bewegt werden, dafür brauchen wir dann IStream.
Type IStream EXTENDS ISequentialStream
Declare Constructor (ByRef filepath As String)
Declare Destructor ()
'Methods:
'IUnknown Interface:
Declare Virtual Function QueryInterface ( ByVal iid As REFIID, ByVal ppvObject As PVOID Ptr) As HRESULT
'IStream Interface:
Declare Virtual Function Seekx Alias "Seek" ( ByVal dilbMove As LARGE_INTEGER, ByVal dwOrigin As DWORD, ByVal plibNewPosition As ULARGE_INTEGER Ptr) As HRESULT
Declare Virtual Function SetSize ( ByVal libNewSize As ULARGE_INTEGER) As HRESULT
Declare Virtual Function CopyTo ( ByVal pstm As IStream Ptr, ByVal cb As ULARGE_INTEGER, ByVal pcbRead As ULARGE_INTEGER Ptr, ByVal pcbWritten As ULARGE_INTEGER Ptr) As HRESULT
Declare Virtual Function Commit ( ByVal grfCommitFlags As DWORD) As HRESULT
Declare Virtual Function Revert () As HRESULT
Declare Virtual Function LockRegion ( ByVal libOffset As ULARGE_INTEGER, ByVal cb As ULARGE_INTEGER, ByVal dwLockType As DWORD) As HRESULT
Declare Virtual Function UnlockRegion ( ByVal libOffset As ULARGE_INTEGER, ByVal cb As ULARGE_INTEGER, ByVal dwLockType As DWORD) As HRESULT
Declare Virtual Function Stat ( ByVal pstatstg As STATSTG Ptr, ByVal grfStatFlag As DWORD) As HRESULT
Declare Virtual Function Clone ( ByVal ppstm As ISTREAM Ptr) As HRESULT
End Type
Constructor IStream (ByRef filepath As String)
Print "IStream::Constructor"
'data source:
m_Filepath = filepath
End Constructor
Wiederholungen gefallen nicht, wir brauchen nur Seek, auf den Rest antworden wir mit E_NOTIMPL.
'ISequentialStream::Seek
Function IStream.Seekx ( ByVal dlibMove As LARGE_INTEGER, ByVal dwOrigin As DWORD, ByVal plibNewPosition As ULARGE_INTEGER Ptr) As HRESULT
'moves the Seekpointer
Print "IStream::Seek -> ";
Select Case dwOrigin
Case STREAM_SEEK_SET
Print "STREAM_SEEK_SET : Move ";
m_Seekpointer = 0 + dlibMove.QuadPart
Case STREAM_SEEK_CUR
Print "STREAM_SEEK_CUR : Move ";
m_Seekpointer += dlibMove.QuadPart
Case STREAM_SEEK_END
Print "STREAM_SEEK_END : Move ";
m_Seekpointer = FileLen(m_Filepath) + dlibMove.QuadPart
End Select
Print dlibMove.QuadPart
plibNewPosition->QuadPart = m_Seekpointer
Return S_OK
End Function
Da Dateien größer als 2GB sein können, reicht hier eine Integer-Variable für die Dateilänge und den Seekpointer nicht aus. Der LARGE_INTEGER von Windows ist ein Type mit zwei Integerwerten und einem Quadpart, mit dem wir dann wie mit einem Freebasic Longint rechnen können. dwOrigin gibt an, von wo aus wir verschieben sollen, vom Anfang, vom Ende, oder von der aktuellen Position. Wenn wir also nicht die aktuelle Datei mit abspeichern, so müssen wir zumindest die Länge mit unterbringen um auf die Anfrage nach dem Ende korrekt anworten zu können.
Zusätzliche Informationen und Funktionen | |||||||
---|---|---|---|---|---|---|---|
|
|