![]() PKZip compatible Zip files using the ZLib.dll |
Home
CommentsA big thanks to Jean-loup Gailly and Mark Adler for their ZLib compression/decompression Library (you'll need it if you want to use this code, download it from the links at the bottom of the page - best to copy the dll into your system directory). I've put it to good use with my classes for writing PKZip compatible files in Visual Basic. I'm not a big fan of in-line documentation (it just gets in the way) so I hope you can follow it. There's two classes (ZipClass and ZipFile). ZipFile should be a private class, it handles all the nasty stuff. I intend to expand the code to read Zip files at some stage when I get the time but don't hold your breath.
What it can do
What it can't do (yet?)
ReferenceZipClass Properties Comment(s as String) Write-Only Sets the Zip File Comment FileCount() as Long Read-Only Returns: The File Count ZipClass Methods AddFile(FilePath as String, [FileName as String], [Comment as String]) as Boolean Adds a file to the Zip File FilePath: The Path of the File to Add FileName (Optional): The File Name to add to the Zip File (if different from the FilePath File Name) Comment (Optional): The Comment for the File Returns: True if successful WriteZip(FilePath as String, [OverWrite as Boolean]) as Boolean Write the Zip File To Disk FilePath: The Path of the Zip File to Write OverWrite (Optional): OverWrite if the file already exists (default is False) Returns: True if successful UsageAdd ZipClass.cls and ZipFile.cls to your project and ensure that zlib.dll is reachable Dim z As ZipClass Set z = New ZipClass z.AddFile "c:\test.doc" z.AddFile "c:\test.jpg" z.WriteZip "c:\test.zip", True Set z = Nothing The CodeZipClass.cls
Option Explicit
Private Type typCenteralDirEnd
EndOFCentralDirSignature As Long
NumberOfThisDisk As Integer
NumberOfDiskWithCentralDir As Integer
EntriesInTheCentralDirThisOnDisk As Integer
EntriesInTheCentralDir As Integer
SizeOfCentralDir As Long
OffSetOfCentralDir As Long
ZipFileCommentLength As Integer
End Type
Private ZipFileComment As String
Private CentralDirEnd As typCenteralDirEnd
Private colFiles As Collection
Public Property Get FileCount() As Long
FileCount = colFiles.Count
End Property
Public Property Let Comment(s As String)
ZipFileComment = s
CentralDirEnd.ZipFileCommentLength = Len(s)
End Property
Public Function AddFile(FilePath As String, Optional _
FileName As String, Optional Comment As String) As Boolean
Dim f As ZipFile
If Len(Dir(FilePath)) = 0 Then
Exit Function
End If
Set f = New ZipFile
f.FilePath = FilePath
If Len(FileName) > 0 Then
f.NewFileName = FileName
End If
If Len(Comment) > 0 Then
f.Comment = Comment
End If
colFiles.Add f
Set f = Nothing
AddFile = True
End Function
Public Function WriteZip(FilePath As String, Optional _
Overwrite As Boolean) as Boolean
Dim f As ZipFile
Dim l As Long
Dim fh As Long
If Dir(FilePath) <> "" Then
If Overwrite Then
Kill FilePath
Else
Exit Function
End If
End If
fh = FreeFile
Open FilePath For Binary As #fh
For Each f In colFiles
f.WriteLocalFileHeader fh
Next
l = Seek(fh)
For Each f In colFiles
f.WriteCentralFileHeader fh
Next
With CentralDirEnd
.EndOFCentralDirSignature = &H6054B50
.EntriesInTheCentralDirThisOnDisk = colFiles.Count
.EntriesInTheCentralDir = colFiles.Count
.SizeOfCentralDir = Seek(fh) - l
.OffSetOfCentralDir = l - 1
End With
Put #fh, , CentralDirEnd
Put #fh, , ZipFileComment
Close #fh
WriteZip = True
End Sub
Private Sub Class_Initialize()
Set colFiles = New Collection
End Sub
Private Sub Class_Terminate()
Set colFiles = Nothing
End Sub
ZipFile.cls
Option Explicit
Public Enum eZLib
Z_OK = 0
Z_STREAM_ERROR = -2 'Invalid compression level parameter
Z_DATA_ERROR = -3 'Input data corrupted
Z_MEM_ERROR = -4 'Not Enough Memory
Z_BUF_ERROR = -5 'Not enough space in output buffer
End Enum
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
(Dest As Any, Source As Any, ByVal Length As Long)
Private Declare Function Compress Lib "zlib.dll" Alias "compress" _
(Dest As Any, destLen As Any, Src As Any, ByVal srcLen As Long) As Long
Private Declare Function UnCompress Lib "zlib.dll" Alias "uncompress" _
(Dest As Any, destLen As Any, Src As Any, ByVal srcLen As Long) As Long
Private Declare Function Crc32 Lib "zlib.dll" Alias "crc32" _
(ByVal crc As Long, Buffer As Any, ByVal Length As Long) As Long
Private Type typCentralFileHeader
CentralFileHeaderSigniature As Long
VersionMadeBy As Integer
VersionNeededToExtract As Integer
GeneralPurposeBitFlag As Integer
CompressionMethod As Integer
LastModFileTime As Integer
LastModFileDate As Integer
Crc32 As Long
CompressedSize As Long
UnCompressedSize As Long
FileNameLength As Integer
ExtraFieldLength As Integer
FileCommentLength As Integer
DiskNumberStart As Integer
InternalFileAttributes As Integer
ExternalFileAttributes As Long
RelativeOffsetOfLocalHeader As Long
End Type
Private Type typLocalFileHeader
LocalFileHeaderSignature As Long
VersionNeededToExtract As Integer
GeneralPurposeBitFlag As Integer
CompressionMethod As Integer
LastModFileTime As Integer
LastModFileDate As Integer
Crc32 As Long
CompressedSize As Long
UnCompressedSize As Long
FileNameLength As Integer
ExtraFieldLength As Integer
End Type
Private FileName As String
Private ExtraField As String
Private FileComment As String
Private FileData() As Byte
Private LocalFileHeader As typLocalFileHeader
Private CentralFileHeader As typCentralFileHeader
Private Sub Class_Initialize()
With LocalFileHeader
.LocalFileHeaderSignature = &H4034B50
.VersionNeededToExtract = 20
.GeneralPurposeBitFlag = 2
.CompressionMethod = 8
End With
With CentralFileHeader
.CentralFileHeaderSigniature = &H2014B50
.VersionMadeBy = 20
.VersionNeededToExtract = 20
.GeneralPurposeBitFlag = 2
.CompressionMethod = 8
.InternalFileAttributes = 32
End With
End Sub
Friend Property Let FilePath(s As String)
Dim ModDate As Integer
Dim ModTime As Integer
Dim d As Date
d = FileDateTime(s)
ModDate = GetDOSDate(d)
ModTime = GetDOSTime(d)
FileName = GetFileName(s)
With LocalFileHeader
.FileNameLength = Len(FileName)
.LastModFileDate = ModDate
.LastModFileTime = ModTime
CompressBytes s, FileData, .CompressedSize, .UnCompressedSize
End With
With CentralFileHeader
.FileNameLength = Len(FileName)
.LastModFileDate = ModDate
.LastModFileTime = ModTime
.CompressedSize = LocalFileHeader.CompressedSize
.UnCompressedSize = LocalFileHeader.UnCompressedSize
End With
End Property
Friend Property Let NewFileName(s As String)
FileName = s
LocalFileHeader.FileNameLength = Len(FileName)
CentralFileHeader.FileNameLength = Len(FileName)
End Property
Friend Property Let Comment(s As String)
With CentralFileHeader
FileComment = s
.FileCommentLength = Len(s)
End With
End Property
Private Function GetFileName(FilePath As String) As String
Dim a() As String
a = Split(FilePath, "\")
GetFileName = a(UBound(a))
End Function
Private Sub CompressBytes(FilePath As String, FileData() _
As Byte, CompressedSize As Long, UnCompressedSize As Long)
Dim Buffer() As Byte
Dim BufferSize As Long
Dim FileSize As Long
Dim crc As Long
Dim fh As Long
Dim r As Long
fh = FreeFile
Open FilePath For Binary As #fh
FileSize = LOF(fh)
ReDim FileData(FileSize - 1)
Get #fh, , FileData
Close #fh
crc = Crc32(0&, FileData(0), UBound(FileData) + 1)
BufferSize = FileSize * 1.01 + 12
ReDim Buffer(BufferSize) As Byte
r = Compress(Buffer(0), BufferSize, FileData(0), FileSize)
BufferSize = BufferSize - 6
'When using the Compress method, ZLib adds a 2 byte head
'and a 4 byte tail. The head must be removed for zip
'compatability and the tail is not necessary.
ReDim FileData(BufferSize - 1)
CopyMemory FileData(0), Buffer(2), BufferSize
Erase Buffer
LocalFileHeader.Crc32 = crc
CentralFileHeader.Crc32 = crc
UnCompressedSize = FileSize
CompressedSize = BufferSize
End Sub
Friend Sub WriteLocalFileHeader(fh As Long)
CentralFileHeader.RelativeOffsetOfLocalHeader = Loc(fh)
Put #fh, , LocalFileHeader
Put #fh, , FileName
Put #fh, , ExtraField
Put #fh, , FileData
End Sub
Friend Sub WriteCentralFileHeader(fh As Long)
Put #fh, , CentralFileHeader
Put #fh, , FileName
Put #fh, , ExtraField
Put #fh, , FileComment
End Sub
Private Function GetDOSDate(ModDate As Date) As Integer
Dim Day As Long
Dim Month As Long
Dim Year As Long
Dim b(1) As Byte
'There's a Windows API Function FileTimeToDosDateTime
'but I couldn't get it to work so I did this the hard way
'Bits Contents
'0–4 Day of the month (1–31)
'5–8 Month (1 = January, 2 = February, etc.)
'9–15 Year offset from 1980 (add 1980 to get actual year)
Day = DatePart("d", ModDate)
Month = DatePart("m", ModDate)
Year = DatePart("yyyy", ModDate) - 1980
If GetBit(Day, 0) Then SetBit b(0), 0
If GetBit(Day, 1) Then SetBit b(0), 1
If GetBit(Day, 2) Then SetBit b(0), 2
If GetBit(Day, 3) Then SetBit b(0), 3
If GetBit(Day, 4) Then SetBit b(0), 4
If GetBit(Month, 0) Then SetBit b(0), 5
If GetBit(Month, 1) Then SetBit b(0), 6
If GetBit(Month, 2) Then SetBit b(0), 7
If GetBit(Month, 3) Then SetBit b(1), 0
If GetBit(Year, 0) Then SetBit b(1), 1
If GetBit(Year, 1) Then SetBit b(1), 2
If GetBit(Year, 2) Then SetBit b(1), 3
If GetBit(Year, 3) Then SetBit b(1), 4
If GetBit(Year, 4) Then SetBit b(1), 5
If GetBit(Year, 5) Then SetBit b(1), 6
If GetBit(Year, 6) Then SetBit b(1), 7
CopyMemory GetDOSDate, b(0), 2
End Function
Private Function GetDOSTime(ModDate As Date) As Integer
Dim Second As Long
Dim Minute As Long
Dim Hour As Long
Dim b(1) As Byte
'Bits Contents
'0–4 Second divided by 2
'5–10 Minute (0–59)
'11–15 Hour (0–23 on a 24-hour clock)
Second = DatePart("s", ModDate) \ 2
Minute = DatePart("n", ModDate)
Hour = DatePart("h", ModDate)
If GetBit(Second, 0) Then SetBit b(0), 0
If GetBit(Second, 1) Then SetBit b(0), 1
If GetBit(Second, 2) Then SetBit b(0), 2
If GetBit(Second, 3) Then SetBit b(0), 3
If GetBit(Second, 4) Then SetBit b(0), 4
If GetBit(Minute, 0) Then SetBit b(0), 5
If GetBit(Minute, 1) Then SetBit b(0), 6
If GetBit(Minute, 2) Then SetBit b(0), 7
If GetBit(Minute, 3) Then SetBit b(1), 0
If GetBit(Minute, 4) Then SetBit b(1), 1
If GetBit(Minute, 5) Then SetBit b(1), 2
If GetBit(Hour, 0) Then SetBit b(1), 3
If GetBit(Hour, 1) Then SetBit b(1), 4
If GetBit(Hour, 2) Then SetBit b(1), 5
If GetBit(Hour, 3) Then SetBit b(1), 6
If GetBit(Hour, 4) Then SetBit b(1), 7
CopyMemory GetDOSTime, b(0), 2
End Function
Private Sub SetBit(b As Byte, Bit As Long)
b = b Or (2 ^ Bit)
End Sub
Private Function GetBit(l As Long, Bit As Long) As Boolean
GetBit = ((l And 2 ^ Bit) > 0)
End Function
Downloads
Links
© Copyright NoticeUnless otherwise stated, the code on this site is Copyright to Andrew McMillan. You may use this code in your projects (both commercial and non-commercial) but you are not permitted to republish this code in any form without the Author's prior consent. The code on this site is supplied "as is" and no claims are made as to its soundness. The Author claims no responsibility for or liability from use of said source code. Home |
![]() |