Dennis Crain
Microsoft Developer Network Technology Group
Created: July 20, 1993
Click to open or copy the files in the EMFDCODE sample application for this technical article.
This article describes the process of porting WMFDCODE, the metafile decoding utility in the Microsoft® Windows™ Software Development Kit (SDK), to EMFDCODE, the Win32™ enhanced metafile utility. Issues encountered during the port from WMFDCODE to EMFDCODE and the incorporation of enhanced metafile capabilities into EMFDCODE are discussed. The code for EMFDCODE is also provided. This article assumes that the reader has a good understanding of Win32 enhanced metafiles. For a detailed description of enhanced metafiles, see the "Enhanced Metafiles in Win32" article by Dennis Crain on the Microsoft Developer Network CD.
WMFDCODE is a sample application provided with the Microsoft® Windows™ version 3.1 Software Development Kit (SDK) and Microsoft Visual C++™ version 1.0. It permits the viewing of headers or records and the enumeration of Windows metafiles (.WMF files), Windows version 3.x Clipboard files containing Windows metafiles, and placeable metafiles. With the introduction of the Win32™ enhanced metafile (.EMF files), WMFDCODE became less than useful for developers for Win32 simply because it does not support enhanced metafiles. To address this inadequacy, I ported WMFDCODE to Win32 and renamed it EMFDCODE. EMFDCODE permits the viewing of headers or records and the enumeration of enhanced metafiles in addition to those metafiles supported by WMFDCODE. This article describes general issues related to the porting of the sample code from Windows version 3.1 to Win32 and the incorporation of enhanced metafile capabilities.
Porting the 16-bit WMFDCODE to the 32-bit EMFDCODE was very straightforward. Many of the issues are the same ones that anyone would encounter porting an application from Windows 3.x to Win32, such as differences in message packing, differences in the alignment of data structures, and updating graphics device interface (GDI) functions in Windows 3.x to the widened Win32 GDI functions. For an in-depth discussion of porting, see the "Porting 16-Bit Windows-Based Applications to Win32” article by Randy Kath in the MSDN Library Archive Edition.
The widening of wParam from 16 bits to 32 bits and changes in the packing of wParam and lParam in Win32 obliged me to make a few changes in the way the WM_COMMAND message and the BN_CLICKED and EN_CHANGED notification codes were handled in EMFDCODE. The low-order word of wParam contains the identifiers for menu items, controls, and accelerators. The high-order word contains the notification code if the message is from a control. The following callback illustrates the changes required to correctly obtain messages and notification codes:
BOOL CALLBACK PlayFromListDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_INITDIALOG:
//The default is to play the selected records.
SendDlgItemMessage(hDlg, IDCB_SEL, BM_SETCHECK, 1, 0L);
return (TRUE);
case WM_COMMAND:
//
//NOTE CHANGE: The low-order word of wParam contains the identifier for
//menu items, controls and accelerators. In Windows 3.x this would
//have been "switch(wParam)".
//
switch (LOWORD(wParam))
{
case IDOK:
//Was the 'play selected' or 'play unselected' button checked?
if (IsDlgButtonChecked(hDlg, IDCB_SEL))
bPlaySelList = TRUE;
else
bPlaySelList = FALSE;
EndDialog(hDlg, TRUE);
return (TRUE);
case IDCB_SEL:
//
//NOTE CHANGE : The high-order word of wParam contains notification
//codes if the message is from a control. In Windows 3.1
//this would have been "if (lParam == BN_CLICKED)".
//
if (HIWORD(wParam) == BN_CLICKED)
SendDlgItemMessage(hDlg, IDCB_UNSEL, BM_SETCHECK, 0, 0L);
break;
case IDCB_UNSEL:
//
//NOTE CHANGE : The high-order word of wParam contains notification
//codes if the message is from a control. In Windows 3.1
//this would have been "if (lParam == BN_CLICKED)".
//
if (HIWORD(wParam) == BN_CLICKED)
SendDlgItemMessage(hDlg, IDCB_SEL, BM_SETCHECK, 0, 0L);
break;
default:
return (FALSE);
}
break;
}
return (FALSE);
}
During the porting of WMFDCODE to EMFDCODE, I had to deal with two issues related to data structures: one was the alignment of structures in memory and the other was the obsolescence of data structures previously used in Windows 3.x.
Regarding the alignment of structures in memory, I found myself scratching my head as I tried to figure out why I was moving the file pointer too far into a Windows 3.1 placeable metafile after having read sizeof(ALDUSMFHEADER)
bytes into the file. Thinking that sizeof(ALDUSMFHEADER)
was 22 bytes, I was chagrined to find that when I set a watchpoint on the return value, it was 24 bytes. It didn't take too long to deduce that this was a structure packing alignment problem: The structure was being packed on a 4-byte boundary. To resolve this problem in EMFDCODE, all of the structures used for Windows metafiles were packed on 2-byte boundaries by use of #pragma pack(2)
.
Many of the data structures previously used in Windows 3.x were changed in Win32 to accommodate 32 bits. The three structures used by WMFDCODE that were changed in Win32 were the Windows 3.x Clipboard format header, METAFILEPICT, and the Windows 3.x RECT structure.
Data structures requiring packing included the Windows 3.x Clipboard format header, METAFILEPICT, RECT, and the placeable metafile header. The Windows 3.x metafile header, METAHEADER, is already packed on a 2-byte boundary in the Win32 include file WINGDI.H. These four structures are listed below. Note that the comments to the right of each structure member describe the change to the structure in Win32.
//
//Win 3.x Clipboard format header
//
typedef struct {
WORD FormatID; //DWORD in Win32
DWORD DataLen; //no change in Win32
DWORD DataOffset //no change in Win32;
char Name[CLPMETANAMEMAX]; //no change in Win32
} CLIPFILEFORMAT, FAR *LPCLIPFILEFORMAT;
//
//Win 3.x metafilepict structure
//
typedef struct tagOLDMETAFILEPICT {
short mm; //LONG in Win32
short xExt; //LONG in Win32
short yExt; //LONG in Win32
WORD hMF; //HMETAFILE in Win32
} OLDMETAFILEPICT, FAR *LPOLDMETAFILEPICT;
//
//Win 3.x rectangle structure
//
typedef struct tagOLDRECT
{
short left; //LONG in Win32
short top; //LONG in Win32
short right; //LONG in Win32
short bottom; //LONG in Win32
} OLDRECT;
//
//Placeable metafile header. No changes in Win32 other
//than structure packing alignment of 2 bytes required.
//
typedef struct {
DWORD key;
WORD hmf;
OLDRECT bbox;
WORD inch;
DWORD reserved;
WORD checksum;
}ALDUSMFHEADER;
Several Windows 3.x GDI functions required modification to work correctly in the Win32 environment. Those functions neatly fall into two categories: coordinate space transformation functions and metafile functions.
SetWindowOrg, SetWindowExt, SetViewportOrg, and SetViewportExt were deleted in Win32. They were replaced with SetWindowOrgEx, SetWindowExtEx, SetViewportOrgEx, and SetViewportExtEx respectively in the Win32 environment. In the Windows 3.x functions, the coordinates were signed, 16-bit integers. In Win32, these coordinates are signed, 32-bit integers. The new Win32 functions take an additional pointer to a POINT or a SIZE structure in which the old coordinate values are returned. This is in contrast to the packed DWORD return value of the previous Windows 3.x functions. The code below illustrates the differences between the Windows 3.x SetViewportExt and the new Win32 SetViewportExtEx:
//
//Example of Windows 3.x use of SetViewportExt.
//nNewXExt and nNewYExt are 16-bit signed ints.
//
DWORD dwOldVPExt;
char szBuf[80];
//
//Set the viewport extent and save the old extents. Note
//that the return value contains the old viewport extents.
//
dwOldVPExt = SetViewportExt(hDC, nNewXExt, nNewYExt);
//
//Format the old extents in a character string.
//
if (dwOldVPExt)
wsprintf((LPSTR)szBuf, (LPSTR)"old VXExt %d old VYExt %d",
LOWORD(dwOldVPExt), HIWORD(dwOldVPExt));
//
//Example of Win32 use of SetViewportExtEx.
//nNewXExt and nNewYExt are 32-bit signed ints.
//
SIZE sizOldVPExt;
char szBuf[80];
//
//Set the viewport extent and save the old extents. Note that the
//return value is a BOOL. The old viewport extents are placed
//in the SIZE structure pointed to by (LPSIZE)&sizOldVPExt.
//
if (SetViewportExtEx(hDC, nNewXExt, nNewYExt, (LPSIZE)&sizOldVPExt))
wsprintf((LPSTR)szBuf, (LPSTR)"old VXExt %l old VYExt %l",
sizOldVPExt.cx, sizOldVPExt.cy);
Needless to say, all instances of the old Windows 3.x functions occurring in WMFDCODE were replaced with the new Win32 functions in EMFDCODE.
After I ported WMFDCODE to EMFDCODE, I sought to ensure that EMFDCODE would deal with metafiles, Clipboard metafiles, and placeable metafiles from Windows 3.x just as WMFDCODE did in Windows 3.x. Luckily, almost all of the Windows 3.x metafile functions are provided in Win32 for reasons of compatibility. This significantly eased the porting process simply because I did not have to rewrite very much of the code required to support Windows metafiles. However, two of the functions did change: SetMetaFileBits and GetMetaFileBits. The equivalents in Windows 3.x to these functions required a handle to a metafile as the sole parameter. GDI took care of the rest. The metafile bits were placed in global memory allocated by the GetMetaFileBits or SetMetaFileBits functions. In Win32, the caller supplies the memory for the metafile bits by providing a handle to the memory and the size of the metafile bits. The following code illustrates the differences between the Windows 3.x GetMetaFileBits/SetMetaFileBits and the Win32 GetMetaFileBitsEx/SetMetaFileBitsEx:
//
//Example of Windows 3.x SetMetaFileBits. The handle
//hNewMF is the handle to the memory metafile created
//by SetMetaFileBits. hMF should not use used thereafter.
//
HMETAFILE hWMF;
HGLOBAL hMFBits;
hMFBits = GetMetaFileBits(hMF);
if (hMFBits)
{
hWMF = SetMetaFileBits(hMFBits);
GlobalFree(hMFBits);
}
//
//Example of Win32 SetMetaFileBitsEx
//
LPBYTE lpWinMFBits;
UINT uiSizeBuf;
HMETAFILE hWMF;
//
//Obtain the size of the metafile bits.
//
uiSizeBuf = GetMetaFileBitsEx(hMF, 0, NULL);
//
//Allocate memory to hold bits.
//
if ((lpWinMFBits = (LPBYTE)GlobalAllocPtr(GHND, uiSizeBuf)));
{
//
//Get the metafile bits.
//
if (GetMetaFileBitsEx(hMF, uiSizeBuf, (LPVOID)lpWinMFBits))
//
//Set the metafile bits.
//
hWMF = SetMetaFileBitsEx(uiSizeBuf, (LPBYTE)lpWinMFBits);
//
//Free memory in which bits were stored.
//
GlobalFreePtr(lpWinMFBits);
}
The WMFMETA.C and MFDCOD32.C modules originally contained calls to GetMetaFileBits and SetMetaFileBits. In Win32 these calls are replaced with calls to the Win32 functions GetMetaFileBitsEx and SetMetaFileBitsEx, respectively.
The great news about dialog boxes has to do with the exported callback associated with them. There is no need to call MakeProcInstance and FreeProcInstance for exported functions. Win32-based applications do not share the concept of multiple instances as found in Windows 3.x. Win32-based applications exist as their own modules and cannot share information among other similar modules. So, the concept of binding an instance data segment to an exported function has no meaning in Win32. Goodbye, MakeProcInstance and FreeProcInstance! These functions are obsolete in Win32. I don't know about you, but I didn't enjoy typing these long function names! Look at the following examples and decide which makes more sense:
//
//Example of MakeProcInstance and FreeProcInstance in Windows 3.x
//
if ((lpEnumRangeDlg = MakeProcInstance(EnumRangeDlgProc, hInst)))
{
iDlgRet = DialogBox(hInst, "ENUMRANGE", hWnd, lpEnumRangeDlg);
FreeProcInstance(lpEnumRangeDlg);
}
//
//Example of Win32's lack of MakeProcInstance and FreeProcInstance.
//Is this nice or what?
//
iDlgRet = DialogBox(hInst,"ENUMRANGE",hWnd,EnumRangeDlgProc);
Having ported the basic functionality of WMFDCODE to EMFDCODE, I then added enhanced metafile functionality to the application. When I wrote WMFDCODE three years ago, I was three years less experienced than I am now. So when I reviewed the design of WMFDCODE, I had a great laugh! This quickly deteriorated into serious chin scratching as I wrestled with incorporating the enhanced metafile functionality into an application designed by my ignorant twin.
I finally decided to stray as little as possible from the original design of WMFDCODE. This meant that the application would make use of global flags for control of program flow and a global handle to the current metafile. I also decided to consolidate functions where it seemed possible. The biggest effect of that effort resulted in the consolidation of some of the content of the enumeration procedures and Clipboard metafile rendering. I also added some functionality to EMFDCODE that did not exist in WMFDCODE. Converting between Windows metafile format and enhanced metafile format is possible largely due to some great conversion functions provided by Win32. More on that later. First, let's look at some of the basic things required to use the enhanced metafile format. Although I discuss these in the context of EMFDCODE, they are things that must take place in any application that uses enhanced metafiles.
To obtain the header of a Windows metafile, the application must take action and read the file at the appropriate location. This is complicated by the fact that the location of the metafile header varies between Windows metafiles and placeable metafiles. For enhanced metafiles, Win32 eases this burden by providing the GetEnhMetaFileHeader function. Before the header can be retrieved, an appropriate amount of memory must be allocated to hold the contents of the header. Unfortunately, the amount of memory required can vary from one metafile to another because the description string (discussed below), which has a variable length, is appended to the header. So how do you determine how much memory to allocate? The header size is obtained by a call to GetEnhMetaFileHeader while specifying the pointer to memory as NULL. The value returned from GetEnhMetaFileHeader is the size of the header, which is the amount of memory that you must allocate. At this point, simply call GetEnhMetaFileHeader once again with a pointer to that memory. The code below in "Obtaining the Metafile Palette" demonstrates obtaining a pointer to memory containing the enhanced metafile header. The definition of the enhanced metafile header is contained in the include file WINGDI.H.
Enhanced metafiles may contain a string containing a description of the metafile. The description is stored in the metafile as a Unicode™ string. The easiest way to extract the description string is by use of the Win32 function GetEnhMetaFileDescription. Similar to GetEnhMetaFileHeader, GetEnhMetaFileDescription returns the size of the description string if the pointer to the memory buffer for the string is specified as NULL. The technique for using this function is illustrated in the code below in the next section.
Palettes exist in enhanced metafiles in the end-of-file record known as EMR_EOF. As enhanced metafiles are recorded, any calls to CreatePalette or SetPaletteEntries cause GDI to add the specified palette entries to the common palette in EMR_EOF. The best method for retrieving these palette entries is to use the Win32 function GetEnhMetaFilePaletteEntries. A pointer to a memory block sufficiently large enough to store the palette must be supplied to GetEnhMetaFilePaletteEntries. As with the GetEnhMetaFileHeader and GetEnhMetaFileDescription, the appropriate size is obtained by specifying the pointer to that memory as NULL. Having allocated memory of that size, GetEnhMetaFilePaletteEntries is called again using the pointer to that memory, thereby placing the palette entries in that memory. The code below illustrates the technique for using all three of these functions: GetEnhMetaFileHeader, GetEnhMetaFileDescription, and GetEnhMetaFilePaletteEntries.
//
//Demonstrates techniques for obtaining the metafile header,
//description string, and metafile palette. This is similar to
//the function GetEMFCoolStuff in the EMFDCODE module WMFMETA.C
//but has been modified for illustrative purposes.
//
BOOL GetEMFCoolStuff(HMETAFILE hemf)
{
LPENHMETAHEADER lpEMFHdr = NULL;
LPTSTR lpDescStr = NULL;
LPPALETTEENTRY pPal = NULL;
//
//If this is a valid handle
//
if (hemf)
{
//
//Obtain the sizes of the emf header, description string, and palette.
//Note that the last parameter of each is NULL. This instructs each of
//these functions to return the size of the metafile component being sought.
//
UINT uiHdrSize = GetEnhMetaFileHeader(hemf, 0, NULL);
UINT uiDescStrSize = GetEnhMetaFileDescription(hemf, 0, NULL);
UINT uiPalEntries = GetEnhMetaFilePaletteEntries(hemf, 0, NULL);
//
//If these sizes are greater than 0, allocate memory of that size.
//
if (uiHdrSize)
lpEMFHdr = (LPENHMETAHEADER)GlobalAllocPtr(GHND, uiHdrSize);
if (uiDescStrSize)
lpDescStr = (LPTSTR)GlobalAllocPtr(GHND, uiDescStrSize);
if (uiPalEntries)
lpPal = (LPPALETTEENTRY)GlobalAllocPtr(GHND, uiPalEntries *
sizeof(PALETTEENTRY));
//
//So far, so good. If the header size was greater than 0, we
//should continue.
//
if (uiHdrSize)
{
//
//Get the emf header. If the function fails, then bail out.
//
if (!GetEnhMetaFileHeader(hemf, uiHdrSize, lpEMFHdr))
{
//
//Failed
//
MessageBox(hWndMain, "Unable to read enhanced metafile header", NULL,
MB_OK | MB_ICONEXCLAMATION);
bValidFile = FALSE;
return (FALSE);
}
else
{
//
//Get the description string if it exists.
//
if (uiDescStrSize)
GetEnhMetaFileDescription(hemf, uiDescStrSize, lpDescStr);
//
//Get the palette entries if they exist.
//
if (uiPalEntries)
{
GetEnhMetaFilePaletteEntries(hemf, uiPalEntries, lpPal);
}
}
}
//
//The header size was reported as 0, so fail.
//
return (FALSE);
}
//
//Apparent success
//
return (TRUE);
}
There is less need to enumerate enhanced metafiles than there is with Windows metafiles because of the improved design of the enhanced metafile, the improved recording of metafiles, and the improved playback of metafiles. However, some applications may need to enumerate metafiles if they are editing the metafiles. EMFDCODE enumerates metafiles to enable the user to view the individual metafile records. The enumeration of enhanced metafiles is similar to enumeration of Windows metafiles. The only significant difference is that a pointer to an enhanced metafile record is passed to the enumeration callback in Win32 as opposed to a pointer to a Windows metafile record in Windows 3.x. Much of the code in the enumeration callback in EMFDCODE is the same for all types of metafiles. Because of the differences between parameters for enhanced metafiles and Windows metafiles, the common code was placed in a function named EnumMFIndirect. The following enumeration callbacks illustrate how EMFDCODE calls the common enumeration code:
//
//Enumeration callback for Win32 enhanced metafiles
//
int CALLBACK EnhMetaFileEnumProc(HDC hDC,
LPHANDLETABLE lpHTable,
LPENHMETARECORD lpEMFR,
int nObj,
LPARAM lpData)
{
//
//Pass lpEMFR to the common enumeration code. Specify the pointer
//to the Windows metafile record as NULL.
//
return EnumMFIndirect(hDC, lpHTable, NULL, lpEMFR, nObj, lpData);
}
//
//Enumeration callback for Windows 3.x metafiles.
//
int CALLBACK MetaEnumProc(HDC hDC,
LPHANDLETABLE lpHTable,
LPMETARECORD lpMFR,
int nObj,
LPARAM lpClientData,
{
//
//Pass lpMFR to the common enumeration code. Specify the pointer
//to the enhanced metafile record as NULL.
//
return EnumMFIndirect(hDC, lpHTable, lpMFR, NULL, nObj, lpClientData);
}
Given that thousands of Windows metafiles exist, two functions are provided in Win32 that enable conversion between the Windows metafile and the enhanced metafile formats: SetWinMetaFileBits and GetWinMetaFileBits. SetWinMetaFileBits converts the bits of a Windows metafile to an enhanced metafile. GetWinMetaFileBits converts the bits of an enhanced metafile into a Windows metafile. SetWinMetaFileBits takes a handle to a previously opened Windows metafile, converts those bits to the enhanced metafile format, and places the bits in memory allocated by the application. The following code demonstrates the use of SetWinMetaFileBits to create an enhanced metafile from a Windows metafile. This code is part of the ConvertWMFtoEMF function, found in the EMFDCODE module WMFMETA.C. Error checking has been removed for the sake of clarity.
//
//Example of the use of SetWinMetaFileBits. This function is used to
//convert the bits of a Windows metafile to those of an enhanced metafile.
//
LPSTR lpWinMFBits;
UINT uiSizeBuf;
HENHMETAFILE hEnhMF;
//
//Get the size of the Windows metafile associated with hMF.
//
uiSizeBuf = GetMetaFileBitsEx(hMF, 0, NULL);
//
//Allocate enough memory to hold metafile bits.
//
lpWinMFBits = GlobalAllocPtr(GHND, uiSizeBuf);
//
//Get the bits of the Windows metafile associated with hMF.
//
GetMetaFileBitsEx(hMF, uiSizeBuf, (LPVOID)lpWinMFBits);
//
//Copy the bits of the Windows metafile into a memory-based enhanced metafile.
//
hEnhMF = SetWinMetaFileBits(uiSizeBuf, (LPBYTE)lpWinMFBits, NULL, NULL);
//
//Copy the memory-based enhanced metafile to a disk-based enhanced metafile.
//
CopyEnhMetaFile(hEnhMF, lpszFileName);
//
//Done with the memory-based enhanced metafile, so get rid of it.
//
DeleteEnhMetaFile(hEnhMF);
//
//Done with the actual memory used to store bits, so nuke it.
//
GlobalFreePtr(lpWinMFBits);
GetWinMetaFileBits takes a handle to a previously opened enhanced metafile and converts the bits of that metafile into the Windows metafile format. Those bits are placed into memory allocated by the application. The following code demonstrates the use of this function. Once again, error checking has been removed for the sake of clarity. The code is part of the ConvertEMFtoWMF function, which is found in the module WMFMETA.C.
//
//Example of the use of GetWinMetaFileBits. This function is used to
//convert the bits of an enhanced metafile to those of a Windows metafile.
//
LPSTR lpEMFBits;
UINT uiSizeBuf;
HMETAFILE hWMF;
//
//Get the size of the Windows metafile associated with hMF.
//
uiSizeBuf = GetWinMetaFileBits(hemf, 0, NULL, MM_TEXT, hrefDC);
//
//Allocate enough memory to hold metafile bits.
//
lpEMFBits = GlobalAllocPtr(GHND, uiSizeBuf);
//
//Get the bits of the enhanced metafile associated with hEMF.
//
GetWinMetaFileBits(hEMF, uiSizeBuf,(LPBYTE)lpEMFBits, MM_TEXT, hrefDC);
//
//Copy the bits into a memory-based Windows metafile.
//
hWMF = SetMetaFileBitsEx(uiSizeBuf, (LPBYTE)lpEMFBits);
//
//Copy the Windows metafile to a disk-based Windows metafile.
//
CopyMetaFile(hWMF, lpszFileName);
//
//Done with the memory-based enhanced metafile, so get rid of it.
//
DeleteMetaFile(hMF);
//
//Done with the actual memory used to store bits, so nuke it.
//
GlobalFreePtr(lpEMFBits);
The porting of WMFDCODE to EMFDCODE was a fairly easy and straightforward process. Incorporation of the enhanced metafile functions into EMFDCODE illustrated how easy it is to deal with enhanced metafiles. A number of Win32 functions are provided that obtain components of the enhanced metafile, while in Windows 3.x, your application had to provide that functionality. Functions to obtain the header, description string, and palette ensure that the underlying structure of enhanced metafiles can be changed and improved with little effect on the applications that use these functions.
As a debugging utility for enhanced metafiles, EMFDCODE does not take advantage of all of the features of enhanced metafiles. The clipping region is not explicitly set in EMFDCODE. If it were, the GDI metafile player would clip the metafile appropriately. EMFDCODE does not permit the user to set the world-to-page transformation values. However, this could easily be added to EMFDCODE. The metafile player queries the world-to-page transformation values of the destination upon which the metafile is drawn before actually playing the metafile, and accommodates them as it plays.