Page 1 of 1

New Scanner Tutorial for Windows using WIA. Rate Topic: -----

#1 snoopy11  Icon User is online

  • Engineering ● Software
  • member icon

Reputation: 1317
  • View blog
  • Posts: 4,025
  • Joined: 20-March 10

Posted 08 October 2017 - 08:34 AM

In this Tutorial I am going to go back a number of years, to when I wrote a Tutorial on how to connect and get an Image or Document from a flatbed scanner.
This old Tutorial which can be found here

http://www.dreaminco...ztwain-library/

was written for Windows XP and used a third party .dll which is still available, but it uses old Technology so you can see the need for a new scanner Tutorial without the need for third party .dll's.
So we will build a new application using WIA which hopefully will look like this.

Posted Image


WIA stands for Windows Imaging Acquistion its a .dll that ships with all Windows OS's from Vista onwards so if you are still using Windows XP this program will be of no use to you.
Visual Studio will be required for this project as the code relies on 'atlbase.h' which is not available yet under MinGW for some unknown reason I am sure with some tweaks to the code that it could run under MinGW my apologies to Code::Blocks users.
This will be a Unicode build so make sure you have this set in your project properties.
The project was built using Visual Studio 2017 Enterprise Edition but it should run under any modern version of VS from 2012 upwards.

The code consists of 6 header files and 6 source files and one resource.rc file.
It seems a lot of files but most of them are thankfully quite short and it was a decision made more for logical code organisation than anything else.

The header files contain class and function definitions for the most part and are not really worthy of explanation so I wont spend any time on them as the implementation of the classes and functions are in the source files and I will go into them in great detail.
I will discuss and post the header and source files in pairs ie
header.h
source.h
then discuss..
It would be wise to add them as you go.
So fire up your Visual Studio and select

from the Start Page -> Create New Project.
then select Visual C++ -> Win32 -> Win32 project as so....

Posted Image

Enter Scanner App as the name of the project.
Then click OK.
Next we want to build an empty Win32 project so do this.

Posted Image

Click on Next as highlighted above do not click on Finish.

This will then bring up this.

Posted Image

Put a tick in the Empty Project box as highlighted below.

Posted Image

then click on Finish.
This will then start a new empty win32 project.

When I ask you to add new .h or .cpp files you will go to the Menu Select -> Project -> Add New Files.
This will bring up this.

Posted Image

At the bottom in the Name section add the name of the file I give you selecting either .h or .cpp respectively.

Right, now thats out of the way we will start.
the six header files are...
AboutDlg.h
bitmapUtilities.h
DataClasses.h
ImageAcquisition.h
ProgressDlg.h
resource.h

the six source or .cpp files are as follows...
AboutDlg.cpp
bitmapUtilities.cpp
DataClasses.cpp
ImageAcquisition.cpp
Mainwindow.cpp
ProgressDlg.cpp

There is also one resource file called resource.rc
The easiest way to add the resource file is to
select Add New Files select the .h option and in the Name box type 'resource.rc' .
I will briefly post and discuss resource.h and resource.rc first of all.

resource.h
#pragma once


//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by Resource.rc

// Next default values for new objects
// 
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        101
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1001
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif

#define IDR_MENU                        201
#define ID_FILE_FROM_SCANNER            202
#define ID_FILE_EXIT                    203
#define ID_HELP_ABOUT                   204
#define IDD_PROGRESS                    205
#define IDC_PROGRESS_BAR                206
#define IDC_MESSAGE                     207
#define ID_FILE_SAVE                    208
#define IDD_ABOUT                       209
#define IDD_MESSAGE                     210
#define Image1                          211
#define ICO1                            212
#define IDS_WAIT                        304
#define IDS_STATUS_TRANSFER_FROM_DEVICE 305
#define IDS_STATUS_PROCESSING_DATA      306
#define IDS_STATUS_TRANSFER_TO_CLIENT   307
#define IDS_ERROR_GET_IMAGE_DLG         308


#define COUNTOF(x) ( sizeof(x) / sizeof(*x) )

#define DEFAULT_STRING_SIZE 256






resource.rc
// Microsoft Visual C++ generated resource script.
//
#include "resource.h"

#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"

/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS

/////////////////////////////////////////////////////////////////////////////
// English (United States) resources

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG)
LANGUAGE 9, 2

#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//

1 TEXTINCLUDE  
BEGIN
    "resource.h\0"
END

2 TEXTINCLUDE  
BEGIN
    "#include ""winres.h""\r\n"
    "\0"
END

3 TEXTINCLUDE  
BEGIN
    "\r\n"
    "\0"
END

#endif    // APSTUDIO_INVOKED

#endif    // English (United States) resources
/////////////////////////////////////////////////////////////////////////////



#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//


/////////////////////////////////////////////////////////////////////////////
#endif    // not APSTUDIO_INVOKED


/////////////////////////////////////////////////////////////////////////////
//
// Images
//
Image1 BITMAP DISCARDABLE "eye.bmp"
ICO1 ICON DISCARDABLE "eye.ico"



/////////////////////////////////////////////////////////////////////////////
//
// Menu
//

IDR_MENU MENU DISCARDABLE
BEGIN
POPUP "&File"
BEGIN
MENUITEM "Capture From Scanner...", ID_FILE_FROM_SCANNER

MENUITEM SEPARATOR
MENUITEM "Save as .jpg...", ID_FILE_SAVE, GRAYED
MENUITEM SEPARATOR
MENUITEM "E&xit", ID_FILE_EXIT
END

POPUP "&Help"
BEGIN
MENUITEM "&About...", ID_HELP_ABOUT
END
END

/////////////////////////////////////////////////////////////////////////////
//
// Progress Dialog
//

IDD_PROGRESS DIALOG DISCARDABLE  0, 0, 184, 63
STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_VISIBLE | WS_CAPTION |
WS_SYSMENU
CAPTION "Progress"
FONT 8, "MS Shell Dlg"
BEGIN
DEFPUSHBUTTON   "Cancel", IDCANCEL, 127, 43, 50, 14
LTEXT           "Reading data from the scanner....", IDC_MESSAGE, 7, 7, 170, 8
CONTROL         "1", IDC_PROGRESS_BAR, "msctls_progress32", WS_BORDER, 7, 22,
170, 14
END

/////////////////////////////////////////////////////////////////////////////
//
// About Dialog
//

IDD_ABOUT DIALOG DISCARDABLE  0, 0, 144, 63
STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_VISIBLE | WS_CAPTION |
WS_SYSMENU
CAPTION "About Scanner App."
FONT 10, "MS Shell Dlg"
BEGIN
DEFPUSHBUTTON   "OK", IDOK, 83, 43, 50, 14
LTEXT           "This Application is for scanning\nDocuments and Images.\n", IDD_MESSAGE, 7, 7, 170,20

END

/////////////////////////////////////////////////////////////////////////////
//
// String Table
//

STRINGTABLE DISCARDABLE
BEGIN



IDS_WAIT "Wait..."
IDS_STATUS_TRANSFER_FROM_DEVICE "Reading data from the device (%d%% complete)"
IDS_STATUS_PROCESSING_DATA "Processing data (%d%% complete)"
IDS_STATUS_TRANSFER_TO_CLIENT "Transferring data (%d%% complete)"
IDS_ERROR_GET_IMAGE_DLG "Unable to retrieve image from device. Verify the device is properly connected and try again."
END





All you need to know about the above files is that they contain resources for dialogs and links to image files etc.
Add them to your project as previously discussed.
You will also need image files which I will supply as links.

These are the bitmap file eye.bmp
https://app.box.com/...crtoqxb88mngicd
and the icon file eye.ico
https://app.box.com/...pswz0irdyhtzyh7

add these image files to your project the easiest way is to open up file explorer go to Documents-> Visual Studio 2017 -> Projects ->Scanner App->Scanner App and you should see the files 'resource.h and resource.rc' place the images there.

AboutDlg.h
#pragma once
#include <Windows.h>
#include "resource.h"

class CAboutDlg
{
public:
	CAboutDlg(HWND hWndParent);
	~CAboutDlg();

private:
	HWND    m_hDlg;
	HWND    m_hWndParent;
	HANDLE  m_hInitDlg;
	LONG m_cRef;
	LONG m_bCancelled;

	INT_PTR nResult;
	static INT_PTR CALLBACK DialogProc(HWND, UINT, WPARAM, LPARAM);

};



AboutDlg.cpp
#include "AboutDlg.h"

static HBITMAP hBitmap;

CAboutDlg::CAboutDlg(HWND hWndParent)
{


	m_cRef = 0;

	m_hDlg = NULL;
	m_hWndParent = hWndParent;
	m_bCancelled = FALSE;

	nResult = DialogBoxParam(
		NULL,
		MAKEINTRESOURCE(IDD_ABOUT),
		m_hWndParent,
		DialogProc,
		(LPARAM)this
	);


	MSG msg;

	while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
		if (msg.message == WM_QUIT)
		{
			
			break;
		}
	}

};


CAboutDlg::~CAboutDlg()
{
	
};

INT_PTR
CALLBACK
CAboutDlg::DialogProc(
	HWND   hDlg,
	UINT   uMsg,
	WPARAM wParam,
	LPARAM lParam
)
{



	switch (uMsg)
	{
	case WM_INITDIALOG:
	{
		// Retrieve and store the "that" pointer
		CAboutDlg *that = (CAboutDlg *)lParam;

		SetWindowLongPtr(hDlg, DWLP_USER, (LONG_PTR)that);
		that->m_hDlg = hDlg;
		
		hBitmap = LoadBitmap(GetModuleHandle(NULL), MAKEINTRESOURCE(Image1));
		InvalidateRect(hDlg, NULL, TRUE);
		return TRUE;
	}
	case WM_PAINT:
	{
		PAINTSTRUCT ps;
		HDC hdc = BeginPaint(hDlg, &ps);
		HDC hdcSrc = CreateCompatibleDC(hdc);

		SelectObject(hdcSrc, hBitmap);
		BitBlt(hdc, 5, 50, 100, 75, hdcSrc, 0, 0, SRCCOPY);

		EndPaint(hDlg, &ps);

		return TRUE;
	}

	case WM_CLOSE:
	{
		// If the user presses the CLOSE button, end the dialog and return.
		EndDialog(hDlg, NULL);
		return TRUE;
	}

	case WM_COMMAND:
	{
		switch (LOWORD(wParam))
		{
		case IDOK:
		{
			// If the user presses the OK button, end the dialog and return.
			EndDialog(hDlg, NULL);
			return TRUE;
		}
		}

		break;
	}
	}

	return FALSE;
}




Add the above files to your project.

In AboutDlg.cpp there is a class called CAboutDlg.
This class creates the About Dialog called from the Help menu in our program, it does this by calling DialogBoxParam from its constructor and using the resource IDD_ABOUT from the resource file. You may if you wish go back to resource.rc file to see how this dialog box was constructed if you are new to this type of thing. The constructor also contains a message loop for the dialog. The callback procedure DialogProc is pretty much like a standard windows procedure except its for dialog boxes it should be fairly familiar. In WM_INITDIALOG which is called only when the dialog box first appears we retrieve a pointer to the dialog box's 'hwnd', we also load our bitmap Image1 which corresponds to eye.bmp. In WM_PAINT we display our bitmap of the eye inside the dialog box. WM_CLOSE ends the dialog box as does IDOK in WM_COMMAND.

