none
Access to both 32 and 64 bit registry using C# and REG_MULTI_SZ

    Question

  • I need a C# application which is delivered as either 32 bit or 64 bit to be able to access both 32 bit and 64 bit registry values.  For instance I'm getting the list of installed SQL Server instances which is in "HKLM\Software\Microsoft\Microsoft SQL Server\InstalledInstances".  When the application is 32 bit, I still want to see the installed 64 bit instances as well as the 32 bit instances.  Same if the app is 64 bit.

    From what I understand, to do this I need to use the Win32 API directly which I'm doing.  I call RegOpenKeyEx where I specify KEY_WOW64_32KEY or KEY_WOW64_64KEY and then I call RegQueryValueEx.  So far it seems to be working except the "InstalledInstances" key is a REG_MULTI_SZ.  The DllImport for RegQueryValueEx uses a StringBuilder for the returned value and I only seem to be able to get at the first string of the "InstalledInstances" key.  The length of the data returned by RegQueryValueEx is 25 which is for "SEISWARE\0SQLEXPRESS2005\0\0" but the Length of the StringBuilder is set to 8.  So how do I get at the other strings?

    Another question I have on this is just how to manage when the key is actually a REG_DWORD or REG_QWORD.  It looks like I get back a StringBuilder that is 4 or 8 characters long and each character is a byte of the data.  In this case, using a StringBuilder isn't really very helpful.  Do I really have to then convert it to a String and then get each Char out of the string and calculate the value?

    Also I have a lot of questions as to how Unicode is managed.  I'm not specifying to call the RegQueryValueW call so am I going to be handling Unicode properly?  If the key is stored in Unicode, will I get it back properly?  If it's not stored in Unicode, is it getting converted to Unicode for me?  Seems like there is some magic happening by saying that the parameter for the data is a StringBuilder and it would be more clear what was happening if I could get the data back in a char[] or even a byte[].

     

    Friday, April 09, 2010 8:29 PM

