Implementing a “Send as ZIP-File” command in Scribble

In a recent project of mine I implemented the “File-Send mail” command the standard way with CDocument::OnFileSendMail/COleServerDoc::OnFileSendMail, but because the document files of that application were of rather big size I had the idea of first compressing the document and then providing it to the MAPI compliant email client program as a compressed file. Since almost every potential recipient of an email attachment can unzip an attachment with applications like winzip, pkunzip or free tools like unzip, the zip format was the file format of choice for me. Another reason for using the zip format was that there is the zlib compression library, a free and industrial strength library with full source code and samples on how to use it.
One of the goals I had for my implementation of the “Send as ZIP-File” command was, that it should be easily added to an existing application that uses the document-view model without having to rewrite all the MAPI-specific code that is already included in MFC. It should also be easily added to an application that already uses the basic MAPI support MFC provides. I will not explain how basic MAPI support is added to an application, because this can be easily figured out from scribble’s code or from creating a new MDI application, backing up the generic project files, adding the MAPI component from the component gallery and running windiff on the modified project and the backup. To demonstrate how easy it is to add the “Send as ZIP-File” command I decided to use the famous scribble sample to outline the basic steps to build the zlib dll and finally add the “Send as ZIP-File” command. This is what you have to do:

Step 1: Download and build the zlib compression library:

The first thing you have to do is to download the zlib compression library from links you can find on the official zlib site. Here is a selection of the links you find on that page:
California,
California (ftp),
Virginia,
France.
Unzip the downloaded zip file recursively with directory preservation into a directory, for example c:\zlib, and copy the files makefile.nt and zlib.dnt from the c:\zlib\nt directory into the zlib directory c:\zlib. To build the zlib library as a dll, start a command prompt and run the vcvars32.bat batch file from your VC bin directory in this command prompt. After that type


nmake /f makefile.nt zlib.dll

in this command prompt and the zlib dll and the associated import library zlib.lib will be built for you.

Step 2: Add the “Send as ZIP-File” command to Scribble


Copy the files of step 8 (step 7 for VC6 users) of the Scribble sample into a separate directory, for example c:\scribble. Scribble step 8 already has the basic MAPI support built in, so you can test your development system with the “File – Send…” command and setup your favourite email client program in case it is not yet the MAPI server for your machine. To enable zip compression for Scribble, add the zlib import library zlib.lib created in Step 1 to the scribble project and add the c:\zlib and c:\zlib\contrib\minzip directories to the include directories the preprocessor searches for in this project. Finally add the zip.c file from that directory to the scribble project files and add the following statement
to the beginning of this file:

#include
“zutil.h”

The author, Gilles Vollant, obviously forgot the statement to include this header file, but besides from that he did some real great work in providing his source code for the rest of us. Because zip.c has no notion of the standard precompiled header of an MFC application built from stdafx.h, we have to change the usage of precompiled headers from “Use precompiled headers – Through header: stdafx.h” to “Automatic use of precompiled headers – Through header: stdafx.h” in the scribble project’s C/C++ settings. Also, should you ever use this file in a real-world application you should set the warning level for this particular source file from level 4 to level 3, because for backward compatibility with older compilers, zlib and zip.c use old-style function declarators.
Now add the “Send as ZIP-File…” menu item with an ID of ID_FILE_SEND_ZIPPED_MAIL to all of Scribble’s menus that have a “Send…” command. Next, add a ID_FILE_SEND_ZIPPED_MAIL command handler (OnFileSendZippedMail) and an update command UI handler (OnUpdateFileSendZippedMail) to the CScribbleDoc class. Now add the following include directives into the file scribdoc.cpp:

#include
“afxpriv.h”
#include “zip.h”

As a last step add this piece of code




#ifndef
_countof

#define
_countof(array) (sizeof(array)/sizeof(array[0]))
#endif

uLong filetime(TCHAR *f, uLong * dt)
/*f: name of file to get info on */
/* dt: dostime */

{
int ret = 0;
{
FILETIME ftLocal;
HANDLE hFind;
WIN32_FIND_DATA ff32;

hFind = FindFirstFile(f,&ff32);
if (hFind != INVALID_HANDLE_VALUE)
{
FileTimeToLocalFileTime(&(ff32.ftLastWriteTime),&ftLocal);
FileTimeToDosDateTime(&ftLocal,((LPWORD)dt)+1,((LPWORD)dt)+0);
FindClose(hFind);
ret = 1;
}
}
return ret;
}

#define WRITEBUFFERSIZE (16384)

just before the empty implementations of CScribbleDoc::OnFileSendZippedMail and CScribbleDoc::OnUpdateFileSendMailzip. Also, add the following code as the implementation of these two handlers:


