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



<< Home

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