Answers

  • Here's an example that doesn't use marshalling. The RegQueryValue method returns object. That object's type depends on the type of the key read.

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Runtime.InteropServices;
    using System.Text;
    
    class Program
    {
      [DllImport("Advapi32.dll", EntryPoint = "RegOpenKeyExW", CharSet = CharSet.Unicode)]
      static extern int RegOpenKeyEx(IntPtr hKey, [In] string lpSubKey, int ulOptions, int samDesired, out IntPtr phkResult);
      [DllImport("Advapi32.dll", EntryPoint = "RegQueryValueExW", CharSet = CharSet.Unicode)]
      static extern int RegQueryValueEx(IntPtr hKey, [In] string lpValueName, IntPtr lpReserved, out int lpType, [Out] byte[] lpData, ref int lpcbData);
      [DllImport("advapi32.dll")]
      static extern int RegCloseKey(IntPtr hKey);
    
      static public readonly IntPtr HKEY_CLASSES_ROOT = new IntPtr(-2147483648);
      static public readonly IntPtr HKEY_CURRENT_USER = new IntPtr(-2147483647);
      static public readonly IntPtr HKEY_LOCAL_MACHINE = new IntPtr(-2147483646);
      static public readonly IntPtr HKEY_USERS = new IntPtr(-2147483645);
      static public readonly IntPtr HKEY_PERFORMANCE_DATA = new IntPtr(-2147483644);
      static public readonly IntPtr HKEY_CURRENT_CONFIG = new IntPtr(-2147483643);
      static public readonly IntPtr HKEY_DYN_DATA = new IntPtr(-2147483642);
    
      public const int KEY_READ = 0x20019;
      public const int KEY_WRITE = 0x20006;
      public const int KEY_QUERY_VALUE = 0x0001;
      public const int KEY_SET_VALUE = 0x0002;
      public const int KEY_WOW64_64KEY = 0x0100;
      public const int KEY_WOW64_32KEY = 0x0200;
    
      public const int REG_NONE = 0;
      public const int REG_SZ = 1;
      public const int REG_EXPAND_SZ = 2;
      public const int REG_BINARY = 3;
      public const int REG_DWORD = 4;
      public const int REG_DWORD_BIG_ENDIAN = 5;
      public const int REG_LINK = 6;
      public const int REG_MULTI_SZ = 7;
      public const int REG_RESOURCE_LIST = 8;
      public const int REG_FULL_RESOURCE_DESCRIPTOR = 9;
      public const int REG_RESOURCE_REQUIREMENTS_LIST = 10;
      public const int REG_QWORD = 11;
    
      static void Main(string[] args)
      {
        IntPtr key;
        int error;
        if ((error = RegOpenKeyEx(HKEY_CURRENT_USER, @"Software\Test", 0, KEY_READ | KEY_WOW64_32KEY, out key)) != 0)
          throw new Win32Exception(error);
        try
        {
          Console.WriteLine(RegQueryValue(key, "String"));
          Console.WriteLine(RegQueryValue(key, "ExpString"));
          foreach (string str in (string[])RegQueryValue(key, "MultiSz"))
            Console.WriteLine(str);
          Console.WriteLine(RegQueryValue(key, "Dword"));
          Console.WriteLine(RegQueryValue(key, "Qword"));
          Console.WriteLine(RegQueryValue(key, "NoValue", "'NoValue' doesn't exist"));
        }
        finally
        {
          RegCloseKey(key);
        }
      }
    
      static object RegQueryValue(IntPtr key, string value)
      {
        return RegQueryValue(key, value, null);
      }
    
      static object RegQueryValue(IntPtr key, string value, object defaultValue)
      {
        int error, type = 0, dataLength = 0xfde8;
        int returnLength = dataLength;
        byte[] data = new byte[dataLength];
        while ((error = RegQueryValueEx(key, value, IntPtr.Zero, out type, data, ref returnLength)) == 0xea)
        {
          dataLength *= 2;
          returnLength = dataLength;
          data = new byte[dataLength];
        }
        if (error == 2)
          return defaultValue; // value doesn't exist
        if (error != 0)
          throw new Win32Exception(error);
    
        switch (type)
        {
          case REG_NONE:
          case REG_BINARY:
            return data;
          case REG_DWORD:
            return (((data[0] | (data[1] << 8)) | (data[2] << 16)) | (data[3] << 24));
          case REG_DWORD_BIG_ENDIAN:
            return (((data[3] | (data[2] << 8)) | (data[1] << 16)) | (data[0] << 24));
          case REG_QWORD:
            {
              uint numLow = (uint)(((data[0] | (data[1] << 8)) | (data[2] << 16)) | (data[3] << 24));
              uint numHigh = (uint)(((data[4] | (data[5] << 8)) | (data[6] << 16)) | (data[7] << 24));
              return (long)(((ulong)numHigh << 32) | (ulong)numLow);
            }
          case REG_SZ:
            return Encoding.Unicode.GetString(data, 0, returnLength);
          case REG_EXPAND_SZ:
            return Environment.ExpandEnvironmentVariables(Encoding.Unicode.GetString(data, 0, returnLength));
          case REG_MULTI_SZ:
            {
              var strings = new List<string>();
              string packed = Encoding.Unicode.GetString(data, 0, returnLength);
              int start = 0;
              int end = packed.IndexOf('\0', start);
              while (end > start)
              {
                strings.Add(packed.Substring(start, end - start));
                start = end + 1;
                end = packed.IndexOf('\0', start);
              }
              return strings.ToArray();
            }
          default:
            throw new NotSupportedException();
        }
      }
    }
    
    • Edited by Tergiver Saturday, April 10, 2010 6:18 PM changed to return null if value doesn't exist
    • Proposed as answer by Ji.ZhouModerator Wednesday, April 14, 2010 3:27 AM
    • Marked as answer by Ji.ZhouModerator Friday, April 16, 2010 3:08 AM
    Saturday, April 10, 2010 5:49 PM

