Click here to Skip to main content
Click here to Skip to main content

C# (.NET) Interface for 7-Zip Archive DLLs

By , 20 Jun 2008
 
Prize winner in Competition "Best C# article of June 2008"

About 7-Zip

7-Zip is an open-source archive program with plug-in interface. New archive formats and/or archive codecs can be added by DLLs. 7-Zip ships with several archive formats preinstalled:

  • 7z — its own format features good compression (LZMA, PPMd) but can be slow in terms of packing/unpacking
  • Packing / unpacking: ZIP, GZIP, BZIP2 and TAR
  • Unpacking only: RAR, CAB, ISO, ARJ, LZH, CHM, Z, CPIO, RPM, DEB and NSIS

The project is written in C++ language.

You can find more on the official 7-Zip site — here.

About this Contribution

This contribution allows you to use 7-zip archive format DLLs in your programs written in .NET languages.

I created this module for my own project to have the ability to work with archives. Currently my project only has extract capabilities, so only this part of 7-Zip interface translated to C#. Later I plan to translate compress capability as well. For now if you need such functionality you can implement it by yourself, with this code, and the 7-Zip source code.

This translation is tested and already working in my own project.

Implementation Details

All communication with archive DLLs is done with COM-like interfaces (why COM-like, and not COM can be see in the known issues section). Callbacks are also implemented as interfaces.

Every DLL contains a class that can implement one or more interface. Some formats allows only extracting, some also provide compress abilities. Public interfaces translated to C#:

  • IProgress - basic progress callback
  • IArchiveOpenCallback - archive open callback
  • ICryptoGetTextPassword - callback for prompt password for archive
  • IArchiveExtractCallback - extract files from archive callback
  • IArchiveOpenVolumeCallback - open additional archive volumes callback
  • ISequentialInStream - simple read-only stream interface
  • ISequentialOutStream - simple write-only stream interface
  • IInStream - input stream interface with seek capability
  • IOutStream - output stream interface
  • IInArchive - main archive interface

Every DLL export function is for creating archive class handlers and functions in order to get archive format properties. These functions are translated as .NET delegates:

  • CreateObject - creates object with given class id. Used mostly for create IInArchive instance.
  • GetHandlerProperty - get archive format description (implemented class ids, default archive extension, etc)

Update (1.3): In 7-Zip 4.45 there are some changes in the DLL interface. Now all archive formats and compression codecs are implemented as one big DLL. So several new exported functions (and delegates for these functions in translation) are added to handle several archive handler classes in one DLL.

Points of Interest

7-Zip interfaces uses variants (PropVariant) for property values. C# does not support such variants as classes and all such parameters are implemented in C# as IntPtr. This is done for compatibility and because I prefer not to use unsafe code in my projects.

Fortunately a managed class System.Runtime.InteropServices.Marshal has a method GetObjectForNativeVariant that you can use for converting such "pointers" to objects. However this method does not handle all PropVariant types (for example VT_FILETIME), so for these cases I added my GetObjectForNativeVariant method to this translation.

7-Zip works with files through its own interfaces, so if you want to open file on disk, or in memory you need to provide class implement one or more necessary interfaces. Several such wrapper classes are also present in this translation (they are wrap around standard .NET Stream class).

Update (1.2): Most of the complexity related to PropVariant processing is now hidden in special PropVariant structure. And interface methods now return PropVariant instead of IntPtr.

Known Issues

The first and most disappointing issue is that you cannot use 7-Zip DLLs directly. This means that you cannot simply take such DLLs from 7-Zip distribution and use them in your projects. This is because of the incomplete COM interfaces implementations in 7-Zip code. All issues are related to IUnknown.QueryInterface implementation. 7-Zip's QueryInterface does not return IUnknown interface if prompted (this part is most critical for working with COM-interfaces in .NET), and some classes do not return any interface at all!

This is done because 7-Zip code is C++ code and works with pointers, and most functions returns direct pointers to interface implementation. That means that 7-Zip code not use QueryInterface at all. Sad, but .NET works in a different way, and the first access to any interface always goes though QueryInterface and IUnknown.

So if we use DLLs directly we have constant InvalidCastException. So we need to make several changes in 7-Zip code and rebuild DLLs. Or ask Igor Pavlov to include such changes to the 7-Zip code itself :)

