potisanのプログラミングメモ

趣味のプログラマーがプログラミング関係で気になったことや調べたことをいつでも忘れられるようにメモするブログです。はてなブログ無料版なので記事の上の方はたぶん広告です。記事中にも広告挿入されるみたいです。

C# Known Folderの表示名と型名を取得する(SHGetFileInfo版)

動作確認環境:C# 8.0(.NET Core 3.1)、C# 9.0(.NET 5.0)

  • SHGetKnownFolderIDList→アイテムIDリスト→SHGetFileInfoW→表示名・型名

Win32 APISHGetKnownFolderIDListSHGetFileInfoWを使用してKnown Folderの表示名と型名を取得するサンプルコードです。型名は常に""です。なお、別の方法としてSHGetNameFromIDList関数(Microsoft Docs)があります。

クラス 概要
Program Main関数
ShellKnownFolderIDList Known FolderのアイテムIDリスト(LPITEMIDLIST)をSafeCoTaskMemPointer型で取得する機能を提供するクラスです。SHGetKnownFolderIDListのラッパーです。
SafeCoTaskMemHandle アンマネージドリソースのMarshal.FreeCoTaskMem関数による解放を保証するためのクラスです。SafeHandleの派生クラスです。
ShellFileStringInfoForPIDL アイテムIDリスト(LPITEMIDLIST)からシェルのファイル情報を取得する機能を提供するクラスです。SHGetFileInfoWのラッパーです。

C# 9(トップレベステートメント

using System;
using System.Runtime.InteropServices;

// デスクトップの表示名を取得する。
{
    using var pidl = ShellKnownFolderIDList.GetDesktopIDList();
    var displayName = ShellFileStringInfoForPIDL.GetDisplayName(pidl, null);
    var typeName = ShellFileStringInfoForPIDL.GetTypeName(pidl, null); // ""
    Console.WriteLine((displayName, typeName));
    // (デスクトップ, )
}

// 更新プログラムフォルダの表示名を取得する。
{
    using var pidl = ShellKnownFolderIDList.GetAppUpdatesIDList();
    var displayName = ShellFileStringInfoForPIDL.GetDisplayName(pidl, null);
    var typeName = ShellFileStringInfoForPIDL.GetTypeName(pidl, null); // ""
    Console.WriteLine((displayName, typeName));
    // (インストールされた更新プログラム, )
}

static class ShellKnownFolderIDList
{
    static Guid FOLDERID_Desktop => new Guid("{B4BFCC3A-DB2C-424C-B029-7FE99A87C641}");
    static Guid FOLDERID_AppUpdates => new Guid("{a305ce99-f527-492b-8b1a-7e76fa98d6e4}");
    public static SafeCoTaskMemHandle GetDesktopIDList()
        => SHGetKnownFolderIDList(FOLDERID_Desktop);
    public static SafeCoTaskMemHandle GetAppUpdatesIDList()
        => SHGetKnownFolderIDList(FOLDERID_AppUpdates);

    public static SafeCoTaskMemHandle SHGetKnownFolderIDList(in Guid folderId)
    {
        NativeMethods.SHGetKnownFolderIDList(folderId, 0, default, out var pidl);
        return pidl;
    }

    static class NativeMethods
    {
        [DllImport("shell32.dll")]
        public static extern int SHGetKnownFolderIDList(
            in Guid rfid,
            uint dwFlags,
            IntPtr hToken,
            out SafeCoTaskMemHandle ppidl);
    }
}

sealed class SafeCoTaskMemHandle : SafeHandle
{
    SafeCoTaskMemHandle()
        : base(default, true)
    { }

    public SafeCoTaskMemHandle(IntPtr handle, bool ownsHandle)
        : base(handle, ownsHandle)
    { }

    public override bool IsInvalid => handle == default;

    protected override bool ReleaseHandle()
    {
        Marshal.FreeCoTaskMem(handle);
        return true;
    }
}

static class ShellFileStringInfoForPIDL
{
    const uint SHGFI_PIDL = 0x000000008;
    const uint SHGFI_DISPLAYNAME = 0x000000200;
    const uint SHGFI_TYPENAME = 0x000000400;

    //public static string GetDisplayName(SafeHandle pidl)
    //{
    //    var ret = NativeMethods.SHGetFileInfoW(
    //        pidl, out var shfi, SHGFI_DISPLAYNAME);
    //    if (ret == default)
    //        Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
    //    return shfi.szDisplayName;
    //}

    public static string GetDisplayName(SafeHandle pidl, string defaultValue)
    {
        var ret = NativeMethods.SHGetFileInfoW(
            pidl, out var shfi, SHGFI_DISPLAYNAME);
        return (ret != default) ? shfi.szDisplayName : defaultValue;
    }

    //public static string GetTypeName(SafeHandle pidl)
    //{
    //    var ret = NativeMethods.SHGetFileInfoW(
    //        pidl, out var shfi, SHGFI_TYPENAME);
    //    if (ret == default)
    //        Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
    //    return shfi.szTypeName;
    //}

