<<
Demoanwendung "Docfile Viewer"
Structured Storage Zugriffe mit VBA
(Demo-MDB)

Die Demoanwendung "Docfile Viewer" ist an das gleichnamige Programm aus den Visual Studio Tools von Microsoft angelehnt. Mit der Demoanwendung soll gezeigt werden, wie man auf ein Compound File (Structured Storage/OLE/COM) zugreifen kann, ohne die dafür vorgesehenen Schnittstellen von Microsoft zu verwenden. Ein Zugriff auf die Methoden der implementierten Schnittstellen scheint mit VBA nicht realisierbar zu sein. Um dennoch auf einen Structured Storage zugreifen zu können, ohne eine zusätzliche Komponente zu benötigen, muss man das Ganze eben "per pedes" erledigen. 

Diese Dokumentation behandelt das Microsoft Compound File Binary File Format, das auch als Structured Storage (OLE/COM) oder Docfile Format bekannt ist. Das Format ist hervorragend von Microsoft dokumentiert. Um eine vollständige Darstellung dieses Formats zu erhalten, ist es ratsam, sich mit der originalen Dokumentation von Microsoft zu beschäftigen. Diese Dokumentation hier dient nur dem Verständnis des gezeigten VBA-Codes mit dem auf ein Compound File (Structured Storage) zugegriffen werden kann. 

Ein Compound File ist eine strukturierte Speichereinheit, die aus einer beliebigen Anzahl von einzelnen Dateneinheiten besteht, die in einer hierarchischen Verzeichnisstruktur verwaltet werden. Eine solche Speichereinheit entspricht in etwa einer Festplatte auf der Dateien in verschiedenen Verzeichnissen gespeichert werden können. Im Compound File Format verwendet man aber statt der Begriffe 'Verzeichnis' und 'Datei', die Begriffe 'Storage' und 'Stream'. Die Verzeichnisstruktur beginnt mit dem Root-Verzeichnis, in dem Dateneinheiten (Stream) und Unterverzeichnisse (Storage) abgelegt werden können. Die Unterverzeichnisse können dann weitere Streams und Unterverzeichnisse enthalten. Alle Informationen zu den einzelnen Elementen der Verzeichnisstruktur, werden in einem speziellen Verzeichnis-Array hinterlegt. Das Verzeichnis-Array entspricht einem Inhaltsverzeichnis, mit dessen Hilfe man auf die einzelnen Dateneinheiten (Streams) zugreifen kann. 

Man sollte sich zuerst einmal vor Augen führen, dass eine Speichereinheit wie ein Compound File nichts anderes ist, als eine lange Abfolge von Bytes. Der effiziente Zugriff auf die Teildaten dieser Abfolge wird nur durch eine spezielle Struktur ermöglicht. Der Bytestream eines Compound Files besteht aus einer variablen Anzahl von einzelnen Sektoren mit fester Größe, die einem 512 Bytes großen Header folgen. Die Sektorengröße wird im Header festgelegt und kann variieren. Die Gesamtgröße eines Compound File ergibt sich aus: 

512  +  [Anzahl Sektoren]  *  [Sektorengröße]     (Bytes)

Wenn man eine Byteabfolge mit einer Länge von 1030 Bytes in einem Compound File speichern will und die Sektorengröße ist z.B. auf 512 Bytes festgelegt, dann benötigt man dafür 3 Sektoren. Der freie Platz im 3.Sektor wird ungenutzt verbraucht. Der Speicherverbrauch innerhalb des Compound File beträgt damit 3 * 512 = 1536 Bytes. Die einzelnen Sektoren einer Dateneinheit müssen nicht hintereinander im Bytestream abgelegt werden, sondern sie können über das ganze Compound File verteilt sein. Um eine solche Abfolge später wieder richtig zusammensetzen zu können, muss man wissen, welche Sektoren in welcher Reihenfolge zusammengehören. Diese Informationen werden in einem speziellen Array gespeichert. In diesem Array ist jedes Element einem bestimmten Sektor im Compound File zugeordnet. Die Zuordnung erfolgt logisch über die analogen Positionen. Das 1.Element des Arrays ist dem 1.Sektor des Compound File zugeordnet, das 2.Element dem 2.Sektor, usw. Ein solches Array nennt man FAT (File Allocation Table). Es ist ein einfaches, 1-dimensionales Array mit Elementen vom Typ Long. In den Elementen werden zu jedem Sektor die zusätzlichen Informationen gespeichert. Es wird hier angeben, ob der Sektor verfügbar ist (FREESECT) oder ob er Daten enthält. Enthält ein Sektor Daten, die zu einer Dateneinheit gehören, die auf mehrere Sektoren verteilt wurde, dann wird im zugehörigen Element der FAT hinterlegt, mit welchem Sektor die Sektorenkette weitergeführt wird. Der letzte Sektor in einer Sektorenkette erhält die Kennzeichnung, dass die Kette mit ihm zu Ende ist (ENDOFCHAIN). 