Important Update: Starting from 7-Zip 4.46 alpha Igor did necessary changes in the code. So, from this version forward, you can use format DLLs directly, without applying any patch. Superb!

The second issue is much smaller one. It is related to multi-threading. If you plan to use 7-Zip interfaces only in one stream you have no problem. The problem comes when you try to use one interface in several threads. In this case all threads except the main one (threads where interfaces are created) throw exceptions on any interface method calls. This is because of RCW behavior. RCW is an object that wraps COM-interface in .NET. When you try to use interface in different thread RCW tries to marshal interface and fails (because this implementation does not support ITypeInfo).

Fortunately I've found simple solutions for this. Main interface (IInArchive) returns as IntPtr, and not as RCW object. When you need to access this interface, call System.Runtime.InteropServices.Marshal.GetTypedObjectForIUnknown or any other related method and get RCW object. If you need to use this interface in another thread simple call System.Runtime.InteropServices.Marshal.FinalReleaseComObject (or ReleaseComObject), and create another RCW wrapper around returned IntPtr pointer. Of course in this case you can use interface only in one thread in time, but this is better than using interface only in one thread. And any logic can be easily implemented with correct thread locking.

And the third issue is a well known issue but I think it must be noted here. It appears that .NET runtime does not support COM interfaces inheritance (interfaces marked with the ComImport attribute). This is definitely a .NET bug, but I don't know when Microsoft fixes this bug, or fix if they fix it at all.

There is simple solution to avoid this bug. Inherited interface must be declared as standalone one and first methods must be methods of inherited interfaces in the order of appearance. You can see sample of such "inheritance" in this translation source.

Demo

Due to many requests, I have spend some time and written a little demo program. The demo program lacks proper error checking, lacks different archives support (zip format is hardcoded in source, but can be easily changed), it lacks almost everything, but it has two advantages: it's simple, and it works.

The demo has only two modes, the first to list all files in archive, and the second is to extract a single file from the archive. I think that this is enough to understand how to use 7-zip interfaces and how to create something more complex.

If you want to run demo, don't forget to put 7z.dll (can be found on official 7-zip site) to the executable folder with executable.

Version History

1.5 - Small demo added
1.3 - Added two new delegate for features added in 7-Zip 4.45
1.2 - Variant type changed from IntPtr to newly created PropVariant structure
1.1 - Stream wrappers added, minor interface translation changes for better usability
1.0 - initial release

License

This article, along with any associated source code and files, is licensed under The MIT License

About the Author

