4

Has anyone tried TwainDotNet for scanning with TWAIN API calls from .NET? Though it works well usually I've some issues with it when used along with WPF application using MVVM. Basically I'm calling Twain scanning functions from a Service, which in turn uses a BackgroundWorker.

List<BitmapSource> bitmapSources = new List<BitmapSource>();
Twain twain = new Twain(new WpfWindowMessageHook(_window));
ScanSettings settings = new ScanSettings() { ShowTwainUI = false };
using (BackgroundWorker worker = new BackgroundWorker())
{
    worker.DoWork += (sndr, evnt) =>
    {
        AutoResetEvent waitHandle = new AutoResetEvent(false);
        EventHandler scanCompleteHandler = (se, ev) => { waitHandle.Set(); };
        twain.ScanningComplete += scanCompleteHandler;
        twain.StartScanning(settings);
        waitHandle.WaitOne();

        if (twain.Images.Count > 0)
        {
            foreach (var image in twain.Images)
            {
                BitmapSource bitmapSource = Imaging.CreateBitmapSourceFromHBitmap(new Bitmap(image).GetHbitmap(),
                    IntPtr.Zero, Int32Rect.Empty, System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
                bitmapSources.Add(bitmapSource);
            }
        }
    };
    worker.RunWorkerCompleted += (sndr, evnt) => { image1.Source = bitmapSources[0]; };
    worker.RunWorkerAsync();
}

ScanningComplete event handler is never fired when we are working with a BackgroundWorker. Any suggestions to resolve this issue?

Raj
  • 4,405
  • 13
  • 59
  • 74
  • Raj, I downloaded your example project from the google TwainDotNet Issues page. I want to do the same thing with the backgroundworker because I want to show progress/status on the window while scanning. I am having the same problem with accessing the images. however, I also can't get the window to respond while scanning in the backgroundworker. were you able to get the window to update/respond while you are scanning with this solution? pls let me know. – Dave Feb 18 '10 at 04:15
  • Dave, sample solution should be doing this right? – Raj Feb 18 '10 at 14:38
  • No.. I want to put a progress bar on the window and show some feedback. DoWork is processing the scan. So, I need another thread to report progress. I started a DispatcherTimer and call: Dispatcher.Invoke(updatePbDelegate, System.Windows.Threading.DispatcherPriority.Background, new object[] { ProgressBar.ValueProperty, progressBarValue }); but the events to update the progress bar do not fire until after scanning is complete. if you have another idea, please let me know. – Dave Feb 18 '10 at 18:04

2 Answers2

6

The fact that the Twain object requires a window handle in its object constructor suggests that something inside the Twain object requires message handling. Cross-thread message handling is tricky to begin with but even more so when it's happening inside an API.

If the twain API creates a window handle (overtly, such as a popup window or dialog, or secretly, such as for interprocess communication (IPC)) as part of one of the API functions you're calling from the background thread, that window handle will be bound to the thread it was created on - the background thread. All messages sent to that window handle will queue up waiting for the background thread to process them in a message loop. You don't have a message loop in your background thread, so that window handle will get stuck in limbo. It won't respond to window messages. Posted messages will go unanswered. SendMessage() will deadlock.

Even if this is not a window handle / message loop problem, it is very likely that if the Twain API was not explicitly and deliberately implemented with multithreading in mind, it will have problems when used across threads. You are creating the twain object in one thread and then using it in another thread, so this is a cross-thread situation. If you could create the twain object in the background thread and only use the twain object in the context of that background thread, this might work around thread affinity issues in the twain API implementation. When window handles and messages are involved, moving everything to the background thread is just as likely to make things worse.

The ability to use an object across threads does not come for free. If the twain API was not designed for use across threads, there is little you can do to make it work across threads. Your best bet is to keep the Twain object in the main UI thread.

dthorpe
  • 35,318
  • 5
  • 75
  • 119
  • 1
    [@dthorpe] your surmises are all spot-on. TWAIN works fine in a thread, but it is best to confine all TWAIN calls to that one thread, and that thread must have a message pump i.e. be a 'UI thread'. – Spike0xff Apr 08 '13 at 15:47
1

Have you tried removing the LINQ'ness from the code and put it into a separate function to actually test this out first, note that I have it wrapped up in a try/catch block to see if there's any error, also notice that I created a simple class WorkerArgs for passing the data around as it is non-LINQ code, it would be interesting to see what results there are (if any):

public class WorkerArgs{
   public List<BitMapSource> _bitmapSources;
   public Twain _twain;
   public ScanSettings _settings;
}
List<BitmapSource> bitmapSources = new List<BitmapSource>();
Twain twain = new Twain(new WpfWindowMessageHook(_window));
ScanSettings settings = new ScanSettings() { ShowTwainUI = false };
WorkerArgs wArgs = new WorkerArgs();
wArgs._bitmapSources = bitmapSources;
wArgs._twain = twain;
wArgs._settings = settings;
using (BackgroundWorker worker = new BackgroundWorker())
{
    worker.DoWork += new DoWorkEventHandler(worker_DoWork);
    worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
    worker.RunWorkerAsync((WorkerArgs)wArgs);
}

void  worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
   try{
    image1.Source = (WorkerArgs(e.Argument))._bitmapSources[0];
   }catch(Exception up){
     throw up; // :P
   }
}

void  worker_DoWork(object sender, DoWorkEventArgs e)
{
   try{
     WorkerArgs thisArgs = (WorkerArgs)e.Argument as WorkerArgs;
     if (thisArgs != null){
        AutoResetEvent waitHandle = new AutoResetEvent(false);
        EventHandler scanCompleteHandler = (se, ev) => { waitHandle.Set(); };
        thisArgs._twain.ScanningComplete += scanCompleteHandler;
        thisArgs._twain.StartScanning(settings);
        waitHandle.WaitOne();

        if (thisArgs._twain.Images.Count &gt; 0)
        {
            foreach (var image in twain.Images)
            {
                BitmapSource bitmapSource = Imaging.CreateBitmapSourceFromHBitmap(new Bitmap(image).GetHbitmap(),
                    IntPtr.Zero, Int32Rect.Empty, System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
                thisArgs._bitmapSources.Add(bitmapSource);
            }
        }
    }
   }catch(Exception up){
     throw up; // :P
   }
}

I couldn't help noticing, it's just after entering the code I noticed this:

Twain twain = new Twain(new WpfWindowMessageHook(_window))

Are you doing hooking or something like that within the background worker - perhaps there's a cross thread problem hence ScanningComplete is not being fired? Just a thought, Can you clarify anyway?

t0mm13b
  • 34,087
  • 8
  • 78
  • 110
  • WpfWindowMessageHook require a handle to work, so I'm passing that value but to avoid cross thread issue I've changed IntPtr WindowHandle in WpfWindowMessageHook as return (IntPtr)_window.Dispatcher.Invoke(new Func(() => _interopHelper.Handle)); This had solved cross threading issue you have correctly anticipated. This post was made after fixing that issue. Let me try your solution now. – Raj Feb 14 '10 at 10:34
  • Hi Tommie, your code changes also does not work. I have removed BackgroundWorker to set it go for now. But TwainDotNet library is out of memory exception when handling scans in high DPI (600+) and document feeder enabled. Usually more than 3 pages when scanned with ADF throws memory exception. – Raj Feb 18 '10 at 14:36