Neben den eigentlichen Daten die man in einem Compound File speichern möchte, müssen dort natürlich auch die Dateneinheiten gespeichert werden, die den Aufbau des Compound Files beschreiben. Diese Dateneinheiten müssen ebenfalls in einzelne Sektoren aufgeteilt werden. 

Die FAT belegt mindestens einen Sektor und besitzt damit eine entsprechende Anzahl von Elementen vom Typ Long (4 Byte). Bei einer Sektorengröße von 512 Byte, besitzt die FAT pro Sektor 512 / 4 = 128 Elemente und kann damit 128 Sektoren verwalten. Das entspricht einer Gesamtgröße von 512 + 128 * 512 = 66048 Bytes, wovon 65024 Bytes zur Speicherung weiterer Daten zur Verfügung stehen. Benötigt man mehr Speicherplatz, dann muss man weitere Sektoren für die FAT verwenden. Damit man die Sektoren der FAT später wieder richtig zusammensetzen kann, benötigt man die entsprechenden Informationen über ihre Verkettung. Diese Angaben werden aber nicht in der FAT selber gespeichert, sondern für sie gibt es eine extra FAT, die doppelt-indirekte FAT (DIF). Die DIF ist auch wieder ein 1-dimensionales Array mit Elementen vom Typ Long. Die Reihenfolge der Elemente im Array entspricht der Reihenfolge der Sektoren der FAT. In den Elementen wird angegeben, in welchem Sektor die Teildaten gespeichert sind. Im ersten Element der DIF steht, in welchem Sektor des Compound File der erste Teil der FAT gespeichert ist, im zweiten Element der DIF steht dann, in welchem Sektor die nächsten Teildaten der FAT gespeichert sind, usw. Werden keine weiteren Sektoren mehr für die FAT verwendet, dann ist das nächste freie Element der DIF als Ende der Kette gekennzeichnet (ENDOFCHAIN). 

Die ersten 109 Elemente der DIF sind bereits im Header des Compound Files untergebracht. Werden mehr als 109 Sektoren für die Speicherung der FAT benötigt, dann muss die DIF auf weitere Sektoren verteilt werden. Die Verkettung dieser Sektoren erfolgt dabei über das jeweils letzte Element eines DIF-Sektors. Entweder hat dieses Element den Wert ENDOFCHAIN oder es gibt an, in welchem Sektor die nächsten Teildaten der DIF gespeichert sind. Dies gilt aber noch nicht für die ersten 109 Elemente der DIF, die im Header untergebracht sind. Werden zusätzliche Sektoren für die DIF benötigt, dann wird der Pointer zum ersten zusätzlichen DIF-Sektor im Header extra angegeben. 

Die Sektoren, die das Compound File beschreiben, werden in der FAT entsprechend gekennzeichnet. In der FAT gibt es dafür die Werte DIFSECT und FATSECT. Neben diesen Werten können in der FAT noch die Werte FREESECT oder ENDOFCHAIN auftauchen. Andernfalls enthält ein Element der FAT die Information, mit welchem Sektor die Kette fortgeführt wird. 

