My current project requires that the user choose registry keys and since I like GUI applications I decided to use RegEdit as a template for the selection. I chose RegEdit because most "power users" are already familiar with it.

The attached file has the source and executable. As with all my published source you are free to use it however you like, even in commercial applications. I would ask (though do not require) that it is not used in software released under the GPL or any license issued by the Free Software Foundation Free means Freedom

The module is no where near complete and has not been debugged yet but I thought I would post it to get ideas and bug reports as it is complicated to test completely.

edit : Fixed the behaviour of the default key
Attachments:
Posted on 2005-07-12 01:08:34 by donkey
looks nice :)

Would be good if there's a visual indication (expanding-plussign-thingy) for keys that have subkeys...
Posted on 2005-07-12 01:33:28 by f0dder
Hi f0dder,

I thought so too but the only way to do it was to use RegQueryInfoKey which I find is completely unreliable and mis-documented. For example it says that lpClass can be NULL but it GPF's on some versions of Windows if it is. Also the subkey count is inaccurate, in some cases reporting 1000's of keys when there are none. I am trying to think of a quick way around the problem that does not involve that flakey API.
Posted on 2005-07-12 01:38:50 by donkey
Humm, you could make your own "HasChildren" function that does a couple of RegEnum* calls to see if there's any child nodes. Didn't know that ReqQuieryInfoKey is bugged :/
Posted on 2005-07-12 01:50:11 by f0dder
The problem appears mostly in Win2K3 and WinXP64, it appears to be fine in other versions, but it is sporatic, seems to depend on the key. It especially has problems with some HKEY_LOCAL_MACHINE keys.
Posted on 2005-07-12 02:02:01 by donkey
Hmmm... perhaps has to do with the SAM or SYSTEM keys? Can you make a test app that reproduces the bug?

I should have access to a win2k3 server, but until nvidia gets their act together and releases some working xp64 drivers for sata raid, there's no xp64 for me >_<
Posted on 2005-07-12 02:23:53 by f0dder
Actually it was a mixture of early morning edits and stupidity, when I installed the app on my XP64/2K3 machines I made a few modifications to allow for some bugs and one of those changes sent the wrong hKey to the API, they seem to work fine now that I have fixed that up. However it is painfully slow, I will have to find another way as it now takes roughly 20 seconds for the app to initialize the 4476 1st level keys in HKEY_CLASSES_ROOT on my Athlon64 3000+, didn't bother to try it on the slower machines.
Posted on 2005-07-12 03:39:14 by donkey
Hmm... do you query *all* subkeys on load, or only when expanding?
Posted on 2005-07-12 03:45:10 by f0dder
It would make no difference, as soon as the user clicked on HKEY_CLASSES_ROOT I would still be required to expand the key and it has nearly 4500 subkeys. I have found that it is remarkably faster just to open each subkey and check with an enumeration...

invoke BuildRegKey, , , offset KeyName, offset cbKeyName
invoke RegOpenKeyEx,,offset KeyName,0,KEY_ALL_ACCESS,offset phkResult
invoke RegEnumKeyEx,, 0,offset KeyName,offset cbKeyName, NULL, NULL, NULL, offset ftLastWrite
push eax
invoke RegCloseKey,
pop eax
cmp eax,ERROR_NO_MORE_ITEMS
je >.NO_KIDS


This will load in less than 2 seconds on my machine but since I use the children flag right now to decide whether to read the key or not I will have to rethink that section of the code first.
Posted on 2005-07-12 03:58:40 by donkey
Hi Donkey
First of all, thanks for sharing your code! :)
I have a question related to the szLen function. I?m a little bit confusing about what happen when you are reading dwords and you reach the end of the string AND the next bytes that follow the zero terminating byte are read protected by the OS. Example, I allocate a Heap with 94 bytes and store a string with 93 ANSI chars + 1 zero byte. What happens when I read the 24th (last) dword? Can I be sure that that works in all cases?

Regards,

Biterider
Posted on 2005-07-13 00:49:02 by Biterider
Bitrerider, it *should* work - protection is done on a 4k page level, and there generally shouldn't be a problem as long as you do this in usermode code.

But I've had the concerns too :)
Posted on 2005-07-13 04:36:18 by f0dder
Hi Biterider,

