Using Zip Files In VB Part 1

Eric Coleman
Copyright May 23, 2005

URL: http://www.vbgamer.com/tutorial.asp?ndx=48


Page 1
Introduction

This is part 1 of a 3 part series of tutorials that will allow you to use zip files in a VB program.  Part 1 demonstrates how to create a VB compatible DLL with Visual C++ 6.0.  Part 2 demonstrates how to create a VB interface for accessing the DLL.  Part 3 of the series shows how to use the C DLL and VB code to create a simple virtual file system for keeping track of game content.

Requirements

To complete this part of the tutorial you'll need Visual C++ 6.0. You can compile this DLL with other versions of C or other versions of Visual C++, but this tutorial is specific to VC++6.0.  Other versions of VC++ are similar, so this can still be used as a guide line for creating your own DLLs.

Optionally, if you don't have a C++ compiler you can skip to part 2 or part 3 of the tutorial and use the compiled DLL  provided in those sections.

Source Code

You can download the source code that accompanies this tutorial by clicking on the following link: Download VBZip1.zip.This file includes version 1.22 of zlib which is the latest version of the source at the time of this writing.  Depending on when you're reading this, there may be an updated version of the source code. The file also includes a working VC++ 6.0 project file that sucessfully compiles a working DLL.

History of .Zip

The zip file format has been around for a very long time.  It gained popularity among bulletin board systems (BBSes) of the late 80's and early 90's.  BBSes were one of the best ways to distribute software, especially games, in the early days of personal computers.  The ability to include multiple files in a single archive with compression made .zip files very usefull, they saved time in downloading files by being smaller and avoiding having to download many files individually. Since most files on the BBSes were in a .zip format, the original creators of the file format distributed a free tool to allow people to extract the files from the archive. That tool, pkunzip, was as widely used as zip files, since a zip file was useless without it.

When the .zip format was eventually released to the public, alternative programs were created that allowed the creation and viewing of zip files. The modern day zip file, despite its widespread and open nature, has become somewhat limited in modern programs.  The reason for this is that companies are now realeasing their own modified versions of zip files that are only compatible with their respective programs.  Since the creator of the .zip file format died, his former company is now in a format war with it's competitors. Because of this disagreement, a file created with one program may not work for some people.

Compatibility

Since the target audience of this tutorial is for people that make games, especially in terms of keeping a game's resources (graphics,sounds, scripts) compact and with some encryption a bit safer from casual theft, compatiblity with modern zip programs shouldn't be that much of a problem if you use this DLL for the creation of the zip files.  If you using a modern zip program to create and encrypte a zip file, then I doubt this DLL could read it.  Another limitation with this DLL is that a zip file is limited to 4GB in size with a maximum number of 65535 files.  These limitations are limitations of the original zip file format and of this DLL, some modern programs have changed the format slightly and can surpass these limitations.  Files created that are larger than 4GB in size or that have more than 65535 files will be incompatible with this DLL.


Creating A New DLL

The first step is to create simple DLL project in VC++.  From the File menu, click New to create a new project.  In the projects list select Win32 Dynamic-Link Library.  You'll also need to specify a project name and a file path for where your files will be created.  For the project name I used "vbgamerzip." You can name your project whatever you want, but the project name you pick will be used to name your default .cpp file, so when I reference the "vbgamerzip.cpp" later in this tutorial, make sure you know that I'm talking about the file that's created by default. Here is ascreenshot of the New dialog box.

Image

After you click "OK", the next window should be displayed.  Select "A simple DLL project" and then click Finish.

Image

After you click finish, your project should have 4 files created for you automatically.  In the workspace section click on the "FileView" tab to view your files.  If you don't see it, press Alt+0 (number zero).  It should look like this:

Image


Project Settings

Before you add files to the DLL, you need to turn off a default setting that can cause some compile errors for this project.

From the Project menu, click on "Settings."  This brings up the project settings window.  There are lots of tabs and within each tab there can be many more pages of settings.

The first thing you need to do is select "All Configurations" from the "Settings For:" drop down combo box in the top left of the window.  By default there are two configuration settings, Win32 Release and Win32 Debug.  Selecting "All Configurations" allows you to do this only once.

Next, find and select the "C/C++" tab.

