1

我有一个 WinForms 应用程序,它使用了一个TaskDialog 库,该库利用 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 );

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

表单的 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 处理程序时才引发异常的任何想法/解决方案???

4

2 回答 2

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

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

核心问题是Windows有两个版本的comctl32.dll。c:\windows\system32 中的那个是版本,以 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 代码包摆脱不稳定的库,它为任务对话框提供了一个包装器。

于 2012-03-30T21:20:53.373 回答
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);
            }
        }
    }
}

我不知道为什么!??评论者可以提供一个理由吗?

于 2012-03-30T20:50:30.927 回答