trancexx Posted October 6, 2013 Share Posted October 6, 2013 (edited) Since XP Windows users have ability to view, compress, extract zipped files natively. Microsoft's developers decided to create shell extension that does this job.Shell extension is library that shell (explorer.exe) uses to communicate with in order to achieve results. In case of ZIP files this particular library is zipfldr.dll. How does it work?By default when you double-click ZIP file, explorer opens it allowing you to browse around it pretty much the same way as you would through the ordinary folder. To have that ability Microsoft developed COM based technology (methodology) known as Shell Extensibility. Every shell extension has to follow strict and documented set of rules which shell then uses to populate graphical interface user uses. I won't be going into details about registering shell extension, only about how they are meant to be implemented.The main interface every shell extension should implement is IShellFolder. That's the basic interface that would give all the information about the extension and it would be used to create instances of every other wanted or needed interface. Obvious question could be how to create object of IShellFolder interface for the extension. In case shell extension is implemented in form of DLL library then that particular dll must export function called DllGetClassObject. This function is used to create object of IClassFactory interface which is then used to get mentioned IShellFolder. In AutoIt it's very simple to do this first step. Ordinary DllCall in combination with ObjCreateInterface and voila, first helper function is done. I will call it __UnZIP_DllGetClassObject:Func __UnZIP_DllGetClassObject($sDll, $sCLSID, $sIID, $tagInterface = "") Local $tCLSID = __UnZIP_GUIDFromString($sCLSID) Local $tIID = __UnZIP_GUIDFromString($sIID) Local $aCall = DllCall($sDll, "long", "DllGetClassObject", "struct*", $tCLSID, "struct*", $tIID, "ptr*", 0) If @error Or $aCall[0] Then Return SetError(1, 0, 0) Local $oObj = ObjCreateInterface($aCall[3], $sIID, $tagInterface) If @error Then Return SetError(2, 0, 0) Return $oObj EndFunc Func __UnZIP_GUIDFromString($sGUID) Local $tGUID = DllStructCreate("byte[16]") DllCall("ole32.dll", "long", "CLSIDFromString", "wstr", $sGUID, "struct*", $tGUID) If @error Then Return SetError(1, 0, 0) Return $tGUID EndFuncTo call the function I have to DllOpen zipfldr.dll, define CLSID_CZipFolder and of course, IClassFactory:Global Const $hZIPFLDR_DLL = DllOpen("zipfldr.dll") Global Const $CLSID_CZipFolder = "{e88dcce0-b7b3-11d1-a9f0-00aa0060fa31}" ;=============================================================================== #interface "IClassFactory" Global Const $sIID_IClassFactory = "{00000001-0000-0000-C000-000000000046}" Global Const $tagIClassFactory = "CreateInstance hresult(ptr;clsid;ptr*);" & _ "LockServer hresult(bool);" ;=============================================================================== Local $oClassFactory = __UnZIP_DllGetClassObject($hZIPFLDR_DLL, $CLSID_CZipFolder, $sIID_IClassFactory, $tagIClassFactory)Now that I have object of IClassFactory interface, I can simply ask for IShellFolder object pointer:Local $pShellFolder $oClassFactory.CreateInstance(0, $sIID_IShellFolder, $pShellFolder)... And after defining IShellFolder interface create the ShellFolder object:;=============================================================================== #interface "IShellFolder" Global Const $sIID_IShellFolder = "{000214E6-0000-0000-C000-000000000046}" Global $tagIShellFolder = "ParseDisplayName hresult(hwnd;ptr;wstr;ulong*;ptr*;ulong*);" & _ "EnumObjects hresult(hwnd;dword;ptr*);" & _ "BindToObject hresult(struct*;ptr;clsid;ptr*);" & _ "BindToStorage hresult(struct*;ptr;clsid;ptr*);" & _ "CompareIDs hresult(lparam;struct*;struct*);" & _ "CreateViewObject hresult(hwnd;clsid;ptr*);" & _ "GetAttributesOf hresult(uint:struct*;ulong*);" & _ "GetUIObjectOf hresult(hwnd;uint;struct*;clsid;uint*;ptr*);" & _ "GetDisplayNameOf hresult(struct*;dword;struct*);" & _ "SetNameOf hresult(hwnd;struct*;wstr;dword;struct*);" ;=============================================================================== $oShellFolder = ObjCreateInterface($pShellFolder, $sIID_IShellFolder, $tagIShellFolder)The next step is to load the ZIP file. IShellFolder doesn't have method for this but it can be asked for either IPersistFile or IPersistFolder interface objects.Which interface can be used to load the ZIP depends on system. In my tests I have found that IPersistFile works on Windows 7 and IPersistFolder on Windows 8 and XP. I will go with IPersistFile first and use IPersistFolder as an alternative in case of failure. It's really irrerelevant as long as ZIP gets loaded. These both interfaces inherit from IPersist so to define them correctly I need to define that one first:expandcollapse popup;=============================================================================== #interface "IPersist" Global Const $sIID_IPersist = "{0000010c-0000-0000-C000-000000000046}" Global $tagIPersist = "GetClassID hresult(ptr*);" ;=============================================================================== ;=============================================================================== #interface "IPersistFile" Global Const $sIID_IPersistFile = "{0000010b-0000-0000-C000-000000000046}" Global $tagIPersistFile = $tagIPersist & _ "IsDirty hresult();" & _ "Load hresult(wstr;dword);" & _ "Save hresult(wstr;bool);" & _ "SaveCompleted hresult(wstr);" & _ "GetCurFile hresult(ptr*);" ;=============================================================================== ;=============================================================================== #interface "IPersistFolder" Global Const $sIID_IPersistFolder = "{000214EA-0000-0000-C000-000000000046}" Global $tagIPersistFolder = $tagIPersist & _ "Initialize hresult(ptr);" ;=============================================================================== ;... code below goes to some function $oPersistF = ObjCreateInterface($pShellFolder, $sIID_IPersistFile, $tagIPersistFile) If @error Then ; Try IPersistFolder $oPersistF = ObjCreateInterface($pShellFolder, $sIID_IPersistFolder, $tagIPersistFolder) If @error Then Return SetError(3, 0, 0) Else If $oPersistF.Initialize(__UnZIP_SHParseDisplayName($sZIP)) < $S_OK Then Return SetError(4, 0, 0) EndIf Else Local Const $STGM_READ = 0x00000000 If $oPersistF.Load($sZIP, $STGM_READ) < $S_OK Then Return SetError(4, 0, 0) EndIf ;... Func __UnZIP_SHParseDisplayName($sPath) Local $aCall = DllCall("shell32.dll", "long", "SHParseDisplayName", "wstr", $sPath, "ptr", 0, "ptr*", 0, "ulong", 0, "ulong*", 0) If @error Or $aCall[0] Then Return SetError(1, 0, 0) Return $aCall[3] EndFuncNow that ZIP is loaded zipfldr will do its job of unzipping a file. If you look back to IShellFolder definition you'll see that the object of that interface exposes function/method called ParseDisplayName. When invoked the interface will internally locate wanted file and give back pointer to PIDL (a form of item identifier uniquely identifying it). This data is then passed to BindToStorage.BindToStorage is function that accepts IID of the interface you want to bind item to. In my case I will go for IStream so that I can easily get decompressed binary data out of it. Before I show how to do that a word about ParseDisplayName. It appears that "paths" inside the ZIP use either forward slash or backslash depending on system. For example if you ZIP have folder "ABC" with file "x.txt" in it, on XP it would be "ABC/x.txt" and on newer systems it'd be "ABCx.txt". So in case of failure with one I'll go with another.Local Const $SFGAO_STREAM = 0x00400000 ;... Local $pPidl = 0 Local $iAttributes = $SFGAO_STREAM ; Find the file inside the ZIP If $oShellFolder.ParseDisplayName(0, 0, $sFile, 0, $pPidl, $iAttributes) < $S_OK Then $sFile = StringReplace($sFile, "\", "/") ; XP uses forward slash apparently If $oShellFolder.ParseDisplayName(0, 0, $sFile, 0, $pPidl, $iAttributes) < $S_OK Then Return SetError(5, 0, 0) EndIf...Back to IStream (inherits from ISequentialStream):;=============================================================================== #interface "ISequentialStream" Global Const $sIID_ISequentialStream = "{0c733a30-2a1c-11ce-ade5-00aa0044773d}" Global Const $tagISequentialStream = "Read hresult(struct*;dword;dword*);" & _ "Write hresult(struct*;dword;dword*);" ;=============================================================================== ;=============================================================================== #interface "IStream" Global Const $sIID_IStream = "{0000000c-0000-0000-C000-000000000046}" Global Const $tagIStream = $tagISequentialStream & _ "Seek hresult(int64;dword;uint64*);" & _ "SetSize hresult(uint64);" & _ "CopyTo hresult(ptr;uint64;uint64*;uint64*);" & _ "Commit hresult(dword);" & _ "Revert hresult();" & _ "LockRegion hresult(uint64;uint64;dword);" & _ "UnlockRegion hresult(uint64;uint64;dword);" & _ "Stat hresult(struct*;dword);" & _ "Clone hresult(ptr*);" ;=============================================================================== ;... Local $pStream If $oShellFolder.BindToStorage($pPidl, 0, $sIID_IStream, $pStream) <> $S_OK Then Return SetError(6, __UnZIP_CoTaskMemFree($pPidl), 0) Local $oStream = ObjCreateInterface($pStream, $sIID_IStream, $tagIStream)And that would be it as far as unzipping goes.Getting binary data out of stream is off-topic here. The udf uses function UnZIP_SaveFileToBinary for that. If you are interested then take a look at that inside the attached file.To use functions from the UDF to for example, extract file named "test.txt" inside folder named "Test" inside the folder named "Abc" inside the ZIP file "Testing.zip", you would call it like this:$sZIP = "Testing.zip" ; full path to your zip $sFile = "Abc\Test\test.txt" ; file inside the zip $bBinary = UnZIP_SaveFileToBinaryOnce($sZIP, $sFile) ; to extract to variable ; Maybe to print to console? ConsoleWrite(BinaryToString($bBinary) & @CRLF)Or if you'd want to extract it to some location on your disk:$sZIP = "Testing.zip" ; full path to your zip $sFile = "Abc\Test\test.txt" ; file inside the zip $sDestination = @DesktopDir & "\test.txt" UnZIP_SaveFileToFileOnce($sZIP, $sFile, $sDestination)For repetitive tasks on the same ZIP file you wouldn't want to use ...Once() functions, but rather than that functions without that suffix to avoid internal parsing of the whole ZIP for every file inside:$sZIP = "Testing.zip" ; full path to your zip $sFile0 = "Abc\Test\test0.txt" ; file 0 inside the zip $sFile1 = "Abc\Test\test1.txt" ; file 1 inside the zip $sFile5 = "Abc\Test\Whatever\test5.txt" ; file 5 inside the zip Local $oShellFolder, $oPersistF $bBinary = UnZIP_SaveFileToBinary($sZIP, $sFile0, $oShellFolder, $oPersistF) ; to extract to variable ; Print content of file 0 to console ConsoleWrite(BinaryToString($bBinary) & @CRLF) $bBinary = UnZIP_SaveFileToBinary($sZIP, $sFile1, $oShellFolder, $oPersistF) ; to extract to variable ; Print content of file 1 to console ConsoleWrite(BinaryToString($bBinary) & @CRLF) $bBinary = UnZIP_SaveFileToBinary($sZIP, $sFile5, $oShellFolder, $oPersistF) ; to extract to variable ; Print content of file 5 to console ConsoleWrite(BinaryToString($bBinary) & @CRLF) ; Free objects $oShellFolder= 0 $oPersistF = 0...You get the idea.Technique used here is pretty much unique. I have't seen any code that uses this to-the-point method written in any language.To use it backward check this article.Oh and I took CLSID_CZipFolder name from here because they deserve it.UDF:UnZIP.au3You can find example of usage >here. Edited October 8, 2013 by trancexx Gianni, mLipok, Mugen and 1 other 4 ♡♡♡ . eMyvnE Link to comment Share on other sites More sharing options...
mLipok Posted October 6, 2013 Share Posted October 6, 2013 For a long time I was looking for something like this, I have not checked, and now I like it. Best regards mlipok Signature beginning:* Please remember: "AutoIt"..... * Wondering who uses AutoIt and what it can be used for ? * Forum Rules ** ADO.au3 UDF * POP3.au3 UDF * XML.au3 UDF * IE on Windows 11 * How to ask ChatGPT for AutoIt Code * for other useful stuff click the following button: Spoiler Any of my own code posted anywhere on the forum is available for use by others without any restriction of any kind. My contribution (my own projects): * Debenu Quick PDF Library - UDF * Debenu PDF Viewer SDK - UDF * Acrobat Reader - ActiveX Viewer * UDF for PDFCreator v1.x.x * XZip - UDF * AppCompatFlags UDF * CrowdinAPI UDF * _WinMergeCompare2Files() * _JavaExceptionAdd() * _IsBeta() * Writing DPI Awareness App - workaround * _AutoIt_RequiredVersion() * Chilkatsoft.au3 UDF * TeamViewer.au3 UDF * JavaManagement UDF * VIES over SOAP * WinSCP UDF * GHAPI UDF - modest begining - comunication with GitHub REST API * ErrorLog.au3 UDF - A logging Library * Include Dependency Tree (Tool for analyzing script relations) * Show_Macro_Values.au3 * My contribution to others projects or UDF based on others projects: * _sql.au3 UDF * POP3.au3 UDF * RTF Printer - UDF * XML.au3 UDF * ADO.au3 UDF * SMTP Mailer UDF * Dual Monitor resolution detection * * 2GUI on Dual Monitor System * _SciLexer.au3 UDF * SciTE - Lexer for console pane * Useful links: * Forum Rules * Forum etiquette * Forum Information and FAQs * How to post code on the forum * AutoIt Online Documentation * AutoIt Online Beta Documentation * SciTE4AutoIt3 getting started * Convert text blocks to AutoIt code * Games made in Autoit * Programming related sites * Polish AutoIt Tutorial * DllCall Code Generator * Wiki: * Expand your knowledge - AutoIt Wiki * Collection of User Defined Functions * How to use HelpFile * Good coding practices in AutoIt * OpenOffice/LibreOffice/XLS Related: WriterDemo.au3 * XLS/MDB from scratch with ADOX IE Related: * How to use IE.au3 UDF with AutoIt v3.3.14.x * Why isn't Autoit able to click a Javascript Dialog? * Clicking javascript button with no ID * IE document >> save as MHT file * IETab Switcher (by LarsJ ) * HTML Entities * _IEquerySelectorAll() (by uncommon) * IE in TaskScheduler * IE Embedded Control Versioning (use IE9+ and HTML5 in a GUI) * PDF Related: * How to get reference to PDF object embeded in IE * IE on Windows 11 * I encourage you to read: * Global Vars * Best Coding Practices * Please explain code used in Help file for several File functions * OOP-like approach in AutoIt * UDF-Spec Questions * EXAMPLE: How To Catch ConsoleWrite() output to a file or to CMD *I also encourage you to check awesome @trancexx code: * Create COM objects from modules without any demand on user to register anything. * Another COM object registering stuff * OnHungApp handler * Avoid "AutoIt Error" message box in unknown errors * HTML editor * winhttp.au3 related : * https://www.autoitscript.com/forum/topic/206771-winhttpau3-download-problem-youre-speaking-plain-http-to-an-ssl-enabled-server-port/ "Homo sum; humani nil a me alienum puto" - Publius Terentius Afer"Program are meant to be read by humans and only incidentally for computers and execute" - Donald Knuth, "The Art of Computer Programming" , be and \\//_. Anticipating Errors : "Any program that accepts data from a user must include code to validate that data before sending it to the data store. You cannot rely on the data store, ...., or even your programming language to notify you of problems. You must check every byte entered by your users, making sure that data is the correct type for its field and that required fields are not empty." Signature last update: 2023-04-24 Link to comment Share on other sites More sharing options...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now