Originally from my Windows Commando column in Visual Developer. If you are interested in MFC development, have a look at the MFC Black Book. This bestselling book is now available again on CDROM at a very special price.
|
I’m writing this on my birthday (July 26). Never mind exactly how old I am – I don’t like to think about it. When I was younger, I looked forward to getting older. But one thing I have noticed is that nature plays several cruel jokes on you as you get older. The one that bothers me the most: time goes faster as you get older. Think back to when you were a kid. The time between when you went into second grade and the following summer was an eternity. Even the summers seemed to take forever (thank goodness). When you get older, the years whiz by at an alarming rate. Remember how long four years seemed like when you entered college? Now four years is just a blip. I’m not sure what this really means. While I was reading Stephen Hawking’s A Brief History of Time, I wondered if time was like a funnel and we swirl along going faster and faster until we fall through the hole at the bottom. I’ve read that there is a simpler explanation. When you are 10 years old, a year is 10% of your life. At 40, it is only 2.5% of your life. But I prefer to visualize it as my life just going down the proverbial drain. This is one of the few times in life that less is definitely not more. Usually though you want less. People want to weigh less, work less, and spend less. With computing there is an impetuous to write programs that take less time to run, use less memory, and cost less. Of course, today’s computers have lots of memory and disk space, so this isn’t as big a problem as it used to be, right? For the desktop PC, you aren’t as constrained as you used to be when it comes to memory and disk space. However, as more users become sophisticated enough to run many programs at once, you should consider economizing as much as possible. As PCs shrink (like my Windows CE palm top I got for my birthday) they may have a reduced amount of memory too. One thing that smart developers have used for some time to make leaner programs is compression. Sometimes compression occurs at a level that is hidden to programmers. For example, some network protocols (and modems too) automatically compress data so that it transports more quickly. Products like DriveSpace compress hard drives on the fly to make them appear bigger. The problem with transparent compression is that some data compresses better than others. Text, for example, is highly compressible. Files that are already compressed (JPEG or ZIP files, for instance) are not very compressible. When you compress everything, you waste some time trying to compress things that are better left uncompressed. In this installment of Windows Commando, I’ll show you how a project known as Info-Zip can help you add ZIP-style compression to your own programs. Info-Zip is an open source group (similar to GNU or Linux) that creates programs and libraries to handle ZIP files. These libraries are free with certain limitations and have been used in products as diverse as OS/2, HotJava, and PGP (Pretty Good Privacy). Info-Zip’s work runs on platforms ranging from Windows and Windows CE to TOPS20, VMS, MVS, and the Acorn. For Windows, you can even have the code in a DLL and use it without having to include the source into your own program. The source code is available if you need to understand what the code is doing or what to modify it for your own purposes. You can find Info-Zip on the Web at http://www.cdrom.com/pub/infozip. Using CompressionThe Info-Zip tools make it a little difficult to get started. The problem is there is so much source code, it often isn’t clear exactly what you want to get. In addition to the DLL code (separate for zipping and unzipping), there are several end-user programs that use the libraries or DLLs. Besides that, Info-Zip makes updates so referring you to a specific file name here wouldn’t work – it would be out of date before you could read it. Suffice it to say that you will have to read a bit on the Web site and download a few files to find all the correct pieces. The documentation for the DLLs seems somewhat sparse, but it is there (along with a nice short example program that uses the DLLs). In addition, you have all the source code, so you can usually look through it and find out the answer to any particular question. To see how this all works, consider the unzipping DLL (the only one I’ll use in this article). It has several different ways you can use it. One way is to call Wiz_SingleEntryUnzip. This call takes several parameters (see Tables 1, 2, and 3). However, it does the entire unzip operation for you. You can ask it to do a listing, extract certain files, and specify all the options you might need. In addition to specifying options, you can supply helper functions for the DLL. For example, the DLL never prints anything to the screen (after all, in Windows there may not be a console for the program). Instead it calls a user-supplied function to do the printing for it (see Table 3). Table 1. Arguments for Wiz_SingleEntryUnzip
Table 2. The DCL Structure
Table 3. The USERFUNCTIONS Structure
The helper functions allow you to completely customize the operation of the DLL. However, you can also make finer-grained calls into the DLL to further control what happens. For example, you can call Wiz_Init followed by Wiz_Unzip and Wiz_UnzipToMemory if you want to unzip a file into a memory buffer instead of a file. A Sample ProgramJust to experiment with the Info-Zip DLL, I decided to try writing a simple program that displays pages of text (like a multipage readme file). I had a few design goals in mind: • I wanted to have multiple pages contained in a single file • Since text is very compressible, I also wanted to compress that file so it was as small as possible The result is in Figure 1 and Listings 1 and 2. Since ZIP archives can contain multiple files, the architecture of the program is quite simple. You prepare the file by simply using a standard ZIP utility to create the archive. Each file within is named for its page number (e.g., 1.TXT, 2.TXT, 3.TXT, etc.). Once the ZIP archive is ready, you rename it to use the PGD extension and you are ready to go. You can run the program with the name of the archive on the command line, or you can use the File | Open command. Either way, you’ll see the first page and have the option to page forwards and backwards. From a Windows programming standpoint, the program was not hard to write. A simple session with MFC’s App Wizard created a CFormView SDI program – nothing special about that. However, integrating with the Info-Zip DLL did have some tricky spots that gave me a little trouble. Starting with Info-ZipAfter I downloaded all the files I thought I would need to get the unzip DLL, it took awhile to navigate through the many files that showed up on my hard drive. I was never able to find LIB files (or even a single header file) that corresponded to the two DLLs. I did find the documentation files and all the pieces required to build the DLLs. I downloaded the Windows binaries for WIZ (the end-user DLLs). However, they were linked with Borland C++ and I decided to go ahead and rebuild them. The problem, of course, Info-Zip’s make files were made by Visual Studio and had many hard-coded path names in them. Naturally none of these paths matched the paths on my machine. Twenty minutes later I got a clean compile. Armed with DLLs and a LIB file, I was off and running. I noticed that all the examples from Info-Zip dynamically load the DLL and don’t use the LIB file. I decided I’d do the same, just for practice. I decided that I wouldn’t need to worry with passwords, progress indicators, and other user interface items, so my helper functions are mostly stubs. That’s a good thing, too. The helper functions are only loosely documented unless you dig into the source where the comments are helpful. The only helper I was really implementing was the print helper. My theory was simple: Write my own print handler and tell the library to extract to standard output. In addition, I’d use the fQuiet flag to turn off messages. My print routine would then route all the text to the edit control on the main form. Sounds simple, right? The only problem is fQuiet doesn’t prevent the library from issuing warnings, just informational messages. My program worked until you tried to move past the last page. Then you’d see the library error message instead of the last page’s text. The answer turned out to be simple. I altered the program so that the print routine stored the outbound text in a CString. Then I check the return value from the library to see if an error occurred. If there was an error, I discard the string. If there is no error, I place the string in the edit control. All of the real work occurs in the document object’s showpage routine. Here is where the use the unzip library to extract the correct file. It would be easy enough to statically link to the import library for the DLL instead of dynamically loading it, but instead, the program dynamically links the DLL in the document constructor. It is tempting to look for other improvements in efficiency. For example, at first glance it appears that it is wasteful to keep finding the edit control window during each call to showpage. Why not find it once and cache the resulting CEdit pointer for later use? That sounds good but there is a problem. When you call GetDlgItem, the pointer it returns is not a permanent MFC object. Instead, it is a temporary object that lives during the current message processing. So if you store this pointer away, later it will not be valid. Of course, another alternative would be to use Class Wizard to map the edit control to a CEdit object directly. This creates a permanent binding and would also work in this case. Other Points of InterestThere are several things in this program that are interesting even though they don’t really relate to using Info-Zip. For example, consider the File | Open menu function. In an ordinary MFC program, MFC does a tremendous amount of work so that it is easy to write File | Open and File | Save. With this program, we don’t want that much help. The Info-Zip DLL wants a file name and that’s all. When MFC notices a File | Open command, it does a few things: 1) Show a file open dialog 2) Match the file name with a document template 3) Creates the correct type of document and view (and possibly a frame in an MDI program) 4) Open the file and bind it to a CArchive object 5) Pass the CArchive object to the document 6) Close the file If the Info-Zip DLL understood the CArchive object, that would be great. However, that would be unreasonable to expect – CArchive is purely an MFC construct. Info-Zip works on lots of different platforms and operating systems. With some libraries you might have to assume all the responsibility of opening the file yourself if you didn’t want the default handling. Luckily, MFC makes good use of virtual functions to let you do as much work as you want to do and leave the rest to the library. For this program, you still want steps 1 to 3 to occur. You just don’t want all the parts relating to CArchive. Luckily each step takes place at a different location in the class library – locations you can override to bend to your own will. The function in question for step 4 is CDocument::OnOpenDocument. By overriding this function (see Listing 2) it is a simple matter to store the file name in an instance variable. Later, the code will pass this variable to the Info-Zip DLL. Another interesting portion of the code deals with resizing. A common problem with form-based programs is that you want the elements in the form to reposition themselves in response to the user resizing the window. Unfortunately, I haven’t found a clever solution for this – just brute force. When designing this type of code you have to think about what elements are fixed in size and which elements resize to match the window size. In this case, the text area resizes with the window, but the buttons do not change size. However, you do want the button positions to change as the window grows and shrinks. The code in the OnSize method is far from elegant. It simply repositions the buttons (retaining their current size) and then adjusts the text field so that it fills the window except for the area with the buttons. Free Software?Is free (or open source) software too good to be true? Apparently not. Many well-respected programs including Linux, Perl, Apache, PHP, and the GNU tools are available more or less for free. The catch? You need to understand exactly what restrictions the license places on you. For example, if you use the GNU C++ compiler, you can redistribute the binaries you create with very few, if any, restrictions. However, until recently if you used the GNU version of YACC (known as BISON), the resulting program also had to be free software. This has changed and you can now redistribute programs you create with BISON under whatever terms you like. So if you are thinking about incorporating free software into your business you’d better get your lawyer to read the license agreement and tell you that it is appropriate for you to use the software. A nice note to the author probably doesn’t hurt either. Another problem with free software (at least from our point of view) is that much of it is written for Unix. This is slowly changing as more and more programmers move to Windows either by choice or by force. Here are some links to get you started with free software: http://www.gnu.org - Richard Stallman’s group started it all. http://www.spi-inc.org/ - The people who own the trademark on Open Source (kind of ironic, isn’t it?) http://www.opensource.org/ - Papers on the open source concept. http://sourceware.cygnus.com/cygwin/ - A major source of GNU utilities ported to Windows. http://www.apache.org/ - This popular open source Web server now has a Win32 version. http://www.gimp.org/~tml/gimp/win32/ - Open source image manipulation. Of course, there is more but this will get you started. If you like free software, perhaps you should consider writing some. While not rewarding financially, it can give you a good feeling. I recently wrote a small program for my own use as a ham radio operator and released it on the Internet. There are now literally several thousand users of the software. I’ve really enjoyed being able to give something back to the community. Like they say, it is better to give than to receive! |

