Wednesday, November 10, 2004

 

Some notes on Screen Capture

1.
捕捉屏幕的时候包含鼠标
By JiangCheng
From http://blog.csdn.net/jiangsheng/archive/2004/12/17/219176.aspx

讨论参见http://search.csdn.net/expert/topicview1.asp?id=2307620

默认情况下屏幕捕捉下来的内容不包含鼠标的,但是可以把鼠标画到捉下来的图像里面

LPBITMAPINFOHEADER captureScreenFrame(int left,int top,int width, int height,int tempDisableRect)
{
#ifndef _DIRECTX_captureScreenFrame
HDC hScreenDC = ::GetDC(NULL);
#else
theApp.DirectXInit();
#endif

//if flashing rect
if (flashingRect && !tempDisableRect) {

if (autopan) {

pFrame->SetUpRegion(left,top,width,height,1);
DrawFlashingRect( TRUE , 1);

}
else
DrawFlashingRect( TRUE , 0);

}

#ifndef _DIRECTX_captureScreenFrame
HDC hMemDC = ::CreateCompatibleDC(hScreenDC);
HBITMAP hbm;

hbm = CreateCompatibleBitmap(hScreenDC, width, height);
HBITMAP oldbm = (HBITMAP) SelectObject(hMemDC, hbm);
BitBlt(hMemDC, 0, 0, width, height, hScreenDC, left, top, SRCCOPY);
#else
theApp.DirectXCapture(left, top,width, height);
HDC hMemDC = NULL;
theApp.DirectXGetDC(hMemDC);
#endif

//Get Cursor Pos
POINT xPoint;
GetCursorPos( &xPoint );
HCURSOR hcur= FetchCursorHandle();
xPoint.x-=left;
xPoint.y-=top;


//Draw the HighLight
if (g_highlightcursor==1) {

POINT highlightPoint;

highlightPoint.x = xPoint.x -64 ;
highlightPoint.y = xPoint.y -64 ;

InsertHighLight( hMemDC, highlightPoint.x, highlightPoint.y);

}

//Draw the Cursor
if (g_recordcursor==1) {



ICONINFO iconinfo ;
BOOL ret;
ret = GetIconInfo( hcur, &iconinfo );
if (ret) {

xPoint.x -= iconinfo.xHotspot;
xPoint.y -= iconinfo.yHotspot;

//need to delete the hbmMask and hbmColor bitmaps
//otherwise the program will crash after a while after running out of resource
if (iconinfo.hbmMask) DeleteObject(iconinfo.hbmMask);
if (iconinfo.hbmColor) DeleteObject(iconinfo.hbmColor);

}


::DrawIcon( hMemDC, xPoint.x, xPoint.y, hcur);

}
//CString strText=COleDateTime::GetCurrentTime().Format();
//CRect rc(0,0,640,480);
//DrawText(hMemDC,strText,-1,&rc,DT_LEFT);
#ifndef _DIRECTX_captureScreenFrame
SelectObject(hMemDC,oldbm);
LPBITMAPINFOHEADER pBM_HEADER = (LPBITMAPINFOHEADER)GlobalLock(Bitmap2Dib(hbm, bits));
//LPBITMAPINFOHEADER pBM_HEADER = (LPBITMAPINFOHEADER)GlobalLock(Bitmap2Dib(hbm, 24));
#else
theApp.DirectXReleaseDC(hMemDC);
LPBITMAPINFOHEADER pBM_HEADER = (LPBITMAPINFOHEADER)GlobalLock(theApp.DirectXGetCaptureBitmap(bits));
#endif
if (pBM_HEADER == NULL) {

//MessageBox(NULL,"Error reading a frame!","Error",MB_OK | MB_ICONEXCLAMATION);
AfxMessageBox(IDS_CAPTURE_FAIL);
AfxPostQuitMessage(0);
//exit(1);
}
#ifndef _DIRECTX_captureScreenFrame
DeleteObject(hbm);
DeleteDC(hMemDC);
#endif
//if flashing rect
if (flashingRect && !tempDisableRect) {

if (autopan) {
DrawFlashingRect(FALSE , 1);
}
else
DrawFlashingRect(FALSE , 0);

}
#ifndef _DIRECTX_captureScreenFrame
ReleaseDC(NULL,hScreenDC) ;
#else
theApp.DirectXUninit();
#endif
return pBM_HEADER;
}

