Understanding NT

The NT and W2K can create "symbolic links in the registry - registry keys that are actually just synonyms for some other key in the registry tree. HKEY_CURRENT_USER is a good example of such a symbolic link. Can your program create symbolic links in the registry as well? Yes, but you'll need a bit of undocumented information that this column provides.


February 01, 2001
URL:http://www.drdobbs.com/understanding-nt/184416290

February 2001/Understanding NT


Last month, I described how the Windows NT/2000 registry is organized in terms of hive files and predefined root keys. Another interesting feature of the Windows NT/2000 registry is support for symbolic links. Symbolic links are not supported on Windows 9x, and they might as well not be supported on Windows NT/2000 as far as the average developer is concerned, since Microsoft doesn’t completely document how to use them. Ironically, because symbolic links are semi-documented, some developers assume they are available for their own use and waste lots of development time trying to make them work.

I’m not sure why Microsoft chose to partially document registry symbolic links. Why tease us by mentioning KEY_CREATE_LINK and REG_LINK in the registry documentation without actually giving us enough information to use them? In all fairness, symbolic links can be a little dangerous. In preparing the sample code for this article, I accidentally created a circular symbolic link that allowed me to hang my machine, just by starting either regedit.exe or regedt32.exe. Still, symbolic links are a powerful feature, and I’d prefer that developers be given the choice whether to use them (responsibly, of course).

Registry Symbolic Links

A registry symbolic link is just a special registry key that is linked to another registry key. Any time you access the symbolic link key, you are actually accessing the underlying key that the symbolic link key is pointing at (linked to). You can see several examples of symbolic links that the operating system uses, including some of the predefined registry keys. (HKEY_CURRENT_USER and HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet are examples of symbolic links used by the operating system.) The concept of symbolic links makes it convenient for one program (or one part of a program) to link an abstract registry key to a specific registry key based on some set of circumstances. Then, other parts of the application can access the abstract key, knowing it points to the right thing. Programmers take advantage of this feature all the time by saving settings under HKEY_CURRENT_USER and not worrying about who the current user.

To create a symbolic link, you need to create a registry key specifying KEY_CREATE_LINK in the samDesired parameter. It’s also extremely important to include REG_OPTION_VOLATILE in the dwOptions parameter. The trickiest thing about symbolic links is that you can’t easily tell the difference between the link and the target key after you create the link. (After all, that’s kind of the definition of a symbolic link.) The other purpose of a symbolic link is to be able to programmatically change what the link points to, and it can be difficult to do this if your program goes away (more on this later). Creating the link as a volatile registry key lets you recreate the link on the next boot.

So far, everything I’ve described is documented by Microsoft. If you create a key with the KEY_CREATE_LINK value, it will show up in regedt32.exe and regedit.exe as a grayed out “unavailable” key (see Figure 1). After all, it’s a symbolic link that doesn’t point to anything yet. To link this key to another key, you might have guessed that it involves the REG_LINK registry data type. The MSDN (Microsfoft Developer Network) online documentation describes REG_LINK as: “A Unicode symbolic link. Used internally; applications should not use this type.” Here’s the part that is not documented: you have to set a special value called “SymbolicLinkValue” of type REG_LINK in the symbolic link key. The data associated with this special value is the target registry key to link to. There’s one more catch: the target registry key needs to be in kernel-mode registry syntax, and it absolutely must be written as a Unicode string.

If you’ve written Windows NT/2000 device drivers, you’ve noticed that the base registry keys are referenced a little differently. Since drivers run in the LocalSystem context, they can only access a limited set of the registry (HKEY_LOCAL_MACHINE and HKEY_USERS). In kernel-mode, HKEY_LOCAL_MACHINE is referred to as “\Registry\MACHINE” and HKEY_USERS is referred to as “\Registry\USER”. So, to point my symbolic link key to HKEY_LOCAL_MACHINE\SOFTWARE\foo, I would use RegSetValueEx() to set a value named “SymbolicLinkValue” of type REG_LINK to the Unicode string “\Registry\MACHINE\SOFTWARE\foo”. Don’t forget the extra set of backslashes needed when entering these strings in C or C++ constants.

Sample Code