// pagerDoc.cpp : implementation of the CPagerDoc class
//
#include "stdafx.h"
#include "pager.h"
#include "pagerDoc.h"
#include "pagerView.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////
// CPagerDoc
IMPLEMENT_DYNCREATE(CPagerDoc, CDocument)
BEGIN_MESSAGE_MAP(CPagerDoc, CDocument)
//{{AFX_MSG_MAP(CPagerDoc) ON_COMMAND(IDC_PREV, OnPrev)
ON_COMMAND(IDC_NEXT, OnNext)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////
// CPagerDoc construction/destruction
CPagerDoc::CPagerDoc()
{ // set up to use library
libh=NULL;
libh=LoadLibrary("unzip32.dll"); Wiz_SingleEntryUnzip=(FTYPE)GetProcAddress(libh,"Wiz_SingleEntryUnzip");
// set up view pointer
}
CPagerDoc::~CPagerDoc()
{ if (libh) FreeLibrary(libh);
}
BOOL CPagerDoc::OnNewDocument()
{ if (!CDocument::OnNewDocument())
return FALSE;
// TODO: add reinitialization code here
// (SDI documents will reuse this document)
return TRUE;
}
/////////////////////////////
// CPagerDoc serialization
void CPagerDoc::Serialize(CArchive& ar)
{ if (ar.IsStoring())
{ // TODO: add storing code here
}
else
{ // TODO: add loading code here
}
}
/////////////////////////////
// CPagerDoc diagnostics
#ifdef _DEBUG
void CPagerDoc::AssertValid() const
{ CDocument::AssertValid();
}
void CPagerDoc::Dump(CDumpContext& dc) const
{ CDocument::Dump(dc);
}
#endif //_DEBUG
/////////////////////////////
// CPagerDoc commands
BOOL CPagerDoc::OnOpenDocument(LPCTSTR lpszPathName)
{ // Subvert normal processing
m_filename = lpszPathName;
showpage(1);
return TRUE;
}
// Global variable to communicate with global helper functions
CString text;
int WINAPI pfunc(LPSTR s,unsigned long l)
{ text+=s;
return 0;
}
int WINAPI pwf(LPSTR, int, LPCSTR, LPCSTR)
{ return 1;
}
int WINAPI repl(LPSTR)
{ return 1;
}
int WINAPI scb(LPCSTR, unsigned long)
{ return 0;
}
void WINAPI sam(unsigned long,unsigned long, unsigned, unsigned, unsigned,unsigned,unsigned,unsigned,
char,LPSTR, LPSTR,unsigned long, char)
{ }
void CPagerDoc::showpage(int n)
{ if (m_filename.IsEmpty()) return; // no file!
text=""; // start from scratch
// assume one view/document
POSITION pos=GetFirstViewPosition();
CView *__view = GetNextView(pos);
CEdit *_view=(CEdit *)__view->GetDlgItem(IDC_TEXT);
// unzip correct file from zip archive and fill in text file from there
char fn[16];
char *afn=fn;
char **ifn=&afn;
DCL dcl;
USERFUNCTIONS ufunc;
memset((void *)&dcl,0,sizeof(dcl));
memset((void *)&ufunc,0,sizeof(ufunc));
wsprintf(fn,"%d.txt",n);
ufunc.print=pfunc;
ufunc.replace=repl;
ufunc.password=pwf;
ufunc.SendApplicationMessage=sam;
ufunc.ServCallBk=scb;
dcl.ncflag=1;
dcl.fQuiet=2;
dcl.lpszZipFN=m_filename.GetBuffer(m_filename.GetLength());
int rv=Wiz_SingleEntryUnzip(1,ifn,0,NULL,&dcl,&ufunc);
m_filename.ReleaseBuffer();
if (rv==0) // if it all worked...
{ m_pagenr=n; // keep page #
_view->SetWindowText(text); // set the text
__view->GetDlgItem(IDC_PREV)->EnableWindow(m_pagenr!=1);
__view->GetDlgItem(IDC_NEXT)->EnableWindow(TRUE);
}
else
{ __view->GetDlgItem(IDC_NEXT)->EnableWindow(FALSE);
__view->GetDlgItem(IDC_PREV)->EnableWindow(TRUE);
}
}
void CPagerDoc::OnPrev()
{ if (m_pagenr==1) return;
showpage(m_pagenr-1);
}
void CPagerDoc::OnNext()
{ showpage(m_pagenr+1);
}
// pagerView.cpp : implementation of the CPagerView class
//
#include "stdafx.h"
#include "pager.h"
#include "pagerDoc.h"
#include "pagerView.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////
// CPagerView
IMPLEMENT_DYNCREATE(CPagerView, CFormView)
BEGIN_MESSAGE_MAP(CPagerView, CFormView)
//{{AFX_MSG_MAP(CPagerView) ON_UPDATE_COMMAND_UI(ID_EDIT_COPY, OnUpdateEditCopy)
ON_WM_SIZE()
//}}AFX_MSG_MAP
// Standard printing commands
ON_COMMAND(ID_FILE_PRINT, CFormView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT, CFormView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_PREVIEW, CFormView::OnFilePrintPreview)
END_MESSAGE_MAP()
/////////////////////////////
// CPagerView construction/destruction
CPagerView::CPagerView()
: CFormView(CPagerView::IDD)
{ //{{AFX_DATA_INIT(CPagerView) // NOTE: the ClassWizard will add member initialization here
//}}AFX_DATA_INIT
// TODO: add construction code here
}
CPagerView::~CPagerView()
{ }
void CPagerView::DoDataExchange(CDataExchange* pDX)
{ CFormView::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CPagerView) // NOTE: the ClassWizard will add DDX and DDV calls here
//}}AFX_DATA_MAP
}
BOOL CPagerView::PreCreateWindow(CREATESTRUCT& cs)
{ // TODO: Modify the Window class or styles here by modifying
// the CREATESTRUCT cs
return CFormView::PreCreateWindow(cs);
}
void CPagerView::OnInitialUpdate()
{ CFormView::OnInitialUpdate();
GetParentFrame()->RecalcLayout();
ResizeParentToFit();
}
/////////////////////////////
// CPagerView printing
BOOL CPagerView::OnPreparePrinting(CPrintInfo* pInfo)
{ // default preparation
return DoPreparePrinting(pInfo);
}
void CPagerView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{ // TODO: add extra initialization before printing
}
void CPagerView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{ // TODO: add cleanup after printing
}
void CPagerView::OnPrint(CDC* pDC, CPrintInfo* pInfo)
{ CString s;
GetDlgItem(IDC_TEXT)->GetWindowText(s);
pDC->DrawText(s,pInfo->m_rectDraw,0);
}
/////////////////////////////
// CPagerView diagnostics
#ifdef _DEBUG
void CPagerView::AssertValid() const
{ CFormView::AssertValid();
}
void CPagerView::Dump(CDumpContext& dc) const
{ CFormView::Dump(dc);
}
CPagerDoc* CPagerView::GetDocument() // non-debug version is inline
{ ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CPagerDoc)));
return (CPagerDoc*)m_pDocument;
}
#endif //_DEBUG
/////////////////////////////
// CPagerView message handlers
void CPagerView::OnUpdateEditCopy(CCmdUI* pCmdUI)
{ CEdit *ec=(CEdit *)GetDlgItem(IDC_TEXT);
int start,end;
ec->GetSel(start,end);
pCmdUI->Enable(start!=end);
}
void CPagerView::OnSize(UINT nType, int cx, int cy)
{ CFormView::OnSize(nType, cx, cy);
CWnd *w=GetDlgItem(IDC_PREV);
if (!w||nType==SIZE_MINIMIZED||cx==0||cy==0) return;
CRect r;
int btnhi,btnwid;
w->GetWindowRect(&r);
btnhi=r.Height();
btnwid=r.Width(); // assume both same size
r.right=btnwid;
r.bottom=cy;
r.left=0;
r.top=cy-btnhi;
w->MoveWindow(&r,TRUE);
w=GetDlgItem(IDC_NEXT);
w->GetWindowRect(&r);
r.right=cx;
r.bottom=cy;
r.left=cx-btnwid;
r.top=cy-btnhi;
btnhi=r.Height();
w->MoveWindow(&r,TRUE);
w=GetDlgItem(IDC_TEXT);
r.top=r.left=2; // give it a little room
r.bottom=cy-btnhi-10;
r.right=cx;
w->MoveWindow(&r,TRUE);
}