potisanのプログラミングメモ

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

C# Windows 10のレジストリキーハンドル情報を取得するコードとクラス

C#Windows 10のレジストリキーハンドルの情報を取得するには非公開のWin32 API NtQueryKeyが使用できます。NtQueryKey関数に渡す構造体は可変長配列を含みますが、バイト配列で受け取ってSystem.BitConverterSystem.TextEncoding.Unicode.GetStringを使えばポインタやunsafeを使わず扱えます。

using System;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Win32;
using Microsoft.Win32.SafeHandles;

class Program
{
    static void Main()
    {
        using var key = Registry.ClassesRoot.OpenSubKey(".txt");
        var basicInfo = QueryRegistryKey.QueryBasicInfo(key.Handle);
        // LastWriteTime: ...
        // Name:          ".txt"
        // TitleIndex:    0
        var name = QueryRegistryKey.QueryNameInfo(key.Handle);
        // \REGISTRY\USER\<ログオンユーザーのSID>_Classes\.txt
    }
}

internal static class QueryRegistryKey
{
    private static class NativeMethods
    {
        [DllImport("ntdll.dll")]
        public static extern int NtQueryKey(
            SafeRegistryHandle KeyHandle,
            KEY_INFORMATION_CLASS KeyInformationClass,
            byte[] KeyInformation,
            uint Length,
            out uint ResultLength);
    }

    public static KeyBasicInformation QueryBasicInfo(SafeRegistryHandle handle)
    {
        var buffer = QueryInfo(handle, KEY_INFORMATION_CLASS.KeyBasicInformation);

        var info = new KeyBasicInformation();
        info.LastWriteTime = new DateTime(BitConverter.ToInt64(buffer, 0));
        info.LastWriteTime = info.LastWriteTime.AddYears(1600);
        info.TitleIndex = BitConverter.ToUInt32(buffer, 8);
        var nameLength = BitConverter.ToUInt32(buffer, 12);
        info.Name = Encoding.Unicode.GetString(buffer, 16, (int)nameLength);
        return info;
    }

    public static string QueryNameInfo(SafeRegistryHandle handle)
    {
        var buffer = QueryInfo(handle, KEY_INFORMATION_CLASS.KeyNameInformation);

        var nameLength = BitConverter.ToUInt32(buffer, 0);
        return Encoding.Unicode.GetString(buffer, 4, (int)nameLength);
    }

    private static byte[] QueryInfo(SafeRegistryHandle handle, KEY_INFORMATION_CLASS infoClass)
    {
        const int STATUS_BUFFER_TOO_SMALL = unchecked((int)0xC0000023);
        var ret = NativeMethods.NtQueryKey(
            handle,
            infoClass,
            null, 0, out var length);
        if (ret != STATUS_BUFFER_TOO_SMALL)
            return null;

        var buffer = new byte[length];
        ret = NativeMethods.NtQueryKey(
            handle,
            infoClass,
            buffer, length, out length);
        return (ret == 0) ? buffer : null;
    }

    public enum KEY_INFORMATION_CLASS : int
    {
        KeyBasicInformation,
        KeyNodeInformation,
        KeyFullInformation,
        KeyNameInformation,
        KeyCachedInformation,
        KeyFlagsInformation,
        KeyVirtualizationInformation,
        KeyHandleTagsInformation,
        KeyTrustInformation,
        KeyLayerInformation,
        MaxKeyInfoClass
    }

    public struct KeyBasicInformation
    {
        public DateTime LastWriteTime;
        public uint TitleIndex;
        public string Name;
    }
}