bitmapUtilities.h
#pragma once
//////////////////////////////////////////////////////////////////////////
//
// GetBitmapHeaderSize
//
#include <Windows.h>

ULONG GetBitmapHeaderSize(LPCVOID pDib);


//////////////////////////////////////////////////////////////////////////
//
// GetBitmapLineWidthInBytes
//

ULONG GetBitmapLineWidthInBytes(ULONG nWidthInPixels, ULONG nBitCount);

//////////////////////////////////////////////////////////////////////////
//
// GetBitmapDimensions
//

BOOL GetBitmapDimensions(LPCVOID pDib, UINT *pWidth, UINT *pHeight);


//////////////////////////////////////////////////////////////////////////
//
// GetBitmapSize
//

ULONG GetBitmapSize(LPCVOID pDib);



//////////////////////////////////////////////////////////////////////////
//
// GetBitmapOffsetBits
//

ULONG GetBitmapOffsetBits(LPCVOID pDib);

//////////////////////////////////////////////////////////////////////////
//
// FixBitmapHeight
//

BOOL FixBitmapHeight(PVOID pDib, ULONG nSize, BOOL bTopDown);


//////////////////////////////////////////////////////////////////////////
//
// FillBitmapFileHeader
//

BOOL FillBitmapFileHeader(LPCVOID pDib, PBITMAPFILEHEADER pbmfh);


bitmapUtilities.cpp
//////////////////////////////////////////////////////////////////////////
//
// GetBitmapHeaderSize
//
#include "bitmapUtilities.h"

ULONG GetBitmapHeaderSize(LPCVOID pDib)
{
	ULONG nHeaderSize = *(PDWORD)pDib;

	switch (nHeaderSize)
	{
	case sizeof(BITMAPCOREHEADER) :
	case sizeof(BITMAPINFOHEADER) :
	case sizeof(BITMAPV4HEADER) :
	case sizeof(BITMAPV5HEADER) :
	{
		return nHeaderSize;
	}
	}

	return 0;
}


//////////////////////////////////////////////////////////////////////////
//
// GetBitmapLineWidthInBytes
//

ULONG GetBitmapLineWidthInBytes(ULONG nWidthInPixels, ULONG nBitCount)
{
	return (((nWidthInPixels * nBitCount) + 31) & ~31) >> 3;
}


//////////////////////////////////////////////////////////////////////////
//
// GetBitmapDimensions
//

BOOL GetBitmapDimensions(LPCVOID pDib, UINT *pWidth, UINT *pHeight)
{
	ULONG nHeaderSize = GetBitmapHeaderSize(pDib);

	if (nHeaderSize == 0)
	{
		return FALSE;
	}

	if (nHeaderSize == sizeof(BITMAPCOREHEADER))
	{
		PBITMAPCOREHEADER pbmch = (PBITMAPCOREHEADER)pDib;

		if (pWidth != NULL)
		{
			*pWidth = pbmch->bcWidth;
		}

		if (pHeight != NULL)
		{
			*pHeight = pbmch->bcHeight;
		}
	}
	else
	{
		PBITMAPINFOHEADER pbmih = (PBITMAPINFOHEADER)pDib;

		if (pWidth != NULL)
		{
			*pWidth = pbmih->biWidth;
		}

		if (pHeight != NULL)
		{
			*pHeight = abs(pbmih->biHeight);
		}
	}

	return TRUE;
}


//////////////////////////////////////////////////////////////////////////
//
// GetBitmapSize
//

ULONG GetBitmapSize(LPCVOID pDib)
{
	ULONG nHeaderSize = GetBitmapHeaderSize(pDib);

	if (nHeaderSize == 0)
	{
		return 0;
	}

	// Start the calculation with the header size

	ULONG nDibSize = nHeaderSize;

	// is this an old style BITMAPCOREHEADER?

	if (nHeaderSize == sizeof(BITMAPCOREHEADER))
	{
		PBITMAPCOREHEADER pbmch = (PBITMAPCOREHEADER)pDib;

		// Add the color table size

		if (pbmch->bcBitCount <= 8)
		{
			nDibSize += (ULONG)sizeof(RGBTRIPLE) * (1 << pbmch->bcBitCount);
		}

		// Add the bitmap size

		ULONG nWidth = GetBitmapLineWidthInBytes(pbmch->bcWidth, pbmch->bcBitCount);

		nDibSize += nWidth * pbmch->bcHeight;
	}
	else
	{
		// this is at least a BITMAPINFOHEADER

		PBITMAPINFOHEADER pbmih = (PBITMAPINFOHEADER)pDib;

		// Add the color table size

		if (pbmih->biClrUsed != 0)
		{
			nDibSize += sizeof(RGBQUAD) * pbmih->biClrUsed;
		}
		else if (pbmih->biBitCount <= 8)
		{
			nDibSize += (ULONG)sizeof(RGBQUAD) * (1 << pbmih->biBitCount);
		}

		// Add the bitmap size

		if (pbmih->biSizeImage != 0)
		{
			nDibSize += pbmih->biSizeImage;
		}
		else
		{
			// biSizeImage must be specified for compressed bitmaps

			if (pbmih->biCompression != BI_RGB &&
				pbmih->biCompression != BI_BITFIELDS)
			{
				return 0;
			}

			ULONG nWidth = GetBitmapLineWidthInBytes(pbmih->biWidth, pbmih->biBitCount);

			nDibSize += nWidth * abs(pbmih->biHeight);
		}

		// Consider special cases

		if (nHeaderSize == sizeof(BITMAPINFOHEADER))
		{
			// If this is a 16 or 32 bit bitmap and BI_BITFIELDS is used, 
			// bmiColors member contains three DWORD color masks.
			// For V4 or V5 headers, this info is included the header

			if (pbmih->biCompression == BI_BITFIELDS)
			{
				nDibSize += 3 * sizeof(DWORD);
			}
		}
		else if (nHeaderSize >= sizeof(BITMAPV5HEADER))
		{
			// If this is a V5 header and an ICM profile is specified,
			// we need to consider the profile data size

			PBITMAPV5HEADER pbV5h = (PBITMAPV5HEADER)pDib;

			// if there is some padding before the profile data, add it

			if (pbV5h->bV5ProfileData > nDibSize)
			{
				nDibSize = pbV5h->bV5ProfileData;
			}

			// add the profile data size

			nDibSize += pbV5h->bV5ProfileSize;
		}
	}

	return nDibSize;
}


//////////////////////////////////////////////////////////////////////////
//
// GetBitmapOffsetBits
//

ULONG GetBitmapOffsetBits(LPCVOID pDib)
{
	ULONG nHeaderSize = GetBitmapHeaderSize(pDib);

	if (nHeaderSize == 0)
	{
		return 0;
	}

	// Start the calculation with the header size

	ULONG nOffsetBits = nHeaderSize;

	// is this an old style BITMAPCOREHEADER?

	if (nHeaderSize == sizeof(BITMAPCOREHEADER))
	{
		PBITMAPCOREHEADER pbmch = (PBITMAPCOREHEADER)pDib;

		// Add the color table size

		if (pbmch->bcBitCount <= 8)
		{
			nOffsetBits += (ULONG)sizeof(RGBTRIPLE) * (1 << pbmch->bcBitCount);
		}
	}
	else
	{
		// this is at least a BITMAPINFOHEADER

		PBITMAPINFOHEADER pbmih = (PBITMAPINFOHEADER)pDib;

		// Add the color table size

		if (pbmih->biClrUsed != 0)
		{
			nOffsetBits += sizeof(RGBQUAD) * pbmih->biClrUsed;
		}
		else if (pbmih->biBitCount <= 8)
		{
			nOffsetBits += (ULONG)sizeof(RGBQUAD) * (1 << pbmih->biBitCount);
		}

		// Consider special cases

		if (nHeaderSize == sizeof(BITMAPINFOHEADER))
		{
			// If this is a 16 or 32 bit bitmap and BI_BITFIELDS is used, 
			// bmiColors member contains three DWORD color masks.
			// For V4 or V5 headers, this info is included in the header

			if (pbmih->biCompression == BI_BITFIELDS)
			{
				nOffsetBits += 3 * sizeof(DWORD);
			}
		}
		else if (nHeaderSize >= sizeof(BITMAPV5HEADER))
		{
			// If this is a V5 header and an ICM profile is specified,
			// we need to consider the profile data size

			PBITMAPV5HEADER pbV5h = (PBITMAPV5HEADER)pDib;

			// if the profile data comes before the pixel data, add it

			if (pbV5h->bV5ProfileData <= nOffsetBits)
			{
				nOffsetBits += pbV5h->bV5ProfileSize;
			}
		}
	}

	return nOffsetBits;
}


//////////////////////////////////////////////////////////////////////////
//
// FixBitmapHeight
//

BOOL FixBitmapHeight(PVOID pDib, ULONG nSize, BOOL bTopDown)
{
	ULONG nHeaderSize = GetBitmapHeaderSize(pDib);

	if (nHeaderSize == 0)
	{
		return FALSE;
	}

	// is this an old style BITMAPCOREHEADER?

	if (nHeaderSize == sizeof(BITMAPCOREHEADER))
	{
		PBITMAPCOREHEADER pbmch = (PBITMAPCOREHEADER)pDib;

		// fix the height value if necessary

		if (pbmch->bcHeight == 0)
		{
			// start the calculation with the header size

			ULONG nSizeImage = nSize - nHeaderSize;

			// subtract the color table size

			if (pbmch->bcBitCount <= 8)
			{
				nSizeImage -= (ULONG)sizeof(RGBTRIPLE) * (1 << pbmch->bcBitCount);
			}

			// calculate the height

			ULONG nWidth = GetBitmapLineWidthInBytes(pbmch->bcWidth, pbmch->bcBitCount);

			if (nWidth == 0)
			{
				return FALSE;
			}

			LONG nHeight = nSizeImage / nWidth;

			pbmch->bcHeight = (WORD)nHeight;
		}
	}
	else
	{
		// this is at least a BITMAPINFOHEADER

		PBITMAPINFOHEADER pbmih = (PBITMAPINFOHEADER)pDib;

		// fix the height value if necessary

		if (pbmih->biHeight == 0)
		{
			// find the size of the image data

			ULONG nSizeImage;

			if (pbmih->biSizeImage != 0)
			{
				// if the size is specified in the header, take it

				nSizeImage = pbmih->biSizeImage;
			}
			else
			{
				// start the calculation with the header size

				nSizeImage = nSize - nHeaderSize;

				// subtract the color table size

				if (pbmih->biClrUsed != 0)
				{
					nSizeImage -= sizeof(RGBQUAD) * pbmih->biClrUsed;
				}
				else if (pbmih->biBitCount <= 8)
				{
					nSizeImage -= (ULONG)sizeof(RGBQUAD) * (1 << pbmih->biBitCount);
				}

				// Consider special cases

				if (nHeaderSize == sizeof(BITMAPINFOHEADER))
				{
					// If this is a 16 or 32 bit bitmap and BI_BITFIELDS is used, 
					// bmiColors member contains three DWORD color masks.
					// For V4 or V5 headers, this info is included the header

					if (pbmih->biCompression == BI_BITFIELDS)
					{
						nSizeImage -= 3 * sizeof(DWORD);
					}
				}
				else if (nHeaderSize >= sizeof(BITMAPV5HEADER))
				{
					// If this is a V5 header and an ICM profile is specified,
					// we need to consider the profile data size

					PBITMAPV5HEADER pbV5h = (PBITMAPV5HEADER)pDib;

					// add the profile data size

					nSizeImage -= pbV5h->bV5ProfileSize;
				}

				// store the image size

				pbmih->biSizeImage = nSizeImage;
			}

			// finally, calculate the height

			ULONG nWidth = GetBitmapLineWidthInBytes(pbmih->biWidth, pbmih->biBitCount);

			if (nWidth == 0)
			{
				return FALSE;
			}

			LONG nHeight = nSizeImage / nWidth;

			pbmih->biHeight = bTopDown ? -nHeight : nHeight;
		}
	}

	return TRUE;
}


