potisanのプログラミングメモ

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

C# シェルの「shell:~」形式からパスを取得する2

過去の投稿では関数を小分けにしていましたが、一つの機能としてまとめる関数を作成しても良いと思います。ついでに投稿後に追加されたトップレベルステートメントも使用しています。

using System.Runtime.InteropServices;

Console.WriteLine(ShellIDListUtility.GetPathFromDisplayName("shell:common startup"));

internal static class ShellIDListUtility
{
    public static SafeCoTaskMemPointer ParseDisplayName(string name)
    {
        var hr = NativeMethods.SHParseDisplayName(name, 0, out var pidl, 0, out var _);
        if (hr < 0)
            Marshal.ThrowExceptionForHR(hr);
        return pidl;
    }

    public static string GetPath(SafeHandle pidl)
    {
        const int EXPANDING = 260;
        for (int i = EXPANDING; i < int.MaxValue; i += EXPANDING)
        {
            var buffer = GC.AllocateUninitializedArray<char>(i);
            if (!NativeMethods.SHGetPathFromIDListEx(pidl, buffer, (uint)i, 0))
                Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
            var len = Array.IndexOf(buffer, '\0');
            if (len != i - 1)
                return new string(buffer, 0, len);
        }
        throw new InvalidDataException();
    }

    public static string GetPathFromDisplayName(string name)
    {
        using var pidl = ParseDisplayName(name);
        return GetPath(pidl);
    }

    private static class NativeMethods
    {
        [DllImport("shell32.dll", ExactSpelling = true, CharSet = CharSet.Unicode)]
        public static extern int SHParseDisplayName(
            string pszName,
            nint pbc,
            out SafeCoTaskMemPointer ppidl,
            uint sfgaoIn,
            out uint psfgaoOut);

        [DllImport("shell32.dll", ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool SHGetPathFromIDListEx(
            SafeHandle pidl,
            [Out] char[] pszPath,
            uint cchPath,
            uint uOpts);
    }
}

internal sealed class SafeCoTaskMemPointer : SafeHandle
{
    public SafeCoTaskMemPointer() : base(0, true) { }

    public SafeCoTaskMemPointer(nint handle, bool ownsHandle) : base(handle, ownsHandle) { }

    public override bool IsInvalid => handle == 0;

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