Once you're on this tab, there is a dropdown combo box called "Category."  Select "Precompiled Headers" from the list.  As you can see, each option in the category list changes what options are visible in the window.

Finally, select "Not using precompiled headers" and then click OK.  Here is a screenshot of what you should see:

Image


Adding Files To The Project

File Move

Before we add the zlib files to the project, we need to move some of the files first.  Depending on where your project is located and where you unzipped the zlib source code, you may need to make some adjustments by moving essential files into your VC++ project's folder, or at least near it to make including files easier.

I have the following directory structure for my project.
There are other folders, but these are the important ones.

The files you need to move are listed below.  The most important move is to move the needed files from the "minizip" folder to the folder containing the zlib files.  For my project I copied the files from "minizip" to the "zlib122" folder.  Optionally you can copy the contents of zlib122 and minizip to the vbgamer zipfolder.  It's your choice, but you'll need to know the relative location of the zlib code (in this case ..zlib122) to the vbgamerzip folder.

Organizing The Files

Back to VC++.  Now that you've moved the essential files (listedbelow), the next step is to add them to the VC++ project.  You don't need to orangize them as I have, so organize the files however you want.

If you don't have the Workspace window open, press Alt+0 (number zero) to open it.  In the File View tab, right click on the "SourceFiles" folder and then select "New Folder..." from the menu.

Image

I used the name "Zlib Source" for this first folder.  This doesn't create a folder on your hard drive, this is simply an organization tool within VC++.

Next, right click on the folder you just created and then select "Add Files to Folder..." from the menu.  Find the folder that contains your zlib files and add the following files to this folder.  Note, you won't need all files in the folder.  Make sure you only add the files in these lists.
  1. alder32.c
  2. compress.c
  3. crc32.c
  4. deflate.c
  5. inflback.c
  6. inffast.c
  7. inftrees.c
  8. trees.c
  9. uncompr.c
  10. zutil.c
The next folder should be created under the "Header Files" folder.  Name this one "Zlib Header" and add the following files:
  1. crc32.h
  2. deflate.h
  3. inffast.h
  4. inffixed.h
  5. inflate.h
  6. inftrees.h
  7. zconf.h
  8. zlib.h
  9. zutil.h
Those 19 files make up the code needed to compress raw data using the "deflate" method of compression.  The inverse of "deflate" is called "inflate," which is used for decompression.  The next set of files will be the files copied from the minizip folder.  These files contain the code needed to create and read zip files.

Under the "Source Files" folder create a new folder called "Minizip Source" and then add the following files.
  1. ioapi.c
  2. iowin32.c
  3. unzip.c
  4. zip.c

Next, create a new folder under "Header Files" called "Minizip Header."  Add the following files:
  1. crypt.h
  2. ioapi.h
  3. iowin32.h
  4. unzip.h
  5. zip.h

Testing

We're not finished, but to make sure everything works, so let's do a test compile of our DLL. 

On the toolbar in VC++ there should be a dropdown combo box that allowsyou select either Win32 Release or Win32 Debug.  Since we don't need a debug version of this DLL, lets select Win32 Release from the list.

You can then click the Build button on the toolbar or select "Build vbgamerzip.dll" from the Build menu.  If you did everthing correctly, there should be a file named "vbgamerzip.dll" in the"vbgamerzip/release" folder. 

If there were any errors reported, go back over the tutorial to make sure you did everything correctly.


Creating The VB Wrapper

The DLL that you created in the last step doesn't export any functions, so it's useless as a DLL.  There are a couple of different ways to export functions in a DLL, but in order for the functions to be compatible with VB we need to make sure they're exported in a specific way, otherwise we would get a "Bad DLL Calling Convention" error when using the DLL in VB.

C Functions
In C, a basic function has a return data type, a function name, and optionally a list of parameters. A basic function named "foo" in C could look like this

    int foo (int bar) { }

The VB equivalent would be

    Function foo(bar as long) As Long : : End Function

To make a function export in C so that it is compatible in VB, we need to add the following information,

    __declspec(dllexport) WINAPI


The compatible C function would look like

    int __declspec(dllexport) WINAPI foo (int bar) { }


vbgamerzip.cpp