All replies

  • you can spin off a 32bit and a 64bit process and have them report back to you via socket/named pipe/remoting/wcf what they have found.

    The following is signature, not part of post
    Please mark the post answered your question as the answer, and mark other helpful posts as helpful.
    Visual C++ MVP
    Friday, April 09, 2010 11:41 PM
  • You have to use a CustomMarshaler to marshal an array of zero-terminated strings like REG_MULTI_SZ. I'll post a sample of how to do that below. The sample shows how to marshal the return value of GetEnvironmentStrings.

    For REG_DWORD or REG_QWORD, just create a different p/invoke signature for fetching those types so that the return value is int or long. Use Reflector to see how the built-in Registry class does it.

    The Unicode/Ascii problem is simple: C# is ALL Unicode. The String type is an array of Unicode characters, char is a single Unicode character. When you marshal, you simply have to know which encoding the source is so you use the correct marshal parameters. Yes, there is some magic happening with the default marshaler, quite a lot of magic actually.

    Saturday, April 10, 2010 2:51 PM
  • using System;
    using System.Collections.Generic;
    using System.Runtime.InteropServices;
    
    class Program
    {
        // Note that we are specifying the W version of GetEnvironmentStrings
        //  and specifying the W version of the marshaler
    
        [DllImport("kernel32.dll", EntryPoint="GetEnvironmentStringsW")]
        [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(StringArrayMarshalerW))]
        static extern string[] GetEnvironmentStrings();
        static void Main(string[] args)
        {
            foreach (string env in GetEnvironmentStrings())
                Console.WriteLine(env);
        }
    }
    
    public class StringArrayMarshalerW : ICustomMarshaler
    {
        static ICustomMarshaler GetInstance(string cookie)
            { return new StringArrayMarshalerW(); }
    
        public void CleanUpManagedData(object ManagedObj) { }
        public void CleanUpNativeData(IntPtr pNativeData) { }
        public int GetNativeDataSize()
            { throw new NotSupportedException(); }
        public IntPtr MarshalManagedToNative(object ManagedObj)
            { throw new NotSupportedException(); }
    
        public unsafe object MarshalNativeToManaged(IntPtr pNativeData)
        {
            var strings = new List<string>();
            char* start = (char*)pNativeData;
            while (*start != '\0')
            {
                string newString = new string(start);
                strings.Add(newString);
                start += newString.Length + 1;
            }
            return strings.ToArray();
        }
    }
    
    public class StringArrayMarshalerA : ICustomMarshaler
    {
        static ICustomMarshaler GetInstance(string cookie)
            { return new StringArrayMarshalerA(); }
    
        public void CleanUpManagedData(object ManagedObj) { }
        public void CleanUpNativeData(IntPtr pNativeData) { }
        public int GetNativeDataSize()
            { throw new NotSupportedException(); }
        public IntPtr MarshalManagedToNative(object ManagedObj)
            { throw new NotSupportedException(); }
    
        public unsafe object MarshalNativeToManaged(IntPtr pNativeData)
        {
            var strings = new List<string>();
            sbyte* start = (sbyte*)pNativeData;
            while (*start != '\0')
            {
                string newString = new string(start);
                strings.Add(newString);
                start += newString.Length + 1;
            }
            return strings.ToArray();
        }
    }
    
    Saturday, April 10, 2010 2:52 PM
  • Here's an example that doesn't use marshalling. The RegQueryValue method returns object. That object's type depends on the type of the key read.

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Runtime.InteropServices;
    using System.Text;
    
    class Program
    {
      [DllImport("Advapi32.dll", EntryPoint = "RegOpenKeyExW", CharSet = CharSet.Unicode)]
      static extern int RegOpenKeyEx(IntPtr hKey, [In] string lpSubKey, int ulOptions, int samDesired, out IntPtr phkResult);
      [DllImport("Advapi32.dll", EntryPoint = "RegQueryValueExW", CharSet = CharSet.Unicode)]
      static extern int RegQueryValueEx(IntPtr hKey, [In] string lpValueName, IntPtr lpReserved, out int lpType, [Out] byte[] lpData, ref int lpcbData);
      [DllImport("advapi32.dll")]
      static extern int RegCloseKey(IntPtr hKey);
    
      static public readonly IntPtr HKEY_CLASSES_ROOT = new IntPtr(-2147483648);
      static public readonly IntPtr HKEY_CURRENT_USER = new IntPtr(-2147483647);
      static public readonly IntPtr HKEY_LOCAL_MACHINE = new IntPtr(-2147483646);
      static public readonly IntPtr HKEY_USERS = new IntPtr(-2147483645);
      static public readonly IntPtr HKEY_PERFORMANCE_DATA = new IntPtr(-2147483644);
      static public readonly IntPtr HKEY_CURRENT_CONFIG = new IntPtr(-2147483643);
      static public readonly IntPtr HKEY_DYN_DATA = new IntPtr(-2147483642);
    
      public const int KEY_READ = 0x20019;
      public const int KEY_WRITE = 0x20006;
      public const int KEY_QUERY_VALUE = 0x0001;
      public const int KEY_SET_VALUE = 0x0002;
      public const int KEY_WOW64_64KEY = 0x0100;
      public const int KEY_WOW64_32KEY = 0x0200;
    
      public const int REG_NONE = 0;
      public const int REG_SZ = 1;
      public const int REG_EXPAND_SZ = 2;
      public const int REG_BINARY = 3;
      public const int REG_DWORD = 4;
      public const int REG_DWORD_BIG_ENDIAN = 5;
      public const int REG_LINK = 6;
      public const int REG_MULTI_SZ = 7;
      public const int REG_RESOURCE_LIST = 8;
      public const int REG_FULL_RESOURCE_DESCRIPTOR = 9;
      public const int REG_RESOURCE_REQUIREMENTS_LIST = 10;
      public const int REG_QWORD = 11;
    
      static void Main(string[] args)
      {
        IntPtr key;
        int error;
        if ((error = RegOpenKeyEx(HKEY_CURRENT_USER, @"Software\Test", 0, KEY_READ | KEY_WOW64_32KEY, out key)) != 0)
          throw new Win32Exception(error);
        try
        {
          Console.WriteLine(RegQueryValue(key, "String"));
          Console.WriteLine(RegQueryValue(key, "ExpString"));
          foreach (string str in (string[])RegQueryValue(key, "MultiSz"))
            Console.WriteLine(str);
          Console.WriteLine(RegQueryValue(key, "Dword"));
          Console.WriteLine(RegQueryValue(key, "Qword"));
          Console.WriteLine(RegQueryValue(key, "NoValue", "'NoValue' doesn't exist"));
        }
        finally
        {
          RegCloseKey(key);
        }
      }
    
      static object RegQueryValue(IntPtr key, string value)
      {
        return RegQueryValue(key, value, null);
      }
    
      static object RegQueryValue(IntPtr key, string value, object defaultValue)
      {
        int error, type = 0, dataLength = 0xfde8;
        int returnLength = dataLength;
        byte[] data = new byte[dataLength];
        while ((error = RegQueryValueEx(key, value, IntPtr.Zero, out type, data, ref returnLength)) == 0xea)
        {
          dataLength *= 2;
          returnLength = dataLength;
          data = new byte[dataLength];
        }
        if (error == 2)
          return defaultValue; // value doesn't exist
        if (error != 0)
          throw new Win32Exception(error);
    
        switch (type)
        {
          case REG_NONE:
          case REG_BINARY:
            return data;
          case REG_DWORD:
            return (((data[0] | (data[1] << 8)) | (data[2] << 16)) | (data[3] << 24));
          case REG_DWORD_BIG_ENDIAN:
            return (((data[3] | (data[2] << 8)) | (data[1] << 16)) | (data[0] << 24));
          case REG_QWORD:
            {
              uint numLow = (uint)(((data[0] | (data[1] << 8)) | (data[2] << 16)) | (data[3] << 24));
              uint numHigh = (uint)(((data[4] | (data[5] << 8)) | (data[6] << 16)) | (data[7] << 24));
              return (long)(((ulong)numHigh << 32) | (ulong)numLow);
            }
          case REG_SZ:
            return Encoding.Unicode.GetString(data, 0, returnLength);
          case REG_EXPAND_SZ:
            return Environment.ExpandEnvironmentVariables(Encoding.Unicode.GetString(data, 0, returnLength));
          case REG_MULTI_SZ:
            {
              var strings = new List<string>();
              string packed = Encoding.Unicode.GetString(data, 0, returnLength);
              int start = 0;
              int end = packed.IndexOf('\0', start);
              while (end > start)
              {
                strings.Add(packed.Substring(start, end - start));
                start = end + 1;
                end = packed.IndexOf('\0', start);
              }
              return strings.ToArray();
            }
          default:
            throw new NotSupportedException();
        }
      }
    }
    
    • Edited by Tergiver Saturday, April 10, 2010 6:18 PM changed to return null if value doesn't exist
    • Proposed as answer by Ji.ZhouModerator Wednesday, April 14, 2010 3:27 AM
    • Marked as answer by Ji.ZhouModerator Friday, April 16, 2010 3:08 AM
    Saturday, April 10, 2010 5:49 PM