First of all, all the zip compression is embedded in an unique Delphi unit, named SynZip.pas, which is meant to replace the default zlib unit of Delphi, and enhance it. It works from
Delphi 7 to Delphi 2010, from the same source code, which is released under a GPL/LGPL/MPL tri-license.
Do you want to compress some data in memory? Use CompressString() and UnCompressString() methods. Both work with RawByteString encoded data:
function test: boolean; var tmp: RawByteString; begin tmp := 'some data'; result := (UnCompressString(CompressString(tmp,False,comp))=tmp); end;
Do you want to create a zip archive file? Use our TZipWrite class.
procedure TestCompression; var FN: TFileName; begin FN := ChangeFileExt(paramstr(0),'.zip'); with TZipWrite.Create(FN) do try AddDeflated('one.exe',pointer(Data),length(Data)); assert(Count=1); AddDeflated('ident.txt',M.Memory,M.Position); assert(Count=2); finally Free; end; end;
Do you want to read a zip archive file? Use our TZipRead class.
procedure TestUnCompression; var FN: TFileName; i: integer; begin FN := ChangeFileExt(paramstr(0),'.zip'); with TZipRead.Create(FN) do try i := NameToIndex('ONE.exe'); assert(i>=0); assert(UnZip(i)=Data); i := NameToIndex('ident.TXT'); assert(i>=0); assert(Entry[i].info^.zcrc32=crc32(0,M.Memory,M.Position)); finally Free; end; DeleteFile(FN); end;
And if you want the file to be embedded to your executable, there is a dedicated Create constructor in the TZipRead just for handling that:
- create your exe file with a TZipRead instance created with it;
- append your zip content to your exe (by using TZipWrite or by using copy /b on command line);
- that's all!
Couldn't it be easier?
Code above was extracted and adapted from our SynSelfTests test unit.
We provided some free and open source classes to read a .zip archive bundled (or not) to an exe. You can therefore append any .zip archive to your exe, then extract any picture inside this .zip with one class.
Use the following method:
constructor TZipRead.Create(const aFileName: TFileName; ZipStartOffset, Size: cardinal);
and provide paramstr(0) - i.e. your exe - as aFileName and ZipStartOffset as a minimal original exe size: it will search for the beginning of the .zip file from this offset. Leave Size parameter as 0: it will get the size from the file size itself.
The same class can get any .zip archive embedded as a resource to your exe, if you prefer.
They are two ways of appending .zip content to an exe:
1. use copy /b original.exe+pictures.zip newembedded.exe
2. use the TZipWrite class provided, and its AddFromZip() method to create your exe from Delphi code: you can even compress and append your images on the fly, with no temporary pictures.zip file.
If you don't need fastest performance available, but very small code size or just prefer one pascal unit, we've release a self-contained 100% pure pascal source code for the same purpose: see our PasZip unit. It contains the same TZipRead and TZipWrite classes. I use this one to create embedded setup files, together with our LVCL units.
The PasZip unit can be accessible in our Source Code repository: login as anonymous, then select Files and get the PasZip unit.
This PasZip unit is expected to work with Delphi up to 2007 version. New Unicode versions (2009/2010) of Delphi are not supported directly: use our SynPas unit instead, of make the corrections.
I've used such a method, embedding all necessary files as a .zip archive inside the exe resources, then extracting on request.
For example, we use HunSpell.dll external library to implement the spell checking of our SynProject open source tool.
Here is some extract from ProjectSpellCheck.pas:
constructor THunSpell.Create(DictionaryName: string=''); var Temp, HunSpell, Aff, Dic: TFileName; i: integer; begin if DictionaryName='' then DictionaryName := 'en_US'; Temp := GetSynopseCommonAppDataPath; HunSpell := Temp+'hunspell.dll'; with TZipRead.Create(HInstance,'Zip','ZIP') do try Aff := DictionaryName+'.aff'; if not FileExists(Temp+Aff) then StringToFile(Temp+Aff,UnZip(NameToIndex(Aff))); Dic := DictionaryName+'.dic'; if not FileExists(Temp+Dic) then StringToFile(Temp+Dic,UnZip(NameToIndex(Dic))); if not FileExists(HunSpell) then StringToFile(HunSpell,UnZip(NameToIndex('hunspell.dll'))); finally Free; end; fHunLib := SafeLoadLibrary(HunSpell); if fHunLib=0 then exit; if not LoadEntryPoints then begin FreeLibrary(fHunLib); fHunLib := 0; exit; end; fDictionaryName := DictionaryName; fHunHandle := Hunspell_create(pointer(Temp+Aff),pointer(Temp+Dic)); if fHunHandle=nil then exit; (....) end;
We extract both hunspell.dll and all dictionary files from an embedded .zip resource. It use our free SynZip unit.
See http://synopse.info/fossil/artifact/4ca … a7726e27f9
Here is the content of the ProjectRes.rc file used to create the resource:
Default TXT "Default.ini" Zip ZIP "ProjectRes.zip" wizard2 10 "wizard2.png"
All necessary files are stored in the ProjectRes.Zip archive, then extracted on the fly.
TZipRead and TZipWrite now handle Unicode file name inside zip (UTF-8 encoded).
Follows appendix D as specified by http://www.pkware.com/documents/casestudies/APPNOTE.TXT
Under Unicode Delphi (Delphi 2009/2010/XE), it will allow full Unicode encoding of file name inside the .zip.
It will also allow TZipWrite to append some content to an existing .zip file, with no copy on disk: it will be very fast e.g. for .log adding into a .zip archive.
For both units SynZip and SynZipFiles
See http://synopse.info/fossil/fdiff?v1=42b … 89b5a55f86
and http://synopse.info/fossil/fdiff?v1=48b … b22b2ca8c1
how can unzip password protected zip?
how can unzip password protected zip?
This feature is not implemented yet.
You can use your own encryption using the SynCrypto unit, if you wish, but it won't be the standard Zip encryption.
For standard Zip encryption (AES or RC4), you'll have to use another unit.
what is `data` in the following code?
var FN: TFileName;
FN := ChangeFileExt(paramstr(0),'.zip');
with TZipWrite.Create(FN) do
AddDeflated('one.exe',pointer(Data),length(Data)); //here what is data?
AddDeflated('ident.txt',M.Memory,M.Position); //here what is M?
im trying this code for compressing a jpg file
var FN: TFileName;
Data : string;
FN := ChangeFileExt(paramstr(0),'.zip');
with TZipWrite.Create(FN) do
AddDeflated('C:\Documents and Settings\All Users\Documents\leakTracker\14_06_12 11_33_50 AM.jpg',pointer(Data),length(Data));
on E : Exception do
it does not work, it shows the zip size a s 1 KB
can you tell me what is wrong?
Both data and M are members of the test class.
They contain some data used to be compressed by the test routines.
Just click on variable with Ctrl on keyboard to navigate to the declaration of each variable.
So you are compressing the test data and name it as a .jpg file.
Ok, got it
procedure SynZipZipper(sZipFileName,sJpegFileName : string); var Data : string; sUmlDPath : string; begin if FileExists(sZipFileName) then DeleteFile(pchar(sZipFileName)); with TZipWrite.Create(sZipFileName) do try AddDeflated(sJpegFileName); AddDeflated(FullUmldFilePath); free; except on E : Exception do begin free; end; end; end;
procedure synZipZipper(const aSourceFilename,aTargetFilename:string); begin if (fileExists(aTargetFilename)) then DeleteFile(aTargetFilename); with TZipWrite.Create(aTargetFilename) do try AddDeflated(aSourceFilename) finally free end end;
This is indeed better with a try... finally Free end to protect your TZipWrite instance.
Like any other Delphi class, by the way.
As I wrote above - see http://synopse.info/forum/viewtopic.php?pid=163#p163
I zipped a file in Java using a ZipOutputStream object and when I try to unzip it in Delphi using the TZipRead class I get the error: "Error -2 during zip/deflate process."
Any ideas on how to fix it?
Thanks a lot!
Thanks for your reply.
Yes, I can open the generated file within Windows Explorer and 7-Zip.
The .zip file you sent to me is in an unsupported format.
The local fileInfo block has zcrc32=0 zzipSize=0 zfullZize=0.
This is clearly stated by the http://www.pkware.com/documents/casestudies/APPNOTE.TXT specifications as a possibility, if the bit 3 of the fileInfo "flags" is set.
A "data descriptor" block has to be recognized and read instead of the local fileInfo block.
We have therefore fixed UnZip() when crc and sizes are stored not within the file header but in this separate data descriptor block, after the compressed data.
It will either:
- Use the ending "central directory" information;
- Or search manually the "data descriptor" from the binary local data.
We have also added a new TZipRead.RetrieveFileInfo() method for this purpose.
Congrats, once again your code is the fastest of various I tried:
KAzip (very old component/library, had to update it for unicode/modern delphi)
Both for listing and extracting your unit was the fastest after a series of benchmarks (listing the contents of 1000 zips, then extracting 1 a few times over).
Synopse was in both cases some ~10-20% faster than the other 2.
How do you extract a file btw? maybe my proc can be improved someway:
ss: TZipRead; aa: RawByteString; ss := TZipRead.Create(zip); c := ss.NameToIndex(fil); SetLength(aa,ss.Entry[c].infoLocal.zfullSize); aa := ss.UnZip(c); With TFileStream.Create(s,fmCreate) do begin Write(aa,ss.Entry[c].infoLocal.zfullSize); Free; end;
The following methods are able to unzip files directly into a destination folder:
/// uncompress a file stored inside the .zip archive into a destination directory function UnZip(aIndex: integer; const DestDir: TFileName; DestDirIsFileName: boolean=false): boolean; overload; /// uncompress a file stored inside the .zip archive into a destination directory function UnZip(const aName, DestDir: TFileName; DestDirIsFileName: boolean=false): boolean; overload; /// uncompress all fields stored inside the .zip archive into the supplied // destination directory // - returns -1 on success, or the index in Entry of the failing file function UnZipAll(DestDir: TFileName): integer;
Ensure you retrieved the latest version from our source code repository.
That works great, didn't see the overloads somehow, thanks.
ps: does the board support watching threads? it would be nice to be able to receive email notifications of replies.
Reporting a possible issue: I'm getting an exception "ZIP format." with a very specific zip file, after calling
it triggers here in SynZip.pas
for i := 0 to 127 do begin // resources size may be rounded up to alignment lhr := @BufZip[Size-sizeof(TLastHeader)]; if lhr^.signature+1=(LASTHEADER_SIGNATURE+1) then break; dec(Size); if Size<=sizeof(lhr^) then break; end; if lhr^.signature+1<>(LASTHEADER_SIGNATURE+1) then begin UnMap; raise ESynZipException.Create('ZIP format'); end;
The file opens fine with winrar and 7zip, and if I make a new file with the contents (unpack and repack with winrar), it works fine as well with SynZip
That works. In the case of 2 files I found with this issue, "i" was 1233 and 2714 after the loop (I set 65535 max)
Another issue with some other files:
"Zip error: ZIP format size=0"
In 2 cases I found (working fine with winrar etc), both zzipSize and zfullSize were 0
TZipRead.UnZip is declared as returning a string, but it seems to be a relic of pre-Unicode Delphi, shouldn't it be a an AnsiString or a byte array? it's in PasZip, which apparently is a relic
In SynZip, TZipRead.Create raises an exception when a an entry has zero bytes, but a zero size file in a zip is valid.
Last edited by Eric (2015-06-04 11:13:14)
How can I store relative paths using TZipWrite? Thanks!
Last edited by paolo.sciarrini (2015-07-30 14:45:16)
I use SynZip.pas on Lazarus. When I create zip archive with unicode filenames, it work fine, but when unzip, have error. After some changes in SynZip.pas I have support for unicode names in zip archive.
1) In uses add LazFileUtils
2) In constructor TZipRead.Create(const aFileName: TFileName; ZipStartOffset, Size: cardinal);
file_ := CreateFile(pointer(aFileName), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, 0, 0);
file_ := CreateFileW(PWideChar(WideString(aFileName)), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, 0, 0);
constructor TZipRead.Create(const aFileName: TFileName; ZipStartOffset, Size: cardinal); begin file_ := CreateFileW(PWideChar(WideString(aFileName)), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, 0, 0); Create(file_,ZipStartOffset, Size); end;
3) In function EnsurePath(var Path: TFileName): boolean
if DirectoryExists(Path) then ... if CreateDirectory(pointer(path),nil) then
if DirectoryExistsUTF8(Path) then ... if CreateDirectoryW(PWideChar(WideString(path)),nil) then
function EnsurePath(var Path: TFileName): boolean; var Parent: TFileName; begin result := false; if Path='' then exit; if Path[length(Path)]<>'\' then Path := Path+'\'; if DirectoryExistsUTF8(Path) then result := true else begin Parent := ExtractFilePath(system.copy(Path,1,length(Path)-1)); if (Parent<>'') and not DirectoryExists(Parent) then if not EnsurePath(Parent) then exit; if CreateDirectoryW(PWideChar(WideString(path)),nil) then result := true; end; end;
After this changes I can unzip files with unicode filenames