(按:难点在于动画光标的透明绘制)

2.
在视图中同步显示鼠标的位置
By happyparrot
From http://blog.csdn.net/happyparrot/archive/2004/06/09/21591.aspx

【实现功能】当鼠标在视图区移动时,在鼠标附近同步显示鼠标所在的坐标位置。这个功能,对许多网友来说并不复杂。简单的办法是在OnDraw中绘制坐标位置就可以了。本文的实现思路是在不调用视图刷新以及不覆盖视图上原有内容的基础上实现鼠标坐标的跟踪显示。

【实现方法】

1。鼠标移动前,计算显示的鼠标坐标字符串所要占的矩形位置。

2。将视图中这个位置的图像复制备份,

3。在视图上绘制坐标

4。鼠标下次移动时,在上次的位置上恢复保存的图像。

【实现代码】

若干常量定义:

#define WORD_HEIGHT 18 //数字的高度,根据系统的字体调整
#define WORD_WIDTH 7 //数字的宽度,根据系统的字体调整
#define OFF_X 15 //坐标显示的位置与鼠标的距离
#define OFF_Y 10 //一般在鼠标的右下角
#define TEXT_COLOR RGB(255,0,0) //坐标文字的颜色

定义视图类的成员变量:

CBitmap m_StoreBmp; //存储位图
CDC m_StoreDC; //存储DC
int m_nCoordStrLen; //坐标字符串长度
CPoint m_OldPt; //上次的鼠标位置
BOOL m_bStart; //鼠标是否开始移动

//拷贝重画位图,将视图中坐标字符串所要占的矩形位置的图像复制备份

//copyPt--鼠标当前的位置

void CDrawMouseView::CopyBitmap(CDC* pDC,CPoint copyPt)
{
if(m_StoreBmp.GetSafeHandle()){//如果已有位图,则先清空
m_StoreDC.DeleteDC();
m_StoreBmp.DeleteObject();
m_StoreBmp.m_hObject=0;
}

if(m_StoreDC.CreateCompatibleDC(pDC))
{
if(m_StoreBmp.CreateCompatibleBitmap(pDC,m_nCoordStrLen,WORD_HEIGHT))
{
m_StoreDC.SelectObject(&m_StoreBmp);
m_StoreDC.BitBlt(0,0,m_nCoordStrLen,WORD_HEIGHT,pDC,copyPt.x+OFF_X,copyPt.y+OFF_Y,SRCCOPY);
}
else
{
if(m_StoreBmp.GetSafeHandle())
m_StoreBmp.DeleteObject();
m_StoreDC.DeleteDC();
}
}
}

//画坐标

coordString---坐标字符串

drawPt--绘制起点坐标
void CDrawMouseView::DrawCoord(CDC* pDC,CString& coordString,CPoint drawPt)
{
if(m_StoreBmp.GetSafeHandle())//有位图才画
{
drawPt.Offset(OFF_X,OFF_Y);
CRect rect(drawPt,CSize(m_nCoordStrLen,WORD_HEIGHT));
pDC->SetBkMode(TRANSPARENT);//设置透明背景
COLORREF crf = pDC->SetTextColor(TEXT_COLOR);
pDC->DrawText(coordString,rect,DT_CENTER);
pDC->SetTextColor(crf);
}
}

//擦除上次的坐标
void CDrawMouseView::DoRubberCoord(CDC* pDC,CPoint showPt)
{
if(m_StoreBmp.GetSafeHandle())
{
m_StoreDC.SelectObject(&m_StoreBmp);
pDC->BitBlt(showPt.x+OFF_X,showPt.y+OFF_Y,m_nCoordStrLen,WORD_HEIGHT,&m_StoreDC,0,0,SRCCOPY);
m_StoreDC.DeleteDC();
m_StoreBmp.DeleteObject();
}
}
//鼠标移动事件
void CDrawMouseView::OnMouseMove(UINT nFlags, CPoint point)
{
CDC* pDC = GetDC();
if(!m_bStart)
m_bStart = true;
else
DoRubberCoord(pDC,m_OldPt);//当不是第一次移动鼠标时才需要先擦除上次的坐标
CString str;
str.Format("[%d,%d]",point.x,point.y);
m_nCoordStrLen=str.GetLength()*WORD_WIDTH;
m_OldPt = point;
CopyBitmap(pDC,point);
DrawCoord(pDC,str,point);
ReleaseDC(pDC);
}

【编者注】本文所用程序在VC6.0下编译运行正确。如果大家有不同的见解和看法,欢迎讨论。

3.
Window Contents Capturing using WM_PRINT

作者:袁峰
出处:http://www.fengyuan.com/article/wmprint.html

Window Contents Capturing using WM_PRINT Message
Copyright (c) 2000 by Feng Yuan (author of Windows Graphics Programming: Win32 GDI and DirectDraw, www.fengyuan.com). All rights reserved.

Version 1.00: November 4, 2000
Version 1.01: November 6, 2000 (add Within_WM_PRINT method)
Version 1.02: December 1, 2000 (add sample program link)
Version 1.03: April 12, 2004 (add DLL injection to capture window of other processes)


--------------------------------------------------------------------------------

Problem
The normal way of capturing the contents of a window into a bitmap is creating a memory device context (CreateCompatibleDC), creating a device-dependent bitmap (CreateCompatibleBitmap) or a DIB section (CreateDIBSection), selecting the bitmap into the memory DC (SelectObject), and then bitblt from the window's device context (GetWindowDC) into the memory DC (Bitblt). After that, a copy of the contents of the window as it appears on the screen is stored in the bitmap.

But what if the window is hidden, or partially blocked with other windows ? When a window is hidden or partially blocked, the non-visible part of the window will be clipped in the device context returned from GetWindowDC. In other words, that part of the window can't be captured using a simple BitBlt.

To capture any window, completely visible, partially visible, or complete invisible, Win32 API provides two special messages, WM_PRINT and WM_PRINTCLIENT. Both these messages take a device context handle as a parameter, and the window handling these messages is supposed to draw itself or its client area into the device context provided.

Sounds good ? There is a catch. Normally only windows implemented by the operating system are knowledgeable to handle these messages. If you send a WM_PRINT message to a window, normally all the non-client area, which includes border, title bar, menu bar, scroll bar, etc., and common controls are drawn properly. Client area of windows implemented by application programs are normally left blank.

This article shows a sophisticated method to trick a window implemented by applications to handle WM_PRINTCLIENT message without its source code.

Test Program
To experiment with WM_PRINT/WM_PRINTCLIENT messages, a text program is written. Actually, the program is a slightly modified version of the "Hello, World" program generated by MSVC Wizard (plain Win32 application).

The routine handling WM_PAINT message is OnPaint, which calls BeginPaint, a custom drawing routine OnDraw, and then EndPaint. The OnDraw routine just draws an ellipse in the client area. Here is how its screen looks.

.

void OnDraw(HWND hWnd, HDC hDC)
{
RECT rt;
GetClientRect(hWnd, &rt);

SelectObject(hDC, GetSysColorBrush(COLOR_INFOBK));
Ellipse(hDC, rt.left+5, rt.top+5, rt.right-3, rt.bottom-5);
}

void OnPaint(HWND hWnd)
{
PAINTSTRUCT ps;

HDC hDC = BeginPaint(hWnd, & ps);

OnDraw(hWnd, hDC);
EndPaint(hWnd, & ps);
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_COMMAND:
switch ( LOWORD(wParam) )
{
case IDM_EXIT:
DestroyWindow(hWnd);
break;

case ID_FILE_PRINT:
PrintWindow(hWnd);
break;

default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;

case WM_PAINT:
OnPaint(hWnd);
break;

case WM_DESTROY:
PostQuitMessage(0);
break;

default:
return DefWindowProc(hWnd, message, wParam, lParam);
}

return 0;
}

To test the WM_PRINT/WM_PRINTCLIENT message, a "Print" menu item is added to the "File" menu. The window procedure calls the following PrintWindow routine to capture the screen using WM_PRINT message. Note the WM_PRINT message creates a memory DC, a DDB, selects the DDB into the memory DC, and then passes the memory DC handle as the WPARAM of the WM_PRINT message. The LPARAM parameter of the message specifies that everything should be drawn, including client/non-client area, background, and any child window.

void PrintWindow(HWND hWnd)
{
HDC hDCMem = CreateCompatibleDC(NULL);

RECT rect;

GetWindowRect(hWnd, & rect);

HBITMAP hBmp = NULL;

{
HDC hDC = GetDC(hWnd);
hBmp = CreateCompatibleBitmap(hDC, rect.right - rect.left, rect.bottom - rect.top);
ReleaseDC(hWnd, hDC);
}

HGDIOBJ hOld = SelectObject(hDCMem, hBmp);
SendMessage(hWnd, WM_PRINT, (WPARAM) hDCMem, PRF_CHILDREN | PRF_CLIENT | PRF_ERASEBKGND | PRF_NONCLIENT | PRF_OWNED);

SelectObject(hDCMem, hOld);
DeleteObject(hDCMem);

OpenClipboard(hWnd);

EmptyClipboard();
SetClipboardData(CF_BITMAP, hBmp);
CloseClipboard();
}

When WM_PRINT message returns, the bitmap is pasted to the clipboard, so you can use any graphics application to view/save the image. Here is what's being captured, everything except the ellipse.



Prototype Solution
The problem with the test program shown above is of course the disconnection between the WM_PRINT message, and the OnPaint routine handling WM_PAINT message. The default window procedure is smart enough to draw non-client area and then send a WM_PRINTCLIENT message to the window. But there is no default processing for the WM_PRINTCLIENT message, which explains why only the client are is left blank.

If you have the source code of the window procedure, adding a handling of WM_PRINTCLIENT to share the WM_PAINT message handling is very easy, as is shown below.

void OnPaint(HWND hWnd, WPARAM wParam)
{
PAINTSTRUCT ps;
HDC hDC;

if ( wParam==0 )
hDC = BeginPaint(hWnd, & ps);
else
hDC = (HDC) wParam;

OnDraw(hWnd, hDC);

if ( wParam==0 )
EndPaint(hWnd, & ps);
}

LRESULT CALLBACK WndProc0(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
...
case WM_PRINTCLIENT:
SendMessage(hWnd, WM_PAINT, wParam, lParam);
break;

case WM_PAINT:
OnPaint(hWnd, wParam);
break;
...
}
return 0;
}

In the code shown above, handling for the WM_PRINTCLIENT is added, which just sends a WM_PAINT message to the window itself with the wParam and lParam. The WPARAM parameter is added to OnPaint routine. When wParam is not 0, it's cast into a device context handle, instead of calling BeginPaint to retrive a device context for the non-client area. Likewise, EndPaint is only called when wParam is 0. These simple changes in source code level makes the whole WM_PRINT message handling complete for client area.

Handling WM_PRINTCLIENT Message without Source Code Change
What if you do not have the source code of the window ? Subclassing the window to add a handling for the WM_PRINTCLIENT is easy. What's hard is how to trick bypass BeginPaint and EndPaint, and how to pass the wParam from WM_PRINTCLIENT to the drawing code after BeginPaint.

Here is the declaraction of the CPaintHook class which handles window subclassing and implementation of WM_PRINTCLIENT message handling.