//////////////////////////////////////////////////////////////////////////
//
// FillBitmapFileHeader
//

BOOL FillBitmapFileHeader(LPCVOID pDib, PBITMAPFILEHEADER pbmfh)
{
	ULONG nSize = GetBitmapSize(pDib);

	if (nSize == 0)
	{
		return FALSE;
	}

	ULONG nOffset = GetBitmapOffsetBits(pDib);

	if (nOffset == 0)
	{
		return FALSE;
	}

	pbmfh->bfType = MAKEWORD('B', 'M');
	pbmfh->bfSize = sizeof(BITMAPFILEHEADER) + nSize;
	pbmfh->bfReserved1 = 0;
	pbmfh->bfReserved2 = 0;
	pbmfh->bfOffBits = sizeof(BITMAPFILEHEADER) + nOffset;

	return TRUE;
}



Add the above files to your project,

bitmapUtilities.cpp contains a series of helper functions for the manipulation of bitmaps as when the scanner returns an image via a stream of data to the OS; Windows will convert it into a bitmap. It will then place it into a Memory Stream object. The functions are really self explanatory GetBitmapHeaderSize() for instance returns the size of the Bitmap Header, GetBitmapSize() returns the size of the bitmap, so pretty self explanatory. I wont waste further time on this as this Tutorial concerns itself with the inner workings of scanning not the inner workings of bitmaps.

DataClasses.h
#pragma once
#include <Windows.h>
#include <Wia.h>
#include <Sti.h>
#include <atlbase.h>


typedef HRESULT(CALLBACK *PFNPROGRESSCALLBACK)(
	LONG   lStatus,
	LONG   lPercentComplete,
	PVOID  pParam
	);

//DataClass

class DataClasses :public IWiaEventCallback
{
public:
	DataClasses();

	LONG getNumDevices();
	

	STDMETHOD(QueryInterface)(REFIID iid, LPVOID *ppvObj);
	STDMETHOD_(ULONG, AddRef)();
	STDMETHOD_(ULONG, Release)();

	// IWiaEventCallback interface

	STDMETHOD(ImageEventCallback)(
		LPCGUID pEventGuid,
		BSTR    bstrEventDescription,
		BSTR    bstrDeviceID,
		BSTR    bstrDeviceDescription,
		DWORD   dwDeviceType,
		BSTR    bstrFullItemName,
		ULONG  *pulEventType,
		ULONG   ulReserved
		);



	HRESULT Register();

	ULONG GetNumDevices() const;

private:
	LONG               m_cRef;
	ULONG              m_nNumDevices;
	CComPtr<IUnknown>  m_pConnectEventObject;
	CComPtr<IUnknown>  m_pDisconnectEventObject;
	HRESULT WiaGetNumDevices(
		IWiaDevMgr *pSuppliedWiaDevMgr,
		ULONG      *pulNumDevices
	);
	CComPtr<IWiaDevMgr> pWiaDevMgr;
};


//CDataCallback class


class CDataCallback : public IWiaDataCallback
{
public:
	CDataCallback(
		PFNPROGRESSCALLBACK  pfnProgressCallback,
		PVOID                pProgressCallbackParam,
		LONG                *plCount,
		IStream             ***pppStream
	);

	// IUnknown interface

	STDMETHOD(QueryInterface)(REFIID iid, LPVOID *ppvObj);
	STDMETHOD_(ULONG, AddRef)();
	STDMETHOD_(ULONG, Release)();

	// IWiaDataCallback interface

	STDMETHOD(BandedDataCallback) (
		LONG  lReason,
		LONG  lStatus,
		LONG  lPercentComplete,
		LONG  lOffset,
		LONG  lLength,
		LONG  lReserved,
		LONG  lResLength,
		PBYTE pbBuffer
		);

	// CDataCallback methods

private:
	HRESULT ReAllocBuffer(ULONG nBufferSize);
	HRESULT CopyToBuffer(ULONG nOffset, LPCVOID pBuffer, ULONG nSize);
	HRESULT StoreBuffer();




private:
	LONG m_cRef;

	BOOL              m_bBMP;
	LONG              m_nHeaderSize;
	LONG              m_nDataSize;
	CComPtr<IStream>  m_pStream;

	PFNPROGRESSCALLBACK  m_pfnProgressCallback;
	PVOID                m_pProgressCallbackParam;

	LONG    *m_plCount;
	IStream ***m_pppStream;
};


DataClasses.cpp
#include "DataClasses.h"
#pragma once
#include "bitmapUtilities.h"


DataClasses::DataClasses()
{
	m_cRef = 0;
	m_nNumDevices = 0;
	HRESULT hr;
	if (pWiaDevMgr == NULL)
	{
		
		hr = pWiaDevMgr.CoCreateInstance(CLSID_WiaDevMgr, NULL, CLSCTX_LOCAL_SERVER);

		if (FAILED(hr))
		{
			delete(this);
		}
	}

	WiaGetNumDevices(pWiaDevMgr, &m_nNumDevices);

	for (unsigned int i = 0; i < m_nNumDevices; i++)
	{
		this->AddRef();
	}
}


LONG DataClasses::getNumDevices()
{
	return (LONG)m_nNumDevices;
}



HRESULT DataClasses::WiaGetNumDevices(
	IWiaDevMgr *pSuppliedWiaDevMgr,
	ULONG      *pulNumDevices
)
{
	HRESULT hr;

	// Validate and initialize output parameters

	if (pulNumDevices == NULL)
	{
		return E_POINTER;
	}

	*pulNumDevices = 0;

	// Create a connection to the local WIA device manager

	CComPtr<IWiaDevMgr> pWiaDevMgr = pSuppliedWiaDevMgr;

	if (pWiaDevMgr == NULL)
	{
		
		hr = pWiaDevMgr.CoCreateInstance(CLSID_WiaDevMgr, NULL, CLSCTX_LOCAL_SERVER);

		if (FAILED(hr))
		{
			return hr;
		}
	}

	// Get a list of all the WIA devices on the system

	CComPtr<IEnumWIA_DEV_INFO> pIEnumWIA_DEV_INFO;

	hr = pWiaDevMgr->EnumDeviceInfo(
		0,
		&pIEnumWIA_DEV_INFO
	);

	if (FAILED(hr))
	{
		return hr;
	}

	// Get the number of WIA devices

	ULONG celt;

	hr = pIEnumWIA_DEV_INFO->GetCount(&celt);

	if (FAILED(hr))
	{
		return hr;
	}

	*pulNumDevices = celt;

	return S_OK;
}



STDMETHODIMP DataClasses::QueryInterface(REFIID iid, LPVOID *ppvObj)
{
	if (ppvObj == NULL)
	{
		return E_POINTER;
	}

	if (iid == IID_IUnknown)
	{
		*ppvObj = (IUnknown *) this;
	}
	else if (iid == IID_IWiaEventCallback)
	{
		*ppvObj = (IWiaEventCallback *) this;
	}
	else
	{
		*ppvObj = NULL;
		return E_NOINTERFACE;
	}

	AddRef();
	return S_OK;
}

STDMETHODIMP_(ULONG) DataClasses::AddRef()
{
	return InterlockedIncrement(&m_cRef);
}

STDMETHODIMP_(ULONG) DataClasses::Release()
{
	ULONG cRef = InterlockedDecrement(&m_cRef);

	if (cRef == 0)
	{
		delete this;
	}
	return cRef;
}

HRESULT DataClasses::Register()
{
	HRESULT hr;


	// Register the callback interface

	hr = pWiaDevMgr->RegisterEventCallbackInterface(
		0,
		NULL,
		&WIA_EVENT_DEVICE_CONNECTED,
		this,
		&m_pConnectEventObject
	);

	if (FAILED(hr))
	{
		return hr;
	}

	hr = pWiaDevMgr->RegisterEventCallbackInterface(
		0,
		NULL,
		&WIA_EVENT_DEVICE_DISCONNECTED,
		this,
		&m_pDisconnectEventObject
	);

	if (FAILED(hr))
	{
		return hr;
	}


	return S_OK;
}

ULONG DataClasses::GetNumDevices() const
{
	return m_nNumDevices;
}


STDMETHODIMP DataClasses::ImageEventCallback(
	LPCGUID,
	BSTR,
	BSTR,
	BSTR,
	DWORD,
	BSTR,
	ULONG*,
	ULONG
)
{
	return WiaGetNumDevices(NULL, &m_nNumDevices);
}

//CDataClasses

CDataCallback::CDataCallback(
	PFNPROGRESSCALLBACK  pfnProgressCallback,
	PVOID                pProgressCallbackParam,
	LONG                *plCount,
	IStream             ***pppStream
)
{
	m_cRef = 0;

	m_bBMP = FALSE;
	m_nHeaderSize = 0;
	m_nDataSize = 0;

	m_pfnProgressCallback = pfnProgressCallback;
	m_pProgressCallbackParam = pProgressCallbackParam;

	m_plCount = plCount;
	m_pppStream = pppStream;



}

STDMETHODIMP CDataCallback::QueryInterface(REFIID iid, LPVOID *ppvObj)
{
	if (ppvObj == NULL)
	{
		return E_POINTER;
	}

	if (iid == IID_IUnknown)
	{
		*ppvObj = (IUnknown*) this;
	}
	else if (iid == IID_IWiaDataCallback)
	{
		*ppvObj = (IWiaDataCallback *) this;
	}
	else
	{
		*ppvObj = NULL;
		return E_NOINTERFACE;
	}

	AddRef();
	return S_OK;
}

//////////////////////////////////////////////////////////////////////////
//
// CDataCallback::AddRef
//

STDMETHODIMP_(ULONG) CDataCallback::AddRef()
{
	return InterlockedIncrement(&m_cRef);
}

//////////////////////////////////////////////////////////////////////////
//
// CDataCallback::Release
//

STDMETHODIMP_(ULONG) CDataCallback::Release()
{
	LONG cRef = InterlockedDecrement(&m_cRef);

	if (cRef == 0)
	{
		delete this;
	}

	return cRef;
}

