Introduction
I started programming with TWAIN in C# by reading NETMaster's article, and reviewing his attached project. I learn best by looking at code, and his article was a great place to start. I'm hoping that my article, and code, can add to the knowledge base on this site in regards to TWAIN in C#.
However, this article is not meant to replace the TWAIN specification. Only to aid the beginner in better understand how to make use of TWAIN from C# and WPF. If you haven't yet read the specification, I highly recommend doing so after reading this. While this article, and the attached project, can help get you started, you should expect to have to read the specification in order to be able to take full advantage of TWAIN. In order to keep this article from growing larger than it already is there are many details that I don't cover.
Please note that TWAINComm implements version 1.9 of the TWAIN specification, while version 2.3 is the most recent version as of time of this posting. I wanted to finish completely implementing v1.9 before I moved on to the most recent version, but it's taken longer that I would've liked and still isn't done. So while the attached code doesn't conform to the latest and greatest it does implement the most important pieces that'll allow the reader to gain an understanding of the basics.
References
v1.9 TWAIN Specification
Current TWAIN Specification
NETMaster's .NET TWAIN Image Scanner
Baruch23's TWAIN for WPF Applications
Scope
This article will cover:
- Basics of TWAIN
- 7 Stages of TWAIN
- Handling TWAIN Errors
- Using TWAINComm
This article will not cover:
- Image transfer methods other than native (such as buffered, and file).
- Using the code with a Forms application.
However, this should be easy to do with only a few minor alterations. Adapting this to a WinForms app would require several changes. - The details of processing the message loop, other than the messages you can receive from the data source. You should refer to the specification, the attached code, and Baruch23's article (link in references) for the specifics.
- Capability negotiation. This is a complex subject that I'm not yet comfortable with myself, and it's not necessary for the majority of projects anyway. You're better off enabling the UI that came with the scanner in most cases. However, for the brave of heart, the attached code does have some rudimentary support for this. You'll need to build it out, but the basic building blocks are there.
- Converting the in-memory Device Independent Bitmap (DIB), returned by the native transfer method, to a PNG file. However, the class that does this is well documented so it shouldn't be difficult to figure it out by looking at the code.
Basics of TWAIN
Data Source Manager (DSM)
The DSM, sometimes referred to as the Source Manager, manages the Data Sources. Essentially, your application can establish a connection to the DSM and through the DSM you then have access to all the installed devices available to that computer.
Data Source (DS)
The DS, sometimes referred to as the Source, is usually the software that the device manufacturer provides to be able to communicate with their device. The scanner's UI that pops up prior to a scan is a part of the DS.
Your application communicates with the Data Source (DS) through the Data Source Manager (DSM), and the DS communicates with the device itself. This allows your application to communicate with any attached TWAIN device without having to know the specifics of how each of the devices work. Because each of the devices conform to the TWAIN specification your application can use the same code to access each of them regardless of the device and manufacturer.
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;
}
This is a particularly important data structure that's used to provide identification. It identifies your application to the DSM and DS, and identifies the DS to your application (separately). In other words, you will populate an instance of this and provide it to the DSM and DS so that they know which application is talking to them. The DSM will provide you with an instance of this structure that identifies the selected DS, and from that point forward you can use it to refer to that DS so that the DSM will know which device to route your messages to.
The basic TWAIN data structures are covered below but I wanted to explain this one separately from the others because you'll be using it a lot.
TWAIN Triplets
TWAIN uses 3 pieces of information in order to communicate with a device called a triplet. The triplet is comprised of the Data Group, Data Argument Type, and Message.
Data Group (DG)
Currently there are 3 data groups that the TWAIN specification supports: Control, Image, and Audio. The groups that we're concerned with, for scanning, are Control and Image.
Data Argument Type (DAT)
The DAT indicates the type of data that's being passed to the device or operated on. The device will use this part of the triplet to determine which data structure it will use to store the data you're passing into, and out of, the device.
Message (MSG)
The MSG indicates what action you wish to take.
You bring all three pieces of the triplet together in order to send a message to the device. For example, in order to open the desired DS you pass the DG_CONTROL/DAT_IDENTITY/MSG_OPENDS triplet. The Control part of the triplet tells the device that the information you're sending to it is going to be used for control. The Identity part of the triplet tells the device that the information you're sending to it is using the identity data structure (TW_IDENTITY
). The open DS part of the triplet tells the device that you want it to open the DS referenced in the provided TW_IDENTITY
instance.
Basic Data Structures
There are many data structures that TWAIN uses other than TW_IDENTITY
, but I'm just going to cover the necessary structures used to perform a basic scan operation.
public class TW_USERINTERFACE
{
public short ShowUI;
public short ModalUI;
public IntPtr hParent;
}
This data structure is used with the DAT_USERINTERFACE DAT triplets. You pass an instance of this when you enable or disable the DS's user interface. There are only two parts of it that apply to us. Set ShowUI to 1 (true) if you want the DS to use it's UI, or set it to 0 (false) if you don't want it (we'll be setting it to 1). Set hParent to the handle of your application's window. ModalUI is only used on the Mac.
public struct TW_EVENT
{
public TW_MEMREF EventPtr;
public TW_UINT16 Message;
}
This data structure is used with the DAT_EVENT DAT triplets. In certain stages TWAINComm will hook into the window event/message loop and listen in on operating system events/messages (the TWAIN specification refers to window messages as events so as to not confuse them with the messages that make up the third part of the triplet). TWAINComm will then send these events to the DS and receive a message back from the device. The EventPtr is a pointer to the window event data structure (not shown), while Message is the message that's sent back to TWAINComm and corresponds to one of the MSG constants (covered in more detail below).
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;
}
This data structure is used with the DAT_IMAGEINFO DAT triplets. When the device is ready to transfer an image you can send an unpopulated instance of this to the DS and it will populate it with details describing the image that's about to be transferred. Since we're going to be using the native transfer mode this information is included in the bitmap header of the in-memory image that the device gives us. For this reason we won't be using this data structure, but you should be aware of it in case you implement one of the other transfer methods.
public struct TW_PENDINGXFERS
{
public TW_UINT16 Count;
public TW_UINT32 EOJ;
}
This data structure is used with the DAT_PENDINGXFERS DAT triplets. When you transfer an image you provide the DS with an unpopulated instance of this and it will populate it with the number of pages remaining to be transferred. If the device is equipped with a multi-document feeder the value of count can be -1. If there are no more pages to transfer the value of count will be 0.
For our purposes we test to see if the count is 0. If it isn't we continue to transfer images. If it is 0 then we discontinue transferring.
EOJ is only used in conjunction with the capability CAP_JOBCONTROL and its use is outside the scope of this article.
public struct TW_STATUS
{
public TW_UINT16 ConditionCode;
public TW_UINT16 Reserved;
}
This data structure is used with the DAT_STATUS DAT triplets. Each call to TWAIN returns a code (TWRC) that indicates whether it was successful or not. If your call returns the failure code (TWRC_FAILURE), then you must send the DG_CONTROL/DAT_STATUS/MSG_GET triplet with this data structure to the DS to find out why it failed. The condition code (TWCC) will give you the information you need to determine what went wrong.
TWAIN DLL Entry Point
In C# we use multiple external references to the TWAIN DLL; one for each variation of the call that we need to make. But TWAINComm only uses one entry point to the DLL.
DSM_Entry( OriginIdentity, DestinationIdentity, DataGroup, DataArgumentType, Message, DataArgument );
Origin Identity
The TW_IDENTITY for your application.
Destination Identity
The destination is either null or TW_IDENTITY. If you want to talk to the DSM then you use null (IntPtr.Zero), or the identity of a DS.
Data Group
One of the DG constants, representing the first triplet component. In the case of TWAINComm it's implemented as an enumerated type. For example: DG_CONTROL would be DG.CONTROL.
Data Argument Type
One of the DAT constants, representing the second triplet component. In the case of TWAINComm it's implemented as an enumerated type. For example: DAT_IDENTITY would be DAT.IDENTITY.
Message
One of the MSG constants, representing the third triplet component. In the case of TWAINComm it's implemented as an enumerated type. For example: MSG_OPENDS would be MSG.OPENDS.
Data Argument
The data argument indicated by the data argument type triplet component. For example: since we used DAT_IDENTITY above we would have to provide an instance of the corresponding TW_IDENTITY data structure, which contains the identity of the DS you want to open.
(In order to obtain an instance of the TW_IDENTITY class for the selected DS you need to call DSM_Entry( appIdentity, NULL, DG_CONTROL, DAT_IDENTITY, MSG_GETDEFAULT, dsIdentity). This sends the DG_CONTROL/DAT_IDENTITY/MSG_GETDEFAULT triplet to the DSM along with an empty instance of TW_IDENTITY as the data argument. The DSM populates the empty data instance and returns it to the application. From this point forward the application can use the returned DS identity to refer to the selected DS. This is covered more thoroughly below.)
7 States of TWAIN
TWAIN has 7 states. TWAINComm must keep track of which one it's in because TWAIN actions can only be performed in certain states. If your application gets out of sync with the DS you're trying to talk to, then your application won't be able to communicate with it. If this happens TWAINComm will attempt a reset, but the user may need to cycle the power to the device to reset it to a known state.
Description
The following describes the actions that cause transitions to the various states from state 1 up to state 7 and back down again to state 1:
- Initial State
- DSM Loaded
- DSM Opened
- DS Opened
- DS Enabled
- DS Ready To Transfer Image
- Image Transferring
- Image Transferred
- DS Done Transferring Image(s)
- DS Disabled
- DS Closed
- DSM Closed
- DSM Unloaded
If there's more than one image/page to transfer/scan, then the state will transition from 6 to 7 and back to 6 for each individual image. Also, when TWAINComm sends a message to the DS to acknowledge the end of the image transfer it will receive a populated instance of TW_PENDINGXFERS. If Count=0 then the DS automatically transitions to state 5 on its own. However, if there's an error, or if the user cancels the scan, then TWAINComm will transition the DS to state 5 manually by resetting the transfer process (see below for details).
Actions
While there are many actions that can be taken in the various states I'm only going to cover the operations necessary to select a DS and perform a basic scan. Some of these actions will transition the state either up or down, while others don't trigger any transition in state.
State 1: Load the DSM - Transitions to State 2
According to the specification you transition from state 1 to state two by loading the DSM into memory. However, with C# you can't explicitly load the DSM, but we can test to make sure that the necessary external DLLs exist using the following code:
Marshal.PrelinkAll( typeof( Extern ) );
If one of the DLLs is missing the most likely explanation is that the DSM is missing, meaning that a TWAIN device has never been installed on that computer.
State 2: Open the DSM - Transitions to State 3
DG_CONTROL/DAT_PARENT/MSG_OPENDSM
You send the above triplet to the DSM in order to load it. The data argument indicated by DAT_PARENT is a handle to your application's window.
Return Codes:
-
TWRC_SUCCESS
If the call returned this code then the DSM has been successfully opened and the application is now in state 3.
-
TWRC_FAILURE
If the call returned this code then the DSM failed to open and the application remains in state 2. You must send the DG_CONTROL/DAT_STATUS/MSG_GET triplet to the DSM in order to get a condition code that indicates why it failed.
State 3: Select the DS
DG_CONTROL/DAT_IDENTITY/MSG_USERSELECT
Once we're in state 3 we can open the data source selection utility. While it's possible to write your own, we're just going to open the one that came with the DSM. The TW_IDENTITY instance we provide to the DSM will be populated with the selected DS if the call's successful.
The user will either click Cancel or select a data source and click Select. If they click Select then the data source that was selected becomes the default, but the state doesn't change.
Return Codes:
-
TWRC_SUCCESS
If the call returned this code then the DSM has successfully set the default to the data source that the user selected.
-
TWRC_CANCEL
If the call returned this code then the user clicked the Cancel button. The default DS didn't change.
-
TWRC_FAILURE
If the call returned this code then the user clicked the Select button but the DSM failed to change the default DS. You must request a condition code from the DSM to find out why it failed.
State 3: Get the Default DS Identity
DG_CONTROL/DAT_IDENTITY/MSG_GETDEFAULT
Once we're in state 3 we can get the TW_IDENTITY for the default DS using the above triplet. The TW_IDENTITY instance we provide to the DSM will be populated with the default DS if the call's sucessful. Save the TW_IDENTITY instance that's returned. You'll need it to open the default DS and talk to it once it's open. Regardless of the call's success the state doesn't change.
Return Codes:
-
TWRC_SUCCESS
If the call returned this code then the DSM has successfully populated the provided TW_IDENTITY instance with the identity of the default DS.
-
TWRC_FAILURE
If the call returned this code then the DSM failed to change the default DS. You must request a condition code from the DSM to find out why it failed.
State 3: Open the DS - Transitions to State 4
DG_CONTROL/DAT_IDENTITY/MSG_OPENDS
You send the above triplet to the DSM in order to open the requested DS. The data argument indicated by DAT_IDENTITY is an instance of TW_IDENTITY that contains the details provided by the DSM for the DS you want to open.
Return Codes:
-
TWRC_SUCCESS
If the call returned this code then the DSM has successfully opened the specified DS and is now in state 4.
-
TWRC_FAILURE
If the call returned this code then the DSM failed to open the specified DS and the state remains unchanged. You must request a condition code from the DSM to find out why it failed.
State 4: Enable the DS - Transitions to State 5
DG_CONTROL/DAT_USERINTERFACE/MSG_ENABLEDS
You send the above triplet to the previously opened DS in order to enable it. The data argument indicated by DAT_USERINTERFACE in an instance of TW_USERINTERFACE that's populated with the settings you want. We're going to turn on the DS's UI so we set ShowUI=1. With ShowUI Enabled the DS will display the scanner's user interface and allow the user to select settings specific to that device and initiate the scan operation.
Return Codes:
-
TWRC_SUCCESS
If the call returned this code then the DS has been successfully enabled and is now in state 5.
-
TWRC_CHECKSTATUS
The DS returns this code if the application disables the UI (ShowUI=0) when the DS doesn't support it. If you want to disable the UI you must first check to see if the DS supports it, but doing that is beyond the scope of this article. The DS will ignore the unsupported setting, show the UI, and transition to state 5.
-
TWRC_FAILURE
If the call returned this code then the DS failed to enable and the state remains unchanged. You must request a condition code from the DS to find out why it failed.
States 5-7: Process Events
DG_CONTROL/DAT_EVENT/MSG_PROCESSEVENT
Once you successfully enable the DS and transition to state 5 you must hook into the event/message loop and send the events to the DS. The data argument indicated by DAT_EVENT is an instance of TW_EVENT that's populated with details of the event. Sending the event to the DS allows it to keep track of what's happening by listening in on events, and also allows it to pass messages of it's own back to your application. You must continue to pass events to the DS as long as you're in states 5 through 7. After you transition back to state 4 you can unhook from the event/message loop.
Return Codes:
-
TWRC_DSEVENT
If the call returned this code then the event that was passed to the DS belongs to it, and the DS processed the event.
-
TWRC_NOTDSEVENT
If the call returned this code then the event that was passed to the DS does not belong to it, and the DS did not process the event.
-
TWRC_FAILURE
If the call returned this code then the DS failed to process the provided event. You must request a condition code from the DS to find out why it failed.
States 5-7: DS Sends Close Request
MSG_CLOSEDSREQ
While passing events to the DS for processing you should watch for messages back. If your application receives the above message, then that means the user closed the UI. The application must unhook from the event loop and step down back to state 2 from whichever state it's currently in.
State 5: DS Sends Transfer Ready - Transitions to State 6
MSG_XFERREADY
While passing events to the DS for processing you should watch for messages back. If your application receives the above message, then that means the user has initiated the scan and the DS is ready for the transfer. The DS has transitioned to state 6 on its own so make sure that you transition your application as well in order to remain in-sync.
State 6: Acquire Image Info
DG_IMAGE/DAT_IMAGEINFO/MSG_GET
Once you transition to state 6 you can send the above triplet to the DS to retrieve details about the image being transferred. The data argument indicated by DAT_IMAGEINFO is an empty instance of TW_IMAGEINFO that's populated by the DS. When using the native transfer method doing this is unnecessary because the same information is provided in the bitmap header.
Return Codes:
-
TWRC_SUCCESS
If the call returns this code then it means that the DS has successfully populated the provided instance of TW_IMAGEINFO.
-
TWRC_FAILURE
If the call returned this code then the DS failed to populated the provided instance of TW_IMAGEINFO. You must request a condition code from the DS to find out why it failed.
State 6: Get Image by Native Transfer - Transitions to State 7
DG_IMAGE/DAT_IMAGENATIVEXFER/MSG_GET
You send the above triplet to the DS in order to initiate an image transfer using the native method. The native method will transfer the full scanned image into memory. The data argument indicated by DAT_IMAGENATIVEXFER is a pointer that's populated by the DS and points to an in-memory Device Independent Bitmap (DIB).
Return Codes:
-
TWRC_XFERDONE
If the call returns this code then the DS has successfully scanned the current image/page and populated the pointer to the DIB.
-
TWRC_CANCEL
If the call returns this code then the user cancelled the transfer. If the returned pointer isn't null (IntPtr.Zero) then it's up to the program to deallocate the memory that was reserved for the DIB that it's pointing to. Don't try to use it, because it's not a valid image. Also, the application will need to unhook from the event loop and step back down to state 2.
-
TWRC_FAILURE
If the call returned this code then the DS failed to transfer the image and set the pointer. There's no need to check the pointer for deallocating memory. You must request a condition code from the DS to find out why it failed.
State 7: End Transfer Confirmation - Transitions to State 6
DG_CONTROL/DAT_PENDINGXFERS/MSG_ENDXFER
You send the above triplet to the DS in order to let it know that the transfer is complete. The data argument indicated by DAT_PENDINGXFERS is a an empty instance of TW_PENDINGXFERS that's populated by the DS. This data structure contains Count which indicates if there are additional images/pages to be transferred. It can be >0, which indicates the number of images left to be transferred. It can be -1, which indicates that the scanner is equipped with an auto-document feeder and that there are an unknown number of pages left to be transferred. Or, it can be 0, which indicates that there are no more pages left to be transferred. When Count=0 the DS will automatically transition to state 5 on it's own.
You can also use the above triplet to cancel the transfer of the current image. If you then wish to cancel the rest of the transfer, check Count first to make sure the DS didn't already transition to state 5.
Return Codes:
-
TWRC_SUCCESS
If the call returns this code then the DS has successfully populated the provided instance of TW_PENDINGXFERS and transitioned to either state 6 or state 5, depending on whether or not there are additional images to be transferred, which is indicated by the value of Count.
-
TWRC_FAILURE
If the call returned this code then the DS failed to populated the provided instance of TW_IMAGEINFO and remains in state 7. You must request a condition code from the DS to find out why it failed.
State 6: Reset Transfer - Transitions to State 5
DG_CONTROL/DAT_PENDINGXFERS/MSG_RESET
You send the above triplet to the DS when you want to cancel the transfer process. The data argument indicated by DAT_PENDINGXFERS is a an empty instance of TW_PENDINGXFERS that's populated by the DS. You can ignore the returned value of TW_PENDINGXFERS.
Return Codes:
-
TWRC_SUCCESS
If the call returns this code then the DS has successfully transitioned to state 5.
-
TWRC_FAILURE
If the call returned this code then the DS remains in state 6. You must request a condition code from the DS to find out why it failed.
State 5: Disable the DS - Transitions to State 4
DG_CONTROL/DAT_USERINTERFACE/MSG_DISABLEDS
You send the above triplet to the DS when you want to disable the DS. Disabling the DS will close the UI if it's open. The data argument indicated by DAT_USERINTERFACE is an empty instance of TW_USERINTERFACE. You can ignore the returned value of TW_USERINTERFACE.
Return Codes:
-
TWRC_SUCCESS
If the call returns this code then the DS has successfully transitioned to state 4.
-
TWRC_FAILURE
If the call returned this code then the DS remains in state 5. You must request a condition code from the DS to find out why it failed.
State 4: Close the DS - Transitions to State 3
DG_CONTROL/DAT_IDENTITY/MSG_CLOSEDS
You send the above triplet to the DSM when you want to close the DS. The data argument indicated by DAT_IDENTITY is the populated instance of TW_IDENTITY that you've been using to communicate with the DS. Once the DS is closed you should set the ID in the DS's instance of TW_IDENTITY to 0. The ID is only valid while the DS is open. A new ID will be generated the next time you open the DS.
Return Codes:
-
TWRC_SUCCESS
If the call returns this code then it means that the DS has been successfully closed and transitioned to state 3.
-
TWRC_FAILURE
If the call returned this code then the DSM failed to close the DS and remains in state 4. You must request a condition code from the DSM to find out why it failed.
State 3: Close the DSM - Transitions to State 2
DG_CONTROL/DAT_PARENT/MSG_CLOSEDSM
You send the above triplet to the DSM when you want to close it. The data argument indicated by DAT_PARENT is a handle to the application window.
Return Codes:
-
TWRC_SUCCESS
If the call returns this code then it means that the DSM has been successfully closed and transitioned to state 2.
-
TWRC_FAILURE
If the call returned this code then the DSM failed to close and remains in state 3. You must request a condition code from the DSM to find out why it failed.
State 2: Unload the DSM - Transitions to State 1
C# can't explicitly unload the DSM the same way C++ does. However, it really isn't necessary for us to do so. When stepping down from an operation TWAINComm will transitioning to state 2 and stop there. The DSM will be unloaded when the user closes the application.
Handling TWAIN Errors
By TWAIN error I mean that a call to TWAIN returned a code of TWRC_FAILURE. When you get this return code you must ask the DSM/DS for a condition code by sending it the DG_CONTROL/DAT_STATUS/MSG_GET triplet. It's this code that tells you why the call failed.
Condition Codes (TWCC)
In TWAINComm the condition codes are implemented as an enumerated type. For example: TWCC_SUCCESS would be TWCC.SUCCESS. Below are the explanations of what each of the possible condition codes in v1.9 of the TWAIN specification mean.
-
TWCC_SUCCESS
There was no failure
-
TWCC_BUMMER
Failure due to unknown causes
-
TWCC_LOWMEMORY
Not enough memory to perform the operation
-
TWCC_NODS
No Data Source is available
-
TWCC_MAXCONNECTIONS
The selected data source is already connected to the maximum number of applications
-
TWCC_OPERATIONERROR
the DS or DSM has already reported the failure; the application shouldn't
-
TWCC_BADCAP
The application attempted to get or set an unknown capability
-
TWCC_BADPROTOCOL
The application attempted to use an unrecognised DG/DAT/MSG combination
-
TWCC_BADVALUE
The provided data parameter is out of range
-
TWCC_SEQERROR
The operation was attempted out of its proper sequence
-
TWCC_BADDEST
The provided destination identification isn't correct
-
TWCC_CAPUNSUPPORTED
The application has attempted to get or set a capability that is not supported by the data source
-
TWCC_CAPBADOPERATION
The attempted operation is not supported by the specified capability
-
TWCC_CAPSEQERROR
The attempted capability is dependent on another capability
-
TWCC_DENIED
The attempted file system operation is denied. The file may be write protected.
-
TWCC_FILEEXISTS
The attempted file system operation failed because the file already exists
-
TWCC_FILENOTFOUND
File not found
-
TWCC_NOTEMPTY
The attempted file system operation failed because the directory is not empty
-
TWCC_PAPERJAM
The scanner detected a feed jam
-
TWCC_PAPERDOUBLEFEED
The scanner detected a multi-page feed error
-
TWCC_FILEWRITEERROR
There was a error while attempting to write to the file system. The drive may be full.
-
TWCC_CHECKDEVICEONLINE
The device went offline prior to, or during, operation
Resetting the TWAIN State
Sometimes the best response to a TWAIN failure is to report the error to the user, reset the TWAIN state, then allow the user to attempt the operation again. To reset the TWAIN state you step down from state 7 to state 2 while ignoring failures.
To accomplish this step down you must send the triplets that trigger transitions (see the above state transfer actions for more detail) to the previous state while in the following states:
- 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
After sending these triplets the DS and DSM should now be back in state 2. At this point you should set the ID of the DS's TW_IDENTITY instance to 0, as well as the ID of the application's TW_IDENTITY. The IDs will be repopulated with new values during the next operation.
If you don't want to completely reset the TWAIN state you can watch the return code while you're stepping down. When you see the TWRC_SUCCESS returned, then you know that your application and DS are back in sync. I find that it's easier to completely reset and start over, but I wanted to point out that it's not always necessary.
Occasionally the DS will become unresponsive. At this point the only thing you can do is put your application in state 2 and set the IDs of the DS's and application's instances of TW_IDENTITY to 0 and have the user cycle the power to the device.
Using TWAINComm
At this point, between reading the above and looking at the code, you should be familiar with how the basics of TWAIN work. In this section I'm going to cover how to set up an application to use the TWAINComm library, how to make use of it, and what it's doing behind the scenes.
If you're not sure about something, take a look at the demo app. At the very least the demo app can be used to debug problems you may be having with a scanner. Between all the various feedback elements you should able to target where the problem is occurring.
Application Setup
Setting up an application to use the TWAINComm library is fairly simple. Just follow these steps:
-
Add the library reference to your application
-
Make sure that the platform target in the build settings is set to x86
TWAINComm performs a DllImport of the external 32 bit twain_32.dll x86 library. Your application must be set to 32 bit in order to use it. If you skip this step it will come back to bite you.
-
Add a TWAIN property that implements the TWAINComm.Twain class in your application, and instantiate it in a method that's called by the Loaded event of your main window, along with the desired feedback delegates
private void ViewLoaded()
{
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 );
CommandManager.InvalidateRequerySuggested();
}
You'll want to change the Manufacturer, ProductFamily, and ProductName properties; and possibly the Version information. Don't change the Id, or SupportedGroups properties.
The use of the feedback delegates are optional. However, you will want to, at the least, provide a callback to the ScanEnd delegate. TWAINComm calls this delegate when it's done scanning all images and provides the application with a list of PNG files. These are temp files, so you'll want to do something more permanent with them (move, upload, etc.). The other delegates are more for informational and debugging purposes.
-
Setup a method that's called by the Closed event of your main window that will call TWAINComm's Dispose method so that it can properly release unmanaged memory
private void ViewClosed()
{
try
{
if ( Twain != null )
{
Twain.Dispose();
Twain = null;
}
}
catch { }
}
Select Source
Once your application is setup to use TWAINComm you can allow a user to select (change) the source. It's simple to do, just call the following:
Twain.SelectSource();
This code assumes that the Twain property is an instance of the TWAINComm.Twain class and properly setup as shown above in step 3.
TWAINComm handles the transition to state 3 and activates the built in Select Source dialogue provided by the DSM. Once the user closes the dialogue box TWAINComm transitions back to state 2.
Acquire (Scan)
Once your application is setup to use TWAINComm you can allow a user to acquire new images by initiating the scan process. It's simple to do, just call the following:
Twain.ScanBegin();
This code assumes that the Twain property is an instance of the TWAINComm.Twain class and properly setup as shown above in step 3.
When TWAINComm is done scanning it will call the ScanEnd feedback delegate and provide the application with a list of image files. If the list is empty (Count=0), then the scan was either cancelled or it failed. If the list is populated, then it will contain an ordered list of file locations to temporary PNG files. It's up to the application to handle them from this point forward.
TWAINComm handles the transition from state 2 to state 5. It will then hook into the message loop and wait for either the MSG_CLOSEDSREQ or MSG_XFERREADY message from the DS. If MSG_CLOSEDSREQ is received, then an empty file list is returned to the application and TWAINComm unhooks from the event loop and handles the transition from state 5 back down to state 2. if MSG_XFERREADY is received, then TWAINComm will initiate the transfer, convert and save the image, check for more pages and transfer them (if any), then it will unhook from the message loop and handle the transition from state 5 back down to state 2. If any errors are encountered along the way, it will handle them and respond as necessary.
Other Projects
TWAINComm is just one possible way to implement TWAIN in C#. Below are two other projects that I'm aware of and might be good examples to look at as well.
twaindotnet
NTwain
Revision History
11 May 2015
13 May 2015
- Minor formatting correction
14 May 2015
- More formatting corrections
- A fix to the project code which could've caused issues if there was a failure while closing the DS or DSM
19 May 2015
- I changed the reference to adapting the library to a WinForms application in the scope. The changes required to adapt it would be more involved that I had originally anticipated.