Question

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???

Was it helpful?

Solution

 [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.

OTHER TIPS

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?

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top