Neben der DIF und der FAT benötigt man Angaben zu den gespeicherten Dateneinheiten und der verwendeten Verzeichnisstruktur. Dafür gibt es das Verzeichnis-Array, das einem Inhaltsverzeichnis des Compound Files entspricht. Ein Element dieses Arrays ist eine 128 Byte große Struktur, die Informationen zu den einzelnen Einträgen des Verzeichnisbaums enthält. Die Einträge (die ich behandle) stehen entweder für ein Verzeichnis oder für einen Stream. In einem Eintrag, der einem Stream zugeordnet ist, findet man einen Pointer zum ersten Sektor des Streams. Bei einem Eintrag, der einem Verzeichnis zugeordnet ist, findet man dagegen einen Pointer zum ersten Element im Verzeichnisbaum, der diesem Verzeichnis untergeordnet ist. Das erste Element im Array enthält immer die Angaben zum Root-Verzeichnis. 

Möchte man einen bestimmten Stream aus einem Compound File lesen, dann durchsucht man das Verzeichnis-Array nach dem entsprechenden Eintrag und erfährt dort, in welchem Sektor die ersten Teildaten gespeichert sind. In der FAT kann man dann, von diesem Sektor aus, die weitere Verkettung abfragen. Hat man alle Sektoren in der richtigen Reihenfolge zusammengesetzt, kann man mit Hilfe der Angabe zur Streamgröße aus dem Verzeichniseintrag, die ggf. überschüssigen Bytes des letzten Sektors entfernen. 

Die einzelnen Einträge des Verzeichnis-Arrays sind, entsprechend der Verzeichnisstruktur, untereinander in Form eines Binärbaums verknüpft. Dafür sind jeweils drei Variablen in den Verzeichniseinträgen vorgesehen. Steht ein Eintrag für einen Storage, dann wird in einer der Variablen angegeben, welcher Verzeichniseintrag das erste Kind dieses Storage darstellt. In den anderen beiden Variablen stehen die Angaben zu den Geschwistereinträgen gemäß der Binärbaum-Methode. Es wird dabei mit einem red-black-tree gearbeitet. Die einfachste Form eines red-black-tree ist ein einfacher Binärbaum, bei dem also alle Knoten black sind. Ich begnüge mich damit, den red-black-tree nur als einfachen Binärbaum zu betrachten(!) Genau genommen sind aber insgesamt vier Variablen pro Verzeichniseintrag für die Gliederung vorgesehen. Ich werde in diesem Demo nicht weiter auf den Binärbaum eingehen, mehr Infos dazu kann man aber in meinem Beispiel "ExtractMSOfficeFiles" finden. Zum Thema Binärbaum gibt es auch gute Seiten im Netz. 

Die Größe eines Elements des Verzeichnis-Arrays beträgt 128 Bytes. Bei einer Sektorengröße von 512 Bytes stehen also 512 / 128 = 4 Elemente pro Sektor zur Verfügung. Benötigt man mehr als 4 Einträge, müssen weitere Sektoren verwendet werden. Diese Sektoren werden auf die übliche Weise über die FAT verkettet. Im Header des Compound File findet man die Angabe, in welchem Sektor das Verzeichnis-Array beginnt. 

Beim ersten Element des Verzeichnis-Arrays, das für das Root-Verzeichnis steht, gibt es noch eine Besonderheit. Obwohl es sich um ein Verzeichnis handelt, gibt es dort einen Pointer zu einem Stream, dem sogenannten Ministream. Der Ministream ist ein normaler Stream im Compound File, der entsprechend seiner Größe, auf mehrere Sektoren verteilt wird, die in der FAT verkettet werden. Der Ministream stellt selber aber einen eigenen Speicherbereich dar, die ebenfalls aus einzelnen Sektoren fester Länge besteht. Die Sektoren des Ministreams sind aber wesentlich kleiner als die Sektoren des Compound File. Wenn man einzelne Datenstreams speichern möchte, dann benötigt man dafür immer mindestens einen Sektor. Ist ein solcher Stream aber viel kleiner als ein Sektor, dann wird Speicherplatz verschwendet. Um auch kleine Dateneinheiten im Compound File speichern zu können, ohne dabei unnötig viel Speicherplatz zu verbrauchen, wird der Ministream verwendet. Der Ministream besitzt seine eigene FAT, die MiniFAT genannt wird. Sie wird, genau wie die FAT, in einzelnen Sektoren aufgeteilt, die aber nicht durch eine DIF verkettet werden, sondern auf übliche Weise mit Hilfe der FAT. Die Sektorengröße des Ministreams und der Pointer zum ersten Sektor der MiniFAT sind im Header des Compound File gespeichert. Dort ist auch der Schwellenwert der Datenmenge festgelegt, bis zu dem die Daten im Ministream gespeichert werden. Diese Information ist wichtig, da man sonst nicht weiß, ob sich die Sektorenangabe in einem Verzeichnis-Array-Eintrag auf den Ministream bezieht oder auf die Sektoren des Compound Files. Typischerweise hat ein Sektor des Ministreams eine Größe von 64 Bytes und der Schwellenwert liegt bei 4096 Bytes pro Stream.  

