仅在Drag Drop上发生异常

时间:2012-03-30 20:09:03

标签: c# winforms winforms-interop comctl32 taskdialog

我有一个WinForms应用程序,它使用了一个TaskDialog library,它利用了ComCtl32.dll的Vista风格对话框,而对于较小的操作系统,它使用了模拟的win形式...

但这不是问题......这个图书馆工作得很好,我们从来没有遇到过这个问题。直到现在......如果我们在正常情况下启动对话,那么它看起来很好。

但是,我在主窗体上添加了一个拖放处理程序来捕获从其他源(例如Windows资源管理器)中删除的文件路径。如果该拖放处理程序是第一次显示对话框,那么我们会得到以下异常:

  

无法在DLL“ComCtl32”中找到名为“TaskDialogIndirect”的入口点。

这发生在第三方图书馆致电:

    /// <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 );

如果已显示对话框,则处理程序将运行OK。

表单的DragDrop处理程序没有显示InvokeRequired,但我还是小心翼翼地通过Form.Invoke提升对话框。

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);
            }
        }
    }
}

作为一方:我正在64位Windows 7计算机上编译(和运行),但使用“AnyCPU”架构标记。

关于为什么仅在第一次调用TaskDialogIndirect时通过DragDrop处理程序引发异常的想法/解决方案???

2 个答案:

答案 0 :(得分:3)

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

该库正在使用comctl32.dll Windows dll非常繁重的快捷方式。这往往会意外地结束,但它会在您的代码中失败。完整的解释是相当冗长的,我会尽量保持简短。

核心问题是Windows有两个版本的comctl32.dll。 c:\ windows \ system32中的一个版本是 legacy 版本,它们在Windows 2000及更早版本中的外观和工作方式实现了通用控件。 Windows XP获得了视觉样式,使这些控件看起来非常不同。有另一个实现这些视觉样式的 DLL,它存储在Windows并排缓存中(c:\ windows \ winsxs)。

应用程序必须明确告诉Windows它支持新版本的DLL。有两种方法可以实现,你可以在清单中(WPF的方式)或者你可以进行操作系统调用,CreateActCtx()函数(Winforms的方式)。

图书馆的工作方式是希望有人做了这两件事之一。并加载了正确版本的comctl32.dll,以便对[DllImport]函数进行直接加载并不会实际加载c:\ windows \ system32版本。旧的没有实现TaskDialogIndirect()。这是偶然的,因为一些代码通常会这样做。事实上,Windows只关心DLL名称,而不关心它来自何处以确定是否需要加载DLL。

我可以猜到你的运气如何。您正在使用Control.Invoke(),这是您在使用线程时唯一需要做的事情。显然,您在另一个线程上显示此表单,而不是主UI线程。这通常是一个非常糟糕的想法,UI线程已经设计为能够处理多个窗口。通常在UI线程上发生的一件事是Application.EnableVisualStyles()调用。告诉Windows你想要新版本的comctl32。

您可以尝试在工作线程上调用它。可能会工作,不知道。到目前为止,最好的解决方案是不在工作线程上创建窗口。您可以使用Windows API代码包摆脱不稳定的库,它为任务对话框提供了包装。

答案 1 :(得分:0)

事实证明,在DragDrop处理程序中,我应该使用BeginInvoke将调用异步排队到Form的UI线程,而不是同步等待它在处理程序中完成...

因此,它解决了:

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);
            }
        }
    }
}

我不确定为什么!??评论者可能提供一个理由吗?