//////////////////////////////////////////////////////////////////////////
//
// CDataCallback::BandedDataCallback
//

STDMETHODIMP CDataCallback::BandedDataCallback(
	LONG   lReason,
	LONG   lStatus,
	LONG   lPercentComplete,
	LONG   lOffset,
	LONG   lLength,
	LONG, LONG,
	PBYTE  pbBuffer
)
{
	HRESULT hr;

	// Parse the message

	switch (lReason)
	{
	case IT_MSG_DATA_HEADER:
	{
		PWIA_DATA_CALLBACK_HEADER pHeader = (PWIA_DATA_CALLBACK_HEADER)pbBuffer;

		// Determine if this is a BMP transfer

		m_bBMP = pHeader->guidFormatID == WiaImgFmt_MEMORYBMP || pHeader->guidFormatID == WiaImgFmt_BMP;

		// For WiaImgFmt_MEMORYBMP transfers, WIA does not send a BITMAPFILEHEADER before the data.
		// In this program, we desire all BMP files to contain a BITMAPFILEHEADER, so add it manually

		m_nHeaderSize = pHeader->guidFormatID == WiaImgFmt_MEMORYBMP ? sizeof(BITMAPFILEHEADER) : 0;

		// Allocate memory for the image if the size is given in the header

		if (pHeader != NULL && pHeader->lBufferSize != 0)
		{
			hr = ReAllocBuffer(m_nHeaderSize + pHeader->lBufferSize);

			if (FAILED(hr))
			{
				return hr;
			}
		}

		break;
	}

	case IT_MSG_DATA:
	{
		// Invoke the callback function

		hr = m_pfnProgressCallback(lStatus, lPercentComplete, m_pProgressCallbackParam);

		if (FAILED(hr) || hr == S_FALSE)
		{
			return hr;
		}

		// If the buffer is not allocated yet and this is the first block, 
		// and the transferred image is in BMP format, allocate the buffer
		// according to the size information in the bitmap header

		if (m_pStream == NULL && lOffset == 0 && m_bBMP)
		{
			LONG nBufferSize = GetBitmapSize(pbBuffer);

			if (nBufferSize != 0)
			{
				hr = ReAllocBuffer(m_nHeaderSize + nBufferSize);

				if (FAILED(hr))
				{
					return hr;
				}
			}
		}

		if (m_nHeaderSize + lOffset + lLength < 0)
		{
			return E_OUTOFMEMORY;
		}

		// If the transfer goes past the buffer, try to expand it
		if (m_nHeaderSize + lOffset + lLength > m_nDataSize)
		{
			hr = ReAllocBuffer(m_nHeaderSize + lOffset + lLength);

			if (FAILED(hr))
			{
				return hr;
			}
		}

		// copy the transfer buffer

		hr = CopyToBuffer(m_nHeaderSize + lOffset, pbBuffer, lLength);

		if (FAILED(hr))
		{
			return hr;
		}

		break;
	}

	case IT_MSG_STATUS:
	{
		// Invoke the progress callback function

		hr = m_pfnProgressCallback(lStatus, lPercentComplete, m_pProgressCallbackParam);

		if (FAILED(hr) || hr == S_FALSE)
		{
			return hr;
		}

		break;
	}

	case IT_MSG_TERMINATION:
	case IT_MSG_NEW_PAGE:
	{
		if (m_pStream != NULL)
		{
			// For BMP files, we should validate the the image header
			// So, obtain the memory buffer from the stream

			if (m_bBMP)
			{
				// Since the stream is created using CreateStreamOnHGlobal,
				// we can get the memory buffer with GetHGlobalFromStream.

				HGLOBAL hBuffer;

				hr = GetHGlobalFromStream(m_pStream, &hBuffer);

				if (FAILED(hr))
				{
					return hr;
				}

				PBITMAPFILEHEADER pBuffer = (PBITMAPFILEHEADER)GlobalLock(hBuffer);

				if (pBuffer == NULL)
				{
					return HRESULT_FROM_WIN32(GetLastError());
				}

				// Some scroll-fed scanners may return 0 as the bitmap height
				// In this case, calculate the image height and modify the header

				FixBitmapHeight(pBuffer + 1, m_nDataSize, TRUE);

				// For WiaImgFmt_MEMORYBMP transfers, the WIA service does not 
				// include a BITMAPFILEHEADER preceeding the bitmap data. 
				// In this case, fill in the BITMAPFILEHEADER structure.

				if (m_nHeaderSize != 0)
				{
					FillBitmapFileHeader(pBuffer + 1, pBuffer);
				}

				GlobalUnlock(hBuffer);
			}

			// Store this buffer in the successfully transferred images array

			hr = StoreBuffer();

			if (FAILED(hr))
			{
				return hr;
			}
		}

		break;
	}
	}

	return S_OK;
}

//////////////////////////////////////////////////////////////////////////
//
// CDataCallback::ReAllocBuffer
//

HRESULT CDataCallback::ReAllocBuffer(ULONG nSize)
{
	HRESULT hr;

	// If m_pStream is not initialized yet, create a new stream object

	if (m_pStream == 0)
	{
		hr = CreateStreamOnHGlobal(0, TRUE, &m_pStream);

		if (FAILED(hr))
		{
			return hr;
		}
	}

	// Next, set the size of the stream object

	ULARGE_INTEGER liSize = { nSize };

	hr = m_pStream->SetSize(liSize);

	if (FAILED(hr))
	{
		return hr;
	}

	m_nDataSize = nSize;

	return S_OK;
}

//////////////////////////////////////////////////////////////////////////
//
// CDataCallback::CopyToBuffer
//

HRESULT CDataCallback::CopyToBuffer(ULONG nOffset, LPCVOID pBuffer, ULONG nSize)
{
	HRESULT hr;

	// First move the stream pointer to the data offset

	LARGE_INTEGER liOffset = { nOffset };

	hr = m_pStream->Seek(liOffset, STREAM_SEEK_SET, 0);

	if (FAILED(hr))
	{
		return hr;
	}

	// Next, write the new data to the stream

	hr = m_pStream->Write(pBuffer, nSize, 0);

	if (FAILED(hr))
	{
		return hr;
	}

	return S_OK;
}


//////////////////////////////////////////////////////////////////////////
//
// CDataCallback::StoreBuffer
//

HRESULT CDataCallback::StoreBuffer()
{
	// Increase the successfully transferred buffers array size

	int nAllocSize = (*m_plCount + 1) * sizeof(IStream *);
	IStream **ppStream = (IStream **)CoTaskMemRealloc(
		*m_pppStream,
		nAllocSize
	);

	if (ppStream == NULL)
	{
		return E_OUTOFMEMORY;
	}

	*m_pppStream = ppStream;

	// Rewind the current buffer

	LARGE_INTEGER liZero = { 0 };

	m_pStream->Seek(liZero, STREAM_SEEK_SET, 0);

	// Store the current buffer as the last successfully transferred buffer

	if (*m_plCount < 0 || *m_plCount > nAllocSize)
	{
		return E_OUTOFMEMORY;
	}

	(*m_pppStream)[*m_plCount] = m_pStream;
	(*m_pppStream)[*m_plCount]->AddRef();

	*m_plCount += 1;

	// Reset the current buffer

	m_pStream.Release();

	m_nDataSize = 0;

	return S_OK;
}

Add the above files to your project.

DataClasses.cpp contains two classes.
class DataClasses
and
class CDataCallback

class DataClasses has only two important functions the ImageEventCallback function and the WiaGetNumDevices function. The first simply returns the output of WiaGetNumDevices. So we will focus on that. So we create a connection to the local WIA device manager. First of all we check if the value of the device manager is NULL, which it will be as our program purposely will call it as NULL. So if it is NULL we call CoCreateInstance which is a COM call with a CLSID of CLSID_WiaDevMgr. A CLSID is a class identifier and COM has specific CLSID's for just about everything and the class identifier for the WIA device manager is CLSID_WiaDevMgr, so you cant change it. We create it with the device context of CLSCTX_LOCAL_SERVER, what that means is we will search for scanners on the local network, if however you plan on using it in an Office where there is likely to be remote networks where there could be scanners you would like to use it would be wise to change this to CLSCTX_ALL. For Home networks however, which is primarliy the focus of this project CLSCTX_LOCAL_SERVER will do just fine. We then get a list of scanners on the network and get the number of them.
We can safely ignore the rest of class DataClasses as the above is primarily what it does. Next is class CDataCallback and we can move directly to the class function BandedDataCallback as this is by far one of the most important functions in the program we will spend some time here. Scanners send their information in bands first of all there is a header which explains the information then there is the image data itself which as I say comes in a series of bands. These bands have to be tied together to form a coherent bitmap Image. While the bands are coming in we have to call our modal progress dialog and update it with a percentage of the information that the scanner has sent. We do this through the callback function PFNPROGRESSCALLBACK m_pfnProgressCallback. If it is the first band of image data we have to set up the transfer buffer according to the size of the Image contained in the Image Header, otherwise we copy the banded data to the transfer buffer, but we also need to check if the buffer is large enough to contain the updated data if not we try to expand the transfer buffer. If we get a Termination or New Page message from the scanner we should get the complete Image from the memory stream and store it in a buffer. We should talk a little about the New Page message some commercial network scanners can handle multiple scan images and therefore return multiple images to the program. The scope of the program will only display one image to get it to display multiple images at once some sort of Multiple Document Interface would have to be set up and you would display each scanned image in each MDI window. Consideration would have to be given also to how to select each image for saving the selected Image if the user so chose to do so. However most Home networks will have a simple flatbed scanner or All in One Printer/Scanner so we do not concern ourselves here with the commercial possibilities and trust that the user of the program only wants one Image and can only scan one Image at a time.

ImageAcquisition.h
#pragma once

#include "ProgressDlg.h"

#ifndef MAX_GUID_STRING_LEN
#define MAX_GUID_STRING_LEN 39
#endif //MAX_GUID_STRING_LEN


typedef HRESULT(CALLBACK *PFNPROGRESSCALLBACK)(
	LONG   lStatus,
	LONG   lPercentComplete,
	PVOID  pParam
	);

template <class T>
class CComPtrArray
{
public:
	CComPtrArray()
	{
		m_pArray = NULL;
		m_nCount = 0;
	}

	explicit CComPtrArray(int nCount)
	{
		m_pArray = (T **)CoTaskMemAlloc(nCount * sizeof(T *));

		m_nCount = m_pArray == NULL ? 0 : nCount;

		for (int i = 0; i < m_nCount; ++i)
		{
			m_pArray[i] = NULL;
		}
	}

	CComPtrArray(const CComPtrArray& rhs)
	{
		Copy(rhs);
	}

	~CComPtrArray()
	{
		Clear();
	}

	CComPtrArray &operator =(const CComPtrArray& rhs)
	{
		if (this != &rhs)
		{
			Clear();
			Copy(rhs);
		}

		return *this;
	}

	operator T **()
	{
		return m_pArray;
	}

	bool operator!()
	{
		return m_pArray == NULL;
	}

	T ***operator&()
	{
		return &m_pArray;
	}

	LONG &Count()
	{
		return m_nCount;
	}