Nachdem nun die einzelnen Teile des Compound File Formats angesprochen wurden, stellt sich jetzt die Frage, wie ein Gesamtablauf aussieht, wenn auf gespeicherte Daten zugegriffen wird. Zuerst müssen natürlich die Informationseinheiten ausgelesen werden, mit deren Hilfe dann die restliche Speichereinheit in die einzelnen Teildaten zerlegt werden kann. Die Reihenfolge ergibt sich fast von selbst: 

Als Erstes wird der Header eingelesen. In ihm findet man einige wichtige Angaben, wie z.B. die Größe der Sektoren. Im Header findet man den ersten Teil der DIF, werden weitere Sektoren für die Speicherung der DIF verwendet, dann findet man im Header den Pointer zum ersten zusätzlichen DIF-Sektor. Gibt es zusätzliche DIF-Sektoren, dann werden diese in der richtigen Reihenfolge eingelesen und das DIF-Array zusammengesetzt. Mit Hilfe des DIF-Arrays werden dann die einzelnen Sektoren der FAT eingelesen und das FAT-Array zusammengesetzt. Mit dem Pointer zum ersten Sektor des Verzeichnis-Arrays, der sich im Header befindet, und den Infos aus der FAT, werden dann die einzelnen Verzeichnissektoren eingelesen und das Verzeichnis-Array zusammengesetzt. Mit dem Pointer zum ersten Sektor der MiniFAT, der sich im Header befindet und der FAT, werden die einzelnen Sektoren eingelesen und das MiniFAT-Array zusammengesetzt. Der Pointer zur MiniFAT ist nur vorhanden, wenn auch der Ministream verwendet wird. Ist dieser vorhanden, dann findet man den Pointer zum ersten Sektor des Ministreams im Root-Eintrag (Root-Entry) im Verzeichnis-Array. Mit diesem Pointer und der FAT werden dann die entsprechenden Sektoren eingelesen und der Ministream zusammengesetzt. Danach sind alle Einzelteile vorhanden, mit deren Hilfe man nun auf die gespeicherten Teildaten zugreifen kann. Um einen Stream auszulesen, durchsucht man das Verzeichnis-Array nach dessen Eintrag. Dort findet man dann den Pointer zum ersten Sektor des Streams. Mit Hilfe des Schwellenwerts aus dem Header wird entschieden, ob der Stream im Ministream oder in den normalen Sektoren gespeichert ist. Mit diesen Angaben und den Infos aus der FAT oder der MiniFAT, werden dann die einzelnen Sektoren eingelesen und der Stream zusammengesetzt. Im Verzeichniseintrag findet man auch die Größe des gespeicherten Streams, mit dieser Angabe können dann ggf. die überschüssigen Bytes des letzen Sektors entfernt werden. Um die Verzeichnisstruktur der Verzeichniseinträge auszulesen, muss man die entsprechenden Werte des Binärbaums auswerten, die sich in den Verzeichniseinträgen des Verzeichnis-Arrays befinden. 

Betrachten wir zunächst den Deklarationsbereich. Hier werden die Konstanten definiert, die später verwendet werden sollen. Solche Konstanten dienen auch dem besseren Verständnis, wenn sie im Code dann eingesetzt werden. 

' Werte die in der FAT verwendet werden.
Private Const DIFSECT As Long = &HFFFFFFFC
Private Const FATSECT As Long = &HFFFFFFFD
Private Const ENDOFCHAIN As Long = &HFFFFFFFE
Private Const FREESECT As Long = &HFFFFFFFF
 