I think the szLen function is from Agner Fog, I do not think it is possible to allocate memory that is not accessible in DWORD chunks when running in 32 bit mode. That is, if you were to allocate 94 bytes on the heap it would be readable up to 96 bytes regardless (actually up to the next page boundary). In your example there is no error and I have yet to see the function fail but I will look into it.

BTW, thanks for the dw2hex and dq2hex functions, I have used them extensively in much of my code, very efficient and reliable.
Posted on 2005-07-13 06:53:41 by donkey
Just adding my 2 cents: what if the beginning of the string is not aligned to DWORD? Then it would be possible to read past the end of the page...
Posted on 2005-07-13 10:32:22 by QvasiModo

Just adding my 2 cents: what if the beginning of the string is not aligned to DWORD? Then it would be possible to read past the end of the page...


Then you would have to write the NULL to the next page and would already have had a GPF. In order to have the ability to write the NULL, you have already gauranteed that the function will suceed as you must be able to read the DWORD where the NULL was written.
Posted on 2005-07-13 18:16:38 by donkey
consider the string "ab", positioned three bytes before the end of a page. This could, theoretically, result in a GPF... but it's a very contrieve example.

Any sane compiler (and assembly coder) would align data to 4 bytes, and I think you'd be hard pressed to find a situation, in the normal ring3 domain, where accessing a single page beyond your code/data would cause a GPF...
Posted on 2005-07-13 18:25:23 by f0dder
You could be using VirtualAlloc to store an ASCIIZ array - other than that I can't think of a real life example. It's just a detail anyway :)
Posted on 2005-07-13 18:27:16 by QvasiModo
I do not think it is possible for the function to fail. Memory is always allocated within a page which is always evenly divisible by four. If you allocate say 17 bytes on the heap you can read and write up to 20 as it will always have to unprotect in DWORD chunks no matter what, it is after all the smallest addressable chunk of memory. In reality BYTES are read in DWORD chunks and "parsed" out. For example if you start offset by 2 and have 4 bytes to read it will actually read 8 bytes...

xx12 34xx

The processor will read xx12 then read 34xx and fix up the DWORD internally, but the actual read still goes past the end of the DWORD in that case. So, thinking about it this afternoon, there is no conceivable way to have a NULL terminated string that will fail the function, at least as far as I can tell. The routine takes advantage of the processor being able to make unaligned reads and aligns itself to a DWORD boundary before beginning the DWORD scan.
Posted on 2005-07-13 18:44:45 by donkey
I have added the treeview buttons for f0dder, though you must select an item before they will appear, a fair compromise I guess that did not require changing any code :)

I have also added images to the listview to indicate the type of data
Posted on 2005-07-13 18:52:08 by donkey

I do not think it is possible for the function to fail. Memory is always allocated within a page which is always evenly divisible by four. If you allocate say 17 bytes on the heap you can read and write up to 20 as it will always have to unprotect in DWORD chunks no matter what, it is after all the smallest addressable chunk of memory. In reality BYTES are read in DWORD chunks and "parsed" out. For example if you start offset by 2 and have 4 bytes to read it will actually read 8 bytes...

xx12 34xx

The processor will read xx12 then read 34xx and fix up the DWORD internally, but the actual read still goes past the end of the DWORD in that case. So, thinking about it this afternoon, there is no conceivable way to have a NULL terminated string that will fail the function, at least as far as I can tell. The routine takes advantage of the processor being able to make unaligned reads and aligns itself to a DWORD boundary before beginning the DWORD scan.


Memory is allocated on DWORD boundaries. But strings don't have to.

---------------------    ------------------------- --------------------------- -------------------------------
| beginning of page | // | unused char (aligned) | | string (unaligned by 1) | | null char (and end of page) |
---------------------    ------------------------- --------------------------- -------------------------------

Will cause the function to read one char past the end of the page, because it reads whole DWORDs, be them aligned or not.
Posted on 2005-07-13 18:52:23 by QvasiModo
The end of the page is at a DWORD boundary, it must be. Regardless of the string, if the NULL could be written you can read the complete DWORD that contains it, that will gaurantee no GPF. Remember that the scan aligns itself by reading the first few bytes up to a DWORD boundary then it begins the DWORD read so it will always end on a DWORD.
Posted on 2005-07-13 18:54:54 by donkey