The previous description seems easy enough, but it’s surprisingly easy to mess it up (another good reason for symbolic links to always be volatile registry keys). To demonstrate the process, I wrote the two simple routines in symlink.c (Listing 1). The prototypes for these routines are in symlink.h (Listing 2). CreateSymLinkKey() creates a volatile symbolic link key under the specified root key and subkey. I first create (or open if it already exists) the specified subkey and then create the symbolic link under that open key. It’s important that only the last part of the key is created as volatile and with the KEY_CREATE_LINK attribute. CreateSymLinkKey() returns the registry handle of the newly created symbolic link key.

This registry handle is then passed to SetSymLink() to actually establish the symbolic link to the specified registry root key and subkey. As long as you have the original handle to the symbolic link key that was created in CreateSymLinkKey(), you can change the target of the symbolic link as often as you like by calling SetSymLink(). Therein lies the catch. Once you’ve lost that original registry key handle, you can no longer distinguish the symbolic link from the registry key it is linked to, and thus you can no longer change what key it links to.

To demonstrate using these routines, I wrote a simple Win32 application in main.c (Listing 3). This program has a very simple dialog-box user interface that is implemented in main.rc (Listing 4) and main.h (Listing 5). WinMain() first creates some dummy registry keys to test symbolic links with. Under HKEY_LOCAL_MACHINE\SOFTWARE, it creates a “paulat” key with three subkeys; “A”, “B”, and “C”. Under each of these subkeys, it creates two values with distinguishing data (the key “A”, will have a REG_SZ value of “AAA” and a DWORD value of 1, the key “B” will have a REG_SZ value of “BBB” and a DWORD value of 2, etc.). Then WinMain() creates a dialog box that is handled by MainDlgProc().

During WM_INITDIALOG processing, I call CreateSymLinkKey() to create a symbolic link key called “Current” under

HKEY_LOCAL_MACHINE
  \SOFTWARE
    \PaulaT

The most likely reason for this routine to fail is if I’ve run the program before and closed it before running it again. In this case, the “Current” key will already be created and may or may not be a symbolic link to a target key. In either case, I no longer have the handle to the newly created symbolic link key, and thus I can no longer alter what the key is linked to. So, I gray out the buttons on the user interface and call Refresh() (more on both of these later).

The dialog box in Figure 2 has three buttons labeled “Current -> A”, “Current -> B”, and “Current -> C”. In response to any of these buttons being pressed, I call SetSymLink() to link the symbolic link key “Current” to the appropriate subkey (“A”, “B”, or “C”). Then, just to demonstrate that it works, I call Refresh(), which opens

HKEY_LOCAL_MACHINE
  \SOFTWARE
    \PaulaT
      \Current

and displays the contents of the “Val1” and “Val2” registry values in static fields on the dialog box. The data in the “Val1” and “Val2” keys will be dependent on which key “Current” is linked to (see Figure 3).

Conclusion

Just to reiterate, if I close symlink.exe and reopen it, my attempt to create the symbolic link key “Current” will fail with an error code of ERROR_ALREADY_EXISTS (183). I’ve now lost the handle to the actual symbolic link key, so I can no longer change what key it’s linked to. I can still access the “Current” key, but I will be accessing the previously assigned key that “Current” is linked to. This is one of the things that makes registry symbolic links particularly useful for services and drivers and a bit less useful for applications. (Services and drivers tend to load early and stay running until the machine is rebooted.)

Also note that deleting a symbolic link registry key will delete the target registry key, which effectively deletes both keys. In the example above, deleting “Current” will also delete the key it is linked to (“A”, “B”, or “C”). If there are any other symbolic links to that key, they will be deleted, as well.

Symbolic links are a powerful and useful concept, but they can also be dangerous, so please use this information responsibly.

Paula Tomlinson has been developing Windows, Windows CE, and Windows NT based applications and device drivers for 13 years. In her spare time, Paula enjoys flying N422HJ, a 1951 Ryan Navion. The opinions expressed here are hers alone. Paula can be reached at [email protected].

February 2001/Understanding NT/Figure 1

Figure 1: A symbolic link pointing nowhere

February 2001/Understanding NT/Figure 2

Figure 2: Demo program user interface

February 2001/Understanding NT/Figure 3

Figure 3: Demonstrating the linked-to value

February 2001/Understanding NT/Listing 1

Listing 1: symlink.c — Creating and setting symbolic registry links

// symlink.c

#define UNICODE
#define _UNICODE