Eugene Sichkar
Architect
Belarus Belarus
Member
My English is not very good and I know this. So, if you find any translation bugs, misspelled words or sentences on these pages, please, drop a line to my email.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
NewsImportant: Interface translation for packing and updated demomemberEugene Sichkar7 Dec '08 - 7:10 
Hello everyone. I have finished packing support, and even more, I've spent some time, and demo was updated with simple packing support, so you can find basic using principles of IOutArchive interface.
Sadly, but I did not found a way to update this article on CodeProject. And right now article is updated only on my site (http://dev.nomad-net.info/articles/sevenzipinterface[^]).
 
P.S. Sorry that update was taken so much time.
P.P.S. Article will be updated as soon as I find way to do this.
 
Liberavi animam meam!

GeneralRe: Important: Interface translation for packing and updated demo [modified]membermidget357 Dec '08 - 7:49 
Hi Eugene.
 

OK I now realise this is related to the 'solid' archive setting. My apologies - been staring at this so long, I missed the obvious! I will open a new thread.
 

 
Thanks for continuing to work on this.
 
I have the latest 7z.dll from the 7zip installation, and have read this forum & docs over and over again, but I still have one problem extracting:
 
if i pass this through cmd line, the file is extracted ok:
sevenzip.exe e "Dir\file.7z" 0
 
i.e. the 1st file extracts perfectly.
 
But if I try to extract another file (and I'm sure there's more than 1 in the archive), the app tells me the extraction is going ahead, but nothing happens.
 
If I comment out the code around line 220: if ((index == FileNumber) && (askExtractMode == AskMode.kExtract))...
 
... other files other than that the one at index 0 WILL extract, but seemingly it overwrites itself many times over (lots of 'filename kOK')
 
I was wondering if you'd encountered this and which 7z.dll version you've tested?
 
Many thanks again

 
modified on Sunday, December 7, 2008 6:11 PM

GeneralRe: Important: Interface translation for packing and updated demomemberlinuxsuperfans27 Dec '08 - 19:18 
your site cannot be opened. can you send the demo with the source code to my email: linuxsuperfans@gmail.com?
Questioncan it make easier to use?memberangle256 Nov '08 - 23:02 
I read this project serveral times , but I could not understand it.
that what this term means, and how to use it.I hope it and easier as another component---SharpZipLib.
 
there is my code to use zip, it is very simplely:
 

using ICSharpCode.SharpZipLib.Zip;
 
void myfunction(){
 
    ZipFile zFile = new ZipFile(File.OpenRead(@"C:\mytest.zip")); 
   foreach (ZipEntry e in zFile) {
         if (!e.IsFile) continue;                 //skip other entry
         Stream strm = zFile.GetInputStream(e);  
         MyWork_ReadData(strm);
         strm.Close();
        }
      zFile.Close();
  }

AnswerRe: can it make easier to use?memberEugene Sichkar7 Dec '08 - 7:22 
This translation is not intended to be easy to use. This is low-level 7z interfaces translation one to one. So this is foundation, you (or somebody else) can build easy-to-use archive framework on top of it. Actually, I have such framework, but right now it is tightly integrated with nomad.net, and it is very hard to cut it from there.
P.S. Of course some problems with article understanding comes from my language.
P.P.S. With SharpZipLib you have only zip support, and it lacks flexibility.
 
Liberavi animam meam!

AnswerRe: can it make easier to use?membermaxoptimus11 Mar '09 - 5:12 
ICSharpCode.SharpZipLib.Zip - very poor compration.
 
7-zip very easy:
CoderPropID[] propIDs =
{
CoderPropID.DictionarySize,
};
object[] properties =
{
(Int32)(23),
 
};
 
Stream inStream = new MemoryStream();
Stream outStream = new MemoryStream();
Stream Stream = new MemoryStream();
 
//...add data...//
inStream.Position = 0;
 
SevenZip.Compression.LZMA.Encoder encoder = new SevenZip.Compression.LZMA.Encoder();
SevenZip.Compression.LZMA.Decoder decoder = new SevenZip.Compression.LZMA.Decoder();
 
System.IO.MemoryStream prop = new System.IO.MemoryStream();
 
encoder.SetCoderProperties(propIDs, properties);
encoder.Code(inStream, outStream, -1, -1, null);
 
encoder.WriteCoderProperties(prop);
byte[] propArray = prop.ToArray();
decoder.SetDecoderProperties(propArray);
outStream.Seek(0, SeekOrigin.Begin);
decoder.Code(outStream, Stream, outStream.Length, inStream.Length, null);
General7 Zip unzipping IssuememberAmlesh Kumar6 Oct '08 - 21:03 
Hai friends,
 
I have done the installation as per the procedure mentioned in the article.But while unzipping i am facing one problem.
 
While unzipping iam getting the issue in the line where i put the code in bold
 
using (SevenZipFormat Format = new SevenZipFormat(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "7z.dll")))
{
IInArchive Archive = Format.CreateInArchive(SevenZipFormat.GetClassIdFromKnownFormat(KnownSevenZipFormat.Zip));
if (Archive == null) {
ShowHelp();
return;
}
 
Here always iam getting the null value for the IInArchive.But its executing that IInArchive assigning line.
 
I am passing the arguments as e "D:\Zip Files\Compressed\pdr_aug_19_2002.tar.zip" """ like this in main.Is this correct?
 
Please reply for this request sa soon as possible.....
 
Thanks in advance.......
 
Amlesh Kumar

GeneralRe: 7 Zip unzipping Issuememberthunder755316 Feb '09 - 21:33 
You need to use the "7z.dll", not the "7za.dll".
GeneralExtracting single stream from an archive [modified]memberplive25 Sep '08 - 22:26 
Hello, I am trying to extract a single file to a memory stream using the
Archive.Extract
function and I am passing the index of the file I need.
The problem is that the callback function is called multiple times (one for each file that is in the compressed archive even if I pass the index of the file I want) and I have to check for the wanted index.
This is my code:
public Stream getFileStream(uint FileNumber, string password)
        {
 
            ArchiveCallback ac = new ArchiveCallback(FileNumber, password); 
            Archive.Extract(new uint[] { FileNumber }, 1, 0, ac);
            return ac.MemStream;
        }
 

        class ArchiveCallback : IArchiveExtractCallback, ICryptoGetTextPassword
        {
            private uint FileNumber;
            private string password;
 
            private MemoryStream memStream;
            public MemoryStream MemStream
            {
                get { return memStream; }
            }
 
            public ArchiveCallback(uint fileNumber, string password)
            {
                this.FileNumber = fileNumber;
                this.password = password;
                this.memStream = new MemoryStream();
            }
 
            #region IArchiveExtractCallback Members
            public void SetTotal(ulong total) { }
            public void PrepareOperation(AskMode askExtractMode) { }
            public void SetCompleted(ref ulong completeValue) { }
 
            public int GetStream(uint index, out ISequentialOutStream outStream, AskMode askExtractMode)
            {
                if (index == FileNumber)
                {
                    outStream = new OutStreamWrapper(this.memStream);
                }
                else
                    outStream = null;
 
                return 0;
            }
 
            public void SetOperationResult(OperationResult resultEOperationResult)
            {
                memStream.Seek(0, SeekOrigin.Begin);
            }
            #endregion
 
Thank you, Paolo
PS: the archive is encrypted
 
modified on Friday, September 26, 2008 4:39 AM

GeneralRe: Extracting single stream from an archivememberplive30 Sep '08 - 10:07 
Never mind, I already got a reply from Pavlov saying that it is normal if the archive was created with the solid option.
Paolo
GeneralCan't get it to workmemberRobert Roose18 Sep '08 - 17:42 
I can't get the demo to work. I downloaded 7z.dll from 7-zip's website, and placed it in the same directory as the demo exe, bu whenever I run it, I receive "Error: %1 is not a valid Win32 Application"
GeneralRe: Can't get it to workmemberring_021 Sep '08 - 21:13 
May be the downloaded dll is corrupted. Try d/ling again.
 
Man having 1 bit brain with parity error

GeneralExcellent Article... [modified]memberLastZolex7 Aug '08 - 22:00 
... I was looking for some help to make use of 7-Zip inside my C# apps for quite a while, because I had no idea of how to make clever use of unmanaged code in .NET myself!
 
Thank you so much!
 
The other source of knowledge I have is this article "7Zip (LZMA) In-Memory Compression with C#"
 
http://www.eggheadcafe.com/tutorials/aspnet/064b41e4-60bc-4d35-9136-368603bcc27a/7zip-lzma-inmemory-com.aspx
 
It makes good use of the original 7-Zip SDK.
 
modified on Friday, August 8, 2008 4:13 AM

General.cab problemmembersprice864 Aug '08 - 3:11 
D'Oh! | :doh:
Hi
Eugene has done us all a great service by writing this interface and I thank him for sharing his work with us.
 
I am having a problem with .cab files. If you change the line
 
IInArchive Archive = Format.CreateInArchive(SevenZipFormat.GetClassIdFromKnownFormat _
(KnownSevenZipFormat.Zip));
 
to
 
IInArchive Archive = Format.CreateInArchive(SevenZipFormat.GetClassIdFromKnownFormat _
(KnownSevenZipFormat.Iso));
 
or whatever type of archive you want to investigate, the demo program works fine -- except with .cab files. With these it is unable to open the archive, failng at
 
Archive.Open(ArchiveStream, ref CheckPos, null)
 
I managed to compile the Clientz and Console demo programs that are with the 7z source files but despite many hours of trying I still cannot find out why the interface does not work with .cab files. Console works fine with cab files (as does ClientZ if you alter the GUID to the correct one for .cab files).
 
I wondered if anyone else might have had this problem, or might have any suggestions.
 
Thanks
 
Bertram

GeneralRe: .cab problemmemberring_07 Aug '08 - 19:55 
Hi, cab handling is a bit different to rest of other formats.
 
Change type KnwonServiceZipFormat.Iso to KnownServiceZipFormat.Cab, in the following line
IInArchive Archive = Format.CreateInArchive(SevenZipFormat.GetClassIdFromKnownFormat _
(KnownSevenZipFormat.Iso)); 
 
to
 
Format.CreateInArchive(SevenZipFormat.GetClassIdFromKnownFormat _
(KnownSevenZipFormat.Iso)); 
 
again,
To open a cab archive do not use null in the last parameter.
do following,
Archive.Open(ArchiveStream, ref CheckPos, new OpenArchiveDelegate());
and finally,
your OpenArchiveCallBack class need to implement IArchiveOpenCallback and IArchiveOpenCallback should also implement IArchiveOpenVolumeCallback.
now the things should work fine.
 
Man having 1 bit brain with parity error

GeneralRe: .cab problemmemberEugene Sichkar8 Aug '08 - 0:27 
Nicely done! Sadly I don't have time to look into the .cab problem for past several weeks, but now when problem solved, I can forget it, and spend some time translating compression interfaces.
 
Liberavi animam meam!

GeneralRe: Here is another issue [modified]memberring_08 Aug '08 - 1:52 
[Edit:] solved Laugh | :laugh:
 
Man having 1 bit brain with parity error
modified on Friday, August 15, 2008 12:32 PM

GeneralRe: .cab problemmembersprice868 Aug '08 - 8:29 
Hi
Thanks ring_0. After fiddling around with callbacks for a while I've finally got it working fine with .cab files. Thanks a million for your help.
 
sprice86 Big Grin | :-D
 
Bertram

GeneralRe: .cab problem [modified]memberring_08 Aug '08 - 18:09 
My pleasure.
 
Man having 1 bit brain with parity error
modified on Friday, August 15, 2008 12:33 PM

QuestionRe: .cab problemmemberMr Peretz23 Sep '08 - 11:54 
Hi,
 
I am trying to implement your suggested solution but, being a C# beginner, I am having some difficulties. I have declared a class ArchiveOpenCallback() that implements both IArchiveCallback and IArchiveOpenVolumeCallback but can't see what you mean by OpenArchiveDelegate(). I have tried passing new ArchiveOpenCallback() and I can open the archive but the extraction does not work properly -- i get 4 files out of 90 Frown | :-(
 
I specifically see a problem in my implementation of IArchiveExtractCallback GetStream as the "index" parameter does not get updated.
 
can you please help?
 
thanks,
 
Marco
AnswerRe: .cab problem [modified]memberring_024 Sep '08 - 1:24 
Hi,
7-zip internally requires a callback before it opens a Cab archive. If you miss to provide a callback then simply you can not open that archive.
 
Now Try adding following code on the GetStream method of the ArchiveCallback class
as
 
public virtual int GetStream(uint index, out ISequentialOutStream outStream, AskMode askExtractMode)
        {
if (askExtractMode == AskMode.kSkip)
            {
                FileStream = new OutStreamWrapper(new MemoryStream());
                outStream = FileStream;
                return 0;
            }
 
}
if (askExtractMode == AskMode.kExtract)
{
///------------ Your default Stream handling goes here
}
Hope this helps. If you have any query please let me know.
 
Man having 1 bit brain with parity error
modified on Wednesday, September 24, 2008 7:31 AM

GeneralRe: .cab problemmemberMr Peretz24 Sep '08 - 2:01 
that was it -- i did not realize i had to create an output stream when kSkip was passed.
 
thanks a million.
 
Marco
GeneralRe: .cab problemmemberring_024 Sep '08 - 2:03 
Glad you found that useful.
 
Man having 1 bit brain with parity error

GeneralRe: .cab problemmemberEugene Sichkar24 Sep '08 - 2:38 
I don't think that dummy stream necessary when mode = kSkip. I believe that problem in demo lies in SetOperationResult method. Demo disposes FileStream without checking it to null, that is ok for demo because it can only extract only one file at once. So code update can look like this:
public void PrepareOperation(AskMode askExtractMode)
{
  CurrentOperation = askExtractMode;
}
 
public void SetOperationResult(OperationResult resultEOperationResult)
{
  if (CurrentOperation == AskMode.kExtract)
    FileStream.Dispose();
}

 
Liberavi animam meam!

GeneralRe: .cab problemmemberring_024 Sep '08 - 3:52 
Thanks for the tip, I could have thought of this before. The Dummy stream itself is not what I wanted but later I had to use. And I don't think there is a problem with MemoryStream, as the stream is later disposed before processing for the next entry.
 
regards
 
Man having 1 bit brain with parity error

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130516.1 | Last Updated 20 Jun 2008
Article Copyright 2008 by Eugene Sichkar
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid