Thursday, November 11, 2004

 

Some notes on EXIF Tags

1.
from http://vbaccelerator.com/home/NET/Code/Libraries/Graphics/Reading_EXIF_Tags_from_JPG_Files/article.asp

Many digital cameras record information about when and how a picture was taken using the EXIF format (which is slowly becoming the DCF format under ISO). You can read and write this information using classes within the System.Drawing namespace, but the .NET library doesn't provide as much as you would like to help you do it.

This article provides a sample application demonstrating how to parse the most popular EXIF tags and populate the data using a background threads, and provides links to resources with more details on the EXIF codes.

About EXIF Tags
EXIF tags are an extension of the JPEG format used to store particular information about the make and model of a camera, when a picture was taken and digitised, information about the resolution of the image and things like the exposure time, focal length and whether a flash was used to take the photo.

if you're interested in the details of EXIF then read TsuruZoh Tachibanaya's Description of Exif file format which goes through most of the EXIF tags and exactly how they are stored.

The System.Drawing.Imaging provides a PropertyItem class which contains the meta-data associated with an image (incidentally this covers all types of image meta-data, and not just EXIF, so you can also use it to investigate information stored in TIF and PNG files too). However, if you take a look you'll see you don't get very much - here's the definition of PropertyItem:

public sealed class PropertyItem : System.Object
{
public int Id [get, set]
public int Len [get, set]
public short Type [get, set]
public byte[] Value [get, set]
}

Hmmm. What are the values of Id? How about Type? The documentation is nice and terse on these subjects (for example, Len "Gets or sets the length of the property.)

If you take a look at the the Platform SDK in GDIPlusImaging.h you'll see that the Type and Id values have associated constants (PropertyTagType.. and PropertyTag.. values), but these don't appear to have made it in to the .NET version. The code presented with this article provides an EXIFPropertyItem class which provides the enumerations you need, plus helper methods for decoding the data in the opaque Value property.

Getting At An Image's EXIF Tags
To actually get the EXIF data for an image, you use the PropertyIdList property of the Image object. The code for this in the download is in the constructor of the EXIFListBoxItem class, and looks like this:

int[] propIds = img.PropertyIdList;
foreach( int id in propIds )
{
try
{
PropertyItem prop = img.GetPropertyItem(id);
// now use the property item:
EXIFPropertyItem exifProp = new EXIFPropertyItem(prop);
this.InnerList.Add(exifProp);
}
catch
{
// can't read this property
}
}

Having got at the property items, you then want to be able to find out what the item represents and decode the data stored by it. This work is assisted by the EXIFPropertyItem class, which contains a documented enumeration of EXIF Code ID values in KnownEXIFCodes and an enumeration of their types in EXIFPropertyTypes. In addition, it provides some helper methods to assist you in parsing the data. To understand the types of parsing which are done, first we'll look at the different types of data stored within EXIF tags.

The list of the twelve different EXIF property types is given below:

public enum EXIFPropertyTypes
{
///
/// Data contains unsigned bytes
///
ExifTypeUnsignedByte = 1,
///
/// Data contains a string
///
ExifTypeString = 2,
///
/// Data contains unsigned 2-byte values
///
ExifTypeUnsignedChar = 3,
///
/// Data contains unsigned 4-byte values
///
ExifTypeUnsignedInt = 4,
///
/// Data is fractional: each item in the data is 8 bytes long.
/// The first 4 bytes of each item in the data contain the
/// numerator (unsigned int), the second 4 bytes the denominator
/// (also unsigned int).
///
ExifTypeUnsignedRational = 5,
///
/// Data contains signed bytes
///
ExifTypeSignedByte = 6,
///
/// Data has arbitrary data type, tag specific
///
ExifTypeUndefined = 7,
///
/// Data contains signed 2-byte values
///
ExifTypeSignedChar = 8,
///
/// Data contains signed 4-byte values
///
ExifTypeSignedInt = 9,
///
/// Data is fractional: each item in the data is 8 bytes long.
/// The first 4 bytes of each item in the data contain the
/// numerator (signed int), the second 4 bytes the denominator
/// (also signed int).
///
ExifTypeSignedRational = 10,
///
/// Data contains 4-byte floating point values
///
ExifTypeFloat = 11,
///
/// Data contains 8-byte floating point values
///
ExifTypeDouble = 12
}

For each type, the PropertyItem may contain zero, one or more values, depending on the tag type. For example, the EXIF Manufacturer tag consists of a string of ASCII bytes containing the manufacturer's name, whereas the Orientation tag is a single short integer value with one of eight values representing the orientation of the camera when the picture was taken.

Examining a typical EXIF file, you see that almost all of the values of interest have either ASCII or 4 byte rational types. So the EXIFPropertyItem class provides methods to parse them. Its typically not difficult to parse any of the other types either: the Marshal class within the System.Runtime.InteropServices namespace provides a host of ways of manipulating bytes; likewise you could construct a MemoryReader instance on the byte data to convert the information.

Here are the functions used to parse strings and rational values:

public string ParsedString
{
get
{
string parsed = "";
if (this.data.Length > 1)
{
IntPtr h = Marshal.AllocHGlobal(this.data.Length);
int i = 0;
foreach (byte b in this.data)
{
Marshal.WriteByte(h, i, b);
i++;
}
parsed = Marshal.PtrToStringAnsi(h);
Marshal.FreeHGlobal(h);
}
return parsed;
}
}

public EXIFRational[] ParsedRationalArray
{
get
{
EXIFRational[] parsed = null;
int arraySize = (this.data.Length / 8);
if (arraySize > 0)
{
parsed = new EXIFRational[arraySize];
for (int i = 0; i < arraySize; i++)
{
parsed[i] = new EXIFRational(this.data, i * 8);
}
}
return parsed;
}
}

Where the EXIFRational structure is defined:

///
/// A structure containing an EXIFRational value
///
public struct EXIFRational
{
///
///
///
public int Denominator;
public int Numerator;

///
/// Creates an EXIFRational object from EXIF byte data,
/// starting at the byte specified by ofs
///
/// EXIF byte data
/// Initial Byte
public EXIFRational(byte[] data, int ofs)
{
Denominator = 0;
Numerator = 0;
if (data.Length >= ofs + 8)
{
Numerator = data[ofs];
Numerator |= (data[ofs + 1] >> 8);
Numerator |= (data[ofs + 2] >> 16);
Numerator |= (data[ofs + 3] >> 24);
Denominator = data[ofs + 4];
Denominator |= (data[ofs + 5] >> 8);
Denominator |= (data[ofs + 6] >> 16);
Denominator |= (data[ofs + 7] >> 24);
}
}
///
/// Returns the value of the fraction as a string
///
/// The value of the fraction as a string
public override string ToString()
{
string ret;
if (this.Denominator == 0)
{
ret = "N/A";
}
else
{
ret = String.Format("{0:F2}", this.Numerator * 1.0/this.Denominator);
}
return ret;
}
}

A Practical Example
An example of using this code would be to output the horizontal and vertical resolution of an image with EXIF tags. Referring to the KnownEXIFIDCodes enumeration, or the EXIF documentation referred to earlier, we see that the resolution is encoded in EXIF items with the following IDs:

///
/// Display/Print resolution of image. Default value is 1/72inch.
/// unsigned rational 1
///
XResolution = 0x011a,
///
/// Display/Print resolution of image. Default value is 1/72inch.
/// unsigned rational 1
///
YResolution = 0x011b,
///
/// Unit of XResolution(0x011a)/YResolution(0x011b).
/// '1' means no-unit, '2' means inch, '3' means centimeter.
/// Default value is '2'(inch).
/// unsigned short 1
///
ResolutionUnit = 0x0128,

This code will take an image, read all of the PropertyItems, and if it finds the appropriate value write out the resolution:

Image img = new Image(@"C:\My Pictures\Summer\Lager1.jpg");

// Get the resolution unit:
string resUnit = "dpi";
try
{
PropertyItem prop = img.GetPropertyItem((int)KnownEXIFIDCodes.ResolutionUnit);
switch (prop.Value[0])
{
case 1:
resUnit = "n/a";
break;
case 2:
resUnit = "dpi";
break;
case 3:
resUnit = "dpcm";
break;
}
}
catch {}

// Now get the horizontal resolution:
string horzRes = "1/72";
try
{
PropertyItem prop = img.GetPropertyItem((int)KnownEXIFIDCodes.XResolution);
EXIFPropertyItem exifProp = new EXIFPropertyItem(prop);
EXIFRational[] xRes = exifProp.ParsedRationalArray;
horzRes = xRes[0].ToString();
}
catch {}

// repeat for vertical:
string vertRes = "1/72";
try
{
PropertyItem prop = img.GetPropertyItem((int)KnownEXIFIDCodes.YResolution);
EXIFPropertyItem exifProp = new EXIFPropertyItem(prop);
EXIFRational yRes = exifProp.ParsedRationalArray;
vertRes = yRes[0].ToString();
}
catch {}

Console.WriteLine("The resolution of the image is {0} {1} x {2} {3}",
horzRes, resUnit, vertRes, resUnit);

The Demonstration Application
The demonstration application wraps up all of these techniques to provide a thumbnail viewer with tag information based on a ListBox. Since digital camera images can often be large, the sample uses a background thread to read images, create the thumbnail and evaluate the tags. Just point it to a directory containing images and it will start populating the list.

The threading model used in the sample is the "fire and forget" type. Images are processed one at a time in this case (it would be nicer to use the Explorer model to do this, with multiple background threads working at once, but doing that increases complexity somewhat). Requested images are added to a Stack, and this is popped whenever the background thread completes (or is idle). A job id is then assigned to the Image reading task. This allows the code to effectively cancel a job at the user's request, since it simply ignores any results from a job id it has given up on. This is a simple way of avoiding the thorny issue of job cancellation and seems reasonable in this case since at worst the job of converting an image takes no more than a few seconds, so it isn't going to hang around for ever on the machine.

The process of reading the image is then started on the background thread using BeginInvoke. One of the rules of threading in a UI is that a background thread must never attempt to invoke any methods or properties of objects in the UI thread directly. Instead, completion is notified through ControlInvoke, which performs the locking and marshalling needed to safely interact with the UI thread. Similar to starting the background image reading, this is a achieved by calling BeginInvoke on a delegate owned by the UI thread. Finally, once the results are in an item is added to the Owner-Draw ListBox in the same which renders the filename as a tile, draws the thumbnail and then lists the primary EXIF tags down the right-hand side.

(按:这个站点不错,我将继续挖宝)

2.
Photo Properties - Reads properties - such as EXIF - from graphic files.
By Jeffrey S. Gangel
from http://www.codeproject.com/cs/media/photoproperties.asp
(按:现成的代码)

3.
JPEG Hammer & PhotoLibrary progress
from http://www.shahine.com/omar/PermaLink,guid,03b81067-bea2-491a-97e8-f6fd81a576d6.aspx

A long time ago, I started a little project to do a simple thing: Automatically rotate Jpeg images based on the EXIF orientation tag that some cameras embed. I also wanted an easy way to adjust the Date & Time of my photos based on a timezone offset. The scenario being: you get on a plane to go somewhere, take a bunch of pictures but forget to change the Date & Time on your camera. Well this fixes that. Then I started to mess around with EXIF information and decided that I wanted to build a Class that essentially wrapped all the EXIF 2.2 tags into a nice object.

Well I think I am ready for a preview release. I created a GDN workspace for the project a while back but I’ve been too busy to finish everything up. In the next few days (maybe this weekend) I’ll make available a preview of JPEG Hammer and PhotoLibrary. I’d love to get some feedback from people on what PhotoLibrary is missing or what could be different. This is the first time I’ve ever designed a class to be re-usable by other folks (I plan on using it in dasBlog for my pending PhotoAlbum feature which is taking me forever to complete).

相关链接
http://wiki.shahine.com/default.aspx/MyWiki.PhotoLibrary
http://www.gotdotnet.com/community/workspaces/workspace.aspx?ID=22112D14-8CD1-4301-9FC1-24300CDEAF75

(按:直接提供属性表,更加方便,爽)

4.
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetcomp/html/FotoVisionDesktop.asp
(按:最后提一下微软的经典Sample应用FotoVision,我从中吸取了不少经验的说。属于GDI+方面的必读代码)

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修改

Tuesday, November 09, 2004

 

How to set wall paper

更改桌面图片有两种方法:

方法一:使用WINAPI函数SystemParametersInfo
我们先来看看SystemParametersInfo函数的定义和参数:

  使用API函数之前必须先在程序中声明如下:
  Private Declare Function SystemParametersInfo Lib "user32" Alias "SystemParametersInfoA" (ByVal uAction As Long, ByVal uParam As Long, ByVal lpvParam As Any, ByVal fuWinIni As Long) As Long

  其中各参数的意义如下表:

参数: 意义
uAction Long,指定要设置的参数。参考uAction常数表
uParam Long,参考uAction常数表
lpvParam Any,按引用调用的Integer、Long和数据结构。
fuWinIni 这个参数规定了在设置系统参数的时候,是否应更新用户设置参数

  下面是部分uAction参数,和使用它们的方法:

参数 意义和使用方法
6 设置视窗的大小,SystemParametersInfo(6, 放大缩小值, P, 0),lpvParam为long型
17 开关屏保程序,SystemParametersInfo(17, False, P, 1),uParam为布尔型
13,24 改变桌面图标水平和垂直间距,uParam为间距值(像素),lpvParam为long型
15 设置屏保等待时间,SystemParametersInfo(15, 秒数, P, 1),lpvParam为long型
20 设置桌面背景墙纸,SystemParametersInfo(20, True, 图片路径, 1)
93 开关鼠标轨迹,SystemParametersInfo(93, 数值, P, 1),uParam为False则关闭
97 开关Ctrl+Alt+Del窗口,SystemParametersInfo(97, False, A, 0),uParam为布尔型

  本例中我们选择图片并取得图片的完整路径,然后通过调用API函数,将这幅图设为墙纸,使用的语法为:SystemParametersInfo SPI_SETDESKWALLPAPER, 0, bmpfile, 1

  其中SystemParametersInfo表示要设置桌面墙纸,bmpfile是要设置的图片的路径。(按:我也在自己的程序了实现了这个应用,真的很方便。:em222:)

而设置桌面背景的平铺、拉伸、居中可以从注册表直接控制

http://www.china-askpro.com/msg15/qa64.shtml

方法就是在调用SystemParametersInfo修改桌面背景之前, 修改注册表的以下设定值:
HKEY_CURRENT_USER\Control Panel\Desktop\TileWallpaper
HKEY_CURRENT_USER\Control Panel\Desktop\WallpaperStyle
这两个设定值的意义如下:

TileWallpaper的设定值 WallpaperStyle的设定值 显示方式
"0" "0" 居中
"0" "2" 平铺
"1" - 拉伸
注:所谓「拉伸」是当图片与萤幕不等大时, 将图片调整成与萤幕等大小, 这是Windows 98 新增的功能, 但如果您的机器安装有 IE 4.x, 则 Windows 95 也具备相同的功能。调用SystemParametersInfo是为了使修改立即生效。

方法二:使用IActiveDesktop接口函数SetWallPaper。

SystemParametersInfo函数使用简单方便,但它也有缺陷,就是它只能更改桌面图片为bmp格式,而不能将jpg格式图片设置为桌面。原因是它主要用在标准模式桌面中,即非Active Desktop模式。
SystemParametersInfo函数使用方法如下:
TCHAR szBuf[MAX_PATH];
SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, (void*)szBuf,
SPIF_UPDATEINIFILE | SPIF_SENDWININICHANGE);
SystemParametersInfo函数根据第一个参数的不同,还有许多其他功能,这里就不赘述了,感兴趣的朋友可以到MSDN中看相应的说明。