void CScribbleDoc::OnFileSendZippedMail()
{
ASSERT_VALID(this);

CString strMessage;
CString strZipFile;
int errclose = ZIP_OK;
int err=0;
BOOL bModified = IsModified(); //remember original modified flag.
CString strOldName = m_strPathName;
CString strDir;

#ifndef NO_COLESERVERDOC
ASSERT(m_bRemember);

LPSTORAGE lpOrigStg = m_lpRootStg;
m_lpRootStg = NULL;
#endif
TRY
{
#ifndef NO_COLESERVERDOC
m_bRemember = FALSE;
#endif
CWaitCursor wait;
TCHAR szTempName[_MAX_PATH];
TCHAR szPath[_MAX_PATH];
BOOL bRemoveTemp = FALSE;
CString strFile;
TCHAR szDrive[_MAX_DRIVE],szDir[_MAX_DIR], szName[_MAX_FNAME], szExt[_MAX_EXT];

VERIFY(GetTempPath(_countof(szPath), szPath) != 0);
if (m_strPathName.IsEmpty() || IsModified())
{
// save to temporary path.
VERIFY(GetTempFileName(szPath, _T(“afx”), 0, szTempName) != 0);

BOOL bResult = DoSave(szTempName, FALSE);

if (!bResult)
{
//We could not save the temp file. Disk full? Not enough privileges?
AfxFormatString1(strMessage, AFX_IDP_FAILED_IO_ERROR_WRITE,szTempName);
THROW (new CFileException);
}
bRemoveTemp = TRUE;
strFile = m_strTitle;
if (m_strTitle.Find(‘.’) == -1) // no extension.
{
CString strExt;
CDocTemplate* pTemplate = GetDocTemplate();
if (pTemplate != NULL && pTemplate->GetDocString(strExt, CDocTemplate::filterExt))
{
strFile += strExt;
}
}
}
else
{
// use actual file since it isn’t modified.
lstrcpyn(szTempName, m_strPathName, _countof(szTempName));
_tsplitpath(m_strPathName,NULL,NULL,szName, szExt);
strFile = CString(szName)+ CString(szExt);
}

zipFile zf;
//We try to create a temporary file in the temp-directory, delete it and create a directory with
//the same name and use that for our newly created ZIP-File:

VERIFY(GetTempFileName(szPath, _T(“afx”), 0, strZipFile.GetBufferSetLength(_MAX_PATH+32) ) != 0);
strZipFile.ReleaseBuffer();
CFile::Remove(strZipFile);
if (!CreateDirectory(strZipFile,NULL))
{
//Huh, our process has been preempted and someone else created a file or directory with our temp name?
//Are we not allowed to create this directory? Not enough space to create this directory?
//Get out of this hell!

AfxFormatString1(strMessage, AFX_IDP_FAILED_ACCESS_WRITE,strZipFile);
THROW (new CFileException);
}

strDir = strZipFile; //save the temp directory in a variable so we can delete the directory afterwards
strZipFile+=CString(_T(“\\”))+strFile;
_tsplitpath(strZipFile,szDrive,szDir,szName, szExt);

_tmakepath(strZipFile.GetBufferSetLength(_MAX_PATH+32),szDrive,szDir,szName, _T(“zip”));
strZipFile.ReleaseBuffer();
#ifdef UNICODE
USES_CONVERSION;
zf = zipOpen(W2CA(strZipFile),0);
#else
zf = zipOpen(strZipFile,0);
#endif

FILE * fin;
void* buf=NULL;
int size_buf=0;
size_buf = WRITEBUFFERSIZE;
buf = (void*)malloc(size_buf);
if (!buf)
{
//Not enough memory.
strMessage.LoadString(AFX_IDS_MEMORY_EXCEPTION);
THROW (new CMemoryException);
}
int size_read;

zip_fileinfo zi;
int opt_compress_level=Z_BEST_COMPRESSION;
//Best compression to save bandwidth at a maximum.

zi.tmz_date.tm_sec = zi.tmz_date.tm_min = zi.tmz_date.tm_hour =
zi.tmz_date.tm_mday = zi.tmz_date.tm_min = zi.tmz_date.tm_year = 0;
zi.dosDate = 0;
zi.internal_fa = 0;
zi.external_fa = 0;
filetime(szTempName,&zi.dosDate);

err = zipOpenNewFileInZip(zf,
#ifdef UNICODE
W2CA(strFile),
#else
strFile,
#endif
&zi,NULL,0,NULL,0,NULL /* comment*/,(opt_compress_level != 0) ? Z_DEFLATED : 0,opt_compress_level);

ASSERT(err == ZIP_OK);
if (err != ZIP_OK)
{
TRACE1(“error in opening %s in zipfile\n”,strFile);
AfxFormatString1(strMessage, AFX_IDP_FAILED_IO_ERROR_WRITE,strFile);
THROW (new CFileException); //if we would not return we would use an uninitialized fin variable
}
else
{
fin = _tfopen(szTempName,_T(“rb”));
if (fin==NULL)
{
err=ZIP_ERRNO;
TRACE1(“error in opening %s for reading\n”,szTempName);
strMessage.LoadString(AFX_IDP_FAILED_TO_OPEN_DOC);
THROW (new CFileException);
}
}

do
{
err = ZIP_OK;
size_read = fread(buf,1,size_buf,fin);
if (size_read < size_buf) if (feof(fin)==0)
{
//Seems like we could not read from the temp name.
AfxFormatString1(strMessage, AFX_IDP_FAILED_IO_ERROR_READ, szTempName);
AfxMessageBox(strMessage);
strMessage.Empty();
err = ZIP_ERRNO;
}

if (err==ZIP_OK && size_read>0)
{
err = zipWriteInFileInZip (zf,buf,size_read);
if (err<0) { //We could not write the file in the ZIP-File for whatever reason.
AfxFormatString1(strMessage, AFX_IDP_FAILED_IO_ERROR_WRITE,strZipFile);
AfxMessageBox(strMessage);
strMessage.Empty();
}

}
}
while (err==ZIP_OK && size_read>0);

fclose(fin);
errclose = zipClose(zf,NULL);
if (bRemoveTemp)
CFile::Remove(szTempName);
free (buf);
}
CATCH_ALL(e)
{
SetModifiedFlag(bModified);
m_strPathName = strOldName;
#ifndef NO_COLESERVERDOC
m_lpRootStg = lpOrigStg;
m_bRemember = TRUE;
#endif
if (!strMessage.IsEmpty())
{
AfxMessageBox(strMessage);
return;
}
else
THROW_LAST();
}
END_CATCH_ALL

#ifndef NO_COLESERVERDOC
m_lpRootStg = lpOrigStg;
m_bRemember = TRUE;
#endif

m_strPathName = strZipFile;

if (errclose != ZIP_OK && err==ZIP_OK)
{
//We could not close the ZIP-File
AfxFormatString1(strMessage, AFX_IDP_FAILED_IO_ERROR_WRITE,strZipFile);
AfxMessageBox(strMessage);
}
else if (err==ZIP_OK) //if err!=ZIP_OK, something in creating the zip file went wrong, we already had a message box.
OnFileSendMail(); //yes, everything went the right way up to now. Now invoke the MFC MAPI call OnFileSendMail().

//After we have cheated the MFC OnFileSendMail() call with a ZIP-File instead of a real document,
//we can reset everything to the previously saved values:

SetModifiedFlag(bModified);
m_strPathName = strOldName;

//From now on we don’t care if calls succeed. Other apps may have our files or directories open, who knows?
if (_tcslen(strZipFile))
CFile::Remove(strZipFile);
if (_tcslen(strDir))
RemoveDirectory( strDir );
}

