Determine which registry key is newer (RegEnumKeyEx?) [C# 2002]

Shaitan00

Junior Contributor
Joined
Aug 11, 2003
Messages
358
Location
Hell
I'm converting code from VC++6 to VC#2002 and have encountered my first roadblock with respect to using "RegEnumKeyEx" to compare the time stamps of registry

keys (when they were created to find the newest one).

Currently I do the following in VC++6 to determine which key is the newest:
Code:
   FILETIME ft, mrft; // mr = most recent
   mrft.dwLowDateTime = 0;
   mrft.dwHighDateTime = 0;

   while (ERROR_SUCCESS == (rc = RegEnumKeyEx( hkBase,
                                       Index++,
                                       Name,
                                       &Length,
                                       NULL,
                                       NULL,
                                       NULL,
                                       &ft))))
      {
      if (CompareFileTime (&mrft, &ft))
         {
         mrft.dwLowDateTime = ft.dwLowDateTime;
         mrft.dwHighDateTime = ft.dwHighDateTime;
         newestName = Name;
         }
   }
   ...
   ...

So my problem is how to accomplish this in C#?
Is there an alternative approach I should be using under the DotNet platform for C#? Or is there a way I can include "RegEnumKeyEx" somehow (like I would do a DECLARE with coredll.dll in VB or something) in the C# project so I can use the function as-is (continue the same way I was before)?

Any help would be greatly appreciated.
Thanks,
 
Platform invoke

The framework provides the Registry and RegistryKey classes in the Microsoft.Win32 namespace. I have never used these types myself, but they do not appear to expose key timestamps, so might not be useful in this case.

The alternative is to use platform invoke. The declare would look something like this:

Code:
[DllImport("Advapi32.dll", EntryPoint="RegEnumKeyExW")]
private static extern int RegEnumKeyEx(
    IntPtr hKey,
    int dwIndex,
    [MarshalAs(UnmanagedType.LPWStr)] StringBuilder lpName,
    ref int lpcName,
    int lpReserved,
    int lpClass,
    int lpcClass,
    [MarshalAs(UnmanagedType.LPStruct)] out FILETIME lpftLastWriteTime
);

To use, you would need to obtain the hKey to query, using some other platform invoke function. Then initialize a StringBuilder with enough space to receive the key name, which is passed in as the lpName parameter. Since your C++ code passes NULL for lpClass and lpcClass, I have declared them of type int so that you may pass 0, but if you wished to receive the class string the same types as lpName and lpcName could be used. The FILETIME structure is provided in the System.Runtime.InteropServices namespace.

I hope this gives you a good starting point.

Good luck :cool:
 
Last edited:
That was a great help - and you also mentioned another problem I really had not thought about - the "hKey" - this also doesn't seem to exist in C#. You mention that I might be able to get this by some other platform invoke, correct me if I am wrong but do you mean doing another DLLImport on something like the "OpenSubKey" function (which is what I use to get my hkey in VC++6)?

Code:
OpenSubKey (HKEY_CURRENT_USER, 
                  "Software\\INFORMATION", 
                  &hkBase,
                  FALSE);

And how would I create the "hkBase" variable if the concept of HKEY doesn't exist in C#? This is getting more complicated then I expected....


And why do you use "RegEnumKeyExW" and not simply "RegEnumKeyEx" as I did in VC++6 for the DLLImport Entry Point?

Thanks a lot...
 
Last edited:
Platform invoke

In C++, the HKEY type is defined as an int, so you can use the int datatype in C#. In the code I posted above I used IntPtr since technically HKEY is a handle and not just any old number, and using IntPtr helps to keep this in mind.

The constants HKEY_CURRENT_USER etc are predefined HKEY values:

Code:
#define HKEY_CLASSES_ROOT          0x80000000
#define HKEY_CURRENT_USER          0x80000001
#define HKEY_LOCAL_MACHINE         0x80000002
#define HKEY_USERS                 0x80000003
#define HKEY_PERFORMANCE_DATA      0x80000004
#define HKEY_PERFORMANCE_TEXT      0x80000050
#define HKEY_PERFORMANCE_NLSTEXT   0x80000060
#define HKEY_CURRENT_CONFIG        0x80000005
#define HKEY_DYN_DATA              0x80000006

These definitions are easily converted to C#.

As for any other Win32 API functions, they will also need to be declared in C#:

Code:
[DllImport("Advapi32.dll", EntryPoint="RegOpenKeyExW")]
private static extern int RegOpenKeyEx(
    IntPtr hKey,
    [MarshalAs(UnmanagedType.LPWStr)] string lpSubKey,
    int ulOptions,
    int samDesired,
    out IntPtr phkResult
);

I've never heard of the OpenSubKey function.

Note that the majority of registry operations can be performed using the Microsoft.Win32 registry types, but as I said above I don't think they expose timestamps.

Good luck :)
 
I added the DLLImports to the top and added the following code to my C# 2002 application:

