' The purpose of this Include file...
' I discovered QRegistry can not access HKEY_LOCAL_MACHINE or HKEY_USERS in Win7.
' If your program just needs to READ info from these RootKeys to function properly...
' this Include file will do the job.
'
' As far as I know... it can READ from ANY RootKey/Path/Key
' and might actually be easier to use than QRegistry.
' A call to "RegReadValue" will return ANY type of value as a string.
' (no need to specify readbinary, or readfloat, or readinteger, or readstring)
' (but you will have to parse and/or convert the return string, if need be)
' (the only returned value that might give you trouble is a 64-bit number, see the notes below)

' The possible return codes
' (from calling the API functions used in this Include file)
Const           ERROR_SUCCESS = 0
Const             ERROR_BADDB = 1009
Const            ERROR_BADKEY = 1010
Const          ERROR_CANTOPEN = 1011
Const          ERROR_CANTREAD = 1012
Const         ERROR_CANTWRITE = 1013
Const       ERROR_OUTOFMEMORY = 14
Const ERROR_INVALID_PARAMETER = 87
Const     ERROR_ACCESS_DENIED = 5
Const     ERROR_NO_MORE_ITEMS = 259
Const         ERROR_MORE_DATA = 234

' The various Key "Types"
' If you do encounter any of the UnTested ones...
' let me know what the RootKey, Path, and Key are.
' (so I can update this Include file. if need be)
Const                       REG_NONE = 0  ' UnTested
Const                         REG_SZ = 1
Const                  REG_EXPAND_SZ = 2
Const                     REG_BINARY = 3
Const                      REG_DWORD = 4
Const        REG_DWORD_LITTLE_ENDIAN = 4
Const           REG_DWORD_BIG_ENDIAN = 5  ' UnTested
Const                       REG_LINK = 6  ' UnTested
Const                   REG_MULTI_SZ = 7
Const              REG_RESOURCE_LIST = 8
Const   REG_FULL_RESOURCE_DESCRIPTOR = 9
Const REG_RESOURCE_REQUIREMENTS_LIST = 10 ' UnTested
Const                      REG_QWORD = 11

' The Desired Action(s) you want to do with the Registry
' This is a full list, but only KEY_READ is being used here
Const          KEY_QUERY_VALUE = &H1
Const            KEY_SET_VALUE = &H2
Const       KEY_CREATE_SUB_KEY = &H4
Const   KEY_ENUMERATE_SUB_KEYS = &H8
Const               KEY_NOTIFY = &H10
Const          KEY_CREATE_LINK = &H20
Const             READ_CONTROL = &H20000
Const                WRITE_DAC = &H40000
Const              WRITE_OWNER = &H80000
Const              SYNCHRONIZE = &H100000
Const STANDARD_RIGHTS_REQUIRED = &HF0000
Const     STANDARD_RIGHTS_READ = READ_CONTROL
Const    STANDARD_RIGHTS_WRITE = READ_CONTROL
Const  STANDARD_RIGHTS_EXECUTE = READ_CONTROL
Const                 KEY_READ = STANDARD_RIGHTS_READ Or KEY_QUERY_VALUE Or KEY_ENUMERATE_SUB_KEYS Or KEY_NOTIFY
Const                KEY_WRITE = STANDARD_RIGHTS_WRITE Or KEY_SET_VALUE Or KEY_CREATE_SUB_KEY
Const              KEY_EXECUTE = KEY_READ

' The various RootKeys
Const     HKEY_CLASSES_ROOT = &H80000000 ' QRegistry:YES, API:Yes
Const     HKEY_CURRENT_USER = &H80000001 ' QRegistry:YES, API:Yes
Const    HKEY_LOCAL_MACHINE = &H80000002 ' QRegistry:NO,  API:Yes
Const            HKEY_USERS = &H80000003 ' QRegistry:NO,  API:Yes
Const HKEY_PERFORMANCE_DATA = &H80000004 ' QRegistry:???, API:Yes
Const   HKEY_CURRENT_CONFIG = &H80000005 ' QRegistry:YES, API:Yes
Const         HKEY_DYN_DATA = &H80000006 ' QRegistry:???, API:Yes

' Note: Some of the parameters (in these API functions) were altered to use a LONG
'       (Why? Because it was easier to work with than a STRING)
'       (Thankfully these API functions allowed me to do this)
Declare Function RegOpenKeyEx _
        Lib "advapi32" Alias "RegOpenKeyExA" _
        (ByVal hKey As Long, ByVal lpSubKey As Long, _
         ByVal ulOptions As Long, ByVal samDesired As Long, _
         ByVal phkResult As Long) As Long