	void Clear()
	{
		if (m_pArray != NULL)
		{
			for (int i = 0; i < m_nCount; ++i)
			{
				if (m_pArray[i] != NULL)
				{
					m_pArray[i]->Release();
				}
			}

			CoTaskMemFree(m_pArray);

			m_pArray = NULL;
			m_nCount = 0;
		}
	}

	void Copy(const CComPtrArray& rhs)
	{
		m_pArray = NULL;
		m_nCount = 0;

		if (rhs.m_pArray != NULL)
		{
			m_pArray = (T**)CoTaskMemAlloc(rhs.m_nCount * sizeof(T *));

			if (m_pArray != NULL)
			{
				m_nCount = rhs.m_nCount;

				for (int i = 0; i < m_nCount; ++i)
				{
					m_pArray[i] = rhs.m_pArray[i];

					if (m_pArray[i] != NULL)
					{
						m_pArray[i]->AddRef();
					}
				}
			}
		}
	}

private:
	T    **m_pArray;
	LONG  m_nCount;
};





HRESULT ReadPropertyLong(
	IWiaPropertyStorage *pWiaPropertyStorage,
	const PROPSPEC      *pPropSpec,
	LONG                *plResult
);



HRESULT
ReadPropertyGuid(
	IWiaPropertyStorage *pWiaPropertyStorage,
	const PROPSPEC      *pPropSpec,
	GUID                *pguidResult
);


HRESULT
WiaGetImage(
	HWND                 hWndParent,
	LONG                 lDeviceType,
	LONG                 lFlags,
	LONG                 lIntent,
	IWiaDevMgr          *pSuppliedWiaDevMgr,
	IWiaItem            *pSuppliedItemRoot,
	PFNPROGRESSCALLBACK  pfnProgressCallback,
	PVOID                pProgressCallbackParam,
	GUID                *pguidFormat,
	LONG                *plCount,
	IStream             ***pppStream
);




ImageAcquisition.cpp
#include "ImageAcquisition.h"



///////////////////////////////////////////////////////////
// ReadPropertyLong
//
HRESULT ReadPropertyLong(
	IWiaPropertyStorage *pWiaPropertyStorage,
	const PROPSPEC      *pPropSpec,
	LONG                *plResult
)
{
	PROPVARIANT PropVariant;

	HRESULT hr = pWiaPropertyStorage->ReadMultiple(
		1,
		pPropSpec,
		&PropVariant
	);

	// Generally, the return value should be checked against S_FALSE.
	// If ReadMultiple returns S_FALSE, it means the property name or ID
	// had valid syntax, but it didn't exist in this property set, so
	// no properties were retrieved, and each PROPVARIANT structure is set 
	// to VT_EMPTY. But the following switch statement will handle this case
	// and return E_FAIL. So the caller of ReadPropertyLong does not need
	// to check for S_FALSE explicitly.

	if (SUCCEEDED(hr))
	{
		switch (PropVariant.vt)
		{
		case VT_I1:
		{
			*plResult = (LONG)PropVariant.cVal;

			hr = S_OK;

			break;
		}

		case VT_UI1:
		{
			*plResult = (LONG)PropVariant.bVal;

			hr = S_OK;

			break;
		}

		case VT_I2:
		{
			*plResult = (LONG)PropVariant.iVal;

			hr = S_OK;

			break;
		}

		case VT_UI2:
		{
			*plResult = (LONG)PropVariant.uiVal;

			hr = S_OK;

			break;
		}

		case VT_I4:
		{
			*plResult = (LONG)PropVariant.lVal;

			hr = S_OK;

			break;
		}

		case VT_UI4:
		{
			*plResult = (LONG)PropVariant.ulVal;

			hr = S_OK;

			break;
		}

		case VT_INT:
		{
			*plResult = (LONG)PropVariant.intVal;

			hr = S_OK;

			break;
		}

		case VT_UINT:
		{
			*plResult = (LONG)PropVariant.uintVal;

			hr = S_OK;

			break;
		}

		case VT_R4:
		{
			*plResult = (LONG)(PropVariant.fltVal + 0.5);

			hr = S_OK;

			break;
		}

		case VT_R8:
		{
			*plResult = (LONG)(PropVariant.dblVal + 0.5);

			hr = S_OK;

			break;
		}

		default:
		{
			hr = E_FAIL;

			break;
		}

		}
		PropVariantClear(&PropVariant);
	}
	return hr;
}



////////////////////////////////////////////////////////////////////////////
// ReadPropertGuid
//

HRESULT
ReadPropertyGuid(
	IWiaPropertyStorage *pWiaPropertyStorage,
	const PROPSPEC      *pPropSpec,
	GUID                *pguidResult
)
{
	PROPVARIANT PropVariant;

	HRESULT hr = pWiaPropertyStorage->ReadMultiple(
		1,
		pPropSpec,
		&PropVariant
	);

	// Generally, the return value should be checked against S_FALSE.
	// If ReadMultiple returns S_FALSE, it means the property name or ID
	// had valid syntax, but it didn't exist in this property set, so
	// no properties were retrieved, and each PROPVARIANT structure is set 
	// to VT_EMPTY. But the following switch statement will handle this case
	// and return E_FAIL. So the caller of ReadPropertyGuid does not need
	// to check for S_FALSE explicitly.

	if (SUCCEEDED(hr))
	{
		switch (PropVariant.vt)
		{
		case VT_CLSID:
		{
			*pguidResult = *PropVariant.puuid;

			hr = S_OK;

			break;
		}

		case VT_BSTR:
		{
			hr = CLSIDFromString(PropVariant.bstrVal, pguidResult);

			break;
		}

		case VT_LPWSTR:
		{
			hr = CLSIDFromString(PropVariant.pwszVal, pguidResult);

			break;
		}

		case VT_LPSTR:
		{
			WCHAR wszGuid[MAX_GUID_STRING_LEN];
			mbstowcs_s(NULL, wszGuid, MAX_GUID_STRING_LEN, PropVariant.pszVal, MAX_GUID_STRING_LEN);

			wszGuid[MAX_GUID_STRING_LEN - 1] = L'\0';

			hr = CLSIDFromString(wszGuid, pguidResult);

			break;
		}

		default:
		{
			hr = E_FAIL;

			break;
		}
		}
	}

	PropVariantClear(&PropVariant);

	return hr;
}


//////////////////////////////////////////////////////////////////////////
// WiaGetImage -- This function does the majority of the work.
//

HRESULT
WiaGetImage(
	HWND                 hWndParent,
	LONG                 lDeviceType,
	LONG                 lFlags,
	LONG                 lIntent,
	IWiaDevMgr          *pSuppliedWiaDevMgr,
	IWiaItem            *pSuppliedItemRoot,
	PFNPROGRESSCALLBACK  pfnProgressCallback,
	PVOID                pProgressCallbackParam,
	GUID                *pguidFormat,
	LONG                *plCount,
	IStream             ***pppStream
)
{
	HRESULT hr;

	// Validate and initialize output parameters

	if (plCount == NULL)
	{
		return E_POINTER;
	}

	if (pppStream == NULL)
	{
		return E_POINTER;
	}

	*plCount = 0;
	*pppStream = NULL;

	// Initialize the local root item variable with the supplied value.
	// If no value is supplied, display the device selection common dialog.

	CComPtr<IWiaItem> pItemRoot = pSuppliedItemRoot;

	if (pItemRoot == NULL)
	{
		// Initialize the device manager pointer with the supplied value
		// If no value is supplied, connect to the local device manager

		CComPtr<IWiaDevMgr> pWiaDevMgr = pSuppliedWiaDevMgr;

		if (pWiaDevMgr == NULL)
		{
			hr = pWiaDevMgr.CoCreateInstance(CLSID_WiaDevMgr,NULL, CLSCTX_LOCAL_SERVER);
			
			if (FAILED(hr))
			{
				return hr;
			}
		}

		// Display the device selection common dialog

		hr = pWiaDevMgr->SelectDeviceDlg(
			hWndParent,
			lDeviceType,
			lFlags,
			0,
			&pItemRoot
		);

		if (FAILED(hr) || hr == S_FALSE)
		{
			return hr;
		}
	}

	// Display the image selection common dialog 

	CComPtrArray<IWiaItem> ppIWiaItem;

	hr = pItemRoot->DeviceDlg(
		hWndParent,
		lFlags,
		lIntent,
		&ppIWiaItem.Count(),
		&ppIWiaItem
	);

	if (FAILED(hr) || hr == S_FALSE)
	{
		return hr;
	}

	// For ADF scanners, the common dialog explicitly sets the page count to one.
	// So in order to transfer multiple images, set the page count to ALL_PAGES
	// if the WIA_DEVICE_DIALOG_SINGLE_IMAGE flag is not specified, 

	if (!(lFlags & WIA_DEVICE_DIALOG_SINGLE_IMAGE))
	{
		
		// Get the property storage interface pointer for the root item

		CComQIPtr<IWiaPropertyStorage> pWiaRootPropertyStorage(pItemRoot);

		if (pWiaRootPropertyStorage == NULL)
		{
			return E_NOINTERFACE;
		}

		// Determine if the selected device is a scanner or not

		PROPSPEC specDevType;

		specDevType.ulKind = PRSPEC_PROPID;
		specDevType.propid = WIA_DIP_DEV_TYPE;

		LONG nDevType;

		hr = ReadPropertyLong(
			pWiaRootPropertyStorage,
			&specDevType,
			&nDevType
		);

		if (SUCCEEDED(hr) && (GET_STIDEVICE_TYPE(nDevType) == StiDeviceTypeScanner))
		{
			// Determine if the document feeder is selected or not

			PROPSPEC specDocumentHandlingSelect;

			specDocumentHandlingSelect.ulKind = PRSPEC_PROPID;
			specDocumentHandlingSelect.propid = WIA_DPS_DOCUMENT_HANDLING_SELECT;

			LONG nDocumentHandlingSelect;

			hr = ReadPropertyLong(
				pWiaRootPropertyStorage,
				&specDocumentHandlingSelect,
				&nDocumentHandlingSelect
			);

			if (SUCCEEDED(hr) && (nDocumentHandlingSelect & FEEDER))
			{
				PROPSPEC specPages;

				specPages.ulKind = PRSPEC_PROPID;
				specPages.propid = WIA_DPS_PAGES;

				PROPVARIANT varPages;

				varPages.vt = VT_I4;
				varPages.lVal = ALL_PAGES;

				pWiaRootPropertyStorage->WriteMultiple(
					1,
					&specPages,
					&varPages,
					WIA_DPS_FIRST
				);

				PropVariantClear(&varPages);
			}
		}
	}

	// If a status callback function is not supplied, use the default.
	// The default displays a simple dialog with a progress bar and cancel button.

	CComPtr<CProgressDlg> pProgressDlg;

	if (pfnProgressCallback == NULL)
	{
		pfnProgressCallback = DefaultProgressCallback;

		pProgressDlg = new CProgressDlg(hWndParent);

		pProgressCallbackParam = (CProgressDlg *)pProgressDlg;
	}

	// Create the data callback interface

	CComPtr<CDataCallback> pDataCallback = new CDataCallback(
		pfnProgressCallback,
		pProgressCallbackParam,
		plCount,
		pppStream
	);

	if (pDataCallback == NULL)
	{
		return E_OUTOFMEMORY;
	}

	// Start the transfer of the selected items

	for (int i = 0; i < ppIWiaItem.Count(); ++i)
	{
		// Get the interface pointers

		CComQIPtr<IWiaPropertyStorage> pWiaPropertyStorage(ppIWiaItem[i]);

		if (pWiaPropertyStorage == NULL)
		{
			return E_NOINTERFACE;
		}

		CComQIPtr<IWiaDataTransfer> pIWiaDataTransfer(ppIWiaItem[i]);

		if (pIWiaDataTransfer == NULL)
		{
			return E_NOINTERFACE;
		}

		// Set the transfer type

		PROPSPEC specTymed;

		specTymed.ulKind = PRSPEC_PROPID;
		specTymed.propid = WIA_IPA_TYMED;

		PROPVARIANT varTymed;

		varTymed.vt = VT_I4;
		varTymed.lVal = TYMED_CALLBACK;

		hr = pWiaPropertyStorage->WriteMultiple(
			1,
			&specTymed,
			&varTymed,
			WIA_IPA_FIRST
		);

		PropVariantClear(&varTymed);

		if (FAILED(hr))
		{
			return hr;
		}

		// If there is no transfer format specified, use the device default

		GUID guidFormat = GUID_NULL;

		if (pguidFormat == NULL)
		{
			pguidFormat = &guidFormat;
		}

		if (*pguidFormat == GUID_NULL)
		{
			PROPSPEC specPreferredFormat;

			specPreferredFormat.ulKind = PRSPEC_PROPID;
			specPreferredFormat.propid = WIA_IPA_PREFERRED_FORMAT;

			hr = ReadPropertyGuid(
				pWiaPropertyStorage,
				&specPreferredFormat,
				pguidFormat
			);

			if (FAILED(hr))
			{
				return hr;
			}
		}

		// Set the transfer format

		PROPSPEC specFormat;

		specFormat.ulKind = PRSPEC_PROPID;
		specFormat.propid = WIA_IPA_FORMAT;

		PROPVARIANT varFormat;

		varFormat.vt = VT_CLSID;
		varFormat.puuid = (CLSID *)CoTaskMemAlloc(sizeof(CLSID));

		if (varFormat.puuid == NULL)
		{
			return E_OUTOFMEMORY;
		}

		*varFormat.puuid = *pguidFormat;

		hr = pWiaPropertyStorage->WriteMultiple(
			1,
			&specFormat,
			&varFormat,
			WIA_IPA_FIRST
		);

		PropVariantClear(&varFormat);

		if (FAILED(hr))
		{
			return hr;
		}

		// Read the transfer buffer size from the device, default to 64K

		PROPSPEC specBufferSize;

		specBufferSize.ulKind = PRSPEC_PROPID;
		specBufferSize.propid = WIA_IPA_BUFFER_SIZE;

		LONG nBufferSize;

		hr = ReadPropertyLong(
			pWiaPropertyStorage,
			&specBufferSize,
			&nBufferSize
		);

		if (FAILED(hr))
		{
			nBufferSize = 64 * 1024;
		}

		// Choose double buffered transfer for better performance

		WIA_DATA_TRANSFER_INFO WiaDataTransferInfo = { 0 };

		WiaDataTransferInfo.ulSize = sizeof(WIA_DATA_TRANSFER_INFO);
		WiaDataTransferInfo.ulBufferSize = 2 * nBufferSize;
		WiaDataTransferInfo.bDoubleBuffer = TRUE;

		// Start the transfer

		hr = pIWiaDataTransfer->idtGetBandedData(
			&WiaDataTransferInfo,
			pDataCallback
		);

		if (FAILED(hr) || hr == S_FALSE)
		{
			return hr;
		}
	}

	return S_OK;
}