Code:
int nLength = 256;
int nIndex = 0;
long rc = 0;
string sName, sMostRecentName;
FILETIME ft, ftMostRecent;
ftMostRecent.dwLowDateTime = 0;
ftMostRecent.dwHighDateTime = 0;

IntPtr nptrHKEY = new IntPtr(0x80000001); // For HKCU
IntPtr nptrResult = new IntPtr(0); // Am I doing this right?

if (0 == RegOpenKeyEx(nptrHKEY, "Software\\INFORMATION", 0, 0, nptrResult))
	{
	while (0 == (rc = RegEnumKeyEx(nptrHKEY, nIndex++, sName, nLength, 0, 0, 0, ft)))
		{
		//Do the compares of the FT times and all that here
		ftMostRecent.dwLowDateTime = ft.dwLowDateTime;
		ftMostRecent.dwHighDateTime = ft.dwHighDateTime;
		sMostRecentName = sName;
		}
	}

This generates the following errors:
>> The best overloaded method match for 'RegOpenKeyEx(System.IntPtr, string, int, int, out System.IntPtr)' has some invalid arguments
>>Argument '5': cannot convert from 'System.IntPtr' to 'out System.IntPtr'
Now I could be way off but if I change the DLLImport and remove the word "out" from the "out IntPtr phkResult" it seems to compile fine (work or not I don't know - so I wanted to ask you ....). And is it ok the way I am declaring and using "nptrResult"?


And - why not post it all - "RegEnumKeyEx" generates the following errors:
>> The best overloaded method match for RegEnumKeyEx(System.IntPtr, int, System.Text.StringBuilder, ref int, int, int, int, System.Runtime.InteropServices.FILETIME)' has some invalid arguments
>> Argument '4': cannot convert from 'int' to 'ref int' // how do I solve this?
>> Argument '3': cannot convert from 'string' to 'System.Text.StringBuilder'
(which is just because I have never used a StringBuilder but I should be able to figure that out).

So I am still doing something wrong - anything stand out?
Thanks a lot for all the help - really appreciated...
 
I was testing out my IntPtr for HKEY_CURRENT_USER and it throws an exception...

Code:
IntPtr nptrHKEY = new IntPtr(0x80000001);

Exception thrown:
System.OverflowException: Arithmetic operation resulted in an overflow.
at System.IntPtr..ctor(Int64 value)
So I guess the HKCU value doesn't fit in the Int64 value ... How do you get around to solving this dilemna?

On top of what I asked above - any clues on this one?
Thanks again ...
 
Okay...

And why do you use "RegEnumKeyExW" and not simply "RegEnumKeyEx" as I did in VC++6 for the DLLImport Entry Point?

Most if not all Win32 API functions involving strings come in two flavours - A for ANSI and W for UNICODE. In C++ the headers will define RegEnumKeyEx as RegEnumKeyExA if the application is being compiled in ANSI mode, or RegEnumKeyExW if in UNICODE mode. With platform invoke you need to be explicit of both the exact name and the parameter types.

This generates the following errors:
>> The best overloaded method match for 'RegOpenKeyEx(System.IntPtr, string, int, int, out System.IntPtr)' has some invalid arguments
>>Argument '5': cannot convert from 'System.IntPtr' to 'out System.IntPtr'

Now I could be way off but if I change the DLLImport and remove the word "out" from the "out IntPtr phkResult" it seems to compile fine (work or not I don't know - so I wanted to ask you ....). And is it ok the way I am declaring and using "nptrResult"?

Yes it will compile but it is unlikely to execute correctly. The out modifier specifies that the function will return a value in this parameter, and must be specified both in the declaration and when being invoked:

Code:
if (0 == RegOpenKeyEx(nptrHKEY, "Software\\INFORMATION", 0, 0, [color=red]out[/color] nptrResult))
{
    //....


And - why not post it all - "RegEnumKeyEx" generates the following errors:
>> The best overloaded method match for RegEnumKeyEx(System.IntPtr, int, System.Text.StringBuilder, ref int, int, int, int, System.Runtime.InteropServices.FILETIME)' has some invalid arguments
>> Argument '4': cannot convert from 'int' to 'ref int' // how do I solve this?

As with out, you must specify ref in both the declaration and where the method is invoked - ref denotes a parameter which must be assigned a value before it is passed and which is used to return a value. Note that the final parameter should probably also be marked as out.

Code:
while (0 == (rc = RegEnumKeyEx(nptrHKEY, nIndex++, sName, [color=red]ref[/color] nLength, 0, 0, 0, [color=red]out[/color] ft)))
{
    //....
 
So the only funky things left are the exception I am getting from "nptrHKEY" (as described above).

Seriously - would have taken me 100x the time to solve this alone - you taking the time to type and answer all this is really much appreciate and is making a lot of sense...
 
Overflow...

Exception thrown:
System.OverflowException: Arithmetic operation resulted in an overflow.
at System.IntPtr..ctor(Int64 value)

At least one of these will work:

Code:
IntPtr nptrHKEY = new IntPtr((int) 0x80000001);

IntPtr nptrHKEY = new IntPtr(int.MinValue + 1);

Good luck :)
 
StringBuilder 101

Basic use of a StringBuilder:

C#:
//**** First of all, using:
using System.Text;

//**** Usage:
StringBuilder buff = new StringBuilder(size_of_buffer);

//Call platfom invoke here

text = buff.ToString();

:cool:
 
IntPtr nptrHKEY = new IntPtr((int) 0x80000001);
>> Constant value '2147483649' cannot be converted to a 'int' (use 'unchecked' syntax to override)

IntPtr nptrHKEY = new IntPtr(int.MinValue + 1);
This compiles okay but fails on "RegOpenKeyEx" with a return code of 5 (ERROR_ACCESS_DENIED);
How would that represent HKEY_CURRENT_USER?

Anyways - I assume Access Denied could also be because I am not passing in the "REGSAM samDesired" falgs to define what kind I want - (for example seeing as I am only reading = KEY_READ)... How do I find out what KEY_READ is as an INTEGER value (because obviously just using "KEY_READ" won't work).

Thanks,
 
Last edited:
Access denied error

IntPtr nptrHKEY = new IntPtr(int.MinValue + 1);
This compiles okay but fails on "RegOpenKeyEx" with a return code of 5.

Also - how would that represent HKEY_CURRENT_USER?
Thanks,

int.MinValue has a value of 0x80000000, and add 1 to that to get 0x80000001. As for returning error 5, this denotes an access denied error.
 
Ya- you answer faster then I can edit hehehehe - I edited my post to say that 5 = Access Denied and I assume that is because I am not passing in "REGSAM samDesired" flags (currently pass in 0) to define what kind I want - (for example seeing as I am only reading the time stamp and name = KEY_READ)... How do I find out what KEY_READ is as an INTEGER value (because obviously just using "KEY_READ" won't work).

I need to find out how I can pass it in...
Thanks,
 
Key_read

Anyways - I assume Access Denied could also be because I am not passing in the "REGSAM samDesired" falgs to define what kind I want - (for example seeing as I am only reading = KEY_READ)... How do I find out what KEY_READ is as an INTEGER value (because obviously just using "KEY_READ" won't work).

It looks like KEY_READ is:

C#:
int KEY_READ = 0x00020000 | 0x0001 | 0x0008 | 0x0010;

Note all these constants are available in the Win32 header files (winnt.h for these ones).
 
Now we are getting somewhere (all thanks to you) - looks like "RegOpenKeyEx" is working fine - I think this might be the last issue - with "RegEnumKeyEx" -when I debug I get the following exception thrown:

System.Runtime.InteropServices.MarshalDirectiveException: Can not marshal parameter #8: Invalid managed/unmanaged type combination (this value type must be paired with Struct).
at RegEnumKeyEx(IntPtr hKey, Int32 dwIndex, StringBuilder lpName, Int32& lpcName, Int32 lpReserved, Int32 lpClass, Int32 lpcClass, FILETIME& lpftLastWriteTime)


Parameter 8 is "ft" ... I'm was going to try to guess (to not look too stupid) but I got no clue on this one...

Code:
FILETIME ft, ftMostRecent;
ftMostRecent.dwLowDateTime = 0;
ftMostRecent.dwHighDateTime = 0;

while (0 == (rc = RegEnumKeyEx(nptrHKEY, nIndex++, sbBuff, ref nLength, 0, 0, 0, out ft)))
 
Last edited:
Marshalling problems

Annoyingly, the FILETIME in System.Runtime.InteropServices has been deprecated, and you should now use the FILETIME from System.Runtime.InteropServices.ComTypes - very confusing I know. You should be able to just change your using statement.

If that doesn't fix it, then try changing the way the parameter is declared in the function declaration:

Code:
//Before
[MarshalAs(UnmanagedType.LPStruct)] out FILETIME lpftLastWriteTime

//Try
[MarshalAs(UnmanagedType.Struct | UnmanagedType.LPStruct)] out FILETIME lpftLastWriteTime
//Or
[MarshalAs(UnmanagedType.Struct)] out FILETIME lpftLastWriteTime

Working out marshalling specifics can be a bit of a black art at times.

:)
 
[MarshalAs(UnmanagedType.Struct)] out FILETIME lpftLastWriteTime worked like a charm (or so it seems)... Compiles and runs...

Absolute last question (as it all seems to work - I just want to ensure I am not causing any leaks or handle issues) - typically I would need to call "LONG RegCloseKey(HKEY hKey);" after doing a "RegOpenKeyEx" - is this somehow no longer needed or do I do to do a DLLImport for it also and close the IntPtr "nptrResult" when I am done with all this?
 
Back
Top