' Arten der Verzeichniseinträge.
Private Const STGTY_INVALID As Long = 0
Private Const STGTY_STORAGE As Long = 1
Private Const STGTY_STREAM As Long = 2
Private Const STGTY_LOCKBYTES As Long = 3
Private Const STGTY_PROPERTY As Long = 4
Private Const STGTY_ROOT As Long = 5
 
' Farbwerte im red-black-tree
Private Const DECOLOR_RED As Long = 0
Private Const DECOLOR_BLACK As Long = 1
 
 

Dann werden die Strukturen benötigt, die das Compound File Format verwendet. Dabei handelt es sich um den Header des Compound File und um den Verzeichniseintrag im Verzeichnis-Array (Directory). 

Private Type GUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(7) As Byte
End Type
 
' Header des Compound File
Private Type StructuredStorageHeader
abSig(7) As Byte ' Compound File Kennzeichnung
clid As GUID
uMinorVersion As Integer
uDllVersion As Integer
uByteOrder As Integer
uSectorShift As Integer ' Angabe zur Sektorgröße (2^x)
uMiniSectorShift As Integer ' Angabe zur Sektorengröße im Ministream (2^x)
usReserved As Integer
ulReserved1 As Long
ulReserved2 As Long
csectFat As Long ' Anzahl der FAT-Sektoren
sectDirStart As Long ' Pointer zum ersten Verzeichnis-Sektor
signature As Long
ulMiniSectorCutoff As Long ' Schwellenwert des Ministreams
sectMiniFatStart As Long ' Pointer zum ersten MiniFAT-Sektor
csectMiniFat As Long ' Anzahl der MiniFAT-Sektoren
sectDifStart As Long ' Pointer zum ersen zusätzlichen DIF-Sektor
csectDif As Long ' Anzahl der zusätzlichen DIF-Sektoren
'sectFat(108) As Long ' Die ersten 109 Elemente der DIF
End Type
 
' Element des Verzeichnis-Arrays
Private Type StructuredStorageDirectoryEntry
ab(63) As Byte ' Name des Eintrags
cb As Integer ' Anzahl der Zeichen im Namen
mse As Byte ' Art des Eintrags (STGTY-Konstanten)
bflags As Byte ' Farbwert red-black-tree
sidLeftSib As Long ' Pointer zum kleinerern Geschwisterknoten (Binärbaum)
sidRightSib As Long ' Pointer zum größeren Geschwisterknoten (Binärbaum)
sidChild As Long ' Pointer zum Kindknoten (wenn Storage)
CLSID As GUID
dwUserFlags As Long
timeCreate(3) As Integer
timeModify(3) As Integer
sectStart As Long ' Pointer zum ersten Sektor des Streams
ulSize As Long ' Anzahl der Bytes im Stream
dptPropType As Integer
wFiller As Integer
End Type
 
 

Zum Schluss des Deklarationsbereichs muss noch eine API-Funktion deklariert werden, mit der man auf Bytestreams im Arbeitsspeicher zugreifen kann. 

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
(pDest As Any, pSource As Any, ByVal cbLength As Long)
 

Damit sind die Vorbereitungen abgeschlossen und wir können nun zu den Funktionen übergehen. An dieser Stelle sollte einmal kurz angesprochen werden, um was es sich bei diesem Demo-Projekt eigentlich handelt. Mit dem Docfile Viewer kann man auf Dateien zugreifen, die auf dem Compound File Format basieren, um sich auf Ebene dieses Formats, deren Inhalte anzeigen zu lassen. Die Anwendung besteht aus zwei Fenstern. Im ersten Fenster wird das Inhaltsverzeichnis des Compound File angezeigt. Das zweite Fenster zeigt dann den Inhalt des Streams an, den man im ersten Fenster ausgewählt hat. Um die Daten für diese Fenster bereitstellen zu können, werden zwei Funktionen benötigt. Zum einen muss die Verzeichnisstruktur eingelesen werden und zum anderen der ausgewählter Stream. Natürlich würde es sich anbieten das Inhaltsverzeichnis eines Compound File mit Hilfe eines Tree-View-Controls anzuzeigen, in dieser Demo-Anwendung verwende ich dafür aber nur eine einfache Konstruktion mit einem Textfeld. Das Inhaltsverzeichnis wird als ein formatierter String ausgegeben. Da das Verständnis für die Gliederung der Verzeichniseinträge stark vom Verständnis der Binärbaum-Methoden abhängt, möchte ich hier nicht näher darauf eingehen. Zum richtigen Umgang mit Binärbäumen gibt es aber gute Beiträge im Netz. Abgesehen von der Gliederung muss man beim Zugriff auf einen Stream natürlich auch verschiedene Informationen aus dem Verzeichnis-Array berücksichtigen. Aus diesem Grund beschränke ich mich hier darauf, nur näher auf die Funktion zum Einlesen eines bestimmten Streams einzugehen. Den vollständigen Programmcode finden Sie dann in der Demo-Anwendung, die Sie downloaden können. 

