Jump to content
Sign in to follow this  
trancexx

Unzip using standard system dll

Recommended Posts

trancexx

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
EndFunc
To 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:

;===============================================================================
#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]
EndFunc
Now 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.au3

You can find example of usage >here.

Edited by trancexx
  • Like 2

♡♡♡

.

eMyvnE

Share this post


Link to post
Share on other sites
mLipok
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:   Wondering who uses AutoIT and what it can be used for ?
* GHAPI UDF - modest begining - comunication with GitHub REST API Forum Rules *
ADO.au3 UDF     POP3.au3 UDF     XML.au3 UDF    How to use IE.au3  UDF with  AutoIt v3.3.14.x  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 APIErrorLog.au3 UDF - A logging Library
 

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 *

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 * 

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

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 *

"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"
:naughty:  :ranting:, 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: 2018-10-31

Share this post


Link to post
Share on other sites

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 account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

×