potisanのプログラミングメモ

プログラミング素人です。昔の自分を育ててくれたネット情報に少しでも貢献できるよう、情報を貯めていこうと思っています。Windows環境のC++やC#がメインです。

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