Using Zip Files In VB Part 1
Eric Coleman
Copyright May 23, 2005
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.
After you click "OK", the next window should be displayed. Select "A simple DLL project" and then click Finish.
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:
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:
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.
- VBZip (root folder)
- vbgamerzip (contains the VC++ project files)
- zlib122 (unzipped directory tree of the zlib code, version 1.22)
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.
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.
- alder32.c
- compress.c
- crc32.c
- deflate.c
- inflback.c
- inffast.c
- inftrees.c
- trees.c
- uncompr.c
- zutil.c
The
next folder should be created under the "Header Files" folder.
Name this one "Zlib Header" and add the following files:
- crc32.h
- deflate.h
- inffast.h
- inffixed.h
- inflate.h
- inftrees.h
- zconf.h
- zlib.h
- 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.
- ioapi.c
- iowin32.c
- unzip.c
- zip.c
Next, create a new folder under "Header Files" called "Minizip Header." Add the following files:
- crypt.h
- ioapi.h
- iowin32.h
- unzip.h
- 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.
- // vbgamerzip.cpp : Defines the entry point for the DLL application.
- //
- #include "stdafx.h"
- BOOL APIENTRY DllMain( HANDLE hModule,
- DWORD ul_reason_for_call,
- LPVOIDlpReserved
- )
- {
- return TRUE;
- }
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.
- int __declspec(dllexport) WINAPI VBCRC32 (unsigned long crc,
- constunsigned char FAR*buf,
- unsigned len)
- {
- return crc32(crc, buf, len);
- }
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:
- zipOpen2
- zipOpenNewFileInZip3
- zipWriteInFileInZip
- zipCloseFileInZip
- zipClose
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.
- int __declspec(dllexport) WINAPI VBZipOpen (const char *pathname,
- intappend,
- zipcharpc*globalcomment)
- {
- zipFile z = zipOpen2(pathname, append, globalcomment, NULL);
- return (int)z;
- }
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.
- int __declspec(dllexport) WINAPI VBZipOpenNewFileInZip(zipFile file,
- constchar* filename,
- constchar* comment,
- constchar* password,
- uLongcrcForCrypting)
- {
- return zipOpenNewFileInZip3(file, filename,
- NULL, NULL, 0, NULL, 0,
- comment,
- Z_DEFLATED,Z_BEST_COMPRESSION, 0,
- -MAX_WBITS,DEF_MEM_LEVEL,Z_DEFAULT_STRATEGY,
- password, crcForCrypting);
- }
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.
- unzOpen
- unzClose
- unzGetGlobalInfo
- unzGetGlobalComment
- unzGoToFirstFile
- unzGoToNextFile
- unzGetCurrentFileInfo
- unzOpenCurrentFile
- unzOpenCurrentFilePassword
- unzCloseCurrentFile
- unzReadCurrentFile
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.
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.
- EXPORTS
- VBCRC32
- VBZipOpen
- VBZipOpenNewFileInZip
- VBZipWriteFile
- VBZipCloseFile
- VBZipClose
- VBUnZipOpen
- VBUnZipClose
- VBUnZipGetGlobalInfo
- VBUnZipGetGlobalComment
- VBUnZipFirstFile
- VBUnZipNextFile
- VBUnZipGetFileInfo
- VBUnZipOpenFile
- VBUnZipOpenFileEx
- VBUnZipCloseFile
- 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.
Once
you click OK a small window should appear. Right click onthe
folder and click on "Insert...." Here is a screenshot.
The next window will give you a bunch of different resources to insert. Select "Version" from the list then click the "New" button.
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:
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]