Der Bytestream eines Compound File ist in einer Datei gespeichert. Um einen bestimmten Stream auslesen zu können, muss man wissen, wie er heißt oder unter welcher Index-Nr sein Eintrag im Verzeichnis-Array abgelegt ist. Die Funktion die einen bestimmten Stream ausliest, erwartet als Parameter das Compound File als Bytestream und außerdem den Namen des Streams oder dessen ID. Die Programme Word, Excel und Power-Point speichern ihre Daten auf Basis des Compound File Formats. Möchte man z.B. den WordDocument-Stream eines Word Dokuments auslesen und speichern, dann sieht der Funktionsaufruf im Prinzip so aus: 

Dim bDocfile() As Byte, bStream() As Byte
Dim iCan As Integer
 
' Bytestream des Compound File laden
iCan = FreeFile
Open "C:\testWord.doc" For Binary Access Read As iCan
ReDim bDocfile(LOF(iCan) - 1)
Get iCan, , bDocfile()
Close iCan
 
' Stream mit Namen WordDocument auslesen
Call ReadDocFileStream(bDocfile(), "WordDocument", bStream())
 
' WordDocument-Stream als Datei speichern
iCan = FreeFile
Open "C:\testWord_WordDocumentStream.dat" For Binary Access Write As iCan
Put iCan, , bStream
Close iCan
 
 

Und hier nun die Funktion, mit der man den Stream aus dem Compound File (Structured Storage/OLE/COM) auslesen kann: 

Public Function ReadDocFileStream(bDocfile() As Byte, sStreamName As String, bStream1() As Byte, _
Optional ByVal iStreamID As Integer) As Long
Dim tSSH As StructuredStorageHeader, tSSDE() As StructuredStorageDirectoryEntry
Dim lSectDIF As Long, lSectFAT As Long, lSectMiniFAT As Long
Dim lDIF() As Long, lFAT() As Long, lMiniFAT() As Long
Dim bMiniStream() As Byte, lSectorSize As Long
Dim lPointer1 As Long, lPointer2 As Long
Dim i As Integer, l As Long
 
' Compound File Header füllen (Ohne DIF-Daten)
CopyMemory tSSH.abSig(0), bDocfile(0), Len(tSSH)
 
' Sektorengröße des Compound File
lSectorSize = (2 ^ tSSH.uSectorShift)
 
' Größe des DIF-Arrays festlegen (Die Verkettungselemente dürfen nicht ins Array übernommen werden)
ReDim lDIF(109 + (((lSectorSize / 4) - 1) * tSSH.csectDif))
' Die ersten 109 DIF-Elemente füllen
CopyMemory lDIF(0), bDocfile(Len(tSSH)), (109 * 4)
 
' Die zusätzlichen DIF-Sektoren laden (wenn vorhanden)
lPointer1 = tSSH.sectDifStart
For l = 1 To tSSH.csectDif
CopyMemory lDIF(109 + (l - 1) * ((lSectorSize / 4) - 1)), _
bDocfile(Len(tSSH) + (109 * 4) + (lPointer1 * lSectorSize)), lSectorSize
lPointer1 = lDIF(109 + l * ((lSectorSize / 4) - 1))
Next l
 