// Copyright (C) 2000 by Feng Yuan (www.fengyuan.com)

class CPaintHook
{
BYTE m_thunk[9];
WNDPROC m_OldWndProc;
HDC m_hDC;

static HDC WINAPI MyBeginPaint(HWND hWnd, LPPAINTSTRUCT lpPaint);
static BOOL WINAPI MyEndPaint(HWND hWnd, LPPAINTSTRUCT lpPaint);

virtual LRESULT WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

public:

bool Within_WM_PRINT(void) const
{
if ( (m_thunk[0]==0xB9) && ((* (unsigned *) (m_thunk+5))==0x20FF018B) )
return m_hDC !=0;
else
return false;
}

void SubClass(HWND hWnd);
};

The CPaintHook class has three member variables, one BYTE array of storing some machine code, a pointer to the original window procedure, and a device context handle. The two static methods replaces the original system provided BeginPaint and EndPaint routines. A virtual window message procedure is provided to override message processing for the window. Finally, the SubClass method subclasses an existing window and makes sure it handles WM_PRINTCLIENT message properly.

The implementation of this seemly simple class is quite tricky. Some knowledge of Win32 API implementation, compiler code generation, Intel machine code, and virtual memory is needed to understand it fully.

// Copyright (C) 2000 by Feng Yuan (www.fengyuan.com)

#include "stdafx.h"
#include

#include "hookpaint.h"

bool Hook(const TCHAR * module, const TCHAR * proc, unsigned & syscall_id, BYTE * & pProc, const void * pNewProc)
{
HINSTANCE hMod = GetModuleHandle(module);

pProc = (BYTE *) GetProcAddress(hMod, proc);

if ( pProc[0] == 0xB8 )
{
syscall_id = * (unsigned *) (pProc + 1);

DWORD flOldProtect;

VirtualProtect(pProc, 5, PAGE_EXECUTE_READWRITE, & flOldProtect);

pProc[0] = 0xE9;
* (unsigned *) (pProc+1) = (unsigned)pNewProc - (unsigned) (pProc+5);

pProc += 5;

return true;
}
else
return false;
}
The Hook routine hooks certain kind of exported function from a module by directly modifying its starting machine code. The benefit of hacking machine code directly is that you only need to hack into a single place, all the call in a process is taken care of. But hacking machine code directly is very tricky because it's not easy to parse machine code to find extra space for a five byte jump instruction. Chapter 4 of my book contains more generic code to handle this problem. What's shown here only applies to a special case, which applies to BeginPaint and EndPaint on Windows NT/2000 machines. On these machine, BeginPaint and EndPaint calls system services provided by Win32K.SYS. These routines follow a strict pattern, the first instruction stores a DWORD index into the EAX register. The instructions after that issue a software interruption (0x2E), which will be served by Win32K.SYS in kernel mode address space.

The Hook routine uses GetModuleHandle to retrieve module handle, GetProcAddress to retrieve the address of an exported Win32 API function. It then checks if the first instruction is a constant move to EAX register instruction (0xB8). If a match is found, VirtualProtect is used to change the protection flag for that page to PAGE_EXECUTE_READWRITE, which makes it writeable. The system service call index is saved, and then the first five bytes are changed to a jump instruction to a function whose address is passed through the pNewProc parameter.

LRESULT CPaintHook::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
assert(m_OldWndProc);

if ( uMsg==WM_PRINTCLIENT )
{
m_hDC = (HDC) wParam;
uMsg = WM_PAINT;
}

LRESULT hRslt = CallWindowProc(m_OldWndProc, hWnd, uMsg, wParam, lParam);

m_hDC = NULL;

return hRslt;
}
Implementing CPaintHook::WndProc is fairly simple. If the current message is WM_PRINTCLIENT, the device context handle passed in WPARAM is saved in member variable m_hDC, and then message is changed to WM_PAINT. CallWindowProc is used to call the original window procedure.

