2

I have a registry HKEY that is openend with RegOpenKeyEx (WinApi). Now I would like to convert the HKEY to the object Microsoft.Win32.RegistryKey. This would allow me to use the more convenient .Net operations to further proceed with this key.

Do you know how this conversion can be done in a reliable way for C# .Net 2.0 not higher?

Thanks for your help!

I tried to use reflection to access RegistryKey.GetBaseKey(hKey) for the conversion from HKEY to RegistryKey but this failed:

[DllImport("advapi32.dll", CharSet = CharSet.Auto)]
public static extern int RegOpenKeyEx(IntPtr hKey, string subKey, int ulOptions, int samDesired, out IntPtr phkResult);

public enum RegWow64Options
{
    None = 0,
    KEY_WOW64_64KEY = 0x0100,
    KEY_WOW64_32KEY = 0x0200
}

public enum RegRights
{
    ReadKey = 131097,
    WriteKey = 131078
}

static void exampleTransformKeytoRegistryKey()
{
    IntPtr hKeyChild;
    IntPtr hKeyParent = getRegistryKeyHandle(Registry.LocalMachine);

    if (hKeyParent != IntPtr.Zero)
    {
        int result = RegOpenKeyEx(
            getRegistryKeyHandle(Registry.LocalMachine),
            @"SOFTWARE\Microsoft",
            0,
            ((int)RegRights.ReadKey) | ((int)RegWow64Options.KEY_WOW64_64KEY),
            out hKeyChild);

        if (result == 0)
        {
            // hKeyChild has been retrieved
            // now convert hKeyChild to RegistryKey keyChild

            Type keyType = typeof(RegistryKey);
            RegistryKey keyChild = (RegistryKey)keyType.InvokeMember(
                "GetBaseKey",
                System.Reflection.BindingFlags.InvokeMethod | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static,
                null,
                keyType,
                new object[] { hKeyChild });

            // work with keyChild...
        }
    }
}

static IntPtr getRegistryKeyHandle(RegistryKey registryKey)
{
    Type registryKeyType = typeof(RegistryKey);
    System.Reflection.FieldInfo fieldInfo =
        registryKeyType.GetField("hkey", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);

    SafeHandle handle = (SafeHandle)fieldInfo.GetValue(registryKey);
    IntPtr dangerousHandle = handle.DangerousGetHandle();
    return dangerousHandle;
}

Update: The following approach will work (to some degree). Please focus on the function getKeyToRegistryKey.

[DllImport("advapi32.dll", CharSet = CharSet.Auto)]
public static extern int RegOpenKeyEx(IntPtr hKey, string subKey, int ulOptions, int samDesired, out IntPtr phkResult);

public enum RegWow64Options
{
    None = 0,
    KEY_WOW64_64KEY = 0x0100,
    KEY_WOW64_32KEY = 0x0200
}

public enum RegRights
{
    ReadKey = 131097,
    WriteKey = 131078
}

static void exampleTransformKeytoRegistryKey2()
{
    IntPtr hKeyChild;
    IntPtr hKeyParent = getRegistryKeyHandle(Registry.LocalMachine);

    if (hKeyParent != IntPtr.Zero)
    {
        int result = RegOpenKeyEx(
            getRegistryKeyHandle(Registry.LocalMachine),
            @"SOFTWARE\Microsoft",
            0,
            ((int)RegRights.ReadKey) | ((int)RegWow64Options.KEY_WOW64_32KEY),
            out hKeyChild);

        if (result == 0)
        {
            // hKeyChild has been retrieved
            // now convert hKeyChild to RegistryKey keyChild
            RegistryKey keyChild = getKeyToRegistryKey(hKeyChild, false, true);

            // work with keyChild...
        }
    }
}

static RegistryKey getKeyToRegistryKey(IntPtr hKey, bool writable, bool ownsHandle)
{
    //Get the BindingFlags for private contructors
    System.Reflection.BindingFlags privateConstructors = System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic;
    //Get the Type for the SafeRegistryHandle
    Type safeRegistryHandleType = typeof(Microsoft.Win32.SafeHandles.SafeHandleZeroOrMinusOneIsInvalid).Assembly.GetType("Microsoft.Win32.SafeHandles.SafeRegistryHandle");
    //Get the array of types matching the args of the ctor we want
    Type[] safeRegistryHandleCtorTypes = new Type[] { typeof(IntPtr), typeof(bool) };
    //Get the constructorinfo for our object
    System.Reflection.ConstructorInfo safeRegistryHandleCtorInfo = safeRegistryHandleType.GetConstructor(
            privateConstructors, null, safeRegistryHandleCtorTypes, null);
    //Invoke the constructor, getting us a SafeRegistryHandle
    Object safeHandle = safeRegistryHandleCtorInfo.Invoke(new Object[] { hKey, ownsHandle });

    //Get the type of a RegistryKey
    Type registryKeyType = typeof(RegistryKey);
    //Get the array of types matching the args of the ctor we want
    Type[] registryKeyConstructorTypes = new Type[] { safeRegistryHandleType, typeof(bool) };
    //Get the constructorinfo for our object
    System.Reflection.ConstructorInfo registryKeyCtorInfo = registryKeyType.GetConstructor(
        privateConstructors, null, registryKeyConstructorTypes, null);
    //Invoke the constructor, getting us a RegistryKey
    RegistryKey resultKey = (RegistryKey)registryKeyCtorInfo.Invoke(new Object[] { safeHandle, writable });
    //return the resulting key
    return resultKey;
}