Declare Function RegQueryValueEx _
        Lib "advapi32" Alias "RegQueryValueExA" _
        (ByVal hKey As Long, ByVal lpValueName As Long, _
         ByVal lpReserved As Long, ByVal lpType As Long, _
         ByVal lpData As Long, ByVal lpcbData As Long) As Long

Declare Function RegCloseKey _
        Lib "advapi32" Alias "RegCloseKey" _
        (ByVal hKey As Long) As Long

' Provides a string version of the returned error code
Function EnumErr(ByVal Err As Long) As String
    Select Case Err
      Case ERROR_SUCCESS
        Result = "ERROR_SUCCESS" ' <- oxymoron?
      Case ERROR_BADDB
        Result = "ERROR_BADDB"
      Case ERROR_BADKEY
        Result = "ERROR_BADKEY"
      Case ERROR_CANTOPEN
        Result = "ERROR_CANTOPEN"
      Case ERROR_CANTREAD
        Result = "ERROR_CANTREAD"
      Case ERROR_CANTWRITE
        Result = "ERROR_CANTWRITE"
      Case ERROR_OUTOFMEMORY
        Result = "ERROR_OUTOFMEMORY"
      Case ERROR_INVALID_PARAMETER
        Result = "ERROR_INVALID_PARAMETER"
      Case ERROR_ACCESS_DENIED
        Result = "ERROR_ACCESS_DENIED"
      Case ERROR_NO_MORE_ITEMS
        Result = "ERROR_NO_MORE_ITEMS"
      Case ERROR_MORE_DATA
        Result = "ERROR_MORE_DATA"
      Case Else
        Result = "ERROR_UNDEFINED"
    End Select
End Function

' NOTE #1: Numeric values (in the Result) are in hexidecimal format.
' Why? Because they provide the shortest possible return string.
' Ex: "FF FF FF FF FF FF FF FF" is shorter than "255 255 255 255 255 255 255 255"
' Use "sDec = ConvBase$(sHex, 16, 10)" to convert hex to dec.
' (sHex is a string version of a hexidecimal number, for example "4F")
' Use "x = Val(sDec)" to convert to a numeric value.
' (Make sure you read the SPECIAL NOTE about 64-bit numbers below, a converted 64-bit number "might be" rounded)
'
' NOTE #2: String values (in the Result) are TAB delimited.
' Why? Because, to the best of my knowledge, a TAB is the only character not used in the registry.
' As an added benefit... it also makes it easy to determine how many values were returned within the Result string.
' Use "x = Tally(result, Chr$(9))" to determine how many string values were returned

' user-made function that will read ANY type of value
Function RegReadValue(ByVal RootKey As Long, ByVal Path As String, ByVal Key As String) As String
    Dim      hKey As Long
    Dim      lRtn As Long
    Dim     lType As Long
    Dim     lSize As Long
    Dim   lBuffer As Long
    Dim         i As Long
    Dim         n As Byte
    Dim     sPath As String
    Dim      sKey As String
    Dim   sBuffer As String
    Dim   MemStrm As QMemoryStream

