2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Windows 11 エクスプローラで任意のフォルダを新しいタブで開く

Last updated at Posted at 2026-01-09

動機

この記事を見て、「WinAPI で実現できそう」と思ったので調べてみました。

環境

Windows 11 25H2
rustc 1.92.0
windows crate 0.62.2

実装

エクスプローラのタブを開くのはShellTabWindowClass0xA21Bを送ればよいらしい。この方法は Microsoft のドキュメントにはない非公式な方法なので、今後変更される可能性があります。↓参考。

開いたタブでフォルダを読み込むにはIWebBrowser2::Navigate2関数を呼べばいいとのこと。↓参考。

上記参考コードを Rust に写経します。短いので全部載せます。

Cargo.toml
[package]
name = "restore_tab"
version = "0.1.0"
edition = "2024"

[dependencies]
anyhow = "1.0"

[dependencies.windows]
version = "0.62"
features = [
  "Win32_UI_Shell",
  "Win32_UI_Shell_Common",
  "Win32_UI_WindowsAndMessaging",  
  "Win32_System_Com",
  "Win32_System_Com_StructuredStorage",
  "Win32_System_Ole",
  "Win32_System_Variant",
]
src/main.rs
use anyhow::{Result, anyhow};
use std::{
    cell::Cell,
    char::{REPLACEMENT_CHARACTER, decode_utf16},
    thread,
    time::Duration,
};
use windows::{
    Win32::{
        Foundation::{DISP_E_PARAMNOTFOUND, HWND, LPARAM, WPARAM},
        System::{
            Com::{CLSCTX_ALL, CoCreateInstance, CoInitialize, CoUninitialize},
            Variant::{InitVariantFromBuffer, VARIANT, VT_ERROR},
        },
        UI::{
            Shell::{ILCreateFromPathW, ILGetSize, IShellWindows, IWebBrowser2},
            WindowsAndMessaging::{
                EnumChildWindows, FindWindowW, GetClassNameW, SendMessageW, WM_COMMAND,
            },
        },
    },
    core::{BOOL, GUID, HSTRING, Interface, w},
};

// ExDisp.Idl
const CLSID_SHELL_WINDOWS: GUID = GUID {
    data1: 0x9BA05972,
    data2: 0xF6A8,
    data3: 0x11CF,
    data4: [0xA4, 0x42, 0x00, 0xA0, 0xC9, 0x0A, 0x8F, 0x39],
};

// ShellTabWindowClass のウィンドウハンドルを保持しておくためのグローバル変数
thread_local! {
    static TAB_HWND: Cell<Option<HWND>> = const { Cell::new(None) };
}

// COM を初期化・終了するための構造体
struct Com;

impl Com {
    fn new() -> Result<Self> {
        unsafe { CoInitialize(None).ok()? };
        Ok(Self)
    }
}

impl Drop for Com {
    fn drop(&mut self) {
        unsafe { CoUninitialize() };
    }
}

// EnumChildWindows に渡すコールバック関数
unsafe extern "system" fn enum_proc(hwnd: HWND, _: LPARAM) -> BOOL {
    let mut buf = [0; 256];
    unsafe { GetClassNameW(hwnd, &mut buf) };

    // Windows の文字列を Rust の文字列に変換
    let class_name = decode_utf16(buf.into_iter().take_while(|c| c.ne(&0)))
        .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))
        .collect::<String>();
        
    // クラス名が ShellTabWindowClass だったらグローバル変数にウィンドウハンドルを保存
    if class_name.eq("ShellTabWindowClass") {
        TAB_HWND.set(Some(hwnd));
        return false.into();
    }
    true.into()
}

// 引数に渡されたパスを新しいタブで開く関数。エクスプローラが起動してないと失敗します。
fn restore_tab(path: &[&str]) -> Result<()> {
    // エクスプローラの親ウィンドウを探す
    let hwnd = unsafe { FindWindowW(w!("CabinetWClass"), None)? };

    // ShellTabWindowClass を探す
    _ = unsafe { EnumChildWindows(Some(hwnd), Some(enum_proc), LPARAM::default()) };

    let Some(hwnd) = TAB_HWND.get() else {
        return Err(anyhow!("failed to retrieve shell tab window class"));
    };

    // COM の初期化
    let _com = Com::new()?;

    // IShellWindows の取得
    let windows: IShellWindows =
        unsafe { CoCreateInstance(&CLSID_SHELL_WINDOWS, None, CLSCTX_ALL)? };

    for p in path {
        // 新しいタブを開く
        unsafe { SendMessageW(hwnd, WM_COMMAND, Some(WPARAM(0xA21B)), None) };
        // タブが開くのを待つ
        thread::sleep(Duration::from_secs(1));

        // タブの数を取得
        let count = unsafe { windows.Count() }?;

        // オプションのVARIANTを構築
        let mut optional: VARIANT = DISP_E_PARAMNOTFOUND.0.into();
        unsafe { (*optional.Anonymous.Anonymous).vt = VT_ERROR };

        // 最後のタブを選択
        let index: VARIANT = (count - 1).into();
        let obj = unsafe { windows.Item(&index) }?;
        
        let app: IWebBrowser2 = obj.cast()?;
        let pidl = unsafe { ILCreateFromPathW(&HSTRING::from(*p)) };
        let url = unsafe { InitVariantFromBuffer(pidl as _, ILGetSize(Some(pidl))) }?;
        unsafe {
            // パスを開く
            app.Navigate2(
                &url,
                Some(&optional),
                Some(&optional),
                Some(&optional),
                Some(&optional),
            )?
        };
    }
    Ok(())
}

fn main() -> Result<()> {
    let path = ["C:\\", "C:\\Users", "C:\\Windows"];
    restore_tab(&path)?;
    Ok(())
}

まとめ

やったぜ!:relaxed:

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?