TWAINComm - 一个 C# TWAIN 通信库
TWAIN 基础知识解释
引言
我通过阅读 NETMaster 的文章并查看他附带的项目,开始用 C# 编程 TWAIN。我通过查看代码学习效果最好,他的文章是一个很好的起点。我希望我的文章和代码能够为本网站关于 C# 中 TWAIN 的知识库添砖加瓦。
然而,本文并非旨在取代 TWAIN 规范。它只是为了帮助初学者更好地理解如何在 C# 和 WPF 中使用 TWAIN。如果您还没有阅读该规范,我强烈建议您在阅读本文后阅读它。虽然本文和随附的项目可以帮助您入门,但您应该期望阅读该规范才能充分利用 TWAIN。为了避免本文篇幅过长,我没有涵盖许多细节。
请注意,TWAINComm 实现的是 TWAIN 规范的 1.9 版本,而截至本文发布时,2.3 版本是最新版本。我本想在转向最新版本之前完全实现 v1.9,但这比我预期的要长,而且仍然没有完成。因此,尽管附带的代码不符合最新版本,但它确实实现了最重要的部分,这将使读者对基础知识有所了解。
参考文献
范围
本文将涵盖
- TWAIN 基础知识
- TWAIN 的 7 个阶段
- 处理 TWAIN 错误
- 使用 TWAINComm
本文将不涵盖
- 除本机方式(如缓冲和文件)以外的图像传输方法。
- 在 Forms 应用程序中使用代码。
但是,只需进行一些小的修改,这应该很容易实现。将其调整为 WinForms 应用程序将需要进行多项更改。 - 处理消息循环的细节,除了您可以从数据源接收到的消息。具体内容请参考规范、随附代码以及 Baruch23 的文章(参考文献中有链接)。
- 能力协商。这是一个我自己还不熟悉的复杂主题,而且对于大多数项目来说也不是必需的。在大多数情况下,最好启用扫描仪附带的 UI。但是,对于勇敢的人来说,附带的代码确实对此有一些初步的支持。您需要进行构建,但基本的构建块已经存在。
- 将本机传输方法返回的内存中设备无关位图 (DIB) 转换为 PNG 文件。但是,执行此操作的类有详细文档,因此通过查看代码应该不难弄清楚。
TWAIN 基础知识
数据源管理器 (DSM)
DSM(有时称为源管理器)管理数据源。本质上,您的应用程序可以与 DSM 建立连接,并通过 DSM 访问该计算机上所有已安装的设备。
数据源 (DS)
DS(有时称为源)通常是设备制造商提供的用于与其设备通信的软件。扫描前弹出的扫描仪 UI 是 DS 的一部分。
您的应用程序通过数据源管理器 (DSM) 与数据源 (DS) 通信,而 DS 则与设备本身通信。这使得您的应用程序可以与任何连接的 TWAIN 设备通信,而无需了解每个设备的工作细节。因为每个设备都符合 TWAIN 规范,所以您的应用程序可以使用相同的代码访问它们,无论设备和制造商如何。
TW_IDENTITY
public class TW_IDENTITY
{
public TW_UINT32 Id;
public TW_VERSION Version;
public TW_UINT16 ProtocolMajor;
public TW_UINT16 ProtocolMinor;
public TW_UINT32 SupportedGroups;
[MarshalAs( UnmanagedType.ByValTStr, SizeConst = (int)TWSTR.STR32 )]
public string Manufacturer;
[MarshalAs( UnmanagedType.ByValTStr, SizeConst = (int)TWSTR.STR32 )]
public string ProductFamily;
[MarshalAs( UnmanagedType.ByValTStr, SizeConst = (int)TWSTR.STR32 )]
public string ProductName;
}
这是一个特别重要的数据结构,用于提供身份识别。它将您的应用程序识别给 DSM 和 DS,并将 DS 识别给您的应用程序(单独)。换句话说,您将填充此结构的一个实例并将其提供给 DSM 和 DS,以便它们知道哪个应用程序正在与它们通信。DSM 将为您提供此结构的一个实例,用于识别所选的 DS,从那时起,您可以使用它来指代该 DS,以便 DSM 知道将您的消息路由到哪个设备。
基本的 TWAIN 数据结构将在下面介绍,但我想单独解释这个,因为您会经常使用它。
TWAIN 三元组
TWAIN 使用 3 部分信息与称为三元组的设备进行通信。三元组由数据组、数据参数类型和消息组成。
数据组 (DG)
目前,TWAIN 规范支持 3 个数据组:控制、图像和音频。对于扫描,我们关心的是控制和图像组。
数据参数类型 (DAT)
DAT 指示正在传递给设备或正在操作的数据类型。设备将使用三元组的这一部分来确定它将使用哪个数据结构来存储您传递进出设备的数据。
消息 (MSG)
MSG 指示您希望采取的行动。
您将三元组的所有三个部分组合在一起以向设备发送消息。例如,为了打开所需的 DS,您传递 DG_CONTROL/DAT_IDENTITY/MSG_OPENDS 三元组。三元组的 Control 部分告诉设备您发送给它的信息将用于控制。三元组的 Identity 部分告诉设备您发送给它的信息正在使用身份数据结构(TW_IDENTITY
)。三元组的 open DS 部分告诉设备您希望它打开所提供的 TW_IDENTITY
实例中引用的 DS。
基本数据结构
TWAIN 使用除了 TW_IDENTITY
之外的许多数据结构,但我只介绍执行基本扫描操作所需的结构。
public class TW_USERINTERFACE
{
public short ShowUI;
public short ModalUI;
public IntPtr hParent;
}
此数据结构与 DAT_USERINTERFACE DAT 三元组一起使用。当您启用或禁用 DS 的用户界面时,您会传递此结构的一个实例。它只有两个部分适用于我们。如果您希望 DS 使用其 UI,则将 ShowUI 设置为 1(真);如果您不希望它使用(我们将设置为 1),则将其设置为 0(假)。将 hParent 设置为您应用程序窗口的句柄。ModalUI 仅在 Mac 上使用。
public struct TW_EVENT
{
public TW_MEMREF EventPtr;
public TW_UINT16 Message;
}
此数据结构与 DAT_EVENT DAT 三元组一起使用。在某些阶段,TWAINComm 将挂接到窗口事件/消息循环中,并监听操作系统事件/消息(TWAIN 规范将窗口消息称为事件,以免与构成三元组第三部分的消息混淆)。然后,TWAINComm 会将这些事件发送到 DS,并从设备接收回一条消息。EventPtr 是指向窗口事件数据结构的指针(未显示),而 Message 是发送回 TWAINComm 的消息,并对应于某个 MSG 常量(下面将更详细地介绍)。
public class TW_IMAGEINFO
{
public TW_FIX32 XResolution;
public TW_FIX32 YResolution;
public TW_INT32 ImageWidth;
public TW_INT32 ImageLength;
public TW_INT16 SamplesPerPixel;
[MarshalAs( UnmanagedType.ByValArray, SizeConst = 8 )]
public TW_INT16[] BitsPerSample;
public TW_INT16 BitsPerPixel;
public TW_BOOL Planar;
public TW_INT16 PixelType;
public TW_UINT16 Compression;
}
此数据结构与 DAT_IMAGEINFO DAT 三元组一起使用。当设备准备好传输图像时,您可以向 DS 发送一个未填充的此结构实例,它将用描述即将传输的图像的详细信息填充它。由于我们将使用本机传输模式,此信息包含在设备提供给我们的内存中图像的位图头中。因此,我们将不使用此数据结构,但您应该了解它,以防您实现其他传输方法之一。
public struct TW_PENDINGXFERS
{
public TW_UINT16 Count;
public TW_UINT32 EOJ;
}
此数据结构与 DAT_PENDINGXFERS DAT 三元组一起使用。当您传输图像时,您向 DS 提供此结构的一个未填充实例,它将用剩余待传输的页数填充它。如果设备配备了多文档进纸器,则 count 的值可以为 -1。如果没有更多页面要传输,则 count 的值将为 0。
为了我们的目的,我们测试计数是否为 0。如果不是,我们继续传输图像。如果是 0,则停止传输。
EOJ 仅与 CAP_JOBCONTROL 能力结合使用,其用途超出本文范围。
public struct TW_STATUS
{
public TW_UINT16 ConditionCode;
public TW_UINT16 Reserved;
}
此数据结构与 DAT_STATUS DAT 三元组一起使用。每次调用 TWAIN 都会返回一个代码 (TWRC),指示它是否成功。如果您的调用返回失败代码 (TWRC_FAILURE),则您必须使用此数据结构向 DS 发送 DG_CONTROL/DAT_STATUS/MSG_GET 三元组,以查明失败原因。条件代码 (TWCC) 将为您提供所需信息,以确定出了什么问题。
TWAIN DLL 入口点
在 C# 中,我们对 TWAIN DLL 使用多个外部引用;我们需要的每个调用变体都有一个。但是 TWAINComm 只使用一个 DLL 入口点。
DSM_Entry( OriginIdentity, DestinationIdentity, DataGroup, DataArgumentType, Message, DataArgument );
源身份
您应用程序的 TW_IDENTITY。
目标身份
目标是 null 或 TW_IDENTITY。如果您想与 DSM 通信,则使用 null (IntPtr.Zero) 或 DS 的身份。
数据组
DG 常量之一,表示三元组的第一个组成部分。在 TWAINComm 中,它被实现为枚举类型。例如:DG_CONTROL 将是 DG.CONTROL。
数据参数类型
DAT 常量之一,表示三元组的第二个组成部分。在 TWAINComm 中,它被实现为枚举类型。例如:DAT_IDENTITY 将是 DAT.IDENTITY。
Message
MSG 常量之一,表示三元组的第三个组成部分。在 TWAINComm 中,它被实现为枚举类型。例如:MSG_OPENDS 将是 MSG.OPENDS。
数据参数
由数据参数类型三元组组件指示的数据参数。例如:由于我们上面使用了 DAT_IDENTITY,我们将必须提供相应的 TW_IDENTITY 数据结构实例,其中包含您要打开的 DS 的身份。
(为了获取所选 DS 的 TW_IDENTITY 类实例,您需要调用 DSM_Entry(appIdentity, NULL, DG_CONTROL, DAT_IDENTITY, MSG_GETDEFAULT, dsIdentity)。这会将 DG_CONTROL/DAT_IDENTITY/MSG_GETDEFAULT 三元组发送到 DSM,同时将 TW_IDENTITY 的空实例作为数据参数。DSM 会填充空数据实例并将其返回给应用程序。从那时起,应用程序可以使用返回的 DS 身份来引用所选的 DS。下面将更详细地介绍这一点。)
TWAIN 的 7 个状态
TWAIN 有 7 种状态。TWAINComm 必须跟踪它所处的状态,因为 TWAIN 操作只能在特定状态下执行。如果您的应用程序与您尝试通信的 DS 不同步,那么您的应用程序将无法与其通信。如果发生这种情况,TWAINComm 将尝试重置,但用户可能需要对设备进行电源循环以将其重置为已知状态。
描述
以下描述了导致从状态 1 到状态 7 再回到状态 1 的各种状态转换的动作
- 初始状态
- DSM 已加载
- DSM 已打开
- DS 已打开
- DS 已启用
- DS 已准备好传输图像
- 图像正在传输
- 图像已传输
- DS 完成图像传输
- DS 已禁用
- DS 已关闭
- DSM 已关闭
- DSM 已卸载
如果有多于一个图像/页面要传输/扫描,则状态将从 6 转换到 7,然后对于每个单独的图像,再转换回 6。此外,当 TWAINComm 向 DS 发送消息以确认图像传输结束时,它将收到一个填充的 TW_PENDINGXFERS 实例。如果 Count=0,则 DS 会自动自行转换到状态 5。但是,如果出现错误,或者用户取消扫描,则 TWAINComm 将通过重置传输过程(详见下文)手动将 DS 转换到状态 5。
Actions
虽然在各种状态下可以执行许多操作,但我只介绍选择 DS 和执行基本扫描所需的操作。其中一些操作将使状态向上或向下转换,而另一些则不会触发任何状态转换。
状态 1:加载 DSM - 转换为状态 2
根据规范,您通过将 DSM 加载到内存中,从状态 1 转换到状态 2。然而,使用 C#,您无法明确加载 DSM,但我们可以使用以下代码来确保必要的外部 DLL 存在
Marshal.PrelinkAll( typeof( Extern ) );
如果缺少其中一个 DLL,最可能的解释是缺少 DSM,这意味着该计算机上从未安装过 TWAIN 设备。
状态 2:打开 DSM - 转换为状态 3
DG_CONTROL/DAT_PARENT/MSG_OPENDSM
您向 DSM 发送上述三元组以加载它。DAT_PARENT 指示的数据参数是您应用程序窗口的句柄。
返回码
-
TWRC_SUCCESS
如果调用返回此代码,则 DSM 已成功打开,应用程序现在处于状态 3。
-
TWRC_FAILURE
如果调用返回此代码,则 DSM 打开失败,应用程序仍处于状态 2。您必须向 DSM 发送 DG_CONTROL/DAT_STATUS/MSG_GET 三元组,以获取指示失败原因的条件代码。
状态 3:选择 DS
DG_CONTROL/DAT_IDENTITY/MSG_USERSELECT
一旦我们处于状态 3,我们就可以打开数据源选择实用程序。虽然可以编写自己的实用程序,但我们只打开 DSM 附带的实用程序。如果调用成功,我们提供给 DSM 的 TW_IDENTITY 实例将填充所选 DS。
用户将点击“取消”或选择数据源并点击“选择”。如果他们点击“选择”,则所选的数据源将成为默认数据源,但状态不会改变。
返回码
-
TWRC_SUCCESS
如果调用返回此代码,则 DSM 已成功将默认值设置为用户选择的数据源。
-
TWRC_CANCEL
如果调用返回此代码,则用户点击了“取消”按钮。默认 DS 未更改。
-
TWRC_FAILURE
如果调用返回此代码,则用户点击了“选择”按钮,但 DSM 未能更改默认 DS。您必须向 DSM 请求条件代码以查明失败原因。
状态 3:获取默认 DS 身份
DG_CONTROL/DAT_IDENTITY/MSG_GETDEFAULT
一旦我们处于状态 3,我们就可以使用上述三元组获取默认 DS 的 TW_IDENTITY。如果调用成功,我们提供给 DSM 的 TW_IDENTITY 实例将填充默认 DS。保存返回的 TW_IDENTITY 实例。您将需要它来打开默认 DS 并在打开后与其通信。无论调用是否成功,状态都不会改变。
返回码
-
TWRC_SUCCESS
如果调用返回此代码,则 DSM 已成功用默认 DS 的身份填充所提供的 TW_IDENTITY 实例。
-
TWRC_FAILURE
如果调用返回此代码,则 DSM 未能更改默认 DS。您必须向 DSM 请求条件代码以查明失败原因。
状态 3:打开 DS - 转换为状态 4
DG_CONTROL/DAT_IDENTITY/MSG_OPENDS
您向 DSM 发送上述三元组以打开请求的 DS。DAT_IDENTITY 指示的数据参数是 TW_IDENTITY 的一个实例,其中包含 DSM 为您要打开的 DS 提供的详细信息。
返回码
-
TWRC_SUCCESS
如果调用返回此代码,则 DSM 已成功打开指定的 DS,并且现在处于状态 4。
-
TWRC_FAILURE
如果调用返回此代码,则 DSM 未能打开指定的 DS,并且状态保持不变。您必须向 DSM 请求条件代码以查明失败原因。
状态 4:启用 DS - 转换为状态 5
DG_CONTROL/DAT_USERINTERFACE/MSG_ENABLEDS
您向先前打开的 DS 发送上述三元组以启用它。DAT_USERINTERFACE 指示的数据参数是填充了您所需设置的 TW_USERINTERFACE 实例。我们将打开 DS 的 UI,因此我们将 ShowUI 设置为 1。启用 ShowUI 后,DS 将显示扫描仪的用户界面,并允许用户选择特定于该设备的设置并启动扫描操作。
返回码
-
TWRC_SUCCESS
如果调用返回此代码,则 DS 已成功启用并现在处于状态 5。
-
TWRC_CHECKSTATUS
如果应用程序在 DS 不支持 UI 时禁用 UI (ShowUI=0),则 DS 返回此代码。如果您想禁用 UI,您必须首先检查 DS 是否支持它,但这样做超出了本文的范围。DS 将忽略不受支持的设置,显示 UI,并转换到状态 5。
-
TWRC_FAILURE
如果调用返回此代码,则 DS 启用失败,状态保持不变。您必须向 DS 请求条件代码以查明失败原因。
状态 5-7:处理事件
DG_CONTROL/DAT_EVENT/MSG_PROCESSEVENT
一旦您成功启用 DS 并转换到状态 5,您必须挂接到事件/消息循环中并将事件发送到 DS。DAT_EVENT 指示的数据参数是填充了事件详细信息的 TW_EVENT 实例。将事件发送到 DS 允许它通过监听事件来跟踪发生的事情,也允许它将自己的消息传回您的应用程序。只要您处于状态 5 到 7,您就必须继续将事件传递给 DS。在您转换回状态 4 后,您可以从事件/消息循环中取消挂接。
返回码
-
TWRC_DSEVENT
如果调用返回此代码,则传递给 DS 的事件属于它,并且 DS 处理了该事件。
-
TWRC_NOTDSEVENT
如果调用返回此代码,则传递给 DS 的事件不属于它,并且 DS 未处理该事件。
-
TWRC_FAILURE
如果调用返回此代码,则 DS 未能处理提供的事件。您必须向 DS 请求条件代码以查明失败原因。
状态 5-7:DS 发送关闭请求
MSG_CLOSEDSREQ
在将事件传递给 DS 进行处理时,您应该注意返回的消息。如果您的应用程序收到上述消息,则表示用户关闭了 UI。应用程序必须从事件循环中脱钩,并从当前所处的状态回退到状态 2。
状态 5:DS 发送传输就绪 - 转换为状态 6
MSG_XFERREADY
在将事件传递给 DS 进行处理时,您应该注意返回的消息。如果您的应用程序收到上述消息,则表示用户已启动扫描并且 DS 已准备好进行传输。DS 已自行转换到状态 6,因此请确保您的应用程序也进行转换以保持同步。
状态 6:获取图像信息
DG_IMAGE/DAT_IMAGEINFO/MSG_GET
一旦您转换到状态 6,您可以向 DS 发送上述三元组以检索有关正在传输的图像的详细信息。DAT_IMAGEINFO 指示的数据参数是 TW_IMAGEINFO 的空实例,由 DS 填充。使用本机传输方法时,这样做是不必要的,因为相同的信息在位图头中提供。
返回码
-
TWRC_SUCCESS
如果调用返回此代码,则表示 DS 已成功填充所提供的 TW_IMAGEINFO 实例。
-
TWRC_FAILURE
如果调用返回此代码,则 DS 未能填充提供的 TW_IMAGEINFO 实例。您必须向 DS 请求条件代码以查明失败原因。
状态 6:通过本机传输获取图像 - 转换为状态 7
DG_IMAGE/DAT_IMAGENATIVEXFER/MSG_GET
您向 DS 发送上述三元组,以使用本机方法启动图像传输。本机方法将把完整扫描图像传输到内存中。DAT_IMAGENATIVEXFER 指示的数据参数是一个指针,由 DS 填充并指向内存中的设备无关位图 (DIB)。
返回码
-
TWRC_XFERDONE
如果调用返回此代码,则 DS 已成功扫描当前图像/页面并填充了指向 DIB 的指针。
-
TWRC_CANCEL
如果调用返回此代码,则用户取消了传输。如果返回的指针不为空 (IntPtr.Zero),则由程序负责释放为该 DIB 保留的内存。不要尝试使用它,因为它不是有效的图像。此外,应用程序需要从事件循环中脱钩并回退到状态 2。
-
TWRC_FAILURE
如果调用返回此代码,则 DS 未能传输图像并设置指针。无需检查指针以释放内存。您必须向 DS 请求条件代码以查明失败原因。
状态 7:结束传输确认 - 转换为状态 6
DG_CONTROL/DAT_PENDINGXFERS/MSG_ENDXFER
您向 DS 发送上述三元组以告知它传输已完成。DAT_PENDINGXFERS 指示的数据参数是 TW_PENDINGXFERS 的空实例,由 DS 填充。此数据结构包含 Count,它指示是否还有额外的图像/页面要传输。它可以是 >0,表示剩余要传输的图像数量。它可以是 -1,表示扫描仪配备了自动文档进纸器,并且剩余要传输的页面数量未知。或者,它可以是 0,表示没有更多页面要传输。当 Count=0 时,DS 将自动自行转换到状态 5。
您还可以使用上述三元组取消当前图像的传输。如果您随后希望取消其余传输,请先检查 Count 以确保 DS 尚未转换到状态 5。
返回码
-
TWRC_SUCCESS
如果调用返回此代码,则 DS 已成功填充所提供的 TW_PENDINGXFERS 实例,并根据是否有额外的图像要传输(由 Count 的值指示)转换为状态 6 或状态 5。
-
TWRC_FAILURE
如果调用返回此代码,则 DS 未能填充提供的 TW_IMAGEINFO 实例并保持在状态 7。您必须向 DS 请求条件代码以查明失败原因。
状态 6:重置传输 - 转换为状态 5
DG_CONTROL/DAT_PENDINGXFERS/MSG_RESET
当您要取消传输过程时,您向 DS 发送上述三元组。DAT_PENDINGXFERS 指示的数据参数是 TW_PENDINGXFERS 的空实例,由 DS 填充。您可以忽略 TW_PENDINGXFERS 的返回值。
返回码
-
TWRC_SUCCESS
如果调用返回此代码,则 DS 已成功转换为状态 5。
-
TWRC_FAILURE
如果调用返回此代码,则 DS 保持在状态 6。您必须向 DS 请求条件代码以查明失败原因。
状态 5:禁用 DS - 转换为状态 4
DG_CONTROL/DAT_USERINTERFACE/MSG_DISABLEDS
当您想禁用 DS 时,您向 DS 发送上述三元组。禁用 DS 将关闭 UI(如果它已打开)。DAT_USERINTERFACE 指示的数据参数是 TW_USERINTERFACE 的空实例。您可以忽略 TW_USERINTERFACE 的返回值。
返回码
-
TWRC_SUCCESS
如果调用返回此代码,则 DS 已成功转换为状态 4。
-
TWRC_FAILURE
如果调用返回此代码,则 DS 保持在状态 5。您必须向 DS 请求条件代码以查明失败原因。
状态 4:关闭 DS - 转换为状态 3
DG_CONTROL/DAT_IDENTITY/MSG_CLOSEDS
当您要关闭 DS 时,您向 DSM 发送上述三元组。DAT_IDENTITY 指示的数据参数是您一直用于与 DS 通信的 TW_IDENTITY 的填充实例。DS 关闭后,您应该将 DS 的 TW_IDENTITY 实例中的 ID 设置为 0。ID 仅在 DS 打开时有效。下次您打开 DS 时将生成新的 ID。
返回码
-
TWRC_SUCCESS
如果调用返回此代码,则表示 DS 已成功关闭并转换为状态 3。
-
TWRC_FAILURE
如果调用返回此代码,则 DSM 未能关闭 DS 并保持在状态 4。您必须向 DSM 请求条件代码以查明失败原因。
状态 3:关闭 DSM - 转换为状态 2
DG_CONTROL/DAT_PARENT/MSG_CLOSEDSM
当您要关闭 DSM 时,您向 DSM 发送上述三元组。DAT_PARENT 指示的数据参数是应用程序窗口的句柄。
返回码
-
TWRC_SUCCESS
如果调用返回此代码,则表示 DSM 已成功关闭并转换为状态 2。
-
TWRC_FAILURE
如果调用返回此代码,则 DSM 关闭失败并保持在状态 3。您必须向 DSM 请求条件代码以查明失败原因。
状态 2:卸载 DSM - 转换为状态 1
C# 不能像 C++ 那样显式卸载 DSM。然而,我们实际上没有必要这样做。从操作中退出时,TWAINComm 将转换到状态 2 并停在那里。DSM 将在用户关闭应用程序时卸载。
处理 TWAIN 错误
TWAIN 错误是指对 TWAIN 的调用返回 TWRC_FAILURE 代码。当您收到此返回代码时,您必须通过向 DSM/DS 发送 DG_CONTROL/DAT_STATUS/MSG_GET 三元组来请求条件代码。正是这个代码告诉您调用失败的原因。
条件代码 (TWCC)
在 TWAINComm 中,条件代码实现为枚举类型。例如:TWCC_SUCCESS 将是 TWCC.SUCCESS。下面是 TWAIN 规范 v1.9 中每个可能的条件代码的解释。
-
TWCC_SUCCESS
没有失败
-
TWCC_BUMMER
由于未知原因失败
-
TWCC_LOWMEMORY
内存不足,无法执行操作
-
TWCC_NODS
没有可用数据源
-
TWCC_MAXCONNECTIONS
选定的数据源已连接到最大数量的应用程序
-
TWCC_OPERATIONERROR
DS 或 DSM 已报告失败;应用程序不应报告
-
TWCC_BADCAP
应用程序尝试获取或设置未知功能
-
TWCC_BADPROTOCOL
应用程序尝试使用无法识别的 DG/DAT/MSG 组合
-
TWCC_BADVALUE
提供的数据参数超出范围
-
TWCC_SEQERROR
操作尝试时序不当
-
TWCC_BADDEST
提供的目标标识不正确
-
TWCC_CAPUNSUPPORTED
应用程序尝试获取或设置数据源不支持的功能
-
TWCC_CAPBADOPERATION
指定功能不支持尝试的操作
-
TWCC_CAPSEQERROR
尝试的功能依赖于另一个功能
-
TWCC_DENIED
尝试的文件系统操作被拒绝。文件可能受写保护。
-
TWCC_FILEEXISTS
尝试的文件系统操作失败,因为文件已存在
-
TWCC_FILENOTFOUND
未找到文件
-
TWCC_NOTEMPTY
尝试的文件系统操作失败,因为目录不为空
-
TWCC_PAPERJAM
扫描仪检测到卡纸
-
TWCC_PAPERDOUBLEFEED
扫描仪检测到多页进纸错误
-
TWCC_FILEWRITEERROR
写入文件系统时出错。驱动器可能已满。
-
TWCC_CHECKDEVICEONLINE
设备在操作之前或期间脱机
重置 TWAIN 状态
有时,对 TWAIN 故障的最佳响应是向用户报告错误,重置 TWAIN 状态,然后允许用户再次尝试操作。要重置 TWAIN 状态,您需要忽略故障,从状态 7 降级到状态 2。
为了实现此降级,您必须在以下状态下向先前的状态发送触发转换的三元组(有关更多详细信息,请参阅上述状态转换操作)
- DG_CONTROL/DAT_PENDINGXFERS/MSG_ENDXFER
- DG_CONTROL/DAT_PENDINGXFERS/MSG_RESET
- DG_CONTROL/DAT_USERINTERFACE/MSG_DISABLEDS
- DG_CONTROL/DAT_IDENTITY/MSG_CLOSEDS
- DG_CONTROL/DAT_PARENT/MSG_CLOSEDSM
发送这些三元组后,DS 和 DSM 现在应该恢复到状态 2。此时,您应该将 DS 的 TW_IDENTITY 实例的 ID 以及应用程序的 TW_IDENTITY 的 ID 设置为 0。ID 将在下次操作期间重新填充新值。
如果您不想完全重置 TWAIN 状态,可以在降级时观察返回码。当您看到返回 TWRC_SUCCESS 时,您就知道您的应用程序和 DS 已重新同步。我发现完全重置并重新开始更容易,但我想指出这并非总是必需的。
有时 DS 会变得无响应。此时,您唯一能做的就是将应用程序置于状态 2,并将 DS 和应用程序的 TW_IDENTITY 实例的 ID 设置为 0,然后让用户对设备进行电源循环。
使用 TWAINComm
至此,通过阅读上述内容并查看代码,您应该熟悉 TWAIN 的基本工作原理。在本节中,我将介绍如何设置应用程序以使用 TWAINComm 库、如何使用它以及它在幕后所做的工作。
如果您对某些内容不确定,请查看演示应用程序。至少,演示应用程序可以用于调试您可能遇到的扫描仪问题。通过各种反馈元素,您应该能够找出问题发生的位置。
应用程序设置
设置应用程序以使用 TWAINComm 库非常简单。只需遵循以下步骤
-
将库引用添加到您的应用程序
-
确保构建设置中的平台目标设置为 x86
TWAINComm 对外部 32 位 twain_32.dll x86 库执行 DllImport。您的应用程序必须设置为 32 位才能使用它。如果您跳过此步骤,它将给您带来麻烦。
-
在您的应用程序中添加一个实现 TWAINComm.Twain 类的 TWAIN 属性,并在主窗口的 Loaded 事件调用的方法中实例化它,同时附带所需的反馈委托
private void ViewLoaded() { // need to setup the Twain property after the window has been created, so that the handle to the window won't be null TWAINComm.TW_IDENTITY applicationIdentity = new TWAINComm.TW_IDENTITY() { Id = 0, Version = new TWAINComm.TW_VERSION() { MajorNum = 1, MinorNum = 0, Language = (ushort)TWAINComm.TWLG.ENGLISH_USA, Country = (ushort)TWAINComm.TWCY.USA, Info = "v1.0" }, SupportedGroups = (uint)( TWAINComm.DG.CONTROL | TWAINComm.DG.IMAGE ), Manufacturer = "Demo Manufacturer", ProductFamily = "TWAINComm", ProductName = "TWAINComm Demo App" }; TWAINComm.Feedback feedback = new TWAINComm.Feedback(); feedback.ScanEnd += Twain_ScanEnd; feedback.ApplicationIdentityChanged += Twain_ApplicationIdentityChanged; feedback.DataSourceIdentityChanged += Twain_DataSourceIdentityChanged; feedback.TwainStateChanged += Twain_TwainStateChanged; feedback.TwainActionChanged += Twain_TwainActionChanged; feedback.TwainCommException += Twain_TwainCommException; Twain = new TWAINComm.Twain( App.Current.MainWindow, feedback, applicationIdentity ); // force re-evaluation of the TwainInitialized property now that the Twain property is no longer null CommandManager.InvalidateRequerySuggested(); }
您需要更改 Manufacturer、ProductFamily 和 ProductName 属性;可能还有版本信息。不要更改 Id 或 SupportedGroups 属性。
反馈委托的使用是可选的。但是,您至少需要提供一个 ScanEnd 委托的回调。TWAINComm 在扫描完所有图像后调用此委托,并向应用程序提供 PNG 文件列表。这些是临时文件,因此您需要对它们进行更永久的处理(移动、上传等)。其他委托更多用于信息和调试目的。
-
设置一个由主窗口的 Closed 事件调用的方法,该方法将调用 TWAINComm 的 Dispose 方法,以便它可以正确释放非托管内存
private void ViewClosed() { try { if ( Twain != null ) { Twain.Dispose(); Twain = null; } } catch { } }
选择源
一旦您的应用程序设置为使用 TWAINComm,您就可以允许用户选择(更改)源。这很简单,只需调用以下内容
Twain.SelectSource();
此代码假定 Twain 属性是 TWAINComm.Twain 类的一个实例,并且已按照上面步骤 3 中所示正确设置。
TWAINComm 处理向状态 3 的转换,并激活 DSM 提供的内置“选择源”对话框。一旦用户关闭对话框,TWAINComm 将转换回状态 2。
获取(扫描)
一旦您的应用程序设置为使用 TWAINComm,您就可以允许用户通过启动扫描过程来获取新图像。这很简单,只需调用以下内容
Twain.ScanBegin();
此代码假定 Twain 属性是 TWAINComm.Twain 类的一个实例,并且已按照上面步骤 3 中所示正确设置。
TWAINComm 完成扫描后,将调用 ScanEnd 反馈委托,并向应用程序提供图像文件列表。如果列表为空 (Count=0),则扫描已被取消或失败。如果列表已填充,则它将包含临时 PNG 文件的有序文件位置列表。从此时起,由应用程序处理它们。
TWAINComm 处理从状态 2 到状态 5 的转换。然后,它将挂接到消息循环中,等待来自 DS 的 MSG_CLOSEDSREQ 或 MSG_XFERREADY 消息。如果收到 MSG_CLOSEDSREQ,则向应用程序返回空文件列表,TWAINComm 从事件循环中脱钩,并处理从状态 5 回退到状态 2 的转换。如果收到 MSG_XFERREADY,则 TWAINComm 将启动传输,转换并保存图像,检查是否有更多页面并传输它们(如果有),然后它将从消息循环中脱钩,并处理从状态 5 回退到状态 2 的转换。如果在途中遇到任何错误,它将处理它们并根据需要进行响应。
其他项目
TWAINComm 只是在 C# 中实现 TWAIN 的一种可能方式。下面是我所了解的另外两个项目,它们可能也是很好的参考示例。
修订历史
2015年5月11日
- 原文
2015年5月13日
- 小格式修正
2015年5月14日
- 更多格式修正
- 修复了项目代码中可能因关闭 DS 或 DSM 失败而导致问题的情况
2015年5月19日
- 我更改了对在 WinForms 应用程序中改编库的引用。适应它所需的更改将比我最初预期的更复杂。