IWebBrowser2::Quit在某些资源管理器标签页上不起作用

后端开发 2026-07-10

我正在尝试用一个文件夹来关闭资源管理器的标签页。要在C#中做到这一点,我有这段漂亮的代码:

[ComImport, Guid("9BA05972-F6A8-11CF-A442-00A0C90A8F39"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IShellWindows : IEnumerable
{
    int Count { get; }
    object Item(object index);
    new IEnumerator GetEnumerator();
}

public void CloseExplorerWindowsForPath(string path)
{
    if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return;

    _logger.LogInformation("Closing Explorer windows for path: {}", path);

    // ShellWindows is an STA COM object. Calling it from the MTA thread pool
    // (used by gRPC) causes Quit() to be silently ignored by Explorer because
    // the cross-apartment message is never pumped. A dedicated STA thread is required.
    Exception? threadException = null;
    var staThread = new Thread(() =>
    {
        var shellType = Type.GetTypeFromCLSID(new Guid("9BA05972-F6A8-11CF-A442-00A0C90A8F39"));
        if (shellType == null) return;

        dynamic shellWindows = Activator.CreateInstance(shellType)!;
        try
        {
            var toClose = new List<dynamic>();

            foreach (dynamic window in shellWindows)
            {
                try
                {
                    string location = window.LocationURL;
                    // Convertir l'URL file:/// en chemin normal
                    string windowPath = Uri.UnescapeDataString(
                        new Uri(location).LocalPath);

                    _logger.LogDebug("{} against {}", windowPath, path);
                    // Fermer si le chemin correspond ou est un sous-dossier
                    if (windowPath.StartsWith(path, StringComparison.OrdinalIgnoreCase))
                    {
                        _logger.LogDebug("Adding window with path {} to close list.", windowPath);
                        toClose.Add(window);
                    }
                }
                catch { /* Ignorer les fenêtres inaccessibles */ }
            }

            foreach (dynamic window in toClose)
            {
                try { window.Quit(); }
                catch (Exception e) { _logger.LogDebug(e.Message); }
            }
        }
        catch (Exception ex)
        {
            threadException = ex;
        }
    });

    staThread.SetApartmentState(ApartmentState.STA);
    staThread.Start();
    staThread.Join();

    if (threadException is not null)
        throw threadException;
}

大体上能起作用。但有一个标签页,无论我把它设置在哪里,它都不肯关掉。我没有异常。可能的原因是什么?显然Stack Overflow想让我多写点字,于是就有了这一段。

解决方案

嗯,我有时确实能重现……这很奇怪,但下面给出一个应该能起作用的变通方法,它使用 IWebBrowser2::HWNDPostMessage,让拥有者窗口来关闭:

var staThread = new Thread(() =>
{
    var shellType = Type.GetTypeFromCLSID(new Guid("9BA05972-F6A8-11CF-A442-00A0C90A8F39"))!;
    dynamic shellWindows = Activator.CreateInstance(shellType!)!;

    // we need to loop here because some windows may not close immediately
    do
    {
        var toClose = new List<dynamic>();
        foreach (var window in shellWindows)
        {
            // add your logic here...
            toClose.Add(window);
        }
        if (toClose.Count == 0)
            break;

        foreach (var window in toClose)
        {
            nint hwnd;
            try
            {
                hwnd = (nint)window.HWND;
            }
            catch
            {
                continue;
            }

            const uint WM_CLOSE = 0x0010;
            PostMessage(hwnd, WM_CLOSE, 0, 0);
        }
    } while (true);
});

staThread.SetApartmentState(ApartmentState.STA);
staThread.Start();
staThread.Join();

[DllImport("user32")]
public static extern nint PostMessage(nint hWnd, uint Msg, nint wParam, nint lParam);

PS:你对 IShellWindows 的定义没用,因为你使用了 dynamicIDispatch undercovers)。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章