' Größe des FAT-Arrays festlegen ([Anzahl FAT-Sektoren] * [Elemente pro Sektor])
ReDim lFAT(((lSectorSize / 4) * tSSH.csectFat) - 1)
' FAT-Array füllen (Sektoren beginnen bei (Sektor - 1) * Sektorgröße | Der erste Sektor hat Offset 0)
For l = 1 To tSSH.csectFat
CopyMemory lFAT((l - 1) * (lSectorSize / 4)), _
bDocfile(Len(tSSH) + (109 * 4) + (lDIF(l - 1) * lSectorSize)), lSectorSize
Next l
 
' MiniFAT-Array anlegen (wenn vorhanden)
If tSSH.csectMiniFat > 0 Then
ReDim lMiniFAT(((lSectorSize / 4) * tSSH.csectMiniFat) - 1)
i = 0
l = tSSH.sectMiniFatStart
' MiniFAT-Array füllen bis zum Ende der Sektorenkette
While Not lFAT(l) = ENDOFCHAIN
CopyMemory lMiniFAT(i * (lSectorSize / 4)), _
bDocfile(Len(tSSH) + (109 * 4) + (l * lSectorSize)), lSectorSize
l = lFAT(l)
i = i + 1
Wend
' Letzten Sektor der MiniFAT laden
CopyMemory lMiniFAT(i * (lSectorSize / 4)), _
bDocfile(Len(tSSH) + (109 * 4) + (l * lSectorSize)), lSectorSize
End If
 
' Verzeichnis-Array laden ([Sektorengröße] / 128 (Bytes) = [Anzahl der Elemente pro Sektor])
i = 0
l = tSSH.sectDirStart
While Not lFAT(l) = ENDOFCHAIN
ReDim Preserve tSSDE(i + (lSectorSize / 128) - 1)
CopyMemory tSSDE(i).ab(0), bDocfile(Len(tSSH) + (109 * 4) + (l * lSectorSize)), lSectorSize
l = lFAT(l)
i = i + (lSectorSize / 128)
Wend
' Letzten Verzeichnissektor der Kette laden
ReDim Preserve tSSDE(i + (lSectorSize / 128) - 1)
CopyMemory tSSDE(i).ab(0), bDocfile(Len(tSSH) + (109 * 4) + (l * lSectorSize)), lSectorSize
 
' Ministream laden (wenn vorhanden)
If tSSDE(0).sectStart > 0 Then
l = tSSDE(0).sectStart
i = 0
While Not lFAT(l) = ENDOFCHAIN
ReDim Preserve bMiniStream((i + 1) * lSectorSize)
CopyMemory bMiniStream(i * lSectorSize), _
bDocfile(Len(tSSH) + (109 * 4) + (l * lSectorSize)), lSectorSize
l = lFAT(l)
i = i + 1
Wend
ReDim Preserve bMiniStream((i + 1) * lSectorSize - 1)
CopyMemory bMiniStream(i * lSectorSize), bDocfile(Len(tSSH) + (109 * 4) + (l * lSectorSize)), lSectorSize
End If
 
' Alle Einzelteile zum Laden eines Stream sind nun bereit.
 
' Den Stream laden (Über Verzeichnis-ID oder Stream-Namen (Ein Streamname ist nur pro Verzeichnis eindeutig!))
If iStreamID > 0 Then
If tSSDE(iStreamID).mse = STGTY_STREAM Then
If (tSSDE(iStreamID).ulSize > 0) And (tSSDE(iStreamID).sectStart >= 0) Then
' Wenn (Eintrag ist Stream und Größe und Pointer zum ersten Sektor vorhanden) dann Streamdaten laden
ReadStream tSSDE(iStreamID).sectStart, tSSDE(iStreamID).ulSize, bStream1(), tSSH, lFAT(), _
lMiniFAT(), bDocfile(), bMiniStream()
' Überschüssige Füllbytes aus dem letzten Sektor entfernen (wenn vorhanden)
If tSSDE(iStreamID).ulSize <> UBound(bStream1) + 1 Then
ReDim Preserve bStream1(tSSDE(iStreamID).ulSize - 1)
End If
Else
ReDim bStream1(0)
End If
End If
 