#include <windows.h>
#include "symlink.h"

DWORD CreateSymLinkKey(HKEY   hLinkRootKey,
                       LPTSTR pszLinkSubKey,
                       LPTSTR pszLinkKey,
                       PHKEY  phLinkKey)
{
    DWORD status = 0, t = 0, size = 0;
    HKEY hKey = NULL, hTempKey = NULL;

    // Create (or open if already existing) the base symbolic
    // link key.

    status = RegCreateKeyEx(hLinkRootKey, pszLinkSubKey, 0, NULL,
                            REG_OPTION_NON_VOLATILE,
                            KEY_ALL_ACCESS, NULL, &hKey, NULL);
    if (status != ERROR_SUCCESS) {
        return status;
    }

    // Create a volatile "link" key under base subkey opened above

    status = RegCreateKeyEx(hKey, pszLinkKey, 0, NULL,
                            REG_OPTION_VOLATILE |
                            REG_OPTION_CREATE_LINK,
                            KEY_ALL_ACCESS | KEY_CREATE_LINK,
                            NULL, phLinkKey, NULL);

    // the symbolic link key has been created but it doesn't link
    // to anything to yet

    RegCloseKey(hKey);
    return status;

} // CreateSymLinkKey

//-----------------------------------------------------------------
DWORD SetSymLink(HKEY   hLinkKey,
                 HKEY   hBaseRootKey,
                 LPTSTR pszBaseKey)
{
    DWORD status = 0;
    HKEY hKey = NULL;
    TCHAR sz[MAX_PATH];

    // Form the path to link to using kernel mode registry syntax

    if (hBaseRootKey == HKEY_LOCAL_MACHINE) {
        wsprintf(sz, TEXT("\\Registry\\MACHINE\\%s"), pszBaseKey);
    } else if (hBaseRootKey == HKEY_USERS) {
        wsprintf(sz, TEXT("\\Registry\\USER\\%s"), pszBaseKey);
    } else {
        return ERROR_INVALID_PARAMETER;
    }

    // Store the link target in the special "SymbolicLinkValue"
    // REG_LINK value in the special link key to form the link.

    status = RegSetValueEx(hLinkKey, L"SymbolicLinkValue", 0,
                           REG_LINK, (LPBYTE)sz, 
                           lstrlen(sz) * sizeof(WCHAR));

    return status;

} // SetSymLink
//End of File
February 2001/Understanding NT/Listing 2

Listing 2: symlink.h — Interface to symlink.c

// symlink.h

DWORD CreateSymLinkKey(HKEY, LPTSTR, LPTSTR, PHKEY);
DWORD SetSymLink(HKEY, HKEY, LPTSTR);
//End of File
February 2001/Understanding NT/Listing 3

Listing 3: main.c — Demo program source

// main.c

#define UNICODE
#define _UNICODE

#include <windows.h>
#include "main.h"
#include "symlink.h"

BOOL CALLBACK MainDlgProc(HWND, UINT, WPARAM, LPARAM);
VOID Init(LPTSTR, LPTSTR, DWORD, DWORD);
BOOL Refresh(HWND);

HINSTANCE hInst;
HKEY      ghLinkKey;

//-----------------------------------------------------------------
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInst,
                     LPSTR lpCmdLine, int nCmdShow)
{
    hInst = hInstance;

    // Create some dummy keys to test symbolic links

    Init(TEXT("Software\\PaulaT\\A"), TEXT("AAA"), 8, 1);
    Init(TEXT("Software\\PaulaT\\B"), TEXT("BBB"), 8, 2);
    Init(TEXT("Software\\PaulaT\\C"), TEXT("CCC"), 8, 3);

    return DialogBox(hInst, MAKEINTRESOURCE(SYMLINK_DLG), 
                     NULL, MainDlgProc);
} // WinMain

//-----------------------------------------------------------------
void Init(LPTSTR szKey, LPTSTR sz, DWORD size, DWORD dw)
{
    HKEY hKey;
    DWORD status;

    status = RegCreateKeyEx(HKEY_LOCAL_MACHINE, szKey, 0, NULL, 
                            REG_OPTION_NON_VOLATILE, 
                            KEY_ALL_ACCESS, 0, &hKey, 0);
    if (status == ERROR_SUCCESS) {
        RegSetValueEx(hKey,TEXT("Val1"),0,REG_SZ,(LPBYTE)sz,size);
        RegSetValueEx(hKey,TEXT("Val2"),0,REG_DWORD,(LPBYTE)&dw,4);
        RegCloseKey(hKey);
    }
} // Init