Open (double click) the file vbgamerzip.cpp.  If you gave your project a different name, then this file will have that name.  You should see the following code in the file.

  1. // vbgamerzip.cpp : Defines the entry point for the DLL application.
  2. //
  3. #include "stdafx.h"
  4. BOOL APIENTRY DllMain( HANDLE hModule,
  5.                       DWORD ul_reason_for_call,
  6.                       LPVOIDlpReserved
  7.                      )
  8. {
  9.     return TRUE;
  10. }


This file will contain all the functions we are going to export. We aren't creating any new functions, we're simply creating wrapper functions for a small set of functions that we need for creating and viewing zip files.  Before we start creating our wrapper functions, we need to include zip.h and unzip.h in this file. 

Depending on where your zlib files are physically located on the harddrive, either in the same folder as your VC++ project files or in the zlib122 folder or elsewhere will determine how you need to include the two header files I mentioned.

Since I copied my minizip files to the zlib122 folder, I have the following two lines of code in my project directly under the #include "stdafx.h" line.

#include "..\zlib122\zip.h"
#include "..\zlib122\unzip.h"


If your zip.h and unzip.h files are in your project directory, then you'll only need

#include "zip.h"
#include "unzip.h"


After adding the include statements you should do a test build of your DLL to make sure your include paths are correct.  If you get errors then you'll need to correct the path or possibly move all the sourcefiles into the project folder.

Adding The Functions

