MetaFile
Extraction – Part II
[This article is written in the hope that it would be
useful. The author Vipin Aravind provides this information with no
warranties. If you find this article interesting give me a note]
Introduction:-
Today I am going to show you something interesting about printing. My aim is to show you how to print from any application and generate a metafile with a printer driver in banding mode. The metafile generated can then be inserted in a word application using “InsertàPictureà”From File” and then be sent to anyone in an rtf,.doc formats. In my article “MetaFile Extraction”( http://www.microsoft.com/india/msdn/articles/130.aspx), I showed how you can extract metafiles from spool file. Well, then what is this article all about? It’s regarding the second part, the “Print to File” part. But, unlike my other article which is accompanied by a tool, this isn’t accompanied by a standalone tool. You need to have a debugger to achieve this. So for those who want to sharpen their debugging skills, consider this to be a chance too.
Note:- The author’s experiments apply to windows NT/2000/XP series.
Before we proceed, let me explain some concepts:-
Banding driver:-This is a driver in which rendering happens in bands. It is driver meant for printers which support minimal functionality [e.g:- Can print only raster data].In this driver the page is considered to be consisting of bands and each band would be transmitted to the printer as a raster image, say for example. So let us consider a banding driver named BANDER which considers a page to be constituted of 5 bands. Let us consider notepad as the application for sake of explanation. If you had typed in “Hello World” and printed to BANDER, this is what happens:-
Application does:-
StartDoc(hdc,&di)
StartPage(hdc)
TextOut(hdc,x,y,”Hello
World”,11); // textout GDI call
EndPage(hdc)
EndDoc(…)
When the application makes the above rendering calls on the printer DC, the GDI would be generating spool file on the disk for playback in the spooler context 5 times(because the driver has 5 bands for a page in this example). Had you done a “Print to File”, the playback happens in the application context itself, there isn’t any backing with an EMF based spool file on the hard disk.
Requirements:- Windows 2000/XP system, VC++ debugger, banding driver(you can install HP Color LaserJet 5, one that comes from the operating system and having the setting PropertiesàOptionsàGraphics Mode: Raster to make it work as a banding driver)
Here are the components for accomplishing it:-
1) RemotePeek, an executeable to create a remote thread in another process. Place the below code in a console application from vc++ and compile it to create the peeker, lets say your executeable is remotepeek.exe. The usage is “remotepeek process-id”. The process-id of the process you plan to experiment can be obtained from the Task Manager.
#define UNICODE
#include <windows.h>
#include <stdio.h>
typedef HMODULE (WINAPI *pfnLoadLibrary)(LPCTSTR);
main(int argc , char **argv)
{
if(argc < 2)
return 0;
printf("doing.....\n");
const WCHAR DllName[256] = L"loader.dll";
HANDLE hProcess = OpenProcess(PROCESS_VM_OPERATION |PROCESS_CREATE_THREAD| PROCESS_VM_WRITE , FALSE , atoi(argv[1]));
if(!hProcess)
{
wprintf(L"openprocess failed %d\n",GetLastError());
return 0;
}
pfnLoadLibrary pLoadLibrary = (pfnLoadLibrary)GetProcAddress(GetModuleHandle(L"Kernel32"),"LoadLibraryW");
LPVOID pRemote = VirtualAllocEx( hProcess, NULL , (lstrlen(DllName) + 1) * sizeof(WCHAR), MEM_COMMIT, PAGE_READWRITE );
SIZE_T numWritten;
WriteProcessMemory( hProcess, pRemote, (void *)DllName,
(lstrlen(DllName) + 1) * sizeof(WCHAR) , &numWritten);
DWORD ThreadId;
HANDLE hRemoteThread = CreateRemoteThread( hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE) pLoadLibrary, pRemote, 0, &ThreadId);
CloseHandle(hProcess);
//Cleanup code is omitted for the sake of
clarity
return 0;
}
Listing 1
Lets say the process ID is 3304, then run remotepeek 3304 on the command line.
This creates a thread (LoadLibrary routine) in the process whose ID is 3304. The LoadLibrary in turn loads our helper dll(shown in Listing 2). lets refer the dll as loader.dll. Ensure that loader.dll is a debug build, so you could put the breakpoint in the code indicated by red box in Listing :-
#include <process.h>
#include <stdlib.h>
#include <windows.h>
void MetaThread(void *)
{
HENHMETAFILE hemf = NULL;
while(1)
{
Sleep( 5000L );
if(hemf != NULL) |
{
CopyEnhMetaFile(hemf ,"c:\\metadump.emf");
break;
}
}
}
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch(ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
_beginthread( MetaThread, 0, NULL );
}
return TRUE;
}
Listing 2.
The remote thread created using CreateRemoteThread(…) exits after the Dll is loaded. To get a running thread, when a DLL_PROCESS_ATTACH notification is obtained by DllMain, a thread is created using _beginthread(…) which dies only after a metafile is created.
2) Debugger:- Now go to the Task Manger, right click the process and attach the vc++ debugger using the debug option or attach to the process from the vc++ using BuildàStart DebugàAttach to the Process. Set a breakpoint on PlayEnhMetaFile from EditàBreakPoints. Enter {,gdi32} PlayEnhMetaFile in “Break at” editbox. You won’t be able to break at the function if you have attached to the process with the “Tools àOptionsàdebugà”Load COFF and Exports” unchecked, so ensure it is enabled before attaching, also ensure you have symbols corresponding to the windows service pack, symbols can be obtained from the Service Pack CDs.
When the debugger breaks at PlayEnhMetaFile, check for EndPage in the Call Stack. If yes, then check the stack pointed by ESP(stack pointer),the DWORD pointed at ESP + 8(0x4B4614AF in Figure 1) is the HENHMETAFILE (handle to the metafile) which is getting played on the bands for the current page. Make a note of that value. Open Loader.cpp in the debugger and put a breakpoint at the position indicated in Listing 2.I ask you to put a breakpoint at this point of time and not before to avoid the debugger from breaking every 5 seconds(5 sec is to avoid thread from eating the CPU) as in Listing 1. The debugger will break in loader.cpp , edit the variable hemf to have the value that was noted. When CopyEnhMetaFile(…) is called, the metafile is created on the disk. The thread would exit after that. Figure 1 shows how everything would look like in the debugger.
Figure 1.
Now you can take the metafile obtained and insert into a word/powerpoint document using InsertàPictureàFrom File and send the document to your friends who don’t have the rare application you use for editing and viewing. Figure 2 shows Microsoft India Home page viewed in PowerPoint, metafile was extracted from Internet explorer on printing.
Figure 2.
Microsoft India Home Page metafile extracted from IE viewed in
PowerPoint.
The above procedure works for a single page in a given application context, extending it to handle multiple pages is a matter of wrapping the above code appropriately.
Note:- Adjust the timeout value in Sleep if you experience any timing issues on your system.
Conclusion:-
This article is a must read for any inquisitive developer who wants to know what is happening underneath with Spooling and Banding drivers when printing to file(Print to File: ) and use your development tools for accomplishing some cool things with the extracted metafiles.
Vipin Aravind
(GDI/GDI+/Printer drivers specialist)