' Setting up everything that was Dim'ed, for usage below
                hKey = 0
                lRtn = -1
               lType = 0
               lSize = 4
             lBuffer = 0
                   i = 0
                   n = 0
               sPath = Path
                sKey = Key
             sBuffer = ""
    MemStrm.Position = 0
        MemStrm.Size = 0
    MemStrm.Close
 
    ' provides a Handle to the registry (after validating the RootKey and Path parameters)
    ' the Handle is a required parameter in the following function calls
    '  the use of VarPtr for the sPath: is for the ease of passing a string as a required parameter
    '  the use of VarPtr for hKey: is because this function will store the Handle in this variable (upon completion)
    lRtn = RegOpenKeyEx(RootKey, VarPtr(sPath), 0, KEY_READ, VarPtr(hKey))

    If EnumErr(lRtn) = "ERROR_SUCCESS" Then
      ' 1st call: provides the Type and Size of the Value stored at the Key parameter (if the Key is valid)
      ' the Type and Size are required parameters for the 2nd time this function is called (below)
      ' the 5th parameter is set to zero because we only want to find out the Type and Size of the Key
      '  the use of VarPtr for the sKey: is for the ease of passing a string as a required parameter
      '  the use of VarPtr for lType and lSize: is because this function will store the necessary info in these variables (upon completion)
       lRtn = RegQueryValueEx(hKey, VarPtr(sKey), 0, VarPtr(lType), 0, VarPtr(lSize))

       If EnumErr(lRtn) = "ERROR_SUCCESS" Then
         MemStrm.Size = lSize 'allocate enough memory space for the returned Value (which was provided above)
         ' 2nd call: this time it provides the Value stored at the Key
         '           (the only difference between this call and the one above is the 5th parameter)
         '  the Value will now be stored in a QMemorySteam (upon completion)
         '  (Why use a QMemorySteam? Because the Value of the Key can be ANY size and ANY type)
         '  (a QMemoryStream is well suited for holding generic information)
         lRtn = RegQueryValueEx(hKey, VarPtr(sKey), 0, VarPtr(lType), MemStrm.Pointer, VarPtr(lSize))

         If EnumErr(lRtn) = "ERROR_SUCCESS" Then
           Select Case lType 'selectively run the appropriate code based on the Type

             'series of bytes
             ' (in the Result... each is separated by a tab)
             ' (not sure if REG_RESOURCE_REQUIREMENTS_LIST should be here or under a different Type)
             Case REG_BINARY, REG_FULL_RESOURCE_DESCRIPTOR, REG_RESOURCE_LIST, REG_RESOURCE_REQUIREMENTS_LIST
               For i = 0 to (lSize - 1)
                 MemCpy(VarPtr(n), (MemStrm.Pointer + i), 1)
                 sBuffer = sBuffer + Format$("%.2x", n) + Chr$(9)
               Next i
               Result = sBuffer

             '32-bit numbers
             ' (REG_DWORD_BIG_ENDIAN is used in UNIX and not in Windows)
             ' (if I'm understanding it correctly... BIG_ENDIANs are 32-bit but they are stored RightToLeft)
             Case REG_DWORD, REG_DWORD_LITTLE_ENDIAN, REG_DWORD_BIG_ENDIAN
               MemCpy(VarPtr(lBuffer), MemStrm.Pointer, lSize)
               Result = Format$("%.0x", lBuffer)

             '64-bit numbers
             ' (SPECIAL NOTE: a QWORD can not be safely used with a RapidQ Double!!!)
             ' (Numbers OVER 9007199254740991 are stored in a RapidQ Double with the Exponent bits set)
             ' (which basically means... the number will be shifted right and is rounded)
             ' (As long as the number is 9007199254740991 or less... you can use a Double safely)
             Case REG_QWORD
               For i = (lSize - 1) to 0 Step (-1)
                 MemCpy(VarPtr(n), (MemStrm.Pointer + i), 1)
                 sBuffer = sBuffer + Format$("%.2x", n)
               Next i
               Result = sBuffer

             'string (or multiple strings) that are zero-terminated
             ' (in the Result... each string, whether single or multiple, is separated by a tab)
             ' (not sure if REG_NONE and REG_LINK should be here or under a different Type)
             Case REG_NONE, REG_SZ, REG_EXPAND_SZ, REG_LINK, REG_MULTI_SZ
               For i = 0 To (lSize - 1)
                 MemCpy(VarPtr(n), (MemStrm.Pointer + i), 1)
                 sBuffer = IIF((n = 0), sBuffer + Chr$(9), sBuffer + Chr$(n))
               Next i
               Result = sBuffer

           End Select

         Else
           ' Check Result to see if "ERROR" is the left-most 5 characters,
           ' before proceeding with your source code, after calling this function.
           ' If it got to here... all the parameters you passed to this function were valid.
           ' The error here can be caused by various reasons.
           Result = EnumErr(lRtn) + ", ErrCode: " + STR$(lRtn)
         End If

       Else
         ' Check Result to see if "ERROR" is the left-most 5 characters,
         ' before proceeding with your source code, after calling this function.
         ' If it got to here... the Key parameter you passed to this function might be invalid.
         ' Or it was caused by various other reasons.
         Result = IIF(lRtn = 2, "ERROR_Key_Not_Found", EnumErr(lRtn) + ", ErrCode: " + STR$(lRtn))
       End If

    Else
       ' Check Result to see if "ERROR" is the left-most 5 characters,
       ' before proceeding with your source code, after calling this function.
       ' If it got to here... the RootKey and/or the Path parameter(s) you passed to this function might be invalid.
       ' Or it was caused by various other reasons.
       Result = IIF(lRtn = 2, "ERROR_RootKey_or_Path_Not_Found", EnumErr(lRtn) + ", ErrCode: " + STR$(lRtn))
    End If

    ' You MUST close the Handle (that was provided) immediately when you are done using it!
    If hKey Then RegCloseKey(hKey)

' Just cleaning out everything that was Dim'ed, now that we're done using them
                hKey = 0
                lRtn = 0
               lType = 0
               lSize = 0
             lBuffer = 0
                   i = 0
                   n = 0
               sPath = ""
                sKey = ""
             sBuffer = ""
    MemStrm.Position = 0
        MemStrm.Size = 0
    MemStrm.Close
End Function