SetWallPaper函数使用也很方便,但是在使用之前需要做一些前期工作。它可以在Active Desktop模式桌面下将jpg格式的图片设置为桌面。

SetWallPaper函数的使用方法如下:
1。在你的工程初始化代码中加入如下初始化代码段:
IActiveDesktop* pIActiveDesktop;
pIActiveDesktop = NULL;
CoCreateInstance(CLSID_ActiveDesktop, NULL, CLSCTX_INPROC_SERVER,
IID_IActiveDesktop, (void**) &pIActiveDesktop);
2。千万不要忘记在工程退出阶段加入如下释放代码段:
if (pIActiveDesktop != NULL)
{
pIActiveDesktop->Release();
pIActiveDesktop = NULL;
}

3。现在开始真正设置桌面,代码如下:
WALLPAPEROPT wpOptions;
COMPONENTSOPT compOptions;
compOptions.dwSize = sizeof(COMPONENTSOPT);
compOptions.fActiveDesktop = TRUE;
compOptions.fEnableComponents = TRUE;
pIActiveDesktop->SetDesktopItemOptions(&compOptions, 0);

SetDesktopItemOptions函数通过compOptions结构中成员的值设定是否显示桌面项目和是否激活Active Desktop。

wpOptions.dwSize = sizeof(WALLPAPEROPT);
wpOptions.dwStyle = WPSTYLE_TILE;
pIActiveDesktop->SetWallpaperOptions(&wpOptions, 0);

SetWallpaperOptions函数通过wpOptions结构中的成员值设定桌面图片显示的位置,就是我们都很熟悉的平铺、居中和拉伸。

pIActiveDesktop->SetWallpaper(T2CW(szBuf), 0);//设置桌面图片
pIActiveDesktop->ApplyChanges(AD_APPLY_ALL);//应用桌面

这方面的一个不错的例子可以参见:
wallpaperQ: A wallpaper Management Tool
http://www.codeproject.com/tools/wallpaperq.asp?target=wallpaper

9/10/2004补充
下面是zwvista封装的简单函数
from http://blog.csdn.net/foxmail/archive/2004/06/25/26552.aspx

//strPicFile是图像文件名,支持BMP JPEG GIF等格式
//dwStyle是墙纸的样式
//WPSTYLE_CENTER 居中 0
//WPSTYLE_TILE 平铺 1
//WPSTYLE_STRETCH 拉伸 2
//WPSTYLE_MAX 3
//返回值是TRUE时墙纸设置成功,返回FALSE时失败
BOOL SetWallpaper(CString &strPicFile, DWORD dwStyle)
{
HRESULT hr;
IActiveDesktop* pIAD;
//创建接口的实例
hr = CoCreateInstance ( CLSID_ActiveDesktop, NULL, CLSCTX_INPROC_SERVER,
IID_IActiveDesktop, (void**) &pIAD );
if(!SUCCEEDED(hr)) return FALSE;
//将文件名改为宽字符串,这是IActiveDesktop::SetWallpaper的要求
WCHAR wszWallpaper [MAX_PATH];
LPTSTR lpStr = strPicFile.GetBuffer(strPicFile.GetLength() );
MultiByteToWideChar(CP_ACP, 0, lpStr, -1, wszWallpaper, MAX_PATH);
strPicFile.ReleaseBuffer();
//设置墙纸
hr = pIAD->SetWallpaper(wszWallpaper, 0);
if(!SUCCEEDED(hr)) return FALSE;
//设置墙纸的样式
WALLPAPEROPT wpo;
wpo.dwSize = sizeof(wpo);
wpo.dwStyle = dwStyle;
hr = pIAD->SetWallpaperOptions(&wpo, 0);
if(!SUCCEEDED(hr)) return FALSE;
//应用墙纸的设置
hr = pIAD->ApplyChanges(AD_APPLY_ALL);
if(!SUCCEEDED(hr)) return FALSE;
//读取墙纸的文件名并打印在debug窗口内
hr = pIAD->GetWallpaper(wszWallpaper, MAX_PATH, 0);
CString strFile = wszWallpaper;
TRACE(strFile); //如果不用位图的话,这里有你意想不到的发现
//释放接口的实例
pIAD->Release();
return TRUE;
}

在MFC程序中应用此函数时,须注意以下三点。

1.在函数所在文件中加上声明IActiveDesktop的头文件

#include

2.在StdAfx.h中插入#include
位置不能搞错,否则IActiveDesktop将找不到定义

#include // MFC core and standard components
#include // NOTE: corrects compilation errors w/IActiveDesktop!!
#include // MFC extensions

3.由于此函数引用了COM组件,所以必须在C**App::OnInitInstance()中插入以下语句,初始化COM组件。

AfxOleInit();

补充:

只有在安装了IE4.0及以上版本时才能应用IActiveDesktop接口,只有在打开active desktop(活动桌面)的情况下才能应用SetWallpaper函数,下面是启用或关闭active desktop的函数:

//bEnable是TRUE时启用active desktop, 是FALSE时关闭
//返回值是TRUE时启用或关闭active desktop成功,返回FALSE时失败
BOOL EnableActiveDesktop(BOOL bEnable)
{
HRESULT hr;
IActiveDesktop* pIAD;
//创建接口的实例
hr = CoCreateInstance ( CLSID_ActiveDesktop, NULL, CLSCTX_INPROC_SERVER,
IID_IActiveDesktop, (void**) &pIAD );
if(!SUCCEEDED(hr)) return FALSE;
COMPONENTSOPT comp;
comp.dwSize = sizeof(comp);
comp.fEnableComponents = bEnable;
//启用或关闭active desktop
comp.fActiveDesktop = bEnable;
hr = pIAD->SetDesktopItemOptions(&comp, 0);
if(!SUCCEEDED(hr)) return FALSE;
//释放接口的实例
pIAD->Release;
return TRUE;
}

补充1/25/2005

文章来源:互联网 文章作者:未知


此一问题需调用 SystemParametersInfo API 函数,细节如下:
1. API 的声明:
Const SPI_SETDESKWALLPAPER = 20
Const SPIF_UPDATEINIFILE = &H1
Private Declare Function SystemParametersInfo Lib "user32" Alias "
SystemParametersInfoA" (ByVal uAction As Long, ByVal uParam As Long,
ByVal lpvParam As Any, ByVal fuWinIni As Long) As Long
注:如果以上的声明放在「一般模块」底下, 应在 Const 之前加上 Public 保留
字, 并且将 Private 保留字去掉。
2. 程序范例:
Call SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, BMP图档名称,
SPIF_UPDATEINIFILE)
例如:
' 1. 将桌面图片设定成 c:\windows\setup.bmp
Call SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, "c:\windows\setup.bmp
", SPIF_UPDATEINIFILE)
' 2. 将桌面图片清掉
Call SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, "",
SPIF_UPDATEINIFILE)
程序如下:
Call SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, BMP图档名称,
SPIF_UPDATEINIFILE)
例如:
' 1. 将桌面图片设定成 c:\windows\setup.bmp
Call SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, "c:\windows\setup.bmp
", SPIF_UPDATEINIFILE)
' 2. 将桌面图片清掉
Call SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, "",
SPIF_UPDATEINIFILE)
但以上程序设定图片之后, 必须等到下次 Windows 重新启动时才生效, 如果希
望设定之后立刻生效,则程序须修改如下:
Const SPIF_SENDWININICHANGE = &H2
Call SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, "c:\windows\setup.bmp
", SPIF_UPDATEINIFILE + SPIF_SENDWININICHANGE )
此外希望只有本次使用 Windows 时改变桌面图片(下次开机时还原原状), 则程序
如下:
Call SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, "c:\windows\setup.bmp
", SPIF_SENDWININICHANGE ) ' 去掉 SPIF_UPDATEINIFILE

9/9/2005 补充

A more complex approach
http://www.codeproject.com/tools/wallpaperq.asp

Monday, November 08, 2004

 

GDI and GDI+ leaks

1.

To tell the truth, I am not very sure whether this is a good way or not.

原文出处 http://www.relisoft.com/Win32/GdiLeaks.html
翻译出处 未知

Using the techniques of Resource Management you can not only guarantee that your program will leak no memory, it will also leak no other resources. In a Windows program, GDI objects are an important class of resources that should be protected using Resource Management techniques. In our own product, Code Co-op, we make extensive use of Resource Management, so we don't expect any leaks. Imagine our surprise when we found out that the Dispatcher, an important component of Code Co-op, turned out to be a voracious consumer of GDI resources.
使用 资源管理 技术,你不能仅仅保证你的程序无内存泄漏,还要保证没有其它资源泄漏. 在一个 Windows 程序内, GDI 物件是资源中的重要一类,是需要资源管理技术保护的. 在我们自己的项目中, Code Co-op, 我们广泛使用资源管理, 因此我们不希望有任何泄漏. 设想一下, 当我们发现调度器--Code Co-op的一个重要组件--成了个 GDI 资源的消耗大户时, 我们相当惊讶.

First of all, how do you find out such a thing? It turns out that the good old Task Manager can display GDI resources consumed by any running process. Use the Task Manager's menu item View>Select Columns and click the GDI Objects checkbox. A new column GDI Object will appear in the Processes view. If a number displayed in that column keeps increasing with time, you know there is a leak.
首先, 你怎样发现这种事情? 这就体现出了旧的任务管理器可显示任何正在运行的进程占用的资源的好处了. 使用任务管理器的的 查看>选择列...选单项并且单击 GDI 对象检查框. 一个新的纵列GDI 对象会出现在进程视图内.如果显示在这一列的某个数字随着时间增长, 你就知道有资源泄漏了.

By watching the Dispatcher process, we noticed that its allotment of GDI objects kept increasing every time the Dispatcher was checking email. Now we had to pinpoint exactly where in our code these resources were leaking. The way to do it is to count the GDI object usage before and after a given operation. The easiest (and most reliable) way to do it is to define an object which counts the resources first in its constructor and then in its destructor. If the numbers differ, it displays the difference. Here's the class we designed for this purpose.
观察调度器进程, 我们注意到,在每次调度器检查 email 时,它对GDI物件的分配就会增加. 现在我们就得精确查找我们的代码在哪里发生了资源泄漏. 做到这一点的方法就是在一个指定的操作之前和之后统计 GDI 物件的使用. 最容易(并且是最可靠的)的方法就是资源的构造器和析构器内定义一个计数器物件, 如果数值不一样, 它就显示差别. 下面就是我们为这个目的设计的一个类:

class DbgGuiLeak
{
public:
explicit DbgGuiLeak ()
{
_guiResCount = ::GetGuiResources (::GetCurrentProcess (),
GR_GDIOBJECTS);
}
~DbgGuiLeak ()
{
int leaks = ::GetGuiResources (::GetCurrentProcess (),
GR_GDIOBJECTS) - _guiResCount;
if (leaks != 0)
{
std::cout << "Gui Resources Leaked: " << leaks << std::endl;
}
}
private:
unsigned _guiResCount;
};
This kind of encapsulation has many advantages. Since we are using Resource Management religiously, all our resources are encapsulated in smart objects. The resource are automatically freed in those object's destructors. So if you define any smart object within the scope of the lifetime of your DbgGuiLeak, that object's destructor is guaranteed to be called before the destructor of DbgGuiLeak. C++ language guarantees this LIFO (last-in-first-out) ordering of lifetimes of stack objects. In other words, you won't see any spurious leaks resulting from the arbitrary order of resource deallocation. Compare the two examples below.
这种封装很有用处. 自从我们必恭必敬的使用资源管理时, 我就将所有资源都封装进智能物件. 在这些物件的析构器里,资源自动释放. 因此如果你在DbgGuiLeak的生存期内定义任何智能物件, 在 DbgGuiLeak的析构器之前, 物件的析构器可保证被调用. C++ 语言能保证这种后进先出的栈物件的排列方式. 换句话说, 从资源释放的任意顺序上, 你不会看到任何虚假的泄漏结果. 比较下边的两个例子.

In the first example, the leak counter will see no leaks (the destructor of gidObj will free its GDI resource before the destructor of leakCounter is called).
在第一个例子内, 泄漏计数器会看到没有泄漏.(gidObj的析构器将在leakCounter的析构器调用之前释放它的GDI资源)

{
DbgGuiLeak leakCounter;
SmartGdiObject gdiObj;
// destructor of gdiObj
// destructor of leakCounter
}
In the second example, a spurious leak will be displayed only because the implicit destructor of gidObj is called after the last explicit line of code is executed.
第二个例子, 一个虚假的泄漏将会出现,只是因为gidObj的隐式的析构器是在最后的显式的代码行被执行之后才被调用. (按:这种情况在调试中常常出现,十分讨厌)

{ // bad code!
unsigned gdiCount = ::GetGuiResources (::GetCurrentProcess (),
GR_GDIOBJECTS);
SmartGdiObject gdiObj;
int leaks = ::GetGuiResources (::GetCurrentProcess (),
GR_GDIOBJECTS) - gdiCount;
std::cout << "GDI Resources Leaked: " << leaks << std::endl;
// destructor of gdiObj
}
Using a DLL
使用 DLL
To get access to Simple MAPI, we have to use mapi32.dll, a dynamic-load library located in the Windows system directory. A DLL is a resource, so it has to be managed (in the RM sense, not the .NET sense). We define a simple class, Dll, to take care of loading and freeing any DLL.
为了访问 MAPI 的例子, 我们得使用 mapi32.dll, 一个位于 Windows 系统目录的动态加载的库文件. 一个 DLL 是个资源, 因此它得被管理(资源管理的方式, 不是 .NET 的方式). 我们定义一个简单类, Dll, 来处理加载和释放任何 DLL.

To call a function defined in a DLL, you have to retrieve a function pointer either by ordinal or, as we do it here, by name. You not only have to know the name of the function, but, because you would like to call it, you must know its signature. The name is passed to Dll::GetFunction as a string argument, but the signature (which is a type) must be passed as a template argument. Internally, we cast the typeless pointer returned by the API to a function pointer of the appropriate signature.
调用一个定义于DLL内的函数, 无论是按照顺序还是像我们这里的做法,按照函数名,你必须先获得函数的指针. 你不能仅仅知道函数的名字, 而且, 因为你将调用它, 你还得知道它的函数原型. 函数名作为一个字符参数传递给Dll::GetFunction, 但是识别标志(一个类型)必须作为一个参数模板传给Dll::GetFunction. 实质上, 我们将 API 返回的无类型指针作为有适当参数识别标志的函数指针.

class Dll
{
public:
Dll (std::string const & filename);
~Dll ();

std::string const & GetFilename () const { return _filename; }

template
void GetFunction (std::string const & funcName, T & funPointer)
{
funPointer = static_cast (GetFunction (funcName));
}

private:
void * GetFunction (std::string const & funcName) const;

std::string const _filename;
HINSTANCE _h;
};
The constructor of Dll calls the LoadLibrary API. (Strictly speaking, we don't need to store the name of the DLL, but it might get handy in case we wanted to display meaningful error reports.)
Dll的构造器调用LoadLibrary API 函数. (严格来讲, 我们不需要保存DLL的名字, 但可以方便我们显示更直观的错误报告. )

Dll::Dll (std::string const & filename)
: _filename (filename),
_h (::LoadLibrary (filename.c_str ()))

{
if (_h == 0)
throw "Cannot load dynamic link library";
}
According to the rules of Resource Management, the destructor of the Dll object must free the DLL resource --it does it by calling FreeLibrary.
按照资源管理的规则, Dll物件的析构器必须释放 DLL 资源 ---- 调用 FreeLibrary API 函数.

Dll::~Dll ()
{
if (_h != 0)
{
::FreeLibrary (_h);
}
}
A (typeless) function pointer is retrieved from the DLL by calling the GetProcAddress API.
从 DLL 内获得一个(无类型)的函数指针要通过调用 GetProcAddress API 函数.

void * Dll::GetFunction (std::string const & funcName) const
{
void * pFunction = ::GetProcAddress (_h, funcName.c_str ());
if (pFunction == 0)
{
throw "Cannot find function in the dynamic link library";
}
return pFunction;
}
Incidentally, when calling the template member function Dll::GetFunction you don't have to explicitly specify the template argument. The complier will deduce it from the type of the second argument. You'll see an example soon.
顺便说一下, 调用模板成员函数Dll::GetFunction时, 你不必明确的指定模板参数. 编译器会从第二个参数的类型推导出来的. 一会儿你会看到一个例子.

Simple MAPI
简单的 MAPI 例子
For the purpose of our test, all we need from Simple MAPI is to log ourselves on and off. The result of a logon is a session. Since a session is associated with internal resources (including some GDI resources, which are of particular interest to us), it has to be managed in the RM sense. We define a SimpleMapi::Session to encapsulate this resource. It calls MAPILogon in its constructor and MAPILogoff in its destructor. The signatures of these two functions are typedef'd inside the class definition (that's the only place we will need them). Logon and Logoff are both very specific pointer-to-function types. Those typedefs not only specify the return- and argument types, but also the calling conventions (FAR PASCAL in this case).
为了我们的测试目的, 我们从简单MAPI中的操作都用日志的方式记录下来. 登录的结果是个事务. 既然事务与内部资源有关(包含某些 GDI 资源, 这正是我们感兴趣的部分), 它得用资源管理的方式被管理. 我们定义一个 SimpleMapi::Session 来封装这个资源. 在构造器内调用MAPILogon,在析构器内调用MAPILogoff. 这两个函数的原型定义识别标志定义在类的定义内部(这是我们需要它的唯一场合). Logon 和 Logoff 都是很特殊的指针到函数的类型. 这些 typedef 语句不止指出了返回和参数类型, 还标示出了调用约定(这个例子是 FAR PASCAL).
namespace SimpleMapi
{
class Session
{
public:
Session (Dll & mapi);
~Session ();
private:
Dll &_mapi;
LHANDLE _h;
private:
typedef ULONG (FAR PASCAL *Logon) (ULONG ulUIParam,
LPTSTR lpszProfileName,
LPTSTR lpszPassword,
FLAGS flFlags,
ULONG ulReserved,
LPLHANDLE lplhSession);
typedef ULONG (FAR PASCAL *Logoff) (LHANDLE session,
ULONG ulUIParam,
FLAGS flFlags,
ULONG reserved);
};
}
Here is the constructor of a Simple MAPI session. Note that we call the GetFunction template method of Dll without specifying the template parameter. That's because we are giving the compiler enough information through the type Logon (see the typedef above) of the logon argument. Once the (correctly typed) function pointer is initialized, we can make a call through it using the regular function-call syntax.
这就是简易 MAPI 事务的构造器. 请留意我们调用 Dll类的GetFunction 模板过程, 但并不指定模板参数. 这是因为我们已经通过 logon参数的Logon类型(察看上面的 typedef 语句)为编译器提供了足够的资讯. (正确类型的)函数指针一旦初始化, 我们就能够通过它用普通的函数调用语法来做一个调用了.

namespace SimpleMapi
{
Session::Session (Dll & mapi)
: _mapi (mapi)
{
Logon logon; // logon is a pointer to function
_mapi.GetFunction ("MAPILogon", logon);
std::cout << "Mapi Logon" << std::endl;
ULONG rCode = logon (0, // Handle to window for MAPI dialogs
0, // Use default profile
0, // No password
0, // No flags
0, // Reserved -- must be zero
&_h); // Session handle
if (rCode != SUCCESS_SUCCESS)
throw "Logon failed";
}
}
The destructor of SimpleMapi::Session calls MAPILogoff function thus closing the session and (hopefully!) freeing all the resources allocated on its behalf.
SimpleMapi::Session的析构器调用MAPILogoff函数, 因而关闭事务并(满怀希望地!)释放它自己分配的所有资源.

namespace SimpleMapi
{
Session::~Session ()
{
Logoff logoff;
_mapi.GetFunction ("MAPILogoff", logoff);
std::cout << "Mapi Logoff" << std::endl;
ULONG rCode = logoff (_h, 0, 0, 0);

if (rCode != SUCCESS_SUCCESS)
throw "Logoff failed";
}
}
Testing for Leaks
检测泄漏
Here's our simple test. First we create a DbgGuiLeak object which will remember how many GUI resources are in use before the test starts. Then we create the Dll object which loads mapi32.dll into our address space. Finally, we create a SimpleMapi::Session which logs us into the MAPI subsystem.
这里是我们的简易测试. 首先我们创建一个 DbgGuiLeak 物件, 它会记得测试开始之前使用了多少 GUI 资源. 然后我们创建 Dll 物件, 它加载 mapi32.dll 到我们的地址空间. 最后, 我们创建 SimpleMapi::Session 物件,它展示给我们进入 MAPI 子系统的记录.
What happens next is all the destructors are called in the reverse order of creation. So first the destructor of SimpleMapi::Session logs us off and closes the session. Then the destructor of Dll frees the DLL. Finally, the destructor of DbgGuiLeak checks if any GUI resources have been leaked.
接下来的事情就是 按构造时的相反顺序调用所有析构器. 因此 SimpleMapi::Session 析构器首先告诉我们已退出登陆并关闭事务. 然后 Dll 的析构器释放DLL. 最后, DbgGuiLeak 的析构器检测任何任何GUI资源是否发生了泄漏.

void TestMapi ()
{
DbgGuiLeak leak;
Dll mapi ("mapi32.dll");
SimpleMapi::Session session (mapi);
// Session destructor logs off
// Dll destructor frees library
// DbgGuiLeak destructor prints leak info
}
The main function of our program calls TestMapi three times, to get past any transcient effects (not that we think it's ok for the first logon to leak resources -- but it wouldn't be such a disaster).
我们程序的 main 函数调用 TestMapi 三次, 来获得任何可能的结果(不是我们想象的那样一切OK, 因为第一次登录有资源泄漏 -- 但它不应该是这样的灾难).

int main ()
{
try
{
TestMapi ();
TestMapi ();
TestMapi ();
}
catch (char const * msg)
{
std::cerr << msg << std::endl;
}
}
For completeness, these are the files you have to include in the test program for it to compile:
作为一个完整的例子, 下边是你得包含进测试程序的文件, 以便能够编译:

#include
#include
#include

Now you are ready to compile and run the test. Results may vary, depending on what email client is installed on your computer and what security updates you have downloaded from Microsoft. Here are some typical results for a well-updated system with Microsoft Outlook Express.
现在你准备编译运行测试程序了. 结果与预期很不一样, 这有赖于你计算机上的 EMAIL 客户端安装数量和你从 Microsoft 下载的安全补丁的数量. 这里是已很好地升级了 Microsoft Outlook Express 系统的一些典型的结果.

C:\Test\MapiCons\Release>mapicons
Mapi Logon
Mapi Logoff
Gui Resources Leaked: 16
Mapi Logon
Mapi Logoff
Gui Resources Leaked: 2
Mapi Logon
Mapi Logoff
Gui Resources Leaked: 2
The first logon leaks 16 GDI resources, which is bad, but not terminally so. What is really bad is that every subsequent logon/logoff sequence leaks two additional resources. There is no excuse for this kind of shabby programming.
第一次登录泄漏了 16 个GDI资源, 很糟糕, 但并不是很严重. 真正糟糕的是每一次并发的登录/退出登录动作序列就泄漏额外的两个资源.

2.
GDI+ and MFC memory leak detection
By Zoltan Csizmadia

http://www.codeproject.com/vcpp/gdiplus/gdiplush.asp

3.
Leak Checking for GDI+
By Michael Mondry

Describes how to check your app for GDI+ leaks

http://www.codeproject.com/vcpp/gdiplus/leakchkgdiplus.asp

4.

1) CDC是基类
2) CPaintDC可以说是为WM_PAINT专用的,它比CDC多了一个PAINTSTRUCT 结构,它只能操作客户区。
有一点要注意:你调用了基类的OnPaint,那就不要再定义这种对象了,而改用其它。
3) CClientDC 只能操作客户区,实际它在构造中调用了GetDC,然后在析构中调用了ReleaseDC。
(所以我们可以定义一CDC指针,通过GetDC,便可看与CClientDC 一样的操作,因为CClientDC 并未增加任何方法。
但要记得用完后要调用ReleaseDC,否则资源泄漏!)
4) CWindowDC可以操作所有区域,如果要在非客户区操作,那就得使用它了,但实际上它也未增加任何方法。
只是构造时调用了CWindowDC,析构时调用ReleaseDC。


Still, not a good way to handle GDI and GDI+ leaks. We need to check and check to make sure everything is OK.

Sunday, November 07, 2004

 

Some notes on Twain

1.
A simple introduction written by Yonsm
From http://yonsm.reg365.com/index.php?job=art&articleid=a_20041108_224822

一、简介
TWAIN 数据源管理程序 (DSM) 工业标准的软件库,用于从静态图像设备提取图像。绝大部分的扫描仪和数码相机都提供了 TWAIN 驱动程序,利用统一的 TWAIN 接口,应用程序可以非常方便地从这些设备中获取图像。


二、使用步骤
互联网上关于 TWAIN 编程的中文资料很少,代码更是难找到,因为我不得不仔细阅读了 www.twain.org 提供的 TWAIN Specification。下面说说使用 TWAIN 接口获取图像的简要步骤。

Windows 系统中存在一个 TWAIN_32.dll,所有的 TWAIN 操作都是通过这个 DLL 导出的 DSM_Entry 函数来实现的 (说实在话,我个人觉得 TWAIN 接口设计得太差了,看看 MS 的WIA,逻辑上非常清晰)。这个函数的声明如下:

TW_UINT16 FAR PASCAL DSM_Entry(
pTW_IDENTITY pOrigin, // Source of message
pTW_IDENTITY pDest, // Destination of message
TW_UINT32 DG, // Data group ID: DG_xxxx
TW_UINT16 DAT, // Data argument type: DAT_xxxx
TW_UINT16 MSG, // Message ID: MSG_xxxx
TW_MEMREF pData // Pointer to data
);

1. 打开 DSM (Data Source Manager: 数据源管理器)

TWAIN 是一个数据源管理程序,应用程序首先要使用 MSG_OPENDSM 消息,打开数据源管理器。这里需要指定一个窗口句柄,应用程序应该在此窗口的消息循环中处理 TWAIN 消息 (MSG_PROCESSEVENT)。

2. 选择 DS (Data Source: 数据源)

因为一个系统中可能存在多个 TWAIN 设备,因此必须选择一个数据源。选择数据源通常有两种方式: 选择默认数据源 (MSG_GETDEFAULT) 和显示选择数据源对话框,由用户来选择数据源 (MSG_USERSELECT)。

3. 打开 DS

使用 MSG_OPENDS 消息打开数据源。

4. 设置参数

消息为 MSG_SET,设置各种参数,如获取方式、图像数量等。有些参数由设备驱动支持才有效。

5. 显示扫描界面

使用 MSG_ENABLEDS 消息,显示设备驱动提供的用户界面 (UI)。

6. 获取图像

如果用户选择扫描什么的,可以在窗口的消息循环中获取到这个事件 (MSG_XFERREADY)。此时,应用程序可以通过 DAT_SETUPFILEXFER 设置文件名,然后用 DAT_IMAGEFILEXFER 获取图像到文件中。

7. 关闭扫描界面

在窗口的消息循环中获取到 MSG_CLOSEDSREQ 或 MSG_CLOSEDSOK 消息,可以关闭扫描界面 (MSG_DISABLEDS)。

8. 关闭 DS

消息为 MSG_CLOSEDS。

9. 关闭数据源

消息为 MSG_CLOSEDSM。


三、CTwainHelper 助手类
为了使用方便,我写了一个静态 TWAIN 助手类 CTwainHelper。使用 CTwainHelper 的五个函数,就可以简单地从 TWAIN 设备获取图像到文件中。使用方法如下:

调用 CTwainHelper::Initialize() 确定是否有可用的设备。
在窗口消息循环中,调用 CTwainHelper::ProcessMessage() 处理 TWAIN 消息。
要获取图像时,调用 CTwainHelper::GetImage()。
如果图像已准备好 (如用户确定扫描图像),窗口会收到 WM_COMMAND 消息,wParam 为 IDC_TwainHelper。此时应用程序可以调用 CTwainHelper::TransferImage() 获取图像到文件中。
具体使用方法请参看示例代码。

CTwainHelper 可以在 Visual C++ 6.x/7.x 工程中使用,支持 UNICODE 编译。因为是静态类,要改写成 C 代码只需要做一点点少量的工作。


四、后话
当然,上面只是一种常用的步骤。其实应用程序完全可以自定义所有的步骤,比如不使用 TWAIN 驱动提供扫描对话框而直接扫描,或者扫描图像到内存中等等。详细情况请参考 TWAIN Specification,步骤大同小异,消息和参数千差万别,仔细看看应该很容易的。

如果没有 TWAIN 设备又要进行 TWAIN 程序开发,可以到 TWAIN 官方网站下载 TWAIN Developers Toolkit,安装后会有一个虚拟的 TWAIN 设备。不过应用程序在这个虚拟 TWAIN 设备中正常工作,不代表一定能在实际的 TWAIN 设备正常使用,这点需要注意。以前 CTwainHelper 就碰到过这样的情况 在虚拟 TWAIN 设备中明明是好的,在我的扫描仪上却不能扫描图像。检查后发现,原来设置了不支持的参数。

最后,TWAIN 是 Technology Without A Interesting Name 缩写,直译为没有“没有让人感兴趣名字的技术”,真是一个让人摸不着头脑的名字。

TWAIN 官方网站: http://www.twain.org
TWAIN 头文件: http://www.twain.org/devfiles/twain.h
TWAIN Specification: http://www.twain.org/docs/Spec1_9_197.pdf
TWAIN Developers Toolkit: http://www.twain.org/devfiles/twainkit.exe

2.
Help codes written by Yonsm

使用方法如下:

调用 CTwainHelper::Initialize() 确定是否有可用的设备。
在窗口消息循环中,调用 CTwainHelper::ProcessMessage() 处理 TWAIN 消息。
要获取图像时,调用 CTwainHelper::GetImage()。
如果图像已准备好 (如用户确定扫描图像),窗口会收到 WM_COMMAND 消息,wParam 为 IDC_TwainHelper。此时应用程序可以调用 CTwainHelper::TransferImage() 获取图像到文件中。

// 消息: 如果图像已准备好,父窗口将收到 WM_COMMAND 消息
// 参数: LOWORD(wParam) 为 IDC_TwainHelper,HIWORD(wParam) 第一次收到此消息时为 0,以后为总的图像个数
// 返回: 如果返回 S_FALSE,将不再传送后继的图像
// 备注: 收到此消息后,可以调用 TransferImage() 获取图像到文件中
#define IDC_TwainHelper 51234

// CTwainHelper 类
class CTwainHelper
{
public:
// 初始化 TWAIN 库
static BOOL Initialize(HWND hParent);
// 功能: 处理 TWAIN 消息
// 返回: 返回 TRUE 表示是 TWAIN 消息,此时父窗口不应该处理这个消息
// 备注: 在父窗口消息循环中应该调用此函数
static BOOL ProcessMessage(const PMSG pmsgMsg);
// 功能: 从 TWAIN 设备中获取图像,并以消息回调的形式通知父窗口
// 参数: uCount 指定要获取的图像的个数,0 表示不指定个数。此参数需要设备支持
// 返回: 返回 TRUE 表示成功或者用户取消,其它表示失败
// 备注: 如果调用成功,当图像准备好时,父窗口将到 WM_COMMAND 消息,请参看 IDC_TwainHelper 的说明
static BOOL GetImage(UINT uCount = 0);
// 传送图像到文件中
static BOOL TransferImage(PCTSTR ptzFileName);
// 关闭 TWAIN 对话框
static VOID Close();
}

.h file
// 预处理
#pragma once
#include
#include "Include/Twain.h"

#ifndef PCTSTR
#define PCTSTR LPCTSTR
#endif





// 使用方法
// 1. 调用 CTwainHelper::Initialize() 确定是否有可用的设备。
// 2. 在窗口消息循环中,调用 CTwainHelper::ProcessMessage() 处理 TWAIN 消息。
// 3. 要获取图像时,调用 CTwainHelper::GetImage()。
// 4. 如果图像已准备好 (如用户确定扫描图像),窗口会收到 WM_COMMAND 消息,wParam 为 IDC_TwainHelper。
// 此时应用程序可以调用 CTwainHelper::TransferImage() 获取图像到文件中。





// 消息: 如果图像已准备好,父窗口将收到 WM_COMMAND 消息
// 参数: LOWORD(wParam) 为 IDC_TwainHelper,HIWORD(wParam) 第一次收到此消息时为 0,以后为总的图像个数
// 返回: 如果返回 S_FALSE,将不再传送后继的图像
// 备注: 收到此消息后,可以调用 TransferImage() 获取图像到文件中
#define IDC_TwainHelper 51234





// CTwainHelper 类
class CTwainHelper
{
private:
static HWND m_hParent; // 父窗口句柄
static HMODULE m_hTwain; // TWAIN 动态连接库模块句柄
static DSMENTRYPROC m_dpDsmEntry; // 数据源管理器入口函数地址
static TW_IDENTITY m_twidOrigin; // 程序标识信息
static TW_IDENTITY m_twidSource; // 数据源标识信息

public:
// 初始化 TWAIN 库
static BOOL Initialize(HWND hParent);

// 功能: 处理 TWAIN 消息
// 返回: 返回 TRUE 表示是 TWAIN 消息,此时父窗口不应该处理这个消息
// 备注: 在父窗口消息循环中应该调用此函数
static BOOL ProcessMessage(const PMSG pmsgMsg);

// 功能: 从 TWAIN 设备中获取图像,并以消息回调的形式通知父窗口
// 参数: uCount 指定要获取的图像的个数,0 表示不指定个数。此参数需要设备支持
// 返回: 返回 TRUE 表示成功或者用户取消,其它表示失败
// 备注: 如果调用成功,当图像准备好时,父窗口将到 WM_COMMAND 消息,请参看 IDC_TwainHelper 的说明
static BOOL GetImage(UINT uCount = 0);

// 传送图像到文件中
static BOOL TransferImage(PCTSTR ptzFileName);

// 关闭 TWAIN 对话框
static VOID Close();

private:
// 调用数据源管理器入口函数
inline static TW_UINT16 DsmEntry(pTW_IDENTITY ptwidDest,
TW_UINT32 twuGroup, TW_UINT16 twuData, TW_UINT16 twuMsg, TW_MEMREF ptwmData)
{
return m_dpDsmEntry(&m_twidOrigin, ptwidDest, twuGroup, twuData, twuMsg, ptwmData);
}

// 设置单值参数
inline static TW_UINT16 SetOneValueCap(TW_UINT16 twuCap, TW_UINT32 twuValue)
{
TW_CAPABILITY twcCap;
TW_ONEVALUE twovValue;

twcCap.Cap = twuCap;
twcCap.ConType = TWON_ONEVALUE;
twcCap.hContainer = &twovValue;
twovValue.Item = twuValue;
twovValue.ItemType = TWTY_INT16;
return DsmEntry(&m_twidSource, DG_CONTROL, DAT_CAPABILITY, MSG_SET, &twcCap);
}

// 显示获取图像对话框
inline static TW_UINT16 ShowUI()
{
TW_USERINTERFACE twuiUI;

twuiUI.ShowUI = TRUE;
twuiUI.ModalUI = TRUE;
twuiUI.hParent = m_hParent;
return DsmEntry(&m_twidSource, DG_CONTROL, DAT_USERINTERFACE, MSG_ENABLEDS, &twuiUI);
}

// 隐藏获取图像对话框
inline static TW_UINT16 HideUI()
{
TW_USERINTERFACE twuiUI;

twuiUI.ShowUI = FALSE;
twuiUI.ModalUI = TRUE;
twuiUI.hParent = m_hParent;
return DsmEntry(&m_twidSource, DG_CONTROL, DAT_USERINTERFACE, MSG_DISABLEDS, &twuiUI);
}
};

.cpp file




// 预处理
#include "TwainHelper.h"





// CTwainHelper 类静态变量
HWND CTwainHelper::m_hParent = NULL;
HMODULE CTwainHelper::m_hTwain = NULL;
DSMENTRYPROC CTwainHelper::m_dpDsmEntry = NULL;
TW_IDENTITY CTwainHelper::m_twidOrigin = {0};
TW_IDENTITY CTwainHelper::m_twidSource = {0};





// 初始化 TWAIN 库
BOOL CTwainHelper::Initialize(HWND hParent)
{
BOOL bResult;
TW_UINT16 twuResult;

// 初始化变量
bResult = FALSE;

// 初始化 m_twidOrigin 结构
m_twidOrigin.Id = 0;
m_twidOrigin.Version.MajorNum = 1;
m_twidOrigin.Version.MinorNum = 0;
m_twidOrigin.Version.Language = TWLG_CHINESE_PRC;
m_twidOrigin.Version.Country = TWCY_CHINA;
m_twidOrigin.ProtocolMajor = TWON_PROTOCOLMAJOR;
m_twidOrigin.ProtocolMinor = TWON_PROTOCOLMINOR;
m_twidOrigin.SupportedGroups = DG_IMAGE | DG_CONTROL;

lstrcpyA(m_twidOrigin.ProductName, "TwainHelper");
lstrcpyA(m_twidOrigin.ProductFamily, "TwainHelper");
lstrcpyA(m_twidOrigin.Manufacturer, "Yonsm.NET");
lstrcpyA(m_twidOrigin.Version.Info, "1.0.0.0");

// 检查参数
if (hParent)
{
// 设置父窗口
m_hParent = hParent;

// 载入 TWAIN 库
m_hTwain = LoadLibrary(TEXT("TWAIN_32"));
if (m_hTwain)
{
// 获取数据源管理器入口函数地址
m_dpDsmEntry = (DSMENTRYPROC) GetProcAddress(m_hTwain, "DSM_Entry");
if (m_dpDsmEntry)
{
// 打开数据源管理器
twuResult = DsmEntry(NULL, DG_CONTROL, DAT_PARENT, MSG_OPENDSM, &m_hParent);
if (twuResult == TWRC_SUCCESS)
{
// 选择默认数据源
twuResult = DsmEntry(NULL, DG_CONTROL, DAT_IDENTITY, MSG_GETDEFAULT, &m_twidSource);
if (twuResult == TWRC_SUCCESS)
{
bResult = TRUE;
}

// 关闭数据源管理器
DsmEntry(NULL, DG_CONTROL, DAT_PARENT, MSG_CLOSEDSM,&m_hParent);
}

m_dpDsmEntry = NULL;
}

// 释放 TWAIN 库
FreeLibrary(m_hTwain);
m_hTwain = NULL;
}
}

return bResult;
}





// 从 TWAIN 设备中获取图像,并以消息回调的形式通知父窗口
BOOL CTwainHelper::GetImage(UINT uCount)
{
TW_UINT16 twuResult;

// 如果已经在执行获取图像会话过程
if (m_dpDsmEntry)
{
return TRUE;
}

// 初始化变量
twuResult = TWRC_FAILURE;

// 载入 TWAIN 库
m_hTwain = LoadLibrary(TEXT("TWAIN_32"));
if (m_hTwain)
{
// 获取数据源管理器入口函数地址
m_dpDsmEntry = (DSMENTRYPROC) GetProcAddress(m_hTwain, "DSM_Entry");
if (m_dpDsmEntry)
{
// 打开数据源管理器
twuResult = DsmEntry(NULL, DG_CONTROL, DAT_PARENT, MSG_OPENDSM, &m_hParent);
if (twuResult == TWRC_SUCCESS)
{
// 选择数据源
twuResult = DsmEntry(NULL, DG_CONTROL, DAT_IDENTITY, MSG_USERSELECT, &m_twidSource);
if (twuResult == TWRC_SUCCESS)
{
// 打开数据源
twuResult = DsmEntry(NULL, DG_CONTROL, DAT_IDENTITY, MSG_OPENDS, &m_twidSource);
if (twuResult == TWRC_SUCCESS)
{
// 设置获取方式
SetOneValueCap(ICAP_XFERMECH, TWSX_FILE);

if (uCount)
{
// 设置要获取的图像的数量
SetOneValueCap(CAP_XFERCOUNT, uCount);
}

// 显示获取图像对话框
twuResult = ShowUI();
}
}
}
}

if (twuResult != TWRC_SUCCESS)
{
Close();
}
}

return ((twuResult == TWRC_SUCCESS) || (twuResult == TWRC_CANCEL));
}





// 处理 TWAIN 消息
BOOL CTwainHelper::ProcessMessage(const PMSG pmsgMsg)
{
BOOL bCancel;
TW_EVENT tweEvent;
TW_UINT16 twuResult;
TW_PENDINGXFERS twpPending;

// 检查数据源管理器入口函数地址
if (m_dpDsmEntry == NULL)
{
return FALSE;
}

// 处理消息
tweEvent.TWMessage = 0;
tweEvent.pEvent = pmsgMsg;
twuResult = DsmEntry(&m_twidSource, DG_CONTROL, DAT_EVENT, MSG_PROCESSEVENT, &tweEvent);
if (twuResult != TWRC_DSEVENT)
{
return FALSE;
}

switch (tweEvent.TWMessage)
{
case MSG_XFERREADY:
// 数据已准备好
twpPending.Count = 0;
twpPending.Reserved = 0;
do
{
// 通知父窗口准备传送文件
bCancel = (SendMessage(m_hParent, WM_COMMAND, MAKELONG(IDC_TwainHelper, twpPending.Count), 0) == S_FALSE);

// 完成当前获取图像
twuResult = DsmEntry(&m_twidSource, DG_CONTROL, DAT_PENDINGXFERS, MSG_ENDXFER, &twpPending);
}
while ((twpPending.Count != 0) && (twuResult != TWRC_FAILURE) && (bCancel == FALSE));
break;

case MSG_CLOSEDSREQ:
case MSG_CLOSEDSOK:
// 关闭获取图像对话框
Close();
break;
}

return TRUE;
}





// 传送图像到文件中
BOOL CTwainHelper::TransferImage(PCTSTR ptzFileName)
{
TW_UINT16 twuResult;
TW_SETUPFILEXFER twsfFile;

// 检查参数
if (ptzFileName)
{
// 设置获取图像参数
twsfFile.VRefNum = 0;
twsfFile.Format = TWFF_BMP;

#ifdef UNICODE
WideCharToMultiByte(CP_ACP, 0, ptzFileName, -1, twsfFile.FileName, 255, NULL, NULL);
#else // UNICODE
lstrcpy(twsfFile.FileName, ptzFileName);
#endif // UNICODE

twuResult = DsmEntry(&m_twidSource, DG_CONTROL, DAT_SETUPFILEXFER, MSG_SET, &twsfFile);
if (twuResult == TWRC_SUCCESS)
{
// 获取图像
twuResult = DsmEntry(&m_twidSource, DG_IMAGE, DAT_IMAGEFILEXFER, MSG_GET, NULL);
if ((twuResult != TWRC_CANCEL) && (twuResult != TWRC_FAILURE))
{
return TRUE;
}
}
}

return FALSE;
}





// 关闭 TWAIN 对话框
VOID CTwainHelper::Close()
{
if (m_dpDsmEntry)
{
// 隐藏获取图像对话框
HideUI();

// 关闭数据源
DsmEntry(NULL, DG_CONTROL, DAT_IDENTITY, MSG_CLOSEDS, &m_twidSource);

// 关闭数据源管理器
DsmEntry(NULL, DG_CONTROL, DAT_PARENT, MSG_CLOSEDSM,&m_hParent);

m_dpDsmEntry = NULL;
}

// 释放 TWAIN 库
if (m_hTwain)
{
FreeLibrary(m_hTwain);
m_hTwain = NULL;
}
}

3.
Some other good code smaples

A C++ Wrapper for TWAIN
By Rajiv Ramachandran
http://www.codeproject.com/audio/twaintest.asp

.NET TWAIN image scanner
By NETMaster
http://www.codeproject.com/dotnet/twaindotnet.asp

A Copy Utility using TWAIN
By Holger Kloos
http://www.codeproject.com/tools/TwainCopyUtil.asp

Saturday, November 06, 2004

 

Some notes for Windows Image Acquisition Library (WIA)

1.
The foundamentals of WIA can be found in
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wia/wia/overviews/startpage.asp

2.
A simple introduction in Chinese is found as below

By Yonsm
From http://yonsm.reg365.com/blog.php?job=art&articleid=a_20041008_205647

一、 WIA 简介
1.关于 WIA
WIA 是 Windows Image Acquisition 的简称,当前可用版本是 WIA 1.0,它是 Windows Millennium Edition (Windows Me) 或者更高版本的 Windows 系统中提供的数字图像获取服务,同时它也能用于管理数字图像设备。

WIA 接口既是应用程序接口(WIA API),又是设备驱动程序接口(WIA DDI),下面要讲述的都是有关 WIA API 的内容。

通过 WIA API,应用程序可以:

运行在强壮稳定的环境中;
最大可能地减少协同配合问题;
枚举可用的图像获取设备;
同时连接多个设备;
用标准的、可扩展的方式查询设备属性;
用标准的、高性能的传送机制获取数据;
在数据传送过程中维持图像属性;
获取大量的设备事件通知消息。
2.WIA 架构
WIA 是使用进程外(Out of process)服务实现的 COM 组件,和大多数进程外服务程序不同的是,WIA 通过提供自己的数据传送机制(IWiaDataTransfer 接口),避免了图像数据传送过程中的性能损失。高性能的 IWiaDataTransfer 接口使用共享内存来传送数据给客户程序。

WIA 有三个主要的组件:Device Manager,Minidriver Service Library 和 Device Minidriver。

Device Manager: 枚举图像设备,获取设备属,为设备建立事件和创建设备对象;
Minidriver Service Library: 执行所有设备无关的服务;
Device Minidriver 解释映射: WIA 属性和命令到特定的设备。
二、 使用WIA
1.选择设备
应用程序既可以用 WIA 内置的对话框来选择设备,也可以不使用 WIA 的用户界面。下面的程序将弹出一个 WIA 选择设备对话框:

#include #pragma comment (lib, "WiaGuid.lib")int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow){ HRESULT hResult; IWiaItem *pItemRoot; IWiaDevMgr *pWiaDevMgr; CoInitialize(NULL); __try { // Create WIA Device Manager instance pWiaDevMgr = NULL; hResult = CoCreateInstance(CLSID_WiaDevMgr, NULL, CLSCTX_LOCAL_SERVER, IID_IWiaDevMgr, (void**) &pWiaDevMgr); if (hResult != S_OK) { MessageBox(NULL, "Error: CoCreateInstance().", NULL, MB_ICONSTOP); __leave; } // Display a WIA select device dialog pItemRoot = NULL; hResult = pWiaDevMgr->SelectDeviceDlg(NULL, 0, WIA_SELECT_DEVICE_NODEFAULT, NULL, &pItemRoot); // User canceled if (hResult == S_FALSE) { MessageBox(NULL, "User canceled.", NULL, MB_ICONINFORMATION); __leave; } // No device available else if (hResult == WIA_S_NO_DEVICE_AVAILABLE) { MessageBox(NULL, "No device available.", NULL, MB_ICONINFORMATION); __leave; } // OK, Then .......... } __finally { // Release COM interface. if (pItemRoot) pItemRoot->Release(); if (pWiaDevMgr) pWiaDevMgr->Release(); CoUninitialize(); } return 0; }
2.获取图像到文件中
WIA 获取图像非常简单,直接调用 IWiaDevMgr::GetImageDlg(),它集成了 Select Device 和 Select Image 对话框,在传送图像的时候还会自动出现一个进度指示对话框,下面是一个例子:

// ...// Create WIA Device Manager object.hResult = CoCreateInstance(CLSID_WiaDevMgr, NULL, CLSCTX_LOCAL_SERVER, IID_IWiaDevMgr, (void**) &pWiaDevMgr);if (hResult == S_OK){ // Get a image. hResult = pWiaDevMgr->GetImageDlg(hWnd, 0, WIA_DEVICE_DIALOG_SINGLE_IMAGE, WIA_INTENT_MAXIMIZE_QUALITY, NULL, wszFilename, &guidFormat); // ...... }// ......
不过由于 IWiaDevMgr::GetImageDlg() 是以图片文件的形式返回数据的,有的时候并不能满足我们的需要,这时候我们就需要使用 IWiaDataTransfer 接口来传送图片。

3.获取内存中的图像数据
在 IWiaDevMgr::SelectDeviceDlg() 之后,可以用它返回的 RootItem 对象的 IWiaItem::DeviceDlg() 方法显示一个对话框浏览 WIA 设备中图片,请看下面的例子:

// ......// Display a WIA dialog box to the user to prepare for image acquisition.hResult = pRootItem->DeviceDlg(hWnd, 0, WIA_INTENT_MAXIMIZE_QUALITY, &cItem, &ppWiaItems);if (hResult == S_OK){ for (i = 0; i < cItem; i++) { // ...... // ppWiaItems[i] } }// ......
IWiaItem::DeviceDlg() 返回选取的图片总数和每个图片的 WiaItem 指针,我们可以用用 IWiaDataTransfer 接口来传送图片。在传送每个 WiaItem 数据之前,应该先调用 IID_IWiaPropertyStorage 接口设置相应的属性:

// ......// Get the IWiaPropertyStorage interface so we can set required properties.hResult = pWiaItem->QueryInterface(IID_IWiaPropertyStorage, (void**) &pWiaPropertyStorage);if (hResult == S_OK){ // Prepare PROPSPECs and PROPVARIANTs for setting the media type and format. psPropSpec[0].ulKind = PRSPEC_PROPID; psPropSpec[0].propid = WIA_IPA_FORMAT; psPropSpec[1].ulKind = PRSPEC_PROPID; psPropSpec[1].propid = WIA_IPA_TYMED; guidOutputFormat = WiaImgFmt_MEMORYBMP; pvPropVariant[0].vt = VT_CLSID; pvPropVariant[0].puuid = &guidOutputFormat; pvPropVariant[1].vt = VT_I4; pvPropVariant[1].lVal = TYMED_CALLBACK; // Set the properties. hResult = pWiaPropertyStorage->WriteMultiple(sizeof(pvPropVariant) / sizeof(pvPropVariant[0]), psPropSpec, pvPropVariant, WIA_IPA_FIRST); // ...... }// ......
如果用 IWiaDataTransfer 接口传送数据,我们还需要自己写代码实现 IWiaDataCallback 接口,其中 在我们的 IWiaDataCallback::BandedDataCallback() 中可以接收到数据,例如:

// [Summary] Recieve data transfer status notifications.HRESULT CALLBACK CWiaDataCallback::BandedDataCallback(LONG lMessage, LONG lStatus, LONG lPercentComplete, LONG lOffset, LONG lLength, LONG lReserved, LONG lResLength, BYTE *pbData){ PWIA_DATA_CALLBACK_HEADER pHeader = NULL; switch (lMessage) { case IT_MSG_DATA_HEADER: // The data header contains the image's final size. pHeader = (PWIA_DATA_CALLBACK_HEADER) pbData; if ((pHeader) && (pHeader->lBufferSize)) { // Save the buffer size. m_nBufferLength = pHeader->lBufferSize; // Allocate a block of memory to hold the image. m_pBuffer = (PBYTE) HeapAlloc(GetProcessHeap(), 0, m_nBufferLength); if (m_pBuffer == NULL) return S_FALSE; } break; case IT_MSG_DATA: // Make sure a block of memory has been created. if (m_pBuffer) { // Copy the new band. CopyMemory(m_pBuffer + lOffset, pbData, lLength); // Increment the counter. m_nBytesTransfered += lLength; } break; case IT_MSG_TERMINATION: // Notify that we complete to recive a image. break; default: break; } return S_OK; }
然后,我们就可以用 IWiaDataTransfer 接口来传送数据了:

// ...// Create our callback class.pCallback = new CWiaDataCallback(hWnd);if (pCallback){ // Get the IWiaDataCallback interface from our callback class. hResult = pCallback->QueryInterface(IID_IWiaDataCallback, (void**) &pWiaDataCallback); if (hResult == S_OK) { // Perform the transfer. wdtiTransferInfo.ulSize = sizeof(WIA_DATA_TRANSFER_INFO); hResult = pWiaDataTransfer->idtGetBandedData(&wdtiTransferInfo, pWiaDataCallback); // ...... } // ...... }// ......
三、后话

WIA 是 Windows ME 及其以后的操作系统中提供的,Windows 98/2000 均不支持 WIA,因此需要在较新版本的 MSDN Library 中才有 WIA 文档。WIA 1.0 在 MSDN 的文档地址是:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wia/wia/overviews/startpage.asp,或者按目录:MSDN Library -> 图形和多媒体 -> Windows 图像获取 -> WIA 1.0。

另外,因为 Visual C++ 6.0 中没有 WIA 库,所以需要使用 Visual Studio.NET 2002/2003 编译 WIA 程序。

3.
The sample program written by Yonsm
From

可以从 WIA 设备(如数码相机、摄像机、WebCam 等等)中获取 BMP/JPG(同时可获取一个或多个两种方式),只能获取图像到文件中。要获取数据在内存中,需要实现 IDataCallback 才能传送数据,有问题的话可与我联系。


