1

I have a WinForms application that makes use of a TaskDialog library that leverages the Vista style dialogs from ComCtl32.dll and for lesser OS's it uses an emulated win form...

But that's not the problem... This library works fine and we've never had issues with it. Until now... In deed if we launch a dialog under normal circumstances, then it looks fine.

However, I've added a drag-drop handler on my main form to capture file paths dropped from other sources (say Windows Explorer). If that drag-drop handler is the first time the dialog has been shown then we get the following exception:

Unable to find an entry point named 'TaskDialogIndirect' in DLL 'ComCtl32'.

This occurs on the third party library's call to:

    /// <summary>
    /// TaskDialogIndirect taken from commctl.h
    /// </summary>
    /// <param name="pTaskConfig">All the parameters about the Task Dialog to Show.</param>
    /// <param name="pnButton">The push button pressed.</param>
    /// <param name="pnRadioButton">The radio button that was selected.</param>
    /// <param name="pfVerificationFlagChecked">The state of the verification checkbox on dismiss of the Task Dialog.</param>
    [DllImport ( "ComCtl32", CharSet = CharSet.Unicode, PreserveSig = false )]
    internal static extern void TaskDialogIndirect (
        [In] ref TASKDIALOGCONFIG pTaskConfig,
        [Out] out int pnButton,
        [Out] out int pnRadioButton,
        [Out] out bool pfVerificationFlagChecked );

If a dialog has already been shown, then the handler will run OK.

The DragDrop handler for the form does not show InvokeRequired and but I was careful to raise the dialog via Form.Invoke anyway.

private void MainForm_DragDrop(object sender, DragEventArgs e)
{
    if (e.Data.GetDataPresent(DataFormats.FileDrop))
    {
        Array fileNames = (Array)e.Data.GetData(DataFormats.FileDrop);
        if (fileNames != null && fileNames.OfType<string>().Any())
        {
            foreach (var fileName in fileNames.OfType<string>())
            {
                this.Invoke(new Action<string>(this.AttemptOpenFromPath), fileName);
            }
        }
    }
}

As a side: I am compiling (and running) it on a 64-bit, Windows 7 machine but with the "AnyCPU" architecture flag.

Any thoughts/solutions as to why the exception is only raised when the first call to TaskDialogIndirect is via the DragDrop handler???

Reddog
  • 15,219
  • 3
  • 51
  • 63
  • That would depend on how the library works. – SLaks Mar 30 '12 at 20:10
  • What OS does the exception occur on? – SLaks Mar 30 '12 at 20:12
  • The error is telling you that you're trying to call the `TaskDialogIndirect` function when it isn't available. That function doesn't exist until Windows Vista, so on older versions of Windows, it will fail. It has nothing to do with drag and drop. – Cody Gray - on strike Mar 30 '12 at 20:14
  • @SLaks - It's occuring on my Windows 7 machine. However, I'm confused by the fact that it works usually (suggesting the dependent Comctl32 library is there)... But it only fails when the dialog is first raised via the DragDrop handler. – Reddog Mar 30 '12 at 20:18
  • Is there anything unusual about threading in DragDrop handlers? – Reddog Mar 30 '12 at 20:19
  • @CodyGray - In OS' less than Vista it uses an emulation library instead. This is running on Windows 7. My confusion lies in that it usually works, the error only occurs when loading via the DragDrop handler. – Reddog Mar 30 '12 at 20:39

2 Answers2

3
 [DllImport ( "ComCtl32", ...)]

The library is taking a pretty heavy shortcut to use the comctl32.dll Windows dll. This tends to come to a good end by accident, but it falls over in your code. The full explanation is rather long-winded, I'll try to keep it short.

The core problem is that Windows has two versions of comctl32.dll. The one in c:\windows\system32 is a legacy version, implementing the common controls the way they looked and worked back in Windows 2000 and earlier. Windows XP acquired visual styles, making those controls look very different. There's another DLL that implements those visual styles, it is stored in the Windows side-by-side cache (c:\windows\winsxs).

An application must explicitly tell Windows that it supports the new version of the DLL. There are two ways to do it, you can do so in a manifest (the way WPF does it) or you can make an operating system call, the CreateActCtx() function (the way Winforms does it).

The way the library works is that it hopes that somebody has done one of those two things. And loaded the correct version of comctl32.dll so that pinvoking the [DllImport] function doesn't actually load the c:\windows\system32 version. The old one that doesn't implement TaskDialogIndirect(). This works by accident because some code usually does. And the fact that Windows only cares about the DLL name and not where it came from to determine if it needs to load a DLL.

I can somewhat guess how you ran out of luck. You are using Control.Invoke(), something you only ever need to do when you are using threads. Clearly you are displaying this form on another thread, not the main UI thread. This is in general a Really Bad Idea, the UI thread was already designed to be able to handle multiple windows. The one thing that didn't happen that normally happens on the UI thread is the Application.EnableVisualStyles() call. The one that tells Windows that you want the new version of comctl32.

You can try calling it on your worker thread. Might work, no idea. By far the best solution is to not create windows on worker threads. You can get rid of the wonky library by using the Windows API Code Pack, it provides a wrapper for task dialogs.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • I wasn't specifically trying to open the dialog on the non-UI thread... However, I guess DragDrop handlers that are catching dropped items from outside that thread (e.g. another app such as Windows Explorer) do cause it to process off the UI thread. – Reddog Mar 30 '12 at 21:36
  • As to using Windows API Code Pack - that's _absolutely_ in our plans... It's way better. Now I've just gotta make the time for it! – Reddog Mar 30 '12 at 21:36
  • 1
    No, drag+drop doesn't create threads. At some point you got an InvalidOperationException and figured out that you needed to use Control.Invoke to fix the problem. That didn't work however, you should have wondered "why do I need to work around that!". The common case is some kind of I/O completion callback that gets fired on a thread pool thread. Like one for a socket. You can see it in the stack trace. – Hans Passant Mar 30 '12 at 21:42
0

It turned out that within the DragDrop handler, I should be using BeginInvoke to asynchronously queue the call onto the Form's UI thread as opposed to synchronously waiting for it to complete within the handler...

Therefore, it was resolved with:

private void MainForm_DragDrop(object sender, DragEventArgs e)
{
    if (e.Data.GetDataPresent(DataFormats.FileDrop))
    {
        Array fileNames = (Array)e.Data.GetData(DataFormats.FileDrop);
        if (fileNames != null && fileNames.OfType<string>().Any())
        {
            foreach (var fileName in fileNames.OfType<string>())
            {
                this.BeginInvoke(new Action<string>(this.AttemptOpenFromPath), fileName);
            }
        }
    }
}

I'm not sure why though!?? Can a commenter perhaps provide a reason?

Reddog
  • 15,219
  • 3
  • 51
  • 63