    public static string GetTypeName(SafeHandle pidl, string defaultValue)
    {
        var ret = NativeMethods.SHGetFileInfoW(
            pidl, out var shfi, SHGFI_TYPENAME);
        return (ret != default) ? shfi.szTypeName : defaultValue;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    struct SHFILEINFOW
    {
        public IntPtr hIcon;
        public int iIcon;
        public uint dwAttributes;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        public string szDisplayName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
        public string szTypeName;
    }

    static class NativeMethods
    {
        [DllImport("shell32.dll", ExactSpelling = true, CharSet = CharSet.Unicode)]
        public static extern IntPtr SHGetFileInfoW(
            SafeHandle pszPath,
            uint dwFileAttributes,
            out SHFILEINFOW psfi,
            uint cbFileInfo,
            uint uFlags);

        public static IntPtr SHGetFileInfoW(SafeHandle pidl, out SHFILEINFOW psfi, uint flags)
            => SHGetFileInfoW(pidl, 0, out psfi, (uint)Marshal.SizeOf<SHFILEINFOW>(), flags | SHGFI_PIDL);
    }
}

Main関数(C# 8)

using System;
using System.Runtime.InteropServices;

class Program
{
    static void Main()
    {
        // デスクトップの情報を取得する。
        {
            using var pidl = ShellKnownFolderIDList.GetDesktopIDList();
            var displayName = ShellFileStringInfoForPIDL.GetDisplayName(pidl, null);
            var typeName = ShellFileStringInfoForPIDL.GetTypeName(pidl, null);
            Console.WriteLine((displayName, typeName));
            // (デスクトップ, )
        }

        // 更新プログラムフォルダの情報を取得する。
        {
            using var pidl = ShellKnownFolderIDList.GetAppUpdatesIDList();
            var displayName = ShellFileStringInfoForPIDL.GetDisplayName(pidl, null);
            var typeName = ShellFileStringInfoForPIDL.GetTypeName(pidl, null);
            Console.WriteLine((displayName, typeName));
            // (インストールされた更新プログラム, )
        }
    }
}

internal static class ShellKnownFolderIDList
{
    static Guid FOLDERID_Desktop => new Guid("{B4BFCC3A-DB2C-424C-B029-7FE99A87C641}");
    static Guid FOLDERID_AppUpdates => new Guid("{a305ce99-f527-492b-8b1a-7e76fa98d6e4}");
    public static SafeCoTaskMemHandle GetDesktopIDList()
        => SHGetKnownFolderIDList(FOLDERID_Desktop);
    public static SafeCoTaskMemHandle GetAppUpdatesIDList()
        => SHGetKnownFolderIDList(FOLDERID_AppUpdates);

    public static SafeCoTaskMemHandle SHGetKnownFolderIDList(in Guid folderId)
    {
        NativeMethods.SHGetKnownFolderIDList(folderId, 0, default, out var pidl);
        return pidl;
    }

    static class NativeMethods
    {
        [DllImport("shell32.dll")]
        public static extern int SHGetKnownFolderIDList(
            in Guid rfid,
            uint dwFlags,
            IntPtr hToken,
            out SafeCoTaskMemHandle ppidl);
    }
}

internal sealed class SafeCoTaskMemHandle : SafeHandle
{
    SafeCoTaskMemHandle()
        : base(default, true)
    { }

    public SafeCoTaskMemHandle(IntPtr handle, bool ownsHandle)
        : base(handle, ownsHandle)
    { }

    public override bool IsInvalid => handle == default;

    protected override bool ReleaseHandle()
    {
        Marshal.FreeCoTaskMem(handle);
        return true;
    }
}

internal static class ShellFileStringInfoForPIDL
{
    const uint SHGFI_PIDL = 0x000000008;
    const uint SHGFI_DISPLAYNAME = 0x000000200;
    const uint SHGFI_TYPENAME = 0x000000400;

    //public static string GetDisplayName(SafeHandle pidl)
    //{
    //    var ret = NativeMethods.SHGetFileInfoW(
    //        pidl, out var shfi, SHGFI_DISPLAYNAME);
    //    if (ret == default)
    //        Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
    //    return shfi.szDisplayName;
    //}

    public static string GetDisplayName(SafeHandle pidl, string defaultValue)
    {
        var ret = NativeMethods.SHGetFileInfoW(
            pidl, out var shfi, SHGFI_DISPLAYNAME);
        return (ret != default) ? shfi.szDisplayName : defaultValue;
    }

    //public static string GetTypeName(SafeHandle pidl)
    //{
    //    var ret = NativeMethods.SHGetFileInfoW(
    //        pidl, out var shfi, SHGFI_TYPENAME);
    //    if (ret == default)
    //        Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
    //    return shfi.szTypeName;
    //}

    public static string GetTypeName(SafeHandle pidl, string defaultValue)
    {
        var ret = NativeMethods.SHGetFileInfoW(
            pidl, out var shfi, SHGFI_TYPENAME);
        return (ret != default) ? shfi.szTypeName : defaultValue;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    struct SHFILEINFOW
    {
        public IntPtr hIcon;
        public int iIcon;
        public uint dwAttributes;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        public string szDisplayName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
        public string szTypeName;
    }

    static class NativeMethods
    {
        [DllImport("shell32.dll", ExactSpelling = true, CharSet = CharSet.Unicode)]
        public static extern IntPtr SHGetFileInfoW(
            SafeHandle pszPath,
            uint dwFileAttributes,
            out SHFILEINFOW psfi,
            uint cbFileInfo,
            uint uFlags);

        public static IntPtr SHGetFileInfoW(SafeHandle pidl, out SHFILEINFOW psfi, uint flags)
        {
            return SHGetFileInfoW(pidl, 0, out psfi,
                (uint)Marshal.SizeOf<SHFILEINFOW>(), flags | SHGFI_PIDL);
        }
    }
}