static IntPtr getRegistryKeyHandle(RegistryKey registryKey)
{
    Type registryKeyType = typeof(RegistryKey);
    System.Reflection.FieldInfo fieldInfo =
    registryKeyType.GetField("hkey", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);

    SafeHandle handle = (SafeHandle)fieldInfo.GetValue(registryKey);
    IntPtr dangerousHandle = handle.DangerousGetHandle();
    return dangerousHandle;
}

The problem with this approach is that it will only work in pure .Net 2.0 applications. If you use the code in a .Net 2.0 DLL and try to use it from a .Net 4.0 application the code will fail. Thus I still hope to find another solution that works in mixed environments.

Holger Schmeken
  • 605
  • 6
  • 16
  • Can you post some code? – Matt Johnson Jan 17 '14 at 19:47
  • 1
    You might want to use something like http://stackoverflow.com/questions/937044/determine-path-to-registry-key-from-hkey-handle-in-c to get the path, then open it again. – josh poley Jan 17 '14 at 19:52
  • @Josh: I work around registry deflection here. So in my opinion the path alone is not sufficient to describe these differences. By using the native API call to RegOpenKeyEx I can open the 32 bit hive first. When I am done with this hive I will open the 64 bit hive. Since .Net 2.0 is missing the RegistryKey.OpenBaseKey operation I am trying to find a way around this limitation. – Holger Schmeken Jan 17 '14 at 20:28
  • @Hans: could you help me with this reflection? I edited my question to add an example. – Holger Schmeken Jan 18 '14 at 12:50
  • @Hans: according to p/invoke it should be an UIntPtr not IntPtr to prevent an overflow. But it does not matter because the reflection will still not work: http://www.pinvoke.net/default.aspx/advapi32.regopenkeyex – Holger Schmeken Jan 18 '14 at 13:05
  • @Hans: I added more code. – Holger Schmeken Jan 18 '14 at 13:57
  • Presumably, you should wrap your `getRegistryKeyHandle` function in error-checking and if it fails to find the desired field via reflection, look for the later-introduced `RegistryKey.FromHandle` function instead. Or look for the improved constructor first, then fallback. – EricLaw Jan 20 '14 at 16:31

3 Answers3

4

Use RegistryKey.FromHandle(new SafeRegistryHandle(handle,true));