HDC WINAPI CPaintHook::MyBeginPaint(HWND hWnd, LPPAINTSTRUCT lpPaint)
{
const CPaintHook * pThis = (CPaintHook *) GetWindowLong(hWnd, GWL_WNDPROC);

pThis = (const CPaintHook *) ( (unsigned) pThis - (unsigned) & pThis->m_thunk[0] + (unsigned) pThis );

if ( pThis->Within_WM_PRINT() )
{
memset(lpPaint, 0, sizeof(PAINTSTRUCT));

lpPaint->hdc = pThis->m_hDC;

GetClientRect(hWnd, & lpPaint->rcPaint);

return pThis->m_hDC;
}
else
{
__asm mov eax, syscall_BeginPaint
__asm push lpPaint
__asm push hWnd
__asm call pBeginPaint
}
}

BOOL WINAPI CPaintHook::MyEndPaint(HWND hWnd, LPPAINTSTRUCT lpPaint)
{
const CPaintHook * pThis = (CPaintHook *) GetWindowLong(hWnd, GWL_WNDPROC);

pThis = (const CPaintHook *) ( (unsigned) pThis - (unsigned) & pThis->m_thunk[0] + (unsigned) pThis );

if ( pThis->Within_WM_PRINT() )
return TRUE;
else
{
__asm mov eax, syscall_EndPaint
__asm push lpPaint
__asm push hWnd
__asm call pEndPaint
}
}
Implementation of the two static functions, MyBeginPaint and MyEndPaint, are very similar. Beging static member functions, they do not have 'this' pointer to access object member variables. The two functions calculates the current 'this' pointer from the current window procedure address, which is the address of its m_thunk member variable (explained below). Once 'this' pointer is got, the m_hDC member variable is changed to see if we're actually handling a WM_PRINTCLIENT message, instead of normal WM_PAINT message. If a device context handle is given, the original BeginPaint and EndPaint will be skipped. Otherwise, the system service index is set into the EAX register, and the instructions after the first instruction in the original BeginPaint/EndPaint is called as a subroutine, although a jump instruction without pusing the parameters will work too.

static unsigned syscall_BeginPaint = 0;
static BYTE * pBeginPaint = NULL;

static unsigned syscall_EndPaint = 0;
static BYTE * pEndPaint = NULL;

CPaintHook::CPaintHook()
{
static bool s_hooked = false;

if ( ! s_hooked )
{
Hook("USER32.DLL", "BeginPaint", syscall_BeginPaint, pBeginPaint, MyBeginPaint);
Hook("USER32.DLL", "EndPaint", syscall_EndPaint, pEndPaint, MyEndPaint);

s_hooked = true;
}

m_thunk[0] = 0xB9; // mov ecx,
*((DWORD *)(m_thunk+1)) = (DWORD) this; // this
*((DWORD *)(m_thunk+5)) = 0x20FF018B; // mov eax, [ecx]

m_OldWndProc = NULL;
m_hDC = NULL;
}

void CPaintHook::SubClass(HWND hWnd)
{
m_OldWndProc = (WNDPROC) GetWindowLong(hWnd, GWL_WNDPROC);
SetWindowLong(hWnd, GWL_WNDPROC, (LONG) ((void *) m_thunk));
}
The constructor CPaintHook::CPaintHook and the SubClass method are the magic glue which hold everything together. The constructor will make sure the Hook function are called twice to hook Win32 API function BeginPaint and EndPaint, which are both exported from module USER32.DLL. For each instrance of the CPaintHook class, it's m_thunk data member will be initialized to two machine instructions. The first moves 'this' pointer to the ECX register, the second calls the first virtual method of that object, the CPaintHook::WndProc virtual method implementation.

The SubClass method remembers the original window procedure, and passes the address of m_thunk data member as the new window procedure.

With the CPaintHook class, hooking a window to handle WM_PRINTCLIENT message is a piece of cake. Here is the WinMain function of our test program, which creates an instance of the CPaintHook class on the stack, and calls the SubClass method.

int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
WNDCLASSEX wcex;