Else
For i = 1 To UBound(tSSDE)
If tSSDE(i).mse = STGTY_STREAM Then
' Auf Streamnamen prüfen (Nimmt den ersten Fund!)
If Left(CStr(tSSDE(i).ab), tSSDE(i).cb / 2 - 1) = sStreamName Then
If (tSSDE(i).ulSize > 0) And (tSSDE(i).sectStart >= 0) Then
ReadStream tSSDE(i).sectStart, tSSDE(i).ulSize, bStream1(), tSSH, lFAT(), _
lMiniFAT(), bDocfile(), bMiniStream()
If tSSDE(i).ulSize <> UBound(bStream1) + 1 Then
ReDim Preserve bStream1(tSSDE(i).ulSize - 1)
End If
Else
ReDim bStream1(0)
End If
Exit For
End If
End If
Next i
 
End If
 
End Function
 
 

Mit dieser Unterfunktion wird ein Stream aus seinen Sektoren zusammengesetzt. Die Größe des Streams entscheidet, ob die Sektoren im Ministream liegen oder ob es sich um Sektoren des Compound File handelt. 

Private Function ReadStream(ByVal l As Long, lLength As Long, bStream1() As Byte, tSSH As StructuredStorageHeader, _
lFAT() As Long, lMiniFAT() As Long, bDocfile() As Byte, bMiniStream() As Byte)
Dim i As Integer
 
' Prüfen ob die Daten im Ministream liegen oder im Compound File
If lLength >= tSSH.ulMiniSectorCutoff Then
' Stream bis zum Ende der Sektorenkette zusammensetzen
While Not lFAT(l) = ENDOFCHAIN
ReDim Preserve bStream1((i + 1) * (2 ^ tSSH.uSectorShift))
CopyMemory bStream1(i * (2 ^ tSSH.uSectorShift)), _
bDocfile(Len(tSSH) + (109 * 4) + (l * (2 ^ tSSH.uSectorShift))), (2 ^ tSSH.uSectorShift)
l = lFAT(l)
i = i + 1
Wend
' Letzten Sektor der Kette anfügen
ReDim Preserve bStream1((i + 1) * (2 ^ tSSH.uSectorShift) - 1)
CopyMemory bStream1(i * (2 ^ tSSH.uSectorShift)), _
bDocfile(Len(tSSH) + (109 * 4) + (l * (2 ^ tSSH.uSectorShift))), (2 ^ tSSH.uSectorShift)
 
Else
' Stream bis zum Ende der Sektorenkette zusammensetzen (Sektoren liegen im Ministream)
While Not lMiniFAT(l) = ENDOFCHAIN
ReDim Preserve bStream1((i + 1) * (2 ^ tSSH.uMiniSectorShift))
CopyMemory bStream1(i * (2 ^ tSSH.uMiniSectorShift)), _
bMiniStream((l * (2 ^ tSSH.uMiniSectorShift))), (2 ^ tSSH.uMiniSectorShift)
l = lMiniFAT(l)
i = i + 1
Wend
' Letzten Sektor der Kette anfügen (Sektor liegt im Ministream)
ReDim Preserve bStream1((i + 1) * (2 ^ tSSH.uMiniSectorShift) - 1)
CopyMemory bStream1(i * (2 ^ tSSH.uMiniSectorShift)), _
bMiniStream((l * (2 ^ tSSH.uMiniSectorShift))), (2 ^ tSSH.uMiniSectorShift)
 
End If
 
End Function
 
 

Hiermit komme ich zum Ende und hoffe, ich konnte mit meinen Erklärungen dem Thema einigermaßen gerecht werden. Das vollständige Beispiel finden Sie als Download. Bei der Umsetzung des "Docfile Viewers" habe ich ein paar Techniken angewandt, die sicherlich leicht verständlich sind. Laden Sie sich das Docfile-Viewer-Demo herunter, dann steht Ihnen ein lauffähiges Beispiel zur Verfügung. 

Download für Access 2002/2003
Docfile Viewer Demo (Access 2002)
Download für Access 2000
Docfile Viewer Demo (Access 2000)
(downgrade)
Download für Access 97
Docfile Viewer Demo (Access 97)
(downgrade)
Installation:
Einfach runterladen und anschauen.
©   Oliver Straub  -  Fliegenstr. 6  -  80337 München