//-----------------------------------------------------------------
int CALLBACK MainDlgProc(HWND hDlg, UINT Message,WPARAM wParam,
                         LPARAM lParam)
{
    DWORD status;

    switch (Message) {
        case WM_INITDIALOG:
            status = CreateSymLinkKey(HKEY_LOCAL_MACHINE,
                                      TEXT("Software\\PaulaT"),
                                      TEXT("Current"), &ghLinkKey);
            if (status != ERROR_SUCCESS) {
                EnableWindow(GetDlgItem(hDlg, ID_MODE_A), FALSE);
                EnableWindow(GetDlgItem(hDlg, ID_MODE_B), FALSE);
                EnableWindow(GetDlgItem(hDlg, ID_MODE_C), FALSE);
                Refresh(hDlg);
            }
            return TRUE;

        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case ID_MODE_A:
                    SetSymLink(ghLinkKey, HKEY_LOCAL_MACHINE,
                               TEXT("Software\\PaulaT\\A"));
                    Refresh(hDlg);
                    break;

                case ID_MODE_B:
                    SetSymLink(ghLinkKey, HKEY_LOCAL_MACHINE,
                               TEXT("Software\\PaulaT\\B"));
                    Refresh(hDlg);
                    break;
                case ID_MODE_C:
                    SetSymLink(ghLinkKey, HKEY_LOCAL_MACHINE,
                               TEXT("Software\\PaulaT\\C"));
                    Refresh(hDlg);
                    break;
            }
            break;
        

        case WM_CLOSE:
            EndDialog(hDlg, 1);
            break;

        default:
            return FALSE;
    }
    return FALSE;

} // MainDlgProc

//-----------------------------------------------------------------
BOOL Refresh(HWND hDlg)
{
    HKEY hKey = NULL;
    DWORD status, t, s, dw2 = 0;
    TCHAR sz1[MAX_PATH];

    status = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                          TEXT("Software\\PaulaT\\Current"), 0, 
                          KEY_READ, &hKey);

    if (status == ERROR_SUCCESS) {
        s = MAX_PATH * sizeof(TCHAR);
        RegQueryValueEx(hKey,TEXT("Val1"),0,&t,(LPBYTE)sz1,&s);
        SetDlgItemText(hDlg,ID_VAL_1,sz1);

        s = sizeof(DWORD);
        RegQueryValueEx(hKey,TEXT("Val2"),0,&t,(LPBYTE)&dw2,&s);
        SetDlgItemInt(hDlg,ID_VAL_2,dw2,FALSE);

        RegCloseKey(hKey);
        return TRUE;
    }
    return FALSE;

} // Refresh
//End of File
February 2001/Understanding NT/Listing 4

Listing 4: main.rc — Dialog box definition

#include "windows.h"
#include "main.h"

SYMLINK_DLG DIALOG 22, 16, 220, 89
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
STYLE DS_MODALFRAME|WS_POPUP|WS_VISIBLE|WS_CAPTION|WS_SYSMENU
CAPTION "SymLink Tester"
FONT 9, "MS Shell Dlg"
BEGIN
    GROUPBOX        "Make Symolic Link", -1, 8, 7, 203, 74
    PUSHBUTTON      "Current -> A", ID_MODE_A, 16, 20, 62, 14
    PUSHBUTTON      "Current -> B", ID_MODE_B, 16, 38, 62, 14
    PUSHBUTTON      "Current -> C", ID_MODE_C, 16, 56, 62, 14
    LTEXT           "Val1:", -1, 89, 32, 21, 8
    LTEXT           "Val2:", -1, 89, 46, 22, 8
    LTEXT           "", ID_VAL_1, 115, 31, 84, 8
    LTEXT           "", ID_VAL_2, 115, 46, 79, 8
END
February 2001/Understanding NT/Listing 5

Listing 5: main.h — Resource IDs for demo program

// main.h

#define SYMLINK_DLG     1000
#define ID_MODE_A       1001
#define ID_MODE_B       1002
#define ID_MODE_C       1003
#define ID_VAL_1        1004
#define ID_VAL_2        1005
//End of File

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.