EricLaw
  • 56,563
  • 7
  • 151
  • 196
  • Thanks. Sadly this elegant solution needs at least .Net 4.0. In my case I need a solution for .Net 2.0. – Holger Schmeken Jan 17 '14 at 20:17
  • 1
    @Frankenstein: Your solution for .NET 2.0 is to p/invoke the registry APIs. The fact that `FromHandle` was added in .NET 4 is indicative that there was no existing way to do this. – Ben Voigt Jan 17 '14 at 22:25
  • @Frankenstein Have you tried using dotPeek (https://www.jetbrains.com/decompiler/) to see how the .NET source creates a RegistryKey object from a handle? – Alexandru Mar 04 '15 at 13:41
  • @Frankenstein If you could use the RegistryKey constructor part of .NET it would be so easy, because it takes in a handle, but the problem is that its marked as System.Security.SecurityCritical. – Alexandru Mar 04 '15 at 14:11
2

Given that you target .net 2, there is simply no way out for you. You need to p/invoke this all the way. If you were using .net 4 then you would not need any p/invoke at all because you could use the RegistryView enumeration. But for .net 2 the only option is to p/invoke the native registry APIs.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • p/invoke might not be the only solution. At least I am not willing to be defeated yet. I updated my code and added the function exampleTransformKeytoRegistryKey2 to demonstrate the approach. It will work in .Net 2.0 applications. My only problem is that I try to use the solution in a .Net 2.0 DLL from a .Net 4.0 service. Sadly in this scenario my proposed solution will fail. – Holger Schmeken Jan 20 '14 at 11:04
  • Fine. Do it your way if you prefer. – David Heffernan Jan 20 '14 at 11:17
0

Its not pretty but its totally doable using reflection. I found this trick in the XSharper source code, and you may try something similar to fit your needs.

public static class RegistryExtensions
{

    public enum RegistryHiveType
    {
        X86,
        X64
    }

    static Dictionary<RegistryHive, UIntPtr> _hiveKeys = new Dictionary<RegistryHive, UIntPtr> {
        { RegistryHive.ClassesRoot, new UIntPtr(0x80000000u) },
        { RegistryHive.CurrentConfig, new UIntPtr(0x80000005u) },
        { RegistryHive.CurrentUser, new UIntPtr(0x80000001u) },
        { RegistryHive.DynData, new UIntPtr(0x80000006u) },
        { RegistryHive.LocalMachine, new UIntPtr(0x80000002u) },
        { RegistryHive.PerformanceData, new UIntPtr(0x80000004u) },
        { RegistryHive.Users, new UIntPtr(0x80000003u) }
    };

    static Dictionary<RegistryHiveType, RegistryAccessMask> _accessMasks = new Dictionary<RegistryHiveType, RegistryAccessMask> {
        { RegistryHiveType.X64, RegistryAccessMask.Wow6464 },
        { RegistryHiveType.X86, RegistryAccessMask.WoW6432 }
    };

    [Flags]
    public enum RegistryAccessMask
    {
        QueryValue          = 0x0001,
        SetValue            = 0x0002,
        CreateSubKey        = 0x0004,
        EnumerateSubKeys    = 0x0008,
        Notify              = 0x0010,
        CreateLink          = 0x0020,
        WoW6432             = 0x0200,
        Wow6464             = 0x0100,
        Write               = 0x20006,
        Read                = 0x20019,
        Execute             = 0x20019,
        AllAccess           = 0xF003F
    }

    [DllImport("advapi32.dll", CharSet = CharSet.Auto)]
    public static extern int RegOpenKeyEx(
      UIntPtr hKey,
      string subKey,
      uint ulOptions,
      uint samDesired,
      out IntPtr hkResult);

    public static RegistryKey OpenBaseKey(RegistryHive registryHive, RegistryHiveType registryType)
    {
        UIntPtr hiveKey = _hiveKeys[registryHive];
        if (Environment.OSVersion.Platform == PlatformID.Win32NT && Environment.OSVersion.Version.Major > 5)
        {
            RegistryAccessMask flags = RegistryAccessMask.QueryValue | RegistryAccessMask.EnumerateSubKeys | RegistryAccessMask.SetValue | RegistryAccessMask.CreateSubKey | _accessMasks[registryType];
            IntPtr keyHandlePointer = IntPtr.Zero;
            int result = RegOpenKeyEx(hiveKey, String.Empty, 0, (uint)flags, out keyHandlePointer);
            if (result == 0)
            {
                var safeRegistryHandleType = typeof(SafeHandleZeroOrMinusOneIsInvalid).Assembly.GetType("Microsoft.Win32.SafeHandles.SafeRegistryHandle");
                var safeRegistryHandleConstructor = safeRegistryHandleType.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { typeof(IntPtr), typeof(bool) }, null); // .NET < 4
                if (safeRegistryHandleConstructor == null)
                    safeRegistryHandleConstructor = safeRegistryHandleType.GetConstructor(BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(IntPtr), typeof(bool) }, null); // .NET >= 4
                var keyHandle = safeRegistryHandleConstructor.Invoke(new object[] { keyHandlePointer, true });
                var net3Constructor = typeof(RegistryKey).GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { safeRegistryHandleType, typeof(bool) }, null);
                var net4Constructor = typeof(RegistryKey).GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { typeof(IntPtr), typeof(bool), typeof(bool), typeof(bool), typeof(bool) }, null);
                object key;
                if (net4Constructor != null)
                    key = net4Constructor.Invoke(new object[] { keyHandlePointer, true, false, false, hiveKey == _hiveKeys[RegistryHive.PerformanceData] });
                else if (net3Constructor != null)
                    key = net3Constructor.Invoke(new object[] { keyHandle, true });
                else
                {
                    var keyFromHandleMethod = typeof(RegistryKey).GetMethod("FromHandle", BindingFlags.Static | BindingFlags.Public, null, new[] { safeRegistryHandleType }, null);
                    key = keyFromHandleMethod.Invoke(null, new object[] { keyHandle });
                }
                var field = typeof(RegistryKey).GetField("keyName", BindingFlags.Instance | BindingFlags.NonPublic);
                if (field != null)
                    field.SetValue(key, String.Empty);
                return (RegistryKey)key;
            }
            else if (result == 2) // The key does not exist.
                return null;
            throw new Win32Exception(result);
        }
        throw new PlatformNotSupportedException("The platform or operating system must be Windows 2000 or later.");
    }
}

Example usage:

var key64 = RegistryExtensions.OpenBaseKey(RegistryHive.LocalMachine, RegistryExtensions.RegistryHiveType.X64);
var key32 = RegistryExtensions.OpenBaseKey(RegistryHive.LocalMachine, RegistryExtensions.RegistryHiveType.X86);
Alexandru
  • 12,264
  • 17
  • 113
  • 208