memset(&wcex, 0, sizeof(wcex));

wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = (WNDPROC) WndProc;
wcex.hInstance = hInstance;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = (LPCSTR) IDC_PAINT;
wcex.lpszClassName = "Class";

RegisterClassEx(&wcex);

HWND hWnd = CreateWindow("Class", "WM_PRINT", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

assert(hWnd);

CPaintHook hook;

hook.SubClass(hWnd);

ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);

MSG msg;

while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

return msg.wParam;
}

Limitation
The Hook function only handles exported function whose first instruction is "MOV EAX, ". So the implementation shown here only applies to Windows NT/2000. Refer to Chapter 4 for more generic of restrictive API hooking solutions.
Only tested on Windows 2000 machine.

(按:该方法限制较多)

4.

http://support.microsoft.com/default.aspx?scid=kb;EN-US;q316563
发现别人也有和我一样的问题

“I use the GDI+ in VC7 to save an image. It work very well when I want to save as tiff, png and jpeg. But the program fails to do so when I want to save as wmf, exif and emf.

I consult the msdn and find in
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdicpp/GDIPlus/UsingGDIPlus/UsingImageEncodersandDecoders/Retrievingthe.asp

it said that

"The function GetEncoderClsid in the following example receives the Multipurpose Internet Mail Extensions (MIME) type of an encoder and returns the class identifier (CLSID) of that encoder. The MIME types of the encoders built into Microsoft Windows GDI+ are as follows:

image/bmp
image/jpeg
image/gif
image/tiff
image/png"

Thus, it seems that we could not save an image as wmf, exif or emf format. But I also find that

http://www.codeproject.com/cs/media/imagconvert.asp?target=conver

In the C#, we could save an image as wmf, exif or emf format with no pains.

I had used the code on MSDN to enumerate the encoders and getting the available list, but it doesn't work! Here is the function GetImageCLSID

static int GetImageCLSID(const WCHAR* format, CLSID* pCLSID)
{
UINT num = 0; // number of image encoders
UINT size = 0; // size of the image encoder array in bytes

ImageCodecInfo* pImageCodecInfo = NULL;

GetImageEncodersSize(&num, &size);
if(size == 0)
return -1; // Failure

pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
if(pImageCodecInfo == NULL)
return -1; // Failure

GetImageEncoders(num, size, pImageCodecInfo);

for(UINT j = 0; j < num; ++j)
{
if( wcscmp(pImageCodecInfo[j].MimeType, format) == 0 )
{
*pCLSID = pImageCodecInfo[j].Clsid;
free(pImageCodecInfo);
return j; // Success
}
}

free(pImageCodecInfo);
return -1; // Failure
}

Would you please tell me how to solve this puzzle? ”

等着有高人来回答这个问题。

难道昨天我的blog被人访问了700多次?可一般也就是每天10多次而已,而且无人留言。奇怪?

对了,被人告诉有如下的签名档可以玩
http://www.danasoft.com/customsig.php
看了看,还是满有趣的。而且他们的服务器还很强劲,架得住这么大的数据流量。

补充:
终于有了点空闲时间,根据袁峰大人的指点
http://blog.joycode.com/fyuan/posts/17615.aspx
解决了这一问题,答案如下
Actually, the solution code is quite simple. First construct a Metafile object and then draw graphics on it. For examples, we can have

Bitmap* m_pBitmap;
...
// do some paintings on m_pBitmap
...
CString sFileName = "c:\\1.emf";
// you can use "c:\\1.exi" or "c:\\1.wmf" here, and you can also build it as IStream
CDC *cdc = GetDC();
Metafile *metafile = new Metafile(ToWChar(sFileName.GetBuffer(sFileName.GetLength())), cdc->m_hDC);
Graphics graphics(metafile);
graphics.DrawImage(m_pBitmap, 0, 0, m_pBitmap->GetWidth(), m_pBitmap->GetHeight());

4/27/2004修改



<< Home

This page is powered by Blogger. Isn't yours?