Add the above files to your project.

ImageAcquisition.h contains a typedef for our progress callback, a template class CComPtrArray, and three other functions the most important of which is WiaGetImage. But first the typedef, what is a typedef ? Well it allows you to define a new type. What is a type ? Well a type could be an int or a std::string for instance, in this case our new type is PFNPROGRESSCALLBACK and it takes three parameters LONG,LONG and PVOID, it returns a HRESULT. Next is the template class CComPtrArray which basically is there to enable CComPtr to be made into an Array of CComPtr objects. A CComPtr is part of the ATL, Advanced Template Library and is there to ease the pain a little in dealing with COM pointers, all COM pointers have a Release() function which is used along these lines ComObject* myComObject; myComObject->Release();. The trouble is COM objects can be hard to track and when to call Release is important too, so the ATL CComPtr is there so you dont have to call Release it calls it for you when it goes out of scope. So a CComPtrArray is just an Array of these types of objects, you will see it meticously calls Release on each part of the array this is done in the template class function void Clear(). So what you may ask ? Well if you forget to call Release on a COM object you get a memory leak, so thats so what ! Next, WiaGetImage this is the function our program calls from its Window Procedure so we must be getting close to the end of our Tutorial. It starts with the local root item which if it is not supplied and its not, starts the device manager as before the manager is set to CLSCTX_LOCAL_SERVER if in an Office environment change it to CLSCTX_ALL. It displays the device selection dialog. If a status callback function is not supplied use the default for the program which is a simple dialog with a progress bar and a cancel button. We then ask the scanner to start the transfer of the Image or Images, we then set the transfer type, which is a timed data callback. If there is no transfer format specified and there won't be, use the scanners own default this is generally the safest thing to do rather than forcing some transfer format that the scanner may or may not support. After agreeing a transfer format with the scanner, set the transfer format. Next try and read the transfer buffer size from the scanner, if this fails set the buffer size to something safe like 64 kilobytes this may result in slower transfer speeds from the scanner but at least you will be safe in the knowledge that the scanner will be able to handle 64kb at a time. Choose double buffering and then start the transfer of the banded data.

Mainwindow.cpp
#if defined(UNICODE) && !defined(_UNICODE)
#define _UNICODE
#elif defined(_UNICODE) && !defined(UNICODE)
#define UNICODE
#endif

#include <Windows.h>
#include <tchar.h>
#include <iostream>
#include <gdiplus.h>
#include <shobjidl.h>


#pragma once
#include "ImageAcquisition.h"
#include "AboutDlg.h"


/*Global Variables*/
Gdiplus::Status StartupStatus;
ULONG_PTR       Token;
CComPtrArray<IStream> ppStream;
bool draw = false;


/*  Declare Windows procedure  */
LRESULT CALLBACK WindowProcedure(HWND, UINT, WPARAM, LPARAM);


int WINAPI WinMain(HINSTANCE hThisInstance,
	HINSTANCE hPrevInstance,
	LPSTR lpszArgument,
	int nCmdShow)
{
	HWND hwnd;               /* This is the handle for our window */
	MSG messages;            /* Here messages to the application are saved */
	WNDCLASSEX wincl;        /* Data structure for the windowclass */
	wchar_t szClassName[] = _T("ScannerApp"); /* Make the class name into a local variable  */


							 /* The Window structure */
	wincl.hInstance = hThisInstance;
	wincl.lpszClassName = szClassName;
	wincl.lpfnWndProc = WindowProcedure;      /* This function is called by windows */
	wincl.style = CS_DBLCLKS;                 /* Catch double-clicks */
	wincl.cbSize = sizeof(WNDCLASSEX);

	/* Use supplied icon and default mouse-pointer */
	wincl.hIcon = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(ICO1));
	wincl.hIconSm = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(ICO1));
	wincl.hCursor = LoadCursor(NULL, IDC_ARROW);
	wincl.lpszMenuName = NULL;                 /* No menu */
	wincl.cbClsExtra = 0;                      /* No extra bytes after the window class */
	wincl.cbWndExtra = 0;                      /* structure or the window instance */
											   /* Use Windows's default colour as the background of the window */
	wincl.hbrBackground = (HBRUSH)COLOR_BACKGROUND;

	/* Register the window class, and if it fails quit the program */
	if (!RegisterClassEx(&wincl))
		return 0;

	/* The class is registered, let's create the program*/
	hwnd = CreateWindowEx(
		0,                   /* Extended possibilites for variation */
		szClassName,         /* Classname */
		_T("Scanner App"),       /* Title Text */
		WS_OVERLAPPEDWINDOW, /* default window */
		CW_USEDEFAULT,       /* Windows decides the position */
		CW_USEDEFAULT,       /* where the window ends up on the screen */
		545,                 /* The programs width */
		675,                 /* and height in pixels */
		HWND_DESKTOP,        /* The window is a child-window to desktop */
		LoadMenu(hThisInstance, MAKEINTRESOURCE(IDR_MENU)),/* Load the application menu */
		hThisInstance,       /* Program Instance handler */
		NULL                 /* No Window Creation data */
	);

	/* Make the window visible on the screen */
	ShowWindow(hwnd, nCmdShow);

	/* Run the message loop. It will run until GetMessage() returns 0 */
	while (GetMessage(&messages, NULL, 0, 0))
	{
		/* Translate virtual-key messages into character messages */
		TranslateMessage(&messages);
		/* Send message to WindowProcedure */
		DispatchMessage(&messages);
	}

	/* The program return-value is 0 - The value that PostQuitMessage() gave */
	return messages.wParam;
}


/*  This function is called by Windows */

LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{



	switch (message)                  /* handle the messages */
	{
	case WM_DESTROY:
	{
		if (StartupStatus == Gdiplus::Ok)
		{
			Gdiplus::GdiplusShutdown(Token);
		}
		CoUninitialize();
		PostQuitMessage(0);       /* send a WM_QUIT to the message queue */
		break;
	}


	case WM_CREATE:
	{
		// Initialize the COM library 

		HRESULT hr = CoInitialize(NULL);
		if (FAILED(hr))
		{
			SendMessage(hwnd, WM_CLOSE, 0, 0);
			break;
		}

		///////////////////////////////////////////////////////////////////
		// Initialize common controls this is for the progress bar control
		//
		INITCOMMONCONTROLSEX iccex;

		iccex.dwSize = sizeof(iccex);
		iccex.dwICC = ICC_BAR_CLASSES;

		InitCommonControlsEx(&iccex);

		//////////////////////////////////////////////////////////////////////////
		// Initialize GDI+ this is used for drawing and saving the scanned image
		//
		BOOL                    suppressBackgroundThread = FALSE;
		BOOL                    suppressExternalCodecs = FALSE;
		Gdiplus::DebugEventProc debugEventCallback = 0;

		Gdiplus::GdiplusStartupInput StartupInput(
			debugEventCallback,
			suppressBackgroundThread,
			suppressExternalCodecs
		);
		Gdiplus::GdiplusStartupOutput Output;
		StartupStatus = Gdiplus::GdiplusStartup(
			&Token,
			&StartupInput,
			&Output
		);
		break;
	}


	case WM_PAINT:
	{
		PAINTSTRUCT ps;
		HDC hDC = BeginPaint(hwnd, &ps);
		if (hDC != NULL && draw)
		{
			Gdiplus::Graphics graphics(hDC);

			// Get the size of the window

			RECT r;

			GetClientRect(hwnd, &r);

			UINT nWindowWidth = r.right;
			UINT nWindowHeight = r.bottom;

			// Get the size of the image from the stream.
			Gdiplus::Image m_Image(*ppStream);
			UINT nBitmapWidth = m_Image.GetWidth();
			UINT nBitmapHeight = m_Image.GetHeight();

			if (nBitmapHeight != 0)
			{
				// Calculate the coordinates and the size of the image

				Gdiplus::Rect rDest;

				if (nBitmapWidth <= nWindowWidth && nBitmapHeight <= nWindowHeight)
				{
					// If the image is smaller than the window, just draw it.
					

					rDest.X = 0;
					rDest.Y = 0;
					rDest.Width = nWindowWidth;
					rDest.Height = nWindowHeight;;
				}
				else
				{
					// If the image is larger than the window, resize 
					// the image while keeping the aspect ratio

					UINT nStretchedWidth = nWindowWidth;
					UINT nStretchedHeight = MulDiv(nBitmapHeight, nWindowWidth, nBitmapWidth);

					if (nStretchedHeight > nWindowHeight)
					{
						nStretchedWidth = MulDiv(nBitmapWidth, nWindowHeight, nBitmapHeight);
						nStretchedHeight = nWindowHeight;
					}

					rDest.X = 0;
					rDest.Y = 0;
					rDest.Width = nStretchedWidth;
					rDest.Height = nStretchedHeight;
				}

				// Paint the image with a white background
				graphics.SetSmoothingMode(Gdiplus::SmoothingMode::SmoothingModeAntiAlias);
				graphics.DrawImage(&m_Image, rDest);

				graphics.ExcludeClip(rDest);
				Gdiplus::Color white(255, 255, 255, 255);

				graphics.Clear(white);

			}

		}

		EndPaint(hwnd, &ps);
		break;
	}

	case WM_SIZE:
	{
		///////////////////////////////////////////////////////////////
		// Used for resizing the window and the scanned image to suit
		//

		if (wParam == SIZE_MAXIMIZED)
		{
			InvalidateRect(hwnd, NULL, TRUE);
		}
		if (wParam == SIZE_MINIMIZED || wParam == SIZE_RESTORED)
		{
			RECT r;
			r.top = r.left = 0;
			r.right = LOWORD(lParam);
			r.bottom = HIWORD(lParam);

			InvalidateRect(hwnd, &r, TRUE);
		}

		return 0;
	}

	case WM_COMMAND:
	{
		switch (LOWORD(wParam))
		{
		case ID_FILE_FROM_SCANNER:
		{

			draw = false;
			// Register the callback interface
			DataClasses myData;
			myData.Register();
			LONG sample = myData.getNumDevices();
			CDataCallback callBack(NULL, NULL, &sample, &ppStream);


			// Scan the Image into the stream object ppStream
			HRESULT hr = WiaGetImage(
				hwnd,
				StiDeviceTypeDefault,
				0,
				WIA_INTENT_NONE,
				NULL,
				NULL,
				NULL,
				NULL,
				NULL,
				&ppStream.Count(),
				&ppStream
			);
			////////////////////////////////////////////////////////////////
			// If the stream contains an image and the height of the image
			// doesnt equal zero then set the draw flag to true and draw it
			// also enable the grayed out menu item 'Save as .jpg' so the
			// user can save the image as a jpeg file.
			if (SUCCEEDED(hr))
			{
				if (ppStream.Count() != 0)
				{
					Gdiplus::Image m_Image(*ppStream);
					UINT nBitmapHeight = m_Image.GetHeight();
					if (nBitmapHeight != 0)
					{
						draw = true;
						InvalidateRect(hwnd, NULL, FALSE);
						HMENU menu = GetMenu(hwnd);
						EnableMenuItem(menu, ID_FILE_SAVE, MF_BYCOMMAND | MF_ENABLED);
					}


				}
			}
			break;
		}


		case ID_FILE_SAVE:
		{

			// Get the image from the stream
			Gdiplus::Image m_Image(*ppStream);
			UINT nBitmapHeight = m_Image.GetHeight();
			if (nBitmapHeight != 0)
			{
				CLSID   encoder;
				LPWSTR pszFilePath = _T("");
				std::wstring extension(_T(""));
				std::wstring extensionType(_T(".jpg"));
				std::wstring fullPath(_T(""));
				CLSIDFromString(_T("{557cf401-1a04-11d3-9a73-0000f81ef32e}"), &encoder);     /* jpg encoder classid */
				COMDLG_FILTERSPEC filter;
				filter.pszName = _T("A jpeg picture file.");
				filter.pszSpec = _T("*.jpg");
				IFileSaveDialog* pSaveDialog;

				// Create the FileSaveDialog object.
				HRESULT hr = CoCreateInstance(CLSID_FileSaveDialog, NULL, CLSCTX_ALL,
					IID_IFileSaveDialog, reinterpret_cast<void**>(&pSaveDialog));
				if (SUCCEEDED(hr))
				{
					// set the extension to .jpg for the save dialog.
					pSaveDialog->SetFileTypes(1, &filter);
					// Show the Save dialog box.
					hr = pSaveDialog->Show(hwnd);

				}
				// Get the file name from the dialog box.
				if (SUCCEEDED(hr))
				{
					IShellItem *pItem;
					hr = pSaveDialog->GetResult(&pItem);
					if (SUCCEEDED(hr))
					{
						// Check the user has the correct extension ie .jpg for the file.
						hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath);
						BOOL isContent = FALSE;
						extension = PathFindExtension(pszFilePath);

						if (wcscmp(extension.c_str(), extensionType.c_str()) == 0)
						{
							isContent = TRUE;
						}
						// if the user doesn't have the correct .jpg extension add it.
						if (!isContent)
						{
							std::wstring path(pszFilePath);
							std::wstring extType(extensionType);
							fullPath = path + extType;
							hr = -1;
						}

					}

					// Save the file to the user given location.
					if (SUCCEEDED(hr))
					{

						m_Image.Save(pszFilePath, &encoder, NULL);
						CoTaskMemFree(pszFilePath);
					}

					// If the user doesn't have the correct extension use the correct path with extension.
					else
					{
						m_Image.Save(fullPath.c_str(), &encoder, NULL);
						CoTaskMemFree(pszFilePath);
					}
					pItem->Release();

				}
				pSaveDialog->Release();
			}
			break;
		}


		case ID_FILE_EXIT:
		{
			// exit the program
			SendMessage(hwnd, WM_CLOSE, 0, 0);
			break;
		}

		case ID_HELP_about:
		{
			//Show the About Dialog
			CAboutDlg myAboutDlg(hwnd);
			break;
		}

		}
	}

	default:     /* for messages that we don't deal with */
		return DefWindowProc(hwnd, message, wParam, lParam);
	}
	return 0;
}



Add the above to your project.

This is our Window, its our interface with the user and its the most fun part of this Tutorial ! First we start off with four global variables. Now before you tell me globals are evil, all Windowed programs that do anything remotely useful contain globals and besides there are only FOUR ! The main rule is keep your Globals to a minimum. So after that there is WinMain this is the program entry point function its like int main in a console application. Inside WinMain we register our window class and create a shiny new window with CreateWindowEx then we enter a message loop which sends and receives messages from the OS. Then there comes our last function in Mainwindow.cpp the WindowProcedure function this is what gives life to our program how we interact with the user. We will start with WM_CREATE I am going to purposely skip over WM_DESTROY for now. Unlike the rest of the parts of WindowProcedure WM_CREATE is called only once. Its where we initailize things, get everything setup like COM and initalizing the common controls library which we need for the progress bar in the progress dialog. We initialize gdiplus here too. WM_DESTROY shuts everything we have initialized and indeed the window itself down. WM_PAINT draws a scanned image to our window if we indeed have a valid scanned image, otherwise it just skips to the end and exits. It uses Gdiplus to do this and if the Image is larger than the window which is more likely to be the case than not it resizes the image to the window while keeping the aspect ratio. WM_SIZE deals with window resizing events if it is maximized the full length of the window is redrawn if it is minimized or any other resize event occurs it calculates the rectangle which needs redrawn an passes that to WM_PAINT in any case the WM_SIZE forces a recalculation of the drawing.
WM_COMMAND deals with commands from Menu's and Buttons. Since we don't have any Buttons and just have a Menu it deals specifically with Menu selection. It uses a switch statement to evaluate which option of commands has been triggered these values have been set in the resource.rc file these are ID_FILE_FROM_SCANNER
ID_FILE_SAVE
ID_FILE_EXIT
ID_HELP_ABOUT
It attempts to get a file from the scanner and if it succeeds it sets the global boolean flag 'draw' to true, forces a repaint of the window and enables the grayed out menu item 'Save as .jpg...' so it can be used to save an image, its important to have this grayed out in the beginning as you can't save an image as a .jpg if you do not have one scanned in yet.
If save has been selected it gets an image from the stream and if it is a valid image it creates a File Save Dialog using IFileSaveDialog it checks to see if the user has added the .jpg extension if not it adds the extension and saves the file. ID_FILE_EXIT exits the program and ID_HELP_ABOUT shows the About Dialog via the class CAboutDlg which we will come to next.

'ProgressDlg.h'
#pragma once

#include <Windows.h>
#include <Wia.h>
#include <sti.h>
#include <commctrl.h>
#include <shellapi.h>
#include <objbase.h>
#include <atlbase.h>
#include "resource.h"
#include "DataClasses.h"



class CProgressDlg : public IUnknown
{
public:
	CProgressDlg(HWND hWndParent);
	~CProgressDlg();

	// IUnknown interface

	STDMETHOD(QueryInterface)(REFIID iid, LPVOID *ppvObj);
	STDMETHOD_(ULONG, AddRef)();
	STDMETHOD_(ULONG, Release)();

	// CProgressDlg methods
	BOOL Cancelled() const;
    VOID SetTitle(PCTSTR pszTitle);
	VOID SetMessage(PCTSTR pszMessage);
	VOID SetPercent(UINT nPercent);

private:
	static DWORD WINAPI ThreadProc(PVOID pParameter);
	static INT_PTR CALLBACK DialogProc(HWND, UINT, WPARAM, LPARAM);
    
private:
	LONG    m_cRef;
	HWND    m_hDlg;
	HWND    m_hWndParent;
	LONG    m_bCancelled;
	HANDLE  m_hInitDlg;
	HINSTANCE g_hInstance;

};

HRESULT
CALLBACK
DefaultProgressCallback(
	LONG   lStatus,
	LONG   lPercentComplete,
	PVOID  pParam
);



'ProgressDlg.cpp'
#include "ProgressDlg.h"




//////////////////////////////////////////////////////////////////////////
//
// CProgressDlg::CProgressDlg
//



