摘 要 TWAIN是扫描仪厂商共同遵循的规格,是应用程序与图像设备间的标准口。本文介绍了TWAIN的基本原理及开发应用规范,并通过一个开发实例讲解在Delphi环境下控制扫描仪进行图像传输的方法。
关键词 Delphi,TWAIN标准,扫描仪
现在使用扫描仪、数码照相机、以及其他图像获得设备的应用越来越多。因此,如何在应用程序中简单方便地将图像传输到计算机中显得非常重要。而使用TWAIN提供的函数接口,则可以很好地解决这个问题。
一、TWAIN的工作原理
TWAIN 工作组是一个致力于光栅图像输入设备通讯的组织。TWAIN就是它们提供的开发包,它提供了应用程序与图像设备间的标准接口,并且大多数的设备厂家都遵循该接口。利用这个接口就可以有效地解决系统及设备之间的不兼容问题。
TWAIN共包括四个文件,TWAIN_32.DLL、TWAIN.DLL、TWUNKER_32.EXE和 TWUNKER_16.EXE。要使用TWAIN接口,就必须保证上述四个文件安装在系统中。在WindowsXP及其后续版本的操作系统中,已经将这四个文件作为系统文件存放在系统的Windows目录下。其中前两个文件是用于32位应用程序和16位应用程序的支持文件,TWUNKET_32.EXE用于32位应用程序与32位数据源进行通讯,TWUNKET_16.EXE用于32位应用程序与16位数据源进行通讯,后两个文件运行时不可见。
TWAIN依靠三个组件( Application、Source Manager和Source)协同完成与图像设备的通讯和数据传输工作。Application就是用户编写的应用程序,Source Manager是由TWAIN提供的一个Source管理器,它不仅可以收集本地系统已经安装了的图像设备,还可以根据需要去加载设备。同时,它还是Application 与Source通讯的桥梁。(它就是前面提到的组成文件中的dll文件。)Source在这里就是扫描仪在系统中的标识。对用户来说,只要安装好扫描仪的驱动程序并将设备连接上计算机,就可以作为Source使用。
由于TWAIN的标准化,使应用程序可以做到与图像设备无关,即使更换扫描仪也不必对应用程序做任何的改动。
二、DSM_Entry()接口函数
应用程序的目标是从Source中获取数据。通常情况下,应用程序并不能和Source直接通讯。所有的请求包括数据、状态、错误信息等都要通过Source Manager来操作。TWAIN定义了大约140个操作消息,应用程序只要把这些消息通过twain_32.dll中的接口函数DSM_Entry()发送给Source Manager,就可以实现对选定的Source进行相应的操作。
TWAIN把这些操作消息称为Triplets操作,也就是每三个参数表示一组操作。这三个参数类型分别是Data Group(前缀名DG_ )、 Data Argument(前缀名DAT_ ) 和 Message ID(前缀名MSG_ )。
DSM_Entry函数的定义如下:
TW_UINT16 FAR PASCAL DSM_Entry(
pOrigin: pTW_IDENTITY, // 指向操作发起者的指针
pDest: pTW_IDENTITY, //指向目标对象的指针
DG: TW_UINT32, // Data Group参数 : DG_xxxx
DAT: TW_UINT16, // Data Argument参数: DAT_xxxx
MSG:TW_UINT16, //Message ID参数: MSG_xxxx
pData :TW_MEMREF) // 指向返回数据块的指针
其中DG、DAT、MSG参数表示一个Triplets操作。pOrigin、pDest参数会根据不同的Triplets操作而使用不同的值。如果函数成功执行返回值为TWRC_SUCCESS,失败返回TWRC_FAILURE。当然根据Triplets操作的类型不同,还会有其他的返回值,比如TWRC_XFERDONE、TWRC_CANCEL等。
上面的函数中使用了TW_UINT16、TW_UINT32等一些数据类型,在使用TWAIN过程中的所有数据结构可以访问http://www.twain.org/devfiles/twain.h获取。twain.h是C语言的头文件,在Delphi中要将其转换成Pascal语言格式。
三、TWAIN的开发规范
要通过TWAIN实现应用程序和Source的通讯,必须遵循一定的操作规范,它是有逻辑顺序的。比如在Source Manager还没有加载之前,就不可以和Source进行通讯。而这些步骤基本都是利用不同的Triplets操作来实现。下面就结合相应的Triplets操作,给大家介绍一下应用程序和Source之间传输数据的几个基本步骤。
1.加载并获取接口函数
(1)在这个步骤中,没有使用Triplets操作。首先使用LoadLibrary()函数,加载TWAIN_32.DLL文件,并使用GetProcAddress()函数,获得DSM_Entry函数指针。
Type //定义接口函数
DSMENTRYPROC = function(pOrigin: pTW_IDENTITY; pDest: pTW_IDENTITY;
DG: TW_UINT32; DAT: TW_UINT16; MSG: TW_UINT16;
pData: TW_MEMREF): TW_UINT16; stdcall;
var //定义应用程序中用到的全局变量
dsmentry: DSMENTRYPROC;
isdsmopen,isdsopen:Boolean;
hk:hbitmap;
dll:integer;
app,newds,k:ptw_identity;
(2)下面是加载Source Manager并获取DSM_Entry()入口的函数:
Function loaddsm:Boolean;//加载成功返回TRUE
Var
Path:array[0..254] of char;
twRC: TW_UINT16;
Begin
Result:= false;
GetWindowsDirectory(Path,255);
Strcat(Path,'\TWAIN_32.DLL');
if not fileexists(Path) then exit;//系统目录下没有TWAIN_32.DLL则退出
dll:=LoadLibrary(Path);
if dll<>0 then
begin
dsmentry:= GetProcAddress(dll, makeintresource(1));
if @dsmentry = nil then
begin
freelibrary(dll);
exit;
end;
result:=true;
end;
end;
2.打开Source Manager
(1)从这一步开始,所有的操作都要使用Triplets 操作来进行。这里使用的是DG_CONTROL / DAT_PARENT / MSG_OPENDSM组合,在这个操作中,应用程序要指定一个窗体作为Source的父窗体。Source Manager 将通过该窗体,实现Source和应用程序之间的消息传递。
function opendsm:boolean ;
var
twrc:tw_uint16;
begin
//初始化app指针
new(app);
app.Id :=0;
app.Version.MajorNum :=3;
app.Version.MinorNum :=5;
app.Version.Language :=13;
app.Version.Country :=1;
strcopy(app.Version.Info, '1.0');
app.SupportedGroups :=DG_IMAGE or DG_CONTROL;
//指定消息传递的父窗体
winhandle:=form1.Handle;
twrc:=dsmentry(app,NiL,DG_CONTROL,DAT_PARENT,MSG_OPENDSM,TW_MEMREF(@winhandle) );
result:=(twrc=TWRC_SUCCESS);
end;
(2)上面 dsmentry函数中的app参数必须先初始化后再调用,而且它将在下面所有的过程中作为源指针使用。
3.选择Source
Source被打开后,用户需要对其进行选择,因为有可能系统中安装有多个TWAIN设备。这时用户可以通过显示选择数据源对话框来选择或者直接选择默认数据源。这两种方式的Triplets 操作分别是:DG_CONTROL / DAT_IDENTITY / MSG_USERSELECT和DG_CONTROL / DAT_IDENTITY / MSG_GETDEFAULT。下面的代码实现的是显示对话框的方式。
function selds:boolean;
var
twrc:tw_uint16;
begin
new(newds);
newds.Id :=0;//必须为0
newds.ProductName [0]:=#0;//必须为空
twrc:=dsmentry(app, nil, DG_CONTROL, DAT_IDENTITY, MSG_USERSELECT,TW_MEMREF(newds));
result:=( twrc=twrc_success);
end;
函数执行后,newds中保存的是被选择的Source,供后续操作使用。
4.打开Source
现在,用户可以将前面选择的Source打开了。使用的Triplets 操作是DG_CONTROL / DAT_IDENTITY / MSG_OPENDS。代码如下:
function opends:boolean;
var
twrc:tw_uint16;
begin
twrc:=dsmentry(app,nil,DG_CONTROL, DAT_IDENTITY, MSG_opends, TW_MEMREF(newds));
result:=(twrc=TWRC_SUCCESS);
end;
在上面的函数中,dsmentry()的最后一个参数pData必须用前一步骤中产生的newds或者它的副本,本程序中直接使用newds。
5.获取和设置设备性能参数
这一步是可选的。到这一步骤以后,应用程序和Source之间就可以进行消息传递了。获取和设置设备性能参数的操作是:DG_CONTROL / DAT_CAPABILITY / MSG_GET和DG_CONTROL / DAT_CAPABILITY / MSG_SET。使用这两个操作可以查询当前设备是否支持某种功能,如图像获取方式、图像数量等信息。有些参数还必须设备驱动支持才有效,如果支持,还可以按用户的要求设置这些参数。下面的函数用于显示当前设备可以获取的图像数量:
function getinfo:tw_uint16;
var
tc:tw_capability;
p:ptw_onevalue;
begin
result:=0;
tc.Cap := cap_xfercount;
tc.ConType :=twon_dontcare16;
tc.hContainer :=0;
twrc:=dsmentry(app,newds,DG_CONTROL ,DAT_capability,MSG_get,@tc);
if twrc<>TWRC_SUCCESS then exit;
p:=ptw_onevalue(globallock(tc.hContainer ));
result:=p.Item ;
globalfree(tc.hContainer);
end;
6.请求从Source获取数据
Source打开后,应用程序可以允许Source显示它提供的用户界面,准备请求数据传输。当然用户也可以选择不显示它的用户界面。这一步骤使用的Triplets 操作是:DG_CONTROL / DAT_USERINTERFACE / MSG_ENABLEDS。
function enableds(isshow:boolean=true):boolean;
var
twrc:tw_uint16;
twshowui:TW_USERINTERFACE;
begin
twshowui.hParent :=winhandle; //指定消息传递的父窗体
twshowui.ShowUI :=isshow; //指定是否显示Source提供的用户界面
twrc:=dsmentry(app,newds,DG_CONTROL ,DAT_USERINTERFACE,MSG_ENABLEDS,@twshowui);
result:=(twrc=TWRC_SUCCESS);
end;
不同的TWAIN设备提供的用户界面是不同的。因此,通常情况下,都使用Source自己提供的用户界面。
7.确认为数据传输做准备
要注意的是,这一步骤不是由应用程序发出一个Triplets 操作来触发的,而是由Source发起的。Source准备好了要传输数据时,它会发出一个事件信号给它的父窗体,应用程序需在这个窗体的消息循环中使用 DG_CONTROL /DAT_EVENT / MSG_PROCESSEVENT操作,检测Source是否有事件发生。如果检测到包含MSG_XFERREADY的消息,则表示Source已准备好正等待应用程序请求实际的数据传输。消息处理的代码如下。
procedure tform1.wmset(var msg:tmsg;var b:boolean);
var
twevent:tw_event;
begin
if isdsopen then
begin
twevent.pEvent := TW_MEMREF(@Msg);
if(((Msg.wParam <> 0) and (Msg.lParam = 0)) or((Msg.wParam =0) and (Msg.lParam <> 0))) then
begin
dsmentry(app,newds,DG_CONTROL,DAT_EVENT,MSG_PROCESSEVENT,(@twevent));
if twevent.TWMessage =MSG_XFERREADY then
begin
trans;//开始传输数据
end
else
if twevent.TWMessage = MSG_CLOSEDSREQ then // 关闭 Source 用户界面的申请
begin
dsmentry(app,newds,DG_CONTROL ,DAT_USERINTERFACE,MSG_disableDS,@k);
dsmentry(app,nil,DG_CONTROL,DAT_IDENTITY,MSG_closeds, TW_MEMREF(newds));
dsmentry(app,NiL,DG_CONTROL,DAT_PARENT,MSG_closeDSM,TW_MEMREF(newds) );
isdsmopen:=false;
isdsopen:=false;
dispose(app);
dispose(k);
dispose(newds);
freelibrary(dll);
dsmentry:=nil;
end
else
if twevent.TWMessage =MSG_CLOSEDSOK then
showmessage('界面已经成功关闭。')
else
if twevent.TWMessage =MSG_DEVICEEVENT then
showmessage('驱动消息。');
end;
end;
end;
然后在form1的oncreate()事件中加入语句:
application.OnMessage :=wmset;
8.进行数据传输
在这个步骤中有两个Triplets 操作可以使用:DG_IMAGE / DAT_IMAGEINFO / MSG_GET和DG_IMAGE / DAT_IMAGENATIVEXFER / MSG_GET。
传输前,应用程序可以通过 DG_IMAGE / DAT_IMAGEINFO / MSG_GET 操作,获得将要传输的图像的相关信息,比如位图大小、宽度、长度、传输模式等。再通过 DG_IMAGE / DAT_IMAGENATIVEXFER / MSG_GET 操作,实现使用本地传输模式去传输数据。当所有数据传输完成后,Source 将给它的父窗口发送一个包含PM_XFERDONE 的消息, dsmentry() 函数返回后的pData参数将指向一个包含 DIB 位图格式数据的地址。
9.结束传输
在上一步骤中,当应用程序接收到PM_XFERDONE时,将使用DG_CONTROL / DAT_PENDINGXFERS / MSG_ENDXFER操作通知Source已经接收完所有的数据,到此一次数据传输过程全部完成。同时还可以根据dsmentry()的返回值,去检查是否有其他图像等待传送。
下面的过程trans将实现以本地模式进行数据传输和传输完成后中止传输的操作。
procedure trans;
var
twPx: TW_PENDINGXFERS;
pDib, pbmp :PBITMAPINFOHEADER;
pBits :Pointer;
dwColorTableSize:TW_UINT32;
LogPal :TMaxLogPalette;
twRC, twRC2 :TW_UINT16;
hBitMap :TW_UINT32;
hbmp, hDibPal :THandle;
mDC :HDC;
begin
dwColorTableSize := 0;
twPx.count:= 0;
repeat
twRC:=dsmentry(app,newds,DG_IMAGE,DAT_IMAGENATIVEXFER,MSG_GET,@hBitMap);//发送本地模式传输请求
case twRC of
TWRC_XFERDONE:
begin
hbmp := hBitMap;
twRC2:=dsmentry(app,newds,DG_CONTROL,DAT_PENDINGXFERS,MSG_ENDXFER,@twPx);
if twRC2<>TWRC_SUCCESS then
showMessage('DG_CONTROL/DAT_PENDINGXFERS/MSG_ENDXFER操作失败');
if twPx.Count =0 then
begin
pdib := GlobalLock(hbmp);
if pdib<>NIL then
begin
pBmp := pDib;
case pBmp^.biBitCount of
1 : dwColorTableSize := 8;
4 : dwColorTableSize := 64;
8 : dwColorTableSize := 1024;
24 : dwColorTableSize := 0;
end;
pBits:=Pointer(Longint(pDib)+Longint(pBmp^.biSize)+Longint(dwColorTableSize));
mDC :=GetDC(application.Handle );
LogPal.palVersion :=$0300;
LogPal.palNumEntries :=256;
hDibPal:=CreatePalette(PLogPalette(@LogPal)^);
if hDibPal<>0 then
begin
SelectPalette (mDC, hDibPal, FALSE);
RealizePalette (mDC);
end;
hk:= CreateDIBitmap(mDC, (pDib)^, CBM_INIT, pBits,PBitMapInfo(pDib)^ , DIB_RGB_COLORS);
ReleaseDC(application.Handle , mDC);
form1.image1.Picture.Bitmap.Handle :=hk;
GlobalUnlock(hbmp);
end
else showMessage('锁定内存失败。');
end;
end;
TWRC_CANCEL :showMessage('传输被中止。');
TWRC_FAILURE :showMessage('图像传输失败');
else showMessage('其他未知错误。');
end;
until twPx.count=0;
end;
10.断开应用程序和TWAIN的会话
在图像传输完成后,要断开应用程序和TWAIN的这个会话,使打开的Source失效。如果要进行下一次的图像传输,则要回到第6中重新开始请示数据传输或者回到5中重新设置性能参数。断开的Triplets 操作是:DG_CONTROL / DAT_USERINTERFACE / MSG_DISABLEDS,代码如下:
dsmentry(app,newds,DG_CONTROL,DAT_USERINTERFACE,MSG_disableDS,@k);
11.关闭Source 和Source Manager
当所有的操作完成之后,应用程序需要关闭已经打开的Source,然后再关闭Source Manager,这里要注意的是,如果你打开了多个Source的话,必须将它们全部关闭后,才可以关闭Source Manager。有关的Triplets 操作是:DG_CONTROL / DAT_IDENTITY / MSG_CLOSEDS(关闭指定的Source)和DG_CONTROL / DAT_PARENT/MSG_CLOSEDSM(关闭打开的Source Manager)。实现代码如下:
dsmentry(app,nil,DG_CONTROL, DAT_IDENTITY,MSG_closeds, TW_MEMREF(newds));
dsmentry(app,NiL,DG_CONTROL,DAT_PARENT,MSG_closeDSM,TW_MEMREF(newds) );
四、TWAIN的数据传输模式
TWAIN定义了三种数据传输模式:本地模式、文件模式和缓存模式。
1.本地模式
这是所有的输入设备都支持的数据传输模式,也是TWAIN默认的数据传输模式。在这种模式下传输的数据必须是DIB 图像数据。
使用该模式,在数据传输时Source分配一块单独的内存区域,并把图像数据写入这个内存区域内。然后它通过一个指向该内存地址的指针返回给应用程序。应用程序通过访问该内存区域去获得具体的图像数据。注意,应用程序在获得数据后要负责释放分配的内存,而且如果图像数据大于系统当前可用内存,会导致传输失败。
2.文件模式
该模式是让应用程序创建一个文件, Source将对该文件进行读写操作。Source把要传输的数据写到该文件中,应用程序通过访问该文件,就可以获得传输的数据。 在使用本地模式传输一个大的图像文件时,如果内存不够大,可以考虑使用文件传输模式来传输。
3.缓存模式
缓存模式在整个传输过程中,将使用一个或多个内存缓存区,内存缓存区的分配和释放工作由应用程序来控制。在传输过程中,传输数据被当作一个未知格式的位图。应用程序必须管理各个缓存区的信息并把它们正确组织为一个完整的位图。
五、应用实例
下面的实例只是一个基本的应用实现。本程序中选择默认的本地传输模式获取图像。在程序运行之前,如果当前系统中没有安装扫描仪,可以先安装TWAIN提供的Twainkit.exe工具,它可以在系统中安装一个虚拟的扫描仪设备,便于程序调试。
在Delphi6中新建一个工程,在Form1中添加一个按钮控件Button1、一个图像控件Image1和一个标签控件Label1。Button1的Caption设为“获取图像”,设计界面如图1所示。
在Button1的OnClick()事件中添加如下代码:
if loaddsm then
begin
isdsmopen:=opendsm;
if isdsmopen then
if selds then
begin
isdsopen:=opends;
if isdsopen then
begin
label1.Caption :='应用程序可接收的图片数量为:'+inttostr(getinfo);
enableds;
end;
end;
end;
六、结语
从上面的介绍可以看到,要在自己的应用程序中控制扫描仪并不难,只要按照TWAIN的规范和逻辑进行设计就可以轻松实现从扫描仪中获取图像的功能。当然本文所介绍的功能只是其中的一小部分,如果要了解更多的信息,可以访问其官方网站http://www.twain.org。本程序在WindowsXP/Delphi6环境下运行通过。
参考文献
[1] TWAIN Working Group. http://www.twain.org/
|