void CScribbleDoc::OnUpdateFileSendZippedMail(CCmdUI* pCmdUI)
{
OnUpdateFileSendMail(pCmdUI);

}

That’s it. You now have all you need for a “Send as ZIP-File” command. You can minimize that feature’s memory footprint by setting the value of the WRITEBUFFERSIZE constant to a lesser value than 16384. This way you can even make the allocation for the zlib compression completely on the stack in Win32 (OK, unless you still have to program for Win32s), which I did not do because of my Win16 based origins. If you want to know how it all works, just debug it. I think the comments I made in the source code will explain everything sufficiently. Basically it works like this: The document is saved in a temp file, a temp directory and a zip file in it with the same name as the document is created and the previously created temp file is zipped into it. This zip file in the temp directory is then provided to the email client and afterwards deleted with the directory where it resides in.
The code above assumes that your document class is derived from COleServerDoc, just like scribble’s. In case it is just derived from CDocument, don’t worry: All you have to do is define the macro NO_COLESERVERDOC for your project or for the source file of your document class. One final word of caution: The code above presented for scribble’s document relies in some way on the implementation of CDocument::OnFileSendMail() and COleServerDoc::OnFileSendMail(). In case the implementations of these methods should be substantially rewritten by Microsoft in upcoming releases of MFC, you should carefully check, if this code still compiles, and if so, if it works the same way it did before. For now, it works beautifully with MFC 4.x to 6.0 (MSVC 4.0 to 6.0).

Redistribution of the zlib DLL

You can freely distribute the zlib DLL, but you can also build a static library from the zlib sources and link against that. Please don’t forget: If you plan to redistribute the zlib DLL, make sure it is in a private directory of your application and not in the windows, the system or the system32 directory of the target machine, because it then might interfere with other applications that also link to zlib.dll dynamically, but require a different version. The zlib DLL is not a core component of the OS, everyone can build a dll with this name and even identical exported functions but completely different behaviour. This was a big problem for me because I had some nasty crashes while debugging and found out that another application’s setup program had a zlib dll version 1.04 copied into the system32 directory and thus into the global search path of my machine (this is why MS put up setup guidelines that explicitly forbid copying private DLLs into the OS’s directories…). At the time of this writing, zlib’s version is 1.13.

ANSI vs. UNICODE

Although the VC project files for this article have customized settings for both ANSI and UNICODE, it should be noted, that there are some conversions in this article’s source code from UNICODE to ANSI both in MFC’s implementation of basic MAPI support as well as in the calls to the zip.c files functions (which is both just relevant for UNICODE compiles of this project). Maybe some upcoming release of the zlib library and the zip.c file will provide full UNICODE support for the VC (and other compiler vendors’ ) runtime libraries.

Download demo project – 151 KB

If you find any bugs, or if you have comments or suggestions, send an email to the author.

Date Last Update: March 24, 1999

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read