CProgressDlg::CProgressDlg(HWND hWndParent)
{
	m_cRef = 0;

	m_hDlg = NULL;
	m_hWndParent = hWndParent;
	m_bCancelled = FALSE;

	m_hInitDlg = CreateEvent(NULL, FALSE, FALSE, NULL);

	if (m_hInitDlg != NULL)
	{
		// Create the progress dialog in a separate thread so that it
		// remains responsive

		DWORD dwThreadId;

		HANDLE hThread = CreateThread(
			NULL,
			0,
			ThreadProc,
			this,
			0,
			&dwThreadId
		);

		// Wait until the progress dialog is created and ready to process
		// messages. During creation, the new thread will send window messages 
		// to this thread, and this will cause a deadlock if we use 
		// WaitForSingleObject instead of MsgWaitForMultipleObjects

		if (hThread != NULL)
		{
			while (MsgWaitForMultipleObjects(1, &m_hInitDlg, FALSE, INFINITE, QS_ALLINPUT | QS_ALLPOSTMESSAGE) == WAIT_OBJECT_0 + 1)
			{
				MSG msg;

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

			CloseHandle(hThread);
		}

		CloseHandle(m_hInitDlg);
	}
}

//////////////////////////////////////////////////////////////////////////
//
// ~CProgressDlg::CProgressDlg
//

CProgressDlg::~CProgressDlg()
{
	EndDialog(m_hDlg, IDCLOSE);
}

//////////////////////////////////////////////////////////////////////////
//
// CProgressDlg::QueryInterface
//

STDMETHODIMP CProgressDlg::QueryInterface(REFIID iid, LPVOID *ppvObj)
{
	if (ppvObj == NULL)
	{
		return E_POINTER;
	}

	if (iid == IID_IUnknown)
	{
		*ppvObj = (IUnknown *) this;
	}
	else
	{
		*ppvObj = NULL;
		return E_NOINTERFACE;
	}

	AddRef();
	return S_OK;
}

//////////////////////////////////////////////////////////////////////////
//
// CProgressDlg::AddRef
//

STDMETHODIMP_(ULONG) CProgressDlg::AddRef()
{
	return InterlockedIncrement(&m_cRef);
}

//////////////////////////////////////////////////////////////////////////
//
// CProgressDlg::Release
//

STDMETHODIMP_(ULONG) CProgressDlg::Release()
{
	LONG cRef = InterlockedDecrement(&m_cRef);

	if (cRef == 0)
	{
		delete this;
	}

	return cRef;
}

//////////////////////////////////////////////////////////////////////////
//
// CProgressDlg::Cancelled
//

BOOL CProgressDlg::Cancelled() const
{
	return m_bCancelled;
}

//////////////////////////////////////////////////////////////////////////
//
// CProgressDlg::SetTitle
//

VOID CProgressDlg::SetTitle(PCTSTR pszTitle)
{
	SetWindowText(m_hDlg, pszTitle);
}

//////////////////////////////////////////////////////////////////////////
//
// CProgressDlg::SetMessage
//

VOID CProgressDlg::SetMessage(PCTSTR pszMessage)
{
	SetDlgItemText(m_hDlg, IDC_MESSAGE, pszMessage);
}

//////////////////////////////////////////////////////////////////////////
//
// CProgressDlg::SetPercent
//

VOID CProgressDlg::SetPercent(UINT nPercent)
{
	SendDlgItemMessage(m_hDlg, IDC_PROGRESS_BAR, PBM_SETPOS, (WPARAM)nPercent, 0);
}

//////////////////////////////////////////////////////////////////////////
//
// CProgressDlg::ThreadProc
//

DWORD WINAPI CProgressDlg::ThreadProc(PVOID pParameter)
{
	CProgressDlg *that = (CProgressDlg *)pParameter;

	INT_PTR nResult = DialogBoxParam(
		NULL,
		MAKEINTRESOURCE(IDD_PROGRESS),
		that->m_hWndParent,
		DialogProc,
		(LPARAM)that
	);

	return (DWORD)nResult;
}

//////////////////////////////////////////////////////////////////////////
//
// CProgressDlg::DialogProc
//

INT_PTR
CALLBACK
CProgressDlg::DialogProc(
	HWND   hDlg,
	UINT   uMsg,
	WPARAM wParam,
	LPARAM lParam
)
{
	switch (uMsg)
	{
	case WM_INITDIALOG:
	{
		// Retrieve and store the "this" pointer

		CProgressDlg *that = (CProgressDlg *)lParam;

		SetWindowLongPtr(hDlg, DWLP_USER, (LONG_PTR)that);

		that->m_hDlg = hDlg;

		// Initialize progress bar to the range 0 to 100 and set bar colour to purple

		SendDlgItemMessage(hDlg, IDC_PROGRESS_BAR, PBM_SETRANGE32, (WPARAM)0, (LPARAM)100);
		SendDlgItemMessage(hDlg, IDC_PROGRESS_BAR, PBM_SETBARCOLOR, (WPARAM)0, (LPARAM)RGB(130, 63, 158));
		// Signal the main thread that we are ready

		SetEvent(that->m_hInitDlg);

		return TRUE;
	}

	case WM_COMMAND:
	{
		switch (LOWORD(wParam))
		{
		case IDCANCEL:
		{
			// If the user presses the cancel button, set the m_bCancelled flag.

			CProgressDlg *that = (CProgressDlg *)GetWindowLongPtr(hDlg, DWLP_USER);

			InterlockedIncrement(&that->m_bCancelled);

			// The cancel operation will probably take some time, so 
			// change the cancel button text to "Wait...", so that 
			// the user will see the cancel command is received and 
			// is being processed

			wchar_t szWait[DEFAULT_STRING_SIZE] = _T("");

			LoadString(NULL, IDS_WAIT, szWait, COUNTOF(szWait));

			SetDlgItemText(hDlg, IDCANCEL, szWait);

			return TRUE;
		}
		}

		break;
	}
	}

	return FALSE;
}


HRESULT
CALLBACK
DefaultProgressCallback(
	LONG   lStatus,
	LONG   lPercentComplete,
	PVOID  pParam
)
{
	CProgressDlg *pProgressDlg = (CProgressDlg *)pParam;

	if (pProgressDlg == NULL)
	{
		return E_POINTER;
	}

	// If the user has pressed the cancel button, abort transfer

	if (pProgressDlg->Cancelled())
	{
		return S_FALSE;
	}

	// Form the message text

	UINT uID;

	switch (lStatus)
	{
	case IT_STATUS_TRANSFER_FROM_DEVICE:
	{
		uID = IDS_STATUS_TRANSFER_FROM_DEVICE;
		break;
	}

	case IT_STATUS_PROCESSING_DATA:
	{
		uID = IDS_STATUS_PROCESSING_DATA;
		break;
	}

	case IT_STATUS_TRANSFER_TO_CLIENT:
	{
		uID = IDS_STATUS_TRANSFER_TO_CLIENT;
		break;
	}

	default:
	{
		return E_INVALIDARG;
	}
	}

	wchar_t szFormat[DEFAULT_STRING_SIZE] = _T("%d");

	LoadString(NULL, uID, szFormat, COUNTOF(szFormat));

	wchar_t szStatusText[DEFAULT_STRING_SIZE];

	_sntprintf_s(szStatusText, COUNTOF(szStatusText), COUNTOF(szStatusText) - 1, szFormat, lPercentComplete);



	// Update the progress bar values

	pProgressDlg->SetMessage(szStatusText);

	pProgressDlg->SetPercent(lPercentComplete);

	return S_OK;
}





ProgressDlg.cpp contains one class CProgressDlg. Inside the class there are seven public member functions and two private member functions but we will start with the class constructor first. Inside the constructor we create the progress dialog in a separate thread from the Main Window this is because the progress dialog could lead to those annoying (Not Respnding) messages on our main window when in fact we are doing something else, so we create a new thread we wait until the dialog box is formed using MsgWaitForMultipleObjects then start processing the progress dialog messages in a second message loop. QueryInterface queries the scanner hopefully returning an IUnknown interface in ppvObj but deals with other eventualities as well. AddRef increments m_cRef by one. Release() deletes this instance of the class object. Cancelled() just returns the value of m_bCancelled. SetTitle can change the Title of the Dialog Box. SetMessage can change the message inside the Dialog Box. SetPercent updates the progress bar control which is formed inside the resource.rc file. ThreadProc basically starts the creation of a new Progress Dialog by calling DialogBoxParam() with a resource identifier of IDD_PROGRESS. DialogProc is again the same sort of structure as WNDPROC but its for dialog boxes, we switch through the messages. WM_INITDIALOG is like WM_CREATE in a Windows Procedure in that its only called once and thats the first time its created, we retrieve a pointer to the dialog box object and set the range on the bar from zero to one hundred percent. We also make the progress bar color a nice shade of purple, it would have been originally dark blue. We signal the new thread that the box now resides in that we are ready using SetEvent(), and return TRUE.
WM_COMMAND only contains one case and that is for the Cancel button. When it is pressed we load the resource string IDS_WAIT from the resource file string table and set the button with a new text ie 'Wait...'.
DefaultProgressCallback takes the form of PFNPROGRESSCALLBACK if you can remember that far back and its a separate function from the class of course but it does some important work. It basically updates the progress dialog box with messages that it receives directly from the scanner, changing the percentage of the progress in a numerical value given by the scanner and visually via the dialog box's purple progress bar control.
That is all the files you need, however do not press build just yet. First of all set your solution configuration from Debug to Release then go to Project->Scanner App properties.
And select Linker->Input as shown below.


Posted Image

click on additional dependencies, click Edit and replace what is there with the following eight .libs
kernel32.lib
user32.lib
gdi32.lib
comdlg32.lib
wiaguid.lib
comctl32.lib
gdiplus.lib
shlwapi.lib


Next go to C/C++ Code Generation and select the runtime library to Multi-threaded /MT as shown below.

Posted Image

Next go to C/C++ General and set the warning level to Level3 (/W3) as shown below.

Posted Image

This is because setting it to Level4 will bring up some warnings 15 in the Gdiplus headers to be exact, which of course was written by someone at Microsoft and not me it will also bring up three warnings in my code two of which in my opinion should not be classed as warnings namely the unreferenced variables warnings in WinMain that I dont use hPrevInstance and lpszArgument, which is completely immaterial. The other warning you can safely ignore too. Setting it to Level3 should if you have done everything correctly produce a pristine Build with no warnings or errors.
If you have built the solution you can press on Local Windows Debugger and start the program.
Selecting Capture From Scanner...
from the File Menu will bring up this.

Posted Image

I recommend hitting preview first as it will bring up a automatic crop box which you can adjust for better image capture, If the image is to be a Letter selecting Black and white picture or text gives the best results.

Posted Image

After that press Scan which should give you this.

Posted Image

And thats about it for now for further consideration the App could do with .pdf support ie the ability to save the file either as a .jpg or .pdf. There are plenty of third party libraries out there that deal with this and if the reader so wishes it should not be beyond your abilities to do so.

As the scanner says 'There's a Party And You're Invited' !

Good Luck !

This post has been edited by snoopy11: 08 October 2017 - 09:38 AM


Is This A Good Question/Topic? 0
  • +

Replies To: New Scanner Tutorial for Windows using WIA.

#2 snoopy11  Icon User is online

  • Engineering ● Software
  • member icon

Reputation: 1317
  • View blog
  • Posts: 4,025
  • Joined: 20-March 10

Posted 08 October 2017 - 09:29 AM

The code for this is totally messed up by the Editor

I will have to repost with the updated code
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1