If you write programs in C#, Windows Presentation Foundation in .Net 3.0 provides quite nice API to write, generate and manipulate XPS documents. You can get the same feature if you work with managed C++ in .Net 3.0. Even if you work with .Net 2.0, you can get the basic ZIP stream decoding/encoding support.
But if you want to write code in unmamanged world, especially outside of the XPS filter pipeline, there is not enough sample code there. I’m going to write a simple XPS decoder in C++, based on the zlib compression library (http://www.zlib.net/zlib123-dll.zip).
#pragma comment(lib, “zdll.lib”)
HANDLE CreateDirFile(char * pName, DWORD len, HRESULT & hr);
class CXps : CMemoryMappedFile
const CEndCentralDir * m_pEndCentralDir;
const CCentralFileHeader * m_pFileHeader;
HRESULT LoadXps(const wchar_t * pFileName);
HRESULT DecodePiece(const BYTE * pCur, DWORD compressedSize);
The code uses zlib.h and zdll.lib for decompression. It opens up an XPS container as a memory-mapped file, wrapped in the CXps class. The CXps class has a pointer to end central directory, and a pointer to the first central file header.
HRESULT CXps::LoadXps(const wchar_t * pFileName)
m_pEndCentralDir = NULL;
if (! Open(pFileName))
const BYTE * pCur = m_View + m_nFileSize – (sizeof(CEndCentralDir) – 1);
while (pCur > m_View)
DWORD signature = * (DWORD *) pCur;
if (signature == sig_EndCentralDir)
m_pEndCentralDir = (const CEndCentralDir *) pCur;
m_pFileHeader = (const CCentralFileHeader *) (m_View + m_pEndCentralDir->m_offsetCentralDic);
The LoadXps method opens up an XPS file and locates its end central directory by reading backward from the end of the ZIP file.
HRESULT hr = S_OK;
const CCentralFileHeader * pFile = m_pFileHeader;
for (int i = 0; SUCCEEDED(hr) && (i < m_pEndCentralDir->m_entryCentralDic); i ++)
hr = DecodePiece(m_View + pFile->m_offsetLocalHeader, pFile->m_compressedSize);
pFile = (const CCentralFileHeader *) (pFile->m_Data + pFile->m_nFileName + pFile->m_nExtraField + pFile->m_nFileComment);
int XpsExtract(const wchar_t * pFileName)
HRESULT hr = xpsFile.LoadXps(pFileName);
hr = xpsFile.ExtractAllStreams();
printf(“\nhr = %x\n”, hr);
The ExtractAllStreams method tries to extract every pieces in the XPS container to an file on the disk by calling DecodePiece method. The main routine XpsExtract is responsible for calling the loading routine and the main extracting routine.
The most complicated routine here is DecodePiece:
HRESULT CXps::DecodePiece(const BYTE * pCur, DWORD compressedSize)
HRESULT hr = S_OK;
const CLocalFileHeader * pHeader = (const CLocalFileHeader *) pCur;
char * pName = new char[pHeader->m_nFileName + 1];
if (pName == NULL)
memcpy(pName, pHeader->m_Data, pHeader->m_nFileName);
pName[pHeader->m_nFileName] = 0;
printf(“\n%8d %8d %-60s”, pHeader->m_uncompressedSize, compressedSize, pName);
HANDLE hFile = CreateDirFile(pName, pHeader->m_nFileName, hr);
printf(“\nUnable to open file\n”);
pCur = pHeader->m_Data + pHeader->m_nFileName + pHeader->m_nExtraField;
if (pHeader->m_compression != 0)
BYTE * pOutput = new BYTE[pHeader->m_uncompressedSize];
if (pOutput == NULL)
memset(&stream, 0, sizeof(stream));
stream.avail_in = compressedSize;
stream.avail_out = pHeader->m_uncompressedSize;
stream.next_in = (Bytef *) pCur;
stream.next_out = pOutput;
int result = inflateInit2(& stream, -MAX_WBITS);
if (result == Z_OK)
result = inflate(& stream, Z_SYNC_FLUSH);
WriteFile(hFile, pOutput, pHeader->m_uncompressedSize, & dwWritten, NULL);
delete  pOutput;
WriteFile(hFile, pCur, pHeader->m_uncompressedSize, & dwWritten, NULL);
DecocePiece has two parameters: a pointer to local file header structure, and compressed size. Note the compressed size may not be stored in the local file header, so it’s better to pass it in from the end of the container. From the header structure, we can get its name and call CreateDirFile to create a corresponding file on the disk, making sure the proper directory path is created. The ZLIB decompression routine inflate, together with its preparation routine inflateInit2 are called when the piece is actually compressed. The result uncompressed streams are then written to disk.
The structures not shown here can be easily created by following ZIP specification at http://www.pkware.com/documents/casestudies/APPNOTE.TXT. The CreateDirFile routine is a simple wrapper around CreateFile and CreateDirectory.
The program is implicitly linked with ZLIB1 ZDLL.