The first function we're going to add is crc32. This function is the only function that's not defined in zip.h or unzip.h.  This function is defined in crc32.c.  In the Zlib Source section, open the crc32.c file.  At line 215 you'll find the function that we need.  The first 5 lines look like this:
unsigned long ZEXPORT crc32(crc, buf, len)
    unsigned long crc;
    const unsigned char FAR *buf;
    unsigned len;
{
We're going to create a similar function in the vbgamerzip.cpp file.  However, our's will be exported and will have a slightly different return type.   VB doesn't have unsigned data types, but it doesn't really matter in this situation because an "unsignedlong" and a VB LONG are both 32 bits.  This means we can accept values from a C function, as long as it's 32 bits in size, and store it in a 32bit variable.  The main thing is the size of the variables, not what they represent.  As long as the size of the data isn't changed, we can store them with no problem.

Since an "unsigned long" is 32 bits and an "int" is 32 bits, we can safely cast the crc32 function to an "int" data type with no problem.   Here is the listing of the first function we're going to create.

  1. int __declspec(dllexport) WINAPI VBCRC32 (unsigned long crc,
  2.                                     constunsigned char FAR*buf,
  3.                                     unsigned len)
  4. {
  5.     return crc32(crc, buf, len);
  6. }  


The function looks a little different.  The data types of the paramters are written with the variable names instead of after the function.  This is simply a personal preference.

If you look at the function, you'll notice that the function name is "VBCRC32," its return type is "int" instead of "unsigned long." The parameter list is exactly the same as the crc32 function.  For most of the functions you can do a copy and paste of the paramter list. The function also has __declspec(dllexport) WINAPI in the function definition, which lets the compiler know that this function, VBCRC32, needs to be exported in a VB compatible format. 

On line 5 you can see why this is just a wrapper for another function.  All of the functions we're going to create will be like this.

Zip.h

The next set of functions are defined in zip.h.  We're only going to export 5 functions, here is the list:
The only odd function in this list in terms of return type is zipOpen2.  It returns a void pointer, so we have to convert the void pointer to an int in two steps.  The other four functions in the list will all have an "int" return type.

You can open the zip.h file to find the function definitions.

Some of the functions we're exporting have extra paramters that we don't really need.  If you want a more flexible zip dll, then you'll want to read through the zlib and minizip source code. Otherwise you can follow my suggestions here by simplifying the functions.

The function zipOpen2 has a final paramter that is a pointer to a struct of type zlib_filefunc_def. Since it's not really needed, we're going to leave it out of our exported function and simply pass a NULL when calling the wrapped function.  Here is what the final function looks like.

  1. int __declspec(dllexport) WINAPI VBZipOpen (const char *pathname,
  2.                                   intappend,
  3.                                   zipcharpc*globalcomment)
  4. {
  5.     zipFile z = zipOpen2(pathname, append, globalcomment, NULL);
  6.     return (int)z;
  7. }  


As you can see, the VBZipOpen function has 3 parameters (pathname, append, globalcomment), while the zipOpen2 function requires 4 parameters.  If you want access to the 4th paramter, then you'll need to modify the exported function to include the 4th parameter.

The next function, zipOpenNewFileInZip3, has a lot of extra paramters that aren't needed.  This function will be simplified a lot. We're going to use some predefined constants for our function call, as well as pass a few NULL values for some structs that can complicate our VB code.

NULL Pointers
I'd like to point out that I'm not blindly passing a "NULL" value for paramters that I don't need.  I've actually read the source code to these functions, so I know that they can handle situations when a NULL value is passed intead of the required pointer.  These functions were written to allow these paramters to be optional.
If you're following along by reading the zip.h file, you'll see some comments in the file that mention the constants that you need to use for this function.  Other constants may be defined in some of the zlib files, such as zlib.h, zconf.h, and zutil.h.

The zip compression technique allows you to specify a compression level or possibly no compression at all.  For this DLL I'm going to usethe maximum compression level possible.  If you want, you can add a compression parameter to the VB export function, but I've found that I never use anything other than the maximum.

The final version of this function is listed below.  As you can see the VB export function has only 5 parameters instead of 16 parameters for the function it calls.

  1. int __declspec(dllexport) WINAPI VBZipOpenNewFileInZip(zipFile file,
  2.                                            constchar* filename,
  3.                                            constchar* comment,
  4.                                            constchar* password,
  5.                                            uLongcrcForCrypting)
  6. {
  7.     return zipOpenNewFileInZip3(file, filename,
  8.                            NULL, NULL, 0, NULL, 0,
  9.                            comment,
  10.                             Z_DEFLATED,Z_BEST_COMPRESSION, 0,
  11.                             -MAX_WBITS,DEF_MEM_LEVEL,Z_DEFAULT_STRATEGY,
  12.                            password, crcForCrypting);
  13. }  


The remaining 3 functions in zip.h are easy to convert so there is no need to discuss them.  The VB names that I used however are thefollowing: zipWriteInFileInZip becomes VBZipWriteFile, zipCloseFileInZip becomes VBZipCloseFile, zipClose becomes VBZipClose.

Unzip.h

There are 11 functions in unzip.h that we need.  Here is the list of functions.
Just as zipOpen returned a void pointer, so does unzOpen.  It'scast from a void pointer to an int and is done in a similar way.

None of these functions are simplified, and they're all fairly straight forward. 

Here is a complete listing of the vbgamerzip.cpp file.

Extended Code Listing: Code Listing for vbgamerzip.cpp (see below)


The Final Export

At this point you can compile your DLL and everything should work.  At this point you could use it in VB, but the functions you exported are going to have mangled names.  If you installed the program Dependency Walker when you installed Visual Studio you can open the newly compiled vbgamerzip.dll to view its function exports. Depending on if C++ undecoration is turned on in the program, thefunction names will either look like garbage or it will look like source code.  The compiler is renaming the exported functions so that it's paramater list is encoded in the function name.  This can be usefull, but not in our situation.  We already know our paramter list, so lets use the function names we created earlier since VB doesn't support function decoration.  However, you could use the "Alias" paramter in VB  when defining the functions to name them whatever you want.  You can skip this step if you know what you're doing, but I wouldn't advise it.

If the DLL doesn't compile at this point, then look at the errors and try to correct the mistakes.  You can opt to download the project files for this tutorial and compare your code with mine.

Exports.def

Next, you'll need to add a text file to your project.  From the "File" menu, select "New."  Then select "Text File" from the "Files" tab, but make sure the file name has a ".def" extension.

Image

I used the name "exports.def" for this project.

The exports file is very simple.  It is not C or C++ code. You simply type the word "EXPORTS" and then the names of the functions that we're exporting.  This list is what prevents the compiler from creating mangled function names.  Here is what the file should look like.

  1. EXPORTS
  2. VBCRC32
  3. VBZipOpen
  4. VBZipOpenNewFileInZip
  5. VBZipWriteFile
  6. VBZipCloseFile
  7. VBZipClose
  8. VBUnZipOpen
  9. VBUnZipClose
  10. VBUnZipGetGlobalInfo
  11. VBUnZipGetGlobalComment
  12. VBUnZipFirstFile
  13. VBUnZipNextFile
  14. VBUnZipGetFileInfo
  15. VBUnZipOpenFile
  16. VBUnZipOpenFileEx
  17. VBUnZipCloseFile
  18. VBUnZipReadFile



Credit Where Credit Is Due

At this point you can compile your DLL and it should work perfectly.  However, I'd like to point out that since we're using other people's source code, even though it is free, we should give credit where credit is due.

Lets add a resource to the DLL so that we can include copyright information.  Back to the "File" menu, select "New" again. This time we're going to add a Resource Script.  The name I used is simply "resource."  Here is a screenshot.

Image

Once you click OK a small window should appear.  Right click onthe folder and click on "Insert...."  Here is a screenshot.

Image

The next window will give you a bunch of different resources to insert.  Select "Version" from the list then click the "New" button. 

Image

The next window that appears will have a bunch of information thatyou're probably familiar with.  You can click on the fields and edit each one, however, we're going to edit the "LegalCopyright" field first.  Then add the following text to the copyright field:


zlib software Copyright 1995-2004 Jean-loup Gailly Mark Adler, minizip software Copyright 1998-2004 Gilles Vollant

It's a long copyright string, so it may not display fully in the window.  Here is a screenshot:

Image


Finishing The DLL

The final step is to build the DLL.  On the "Build" menu, select "Build".   Make sure you have "Win32 Release" selected as the build type.  There isn't any reason to use a Debug version of the DLL in VB.

If you have any errors, then download the source code that I provided.  You can compare your version to mine, or simply use my wrapper code as a starting point.

Summary

This tutorial should have demonstrated how to create a DLL and export VB compatible functions. The first step is to actually create a skeleton DLL project, then to populate it with functions defined with __declspec(dllexport) WINAPI, then to finally to create a .def file to export non-mangled function names.

Improvement And Extension

With the zlib library you can compress and decompress binary streams of data.  To gain this functionality, you can export the compress (or compress2) and uncompress functions.  Such functions could be used to create your own file format or to compress and decompress data sent over a TCP/IP connection.   If you're creating an online game, you may want to consider compressing data before sending it.

You can also use the techniques shown in this tutorial to create your own functions in a C/C++ DLL to create accelerated functions to use within VB.  Instead of creating seperate DLLs, you can easily add your functions to the  zip DLL to make it easier to update.

References

zlib Home Site (http://www.gzip.org/zlib/)




Code Listing for vbgamerzip.cpp

// vbgamerzip.cpp : Defines the entry point for the DLL application.
//

#include "stdafx.h"
#include "..\zlib122\zip.h"
#include "..\zlib122\unzip.h"


BOOL APIENTRY DllMain( HANDLE hModule,
                      DWORD [vbcrlf]ul_reason_for_call,
                      LPVOID[vbcrlf]lpReserved
                     )
{
    return TRUE;
}


////////////////
// General Export
////////////////

int __declspec(dllexport) WINAPI VBCRC32 (unsigned long crc,
   [vbcrlf]          [vbcrlf]          [vbcrlf]             const[vbcrlf]unsigned char FAR *buf,
       [vbcrlf]          [vbcrlf]          [vbcrlf]         unsigned len)
{   
    return crc32(crc, buf, len);
}

////////////////
// Zip Section
////////////////

int __declspec(dllexport) WINAPI VBZipOpen (const char *pathname,
                                  int[vbcrlf]append,
                                  zipcharpc*[vbcrlf]globalcomment)
{
    zipFile z = zipOpen2(pathname, append, globalcomment, NULL);
    return (int)z;
}

int __declspec(dllexport) WINAPI VBZipOpenNewFileInZip(zipFile file,
                                           const[vbcrlf]char* filename,
                                           const[vbcrlf]char* comment,
                                           const[vbcrlf]char* password,
                                           uLong[vbcrlf]crcForCrypting)
{
   
    return zipOpenNewFileInZip3(file, filename,
   [vbcrlf]          [vbcrlf]          [vbcrlf]    NULL, NULL,    0,NULL, 0,
   [vbcrlf]          [vbcrlf]          [vbcrlf]    comment,
       [vbcrlf]          [vbcrlf]           Z_DEFLATED,[vbcrlf]Z_BEST_COMPRESSION, 0,
       [vbcrlf]          [vbcrlf]           -MAX_WBITS,[vbcrlf]DEF_MEM_LEVEL,Z_DEFAULT_STRATEGY,
   [vbcrlf]          [vbcrlf]          [vbcrlf]    password, crcForCrypting);
}

int __declspec(dllexport) WINAPI VBZipWriteFile(zipFile file,
   [vbcrlf]          [vbcrlf]          [vbcrlf]          [vbcrlf]        const void*buf,
   [vbcrlf]          [vbcrlf]          [vbcrlf]          [vbcrlf]        unsigned len)
{
    return zipWriteInFileInZip(file, buf, len);
}

int __declspec(dllexport) WINAPI VBZipCloseFile(zipFile file)
{
    return zipCloseFileInZip(file);
}


int __declspec(dllexport) WINAPI VBZipClose(zipFile file,
   [vbcrlf]          [vbcrlf]          [vbcrlf]          [vbcrlf]    const char* global_comment)
{
    return zipClose(file, global_comment);
}



////////////////
// UnZip section
////////////////

int __declspec(dllexport) WINAPI VBUnZipOpen (const char *path)
{   
    unzFile z = unzOpen(path);
    return (int)z;
}

int __declspec(dllexport) WINAPI VBUnZipClose (unzFile file)
{     
    return unzClose(file);
}

int __declspec(dllexport) WINAPI VBUnZipGetGlobalInfo(unzFile file, unz_global_info *pglobal_info)
{
    return unzGetGlobalInfo(file, pglobal_info);

}

int __declspec(dllexport) WINAPI VBUnZipGetGlobalComment(unzFile file,
                                          char[vbcrlf]*szComment, uLong uSizeBuf)
{
    return unzGetGlobalComment(file, szComment, uSizeBuf);
}

int __declspec(dllexport) WINAPI VBUnZipFirstFile(unzFile file)
{
    return unzGoToFirstFile(file);
}


int __declspec(dllexport) WINAPI VBUnZipNextFile(unzFile file)
{
    return unzGoToNextFile(file);
}


int __declspec(dllexport) WINAPI VBUnZipGetFileInfo(unzFile file,
                        unz_file_info[vbcrlf]*pfile_info,
       [vbcrlf]          [vbcrlf]    char*szFileName,
   [vbcrlf]          [vbcrlf]        uLongfileNameBufferSize,
   [vbcrlf]          [vbcrlf]        void*extraField,
   [vbcrlf]          [vbcrlf]        uLongextraFieldBufferSize,
                       char*szComment,
   [vbcrlf]          [vbcrlf]        uLongcommentBufferSize )
{
    return unzGetCurrentFileInfo(file, pfile_info,
   [vbcrlf]          [vbcrlf]          [vbcrlf]    szFileName,
   [vbcrlf]          [vbcrlf]          [vbcrlf]    fileNameBufferSize,
   [vbcrlf]          [vbcrlf]          [vbcrlf]    extraField,
       [vbcrlf]          [vbcrlf]          [vbcrlf]extraFieldBufferSize,
       [vbcrlf]          [vbcrlf]           szComment,
   [vbcrlf]          [vbcrlf]          [vbcrlf]    commentBufferSize);
}

int __declspec(dllexport) WINAPI VBUnZipOpenFile(unzFile file)
{
    return unzOpenCurrentFile(file);
}

int __declspec(dllexport) WINAPI VBUnZipOpenFileEx(unzFile file, const char* password )
{
    return unzOpenCurrentFilePassword(file, password);
}

int __declspec(dllexport) WINAPI VBUnZipCloseFile(unzFile file)
{
    return  unzCloseCurrentFile(file);
}

int __declspec(dllexport) WINAPI VBUnZipReadFile(unzFile file, voidp buf, unsigned len)
{
    return unzReadCurrentFile(file, buf, len);
}

[vbcrlf][vbcrlf][vbcrlf][vbcrlf][vbcrlf][vbcrlf][vbcrlf]