Sunday, December 27, 2009Using Windows Explorer from time to time to open ZIP files or create ZIP files, I knew that Windows can internally manage ZIP files so it was a matter of searching via what API this functionality is exposed to use it from applications. And yes, it is effectively exposed, albeit in a limited way via OLE automation with Shell.Application. This API makes it fairly easy to zip or unzip a ZIP file. Here is a function for using the API from Delphi to unzip a file. The filter parameter is optional and can be used to extract only files that match the filter condition.
In this function, the progress dialog has been set hidden (with the flag SHCONTCH_NOPROGRESSBOX). The call CopyHere() is blocking for unzipping files. This means that this function will return after all files are effectively unzipped.
const SHCONTCH_NOPROGRESSBOX = 4; SHCONTCH_AUTORENAME = 8; SHCONTCH_RESPONDYESTOALL = 16; SHCONTF_INCLUDEHIDDEN = 128; SHCONTF_FOLDERS = 32; SHCONTF_NONFOLDERS = 64; function ShellUnzip(zipfile, targetfolder: string; filter: string = ''): boolean; var shellobj: variant; srcfldr, destfldr: variant; shellfldritems: variant; begin shellobj := CreateOleObject('Shell.Application'); srcfldr := shellobj.NameSpace(zipfile); destfldr := shellobj.NameSpace(targetfolder); shellfldritems := srcfldr.Items; if (filter <> '') then shellfldritems.Filter(SHCONTF_INCLUDEHIDDEN or SHCONTF_NONFOLDERS or SHCONTF_FOLDERS,filter); destfldr.CopyHere(shellfldritems, SHCONTCH_NOPROGRESSBOX or SHCONTCH_RESPONDYESTOALL); end;
To create a ZIP file, the Shell.Application CopyHere() API expects that the ZIP file already exists. It is as such necessary to first create an empty ZIP file. This is fortunately easy and this is also what the proposed Delphi function here does. Another problem with creating a ZIP file is that in this case the CopyHere() function is not blocking. This means that the call to CopyHere() returns immediately and the shell creates threads that perform the actual compressing. This is quite inconvenient if your code needs to do further actions on the compressed file. To workaround this issue, we simply track the number of process threads and wait till all threads created by the shell are terminated. The resulting Delphi code is:
Unfortunately, to compress files the flag to hide the shell progress dialog doesn't work. According to Microsoft this is intentional.
// counts the number of threads in the process function NumProcessThreads: integer; var hsnapshot: THandle; Te32: TTHREADENTRY32; proch: dword; procthreads: integer; begin procthreads := 0; proch := GetCurrentProcessID; hSnapShot := CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); Te32.dwSize := sizeof(TTHREADENTRY32); if Thread32First(hSnapShot, Te32) then begin if te32.th32OwnerProcessID = proch then inc(procthreads); while Thread32Next(hSnapShot, Te32) do begin if te32.th32OwnerProcessID = proch then inc(procthreads); end; end; CloseHandle (hSnapShot); Result := procthreads; end; function ShellZip(zipfile, sourcefolder:string; filter: string = ''): boolean; const emptyzip: array[0..23] of byte = (80,75,5,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0); var ms: TMemoryStream; shellobj: variant; srcfldr, destfldr: variant; shellfldritems: variant; numt: integer; begin if not FileExists(zipfile) then begin // create a new empty ZIP file ms := TMemoryStream.Create; ms.WriteBuffer(emptyzip, sizeof(emptyzip)); ms.SaveToFile(zipfile); ms.Free; end; numt := NumProcessThreads; shellobj := CreateOleObject('Shell.Application'); srcfldr := shellobj.NameSpace(sourcefolder); destfldr := shellobj.NameSpace(zipfile); shellfldritems := srcfldr.Items; if (filter <> '') then shellfldritems.Filter(SHCONTF_INCLUDEHIDDEN or SHCONTF_NONFOLDERS or SHCONTF_FOLDERS,filter); destfldr.CopyHere(shellfldritems, 0); // wait till all shell threads are terminated while NumProcessThreads <> numt do begin sleep(100); end; end;
This blog post has received 14 comments.
Previous | Next | Index