potisanのプログラミングメモ

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

C# 4.5,9 FOLDERIDからフォルダのパスを取得する

追記20240902 現行のC# 12ではNull許容型、IntPtrの代わりにはnintが使えます。SHGetKnownFolderPath関数のP/Invokeでは[MarshalAs(UnmanagedType.LPWStr)] out string ppszPathで文字列取得とメモリ解放が可能です。

C# 9.0

using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;

var FOLDERID_DocumentsLibrary = new Guid("{7B0DB17D-9CD2-4A93-9733-46CC89022E7C}");

Console.WriteLine(SHGetKnownFolderPath(FOLDERID_DocumentsLibrary, 0));

[return: MaybeNull]
static string SHGetKnownFolderPath(
    in Guid knownFolderID,
    uint flags = 0,
    IntPtr tokenHandle = default)
{
    SafeCoTaskMemHandle handle = default;
    NativeMethods.SHGetKnownFolderPath(knownFolderID, flags, tokenHandle, out handle);
    using (handle)
    {
        return Marshal.PtrToStringUni(handle.DangerousGetHandle());
    }
}

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

sealed class SafeCoTaskMemHandle : SafeHandle
{
    private SafeCoTaskMemHandle()
        : base(IntPtr.Zero, true)
    {
    }

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

    public override bool IsInvalid => handle == IntPtr.Zero;

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

C# 4.5

失敗時は例外が発生することに注意してください。

using System;
using System.Runtime.InteropServices;
using System.Security;

namespace ConsoleApplication1
{
    [SecurityCritical]
    class Program
    {
        [SuppressUnmanagedCodeSecurity]
        private static class NativeMethods
        {
            [DllImport("shell32.dll", CharSet = CharSet.Unicode, PreserveSig = false)]
            public static extern SafeCoTaskMemHandle SHGetKnownFolderPath(
                ref Guid rfid,
                uint dwFlags,
                IntPtr hToken);
        }

        static void Main()
        {
            Guid FOLDERID_DocumentsLibrary = new Guid(
                "{7B0DB17D-9CD2-4A93-9733-46CC89022E7C}");

            Console.WriteLine(SHGetKnownFolderPath(
                FOLDERID_DocumentsLibrary, 0, IntPtr.Zero));

            Console.WriteLine("PRESS ANY KEY.");
            Console.ReadKey();
        }

        public static string SHGetKnownFolderPath(
            Guid knownFolderID,
            uint flags,
            IntPtr tokenHandle)
        {
            using (var handle = NativeMethods.SHGetKnownFolderPath(
                ref knownFolderID, flags, tokenHandle))
            {
                return Marshal.PtrToStringUni(
                    handle.DangerousGetHandle());
            }
        }
    }

    public sealed class SafeCoTaskMemHandle : SafeHandle
    {
        private SafeCoTaskMemHandle()
            : base(IntPtr.Zero, true)
        {
            // 戻り値としての作成に対応します。
        }

        public SafeCoTaskMemHandle(IntPtr handle, bool ownsHandle)
            : base(IntPtr.Zero, ownsHandle)
        {
            SetHandle(handle);
        }

        public override bool IsInvalid
        {
            get
            {
                return handle == IntPtr.Zero;
            }
        }

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

2021/3/10:この記事は別のブログで投稿した記事を移動したものです。