namespace WiaHelper{ // 消息: 如果图像已准备好,父窗口将收到 WM_COMMAND 消息 // 参数: LOWORD(wParam) 为 IDC_WiaHelper,HIWORD(wParam) 为总的图像个数 // 返回: 如果返回 S_FALSE,将不再传送后继的图像 // 备注: 收到此消息后,可以调用 TransferImage() 获取图像到文件中 const WORD IDC_WiaHelper = 45123; // 获取 WIA 设备个数 ULONG WINAPI GetDeviceCount(); // 功能: 从 WIA 设备中获取一个图像到文件中 // 返回: 返回 TRUE 表示成功或者用户取消,其它表示失败 // 备注: 如果调用成功,将生成 ptzFileName 指定的图像文件 BOOL WINAPI GetImage(PCTSTR ptzFileName, HWND hParent = NULL); // 功能: 从 WIA 设备中获取多个图像,并以消息回调的形式通知父窗口 // 返回: 返回 TRUE 表示成功或者用户取消,其它表示失败 // 备注: 如果调用成功,当图像准备好时,父窗口将到 WM_COMMAND 消息,请参看 IDC_WiaHelper 的说明 BOOL WINAPI GetImage(HWND hParent); // 传送一个图像到文件中 BOOL WINAPI TransferImage(PCTSTR ptzFileName); };


///////////////////////////////////////////////
// 预处理
#pragma once
#include
///////////////////////////////////////////////



///////////////////////////////////////////////
// WiaHelper 命名空间
namespace WiaHelper
{
// 消息: 如果图像已准备好,父窗口将收到 WM_COMMAND 消息
// 参数: LOWORD(wParam) 为 IDC_WiaHelper,HIWORD(wParam) 为总的图像个数
// 返回: 如果返回 S_FALSE,将不再传送后继的图像
// 备注: 收到此消息后,可以调用 TransferImage() 获取图像到文件中
const WORD IDC_WiaHelper = 45123;

// 获取 WIA 设备个数
ULONG WINAPI GetDeviceCount();

// 功能: 从 WIA 设备中获取一个图像到文件中
// 返回: 返回 TRUE 表示成功或者用户取消,其它表示失败
// 备注: 如果调用成功,将生成 ptzFileName 指定的图像文件
BOOL WINAPI GetImage(PCTSTR ptzFileName, HWND hParent = NULL);

// 功能: 从 WIA 设备中获取多个图像,并以消息回调的形式通知父窗口
// 返回: 返回 TRUE 表示成功或者用户取消,其它表示失败
// 备注: 如果调用成功,当图像准备好时,父窗口将到 WM_COMMAND 消息,请参看 IDC_WiaHelper 的说明
BOOL WINAPI GetImage(HWND hParent);

// 传送一个图像到文件中
BOOL WINAPI TransferImage(PCTSTR ptzFileName);
};
///////////////////////////////////////////////




///////////////////////////////////////////////
// 预处理
#include "WiaHelper.h"
#include
#include
///////////////////////////////////////////////



///////////////////////////////////////////////
// WiaHelper 命名空间
namespace WiaHelper
{
// 用于传送图像的 IWiaItem 指针
IWiaItem *m_pWiaItem = NULL;
};
////////////////////////////////////////////



////////////////////////////////////////////
// 获取 WIA 设备个数
ULONG WINAPI WiaHelper::GetDeviceCount()
{
ULONG ulResult;
HRESULT hResult;
IWiaDevMgr *pWiaDevMgr;
IEnumWIA_DEV_INFO *pWiaDevEnum;

ulResult = 0;
hResult = CoCreateInstance(CLSID_WiaDevMgr, NULL, CLSCTX_LOCAL_SERVER, IID_IWiaDevMgr, (PVOID *) &pWiaDevMgr);
if (hResult == S_OK)
{
hResult = pWiaDevMgr->EnumDeviceInfo(WIA_DEVINFO_ENUM_LOCAL, &pWiaDevEnum);
if (hResult == S_OK)
{
pWiaDevEnum->GetCount(&ulResult);
pWiaDevEnum->Release();
}

pWiaDevMgr->Release();
}
return ulResult;
}

// 功能: 从 WIA 设备中获取一个图像到文件中
// 返回: 返回 TRUE 表示成功或者用户取消,其它表示失败
// 备注: 如果调用成功,将生成 ptzFileName 指定的图像文件
BOOL WINAPI WiaHelper::GetImage(PCTSTR ptzFileName, HWND hParent)
{
BOOL bResult;
GUID guidFormat;
IWiaDevMgr *pWiaDevMgr;
WCHAR wszFileName[MAX_PATH];

// 初始化变量
bResult = FALSE;

// 检查参数
if (ptzFileName)
{
// 获得接口指针
CoCreateInstance(CLSID_WiaDevMgr, NULL, CLSCTX_LOCAL_SERVER, IID_IWiaDevMgr, (PVOID *) &pWiaDevMgr);
if (pWiaDevMgr)
{
// 获取图像
if (lstrcmpi(ptzFileName + lstrlen(ptzFileName) - 4, TEXT(".jpg")) == 0)
{
guidFormat = WiaImgFmt_JPEG;
}
else
{
guidFormat = WiaImgFmt_BMP;
}

#ifdef _UNICODE
lstrcpy(wszFileName, ptzFileName);
#else // _UNICODE
MultiByteToWideChar(CP_ACP, 0, ptzFileName, -1, wszFileName, MAX_PATH);
#endif // _UNICODE

if (SUCCEEDED(pWiaDevMgr->GetImageDlg(hParent, 0, WIA_DEVICE_DIALOG_SINGLE_IMAGE,
WIA_INTENT_MAXIMIZE_QUALITY, NULL, wszFileName, &guidFormat)))
{
bResult = TRUE;
}


pWiaDevMgr->Release();
}
}

return bResult;
}

// 功能: 从 WIA 设备中获取多个图像,并以消息回调的形式通知父窗口
// 返回: 返回 TRUE 表示成功或者用户取消,其它表示失败
// 备注: 如果调用成功,当图像准备好时,父窗口将到 WM_COMMAND 消息,请参看 IDC_WiaHelper 的说明
BOOL WINAPI WiaHelper::GetImage(HWND hParent)
{
int i;
LONG lWiaItem;
HRESULT hResult;
IWiaItem *pRootItem;
IWiaItem **ppWiaItems;
IWiaDevMgr *pWiaDevMgr;

// 检查参数
if (hParent == NULL)
{
return FALSE;
}

// 获得接口指针
hResult = CoCreateInstance(CLSID_WiaDevMgr, NULL, CLSCTX_LOCAL_SERVER, IID_IWiaDevMgr, (PVOID *) &pWiaDevMgr);
if (hResult == S_OK)
{
// 显示选择设备对话框
hResult = pWiaDevMgr->SelectDeviceDlg(hParent, 0, 0, NULL, &pRootItem);
if (hResult == S_OK)
{
// 显示选择图片对话框
hResult = pRootItem->DeviceDlg(hParent, 0, WIA_INTENT_MAXIMIZE_QUALITY, &lWiaItem, &ppWiaItems);
if (hResult == S_OK)
{
// 发送消息给父窗口,告知图像已准备好
for (i = 0; i < lWiaItem; i++)
{
m_pWiaItem = ppWiaItems[i];
if (SendMessage(hParent, WM_COMMAND, MAKELONG(IDC_WiaHelper, lWiaItem), 0) == S_FALSE)
{
break;
}
}

m_pWiaItem = NULL;

// 释放
for (i = 0; i < lWiaItem; i++)
{
if (ppWiaItems[i])
{
ppWiaItems[i]->Release();
}
}
}

// 释放
pRootItem->Release();
}

// 释放
pWiaDevMgr->Release();
}

return SUCCEEDED(hResult);
}

// 传送图像到文件中
BOOL WINAPI WiaHelper::TransferImage(PCTSTR ptzFileName)
{
BOOL bResult;
GUID guidFormat;
STGMEDIUM stgmMedium;
PROPSPEC prpsProp[2];
PROPVARIANT prpvProp[2];
WCHAR wszFileName[MAX_PATH];
TCHAR tzFileName[MAX_PATH];
IWiaDataTransfer *pWiaDataTransfer;
IWiaPropertyStorage *pWiaPropertyStorage;

// 初始化变量
bResult = FALSE;

// 检查参数
if (m_pWiaItem && ptzFileName)
{
// 获取 IWiaPropertyStorage 接口指针
m_pWiaItem->QueryInterface(IID_IWiaPropertyStorage, (PVOID *) &pWiaPropertyStorage);
if (pWiaPropertyStorage)
{
// 获取文件名,设置文件格式
lstrcpy(tzFileName, ptzFileName);
lstrcat(tzFileName, TEXT("~"));

#ifdef _UNICODE
lstrcpy(wszFileName, tzFileName);
#else // _UNICODE
MultiByteToWideChar(CP_ACP, 0, tzFileName, -1, wszFileName, MAX_PATH);
#endif // _UNICODE

if (lstrcmpi(ptzFileName + lstrlen(ptzFileName) - 4, TEXT(".jpg")) == 0)
{
guidFormat = WiaImgFmt_JPEG;
}
else
{
guidFormat = WiaImgFmt_BMP;
}

// 设置属性参数
prpsProp[0].ulKind = PRSPEC_PROPID;
prpsProp[0].propid = WIA_IPA_FORMAT;
prpsProp[1].ulKind = PRSPEC_PROPID;
prpsProp[1].propid = WIA_IPA_TYMED;

prpvProp[0].vt = VT_CLSID;
prpvProp[0].puuid = &guidFormat;
prpvProp[1].vt = VT_I4;
prpvProp[1].lVal = TYMED_FILE;

// 设置属性
if (pWiaPropertyStorage->WriteMultiple(2, prpsProp, prpvProp, WIA_IPA_FIRST) == S_OK)
{
// 获取 IWiaDataTransfer 接口指针
m_pWiaItem->QueryInterface(IID_IWiaDataTransfer, (PVOID *) &pWiaDataTransfer);
if (pWiaDataTransfer)
{
// 获取图像
stgmMedium.tymed = TYMED_FILE;
stgmMedium.pUnkForRelease = NULL;
stgmMedium.lpszFileName = wszFileName;

// 重名命图像文件
if (pWiaDataTransfer->idtGetData(&stgmMedium, NULL) == S_OK)
{
MoveFile(tzFileName, ptzFileName);
bResult = TRUE;
}

// 释放
ReleaseStgMedium(&stgmMedium);
pWiaDataTransfer->Release();
}
}

// 释放
pWiaPropertyStorage->Release();
}
}

return bResult;
}

4.
It should be pointed out that WIA supported image formats include PNG, JPG, GIF, BMP and TIFF. These are defined by the FormatID Constants in the WIA library.

A good example for Dotnet user is the famous FotoVision project
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetcomp/html/FotoVisionDesktop.asp

5.
Another more straightforward example is

WIA Scripting and .NET
By NETMaster
http://www.codeproject.com/dotnet/wiascriptingdotnet.asp
How to use Windows Image Acquisition on Windows XP. Useful for integrating scanners, digital cameras, webcams and still-video.

Although WIA is initially developed for C/C++ users, I found it is more easy to handle it in Dotnet.

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