Access registry using Windows API in Qt

Although you can use QSettings to access Windows registry, sometimes it is more convenient to use Windows API directly,i.e., you are porting an old project that uses Windows API. The API functions that are related to registry operation are RegOpenKeyEx,RegQueryValueEx,RegCloseKey,RegSetValueEx,RegCreateKey(or RegCreateKeyEx). The big problem of Windows API is some functions are too complicated. They usually have too many parameters. It may take you a whole day to understand the meaning of the parameters thoroughly.

To read/write a name-value pair in registry, you need to open its key(the key that contains the name-value pair in question) using  RegOpenKeyEx first.

LSTATUS RegOpenKeyExA(
  HKEY   hKey,
  LPCSTR lpSubKey,
  DWORD  ulOptions,
  REGSAM samDesired,
  PHKEY  phkResult
);

This function will open the sub-key(lpSubKey) of an already opend key(hKey) and save its handle in phkResult for you to use later. At the beginning, you have no opened key for hKey. At that time, you can use a predefined key such as HKEY_CLASSES_ROOT, HKEY_CURRENT_CONFIG, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, HKEY_USERS.

Now, you’ve opened a key and get its handle. You can query the name-value pairs in this key with the handle using RegQueryValueEx:

LSTATUS RegQueryValueExA(
  HKEY    hKey,
  LPCSTR  lpValueName,
  LPDWORD lpReserved,
  LPDWORD lpType,
  LPBYTE  lpData,
  LPDWORD lpcbData
);

hKey is the handle of the key you’ve opened. Every name-value pair contained in a key has a name, a value, and a type. You specify the name you want to query in lpValueName, and you prepare the variables to receive the query results: the value(whose address is specified as lpData), the type(whose address is specified as lpType), and the length of the value(whose address is specified as lpcbData). Note that you should allocate the memory to store the value before calling this function, and you need to pass the address of the piece of memory in lpData. You also need to pass the size of the allocated memory in the variable pointed by lpcbData. The function will check lpcbData to see if the piece of memory is large enough to store the value retrieved. If not, the function call will fail. Upon the completion of the function call, the variable pointed by lpcbData will be filled with the actual size of the value.

You can create/change a name-value pair in an opened key with RegSetValueEx:

LSTATUS RegSetValueExA(
  HKEY       hKey,
  LPCSTR     lpValueName,
  DWORD      Reserved,
  DWORD      dwType,
  const BYTE *lpData,
  DWORD      cbData
);

The opened key is specified as hKey. lpValueName is the name of the name-value pair you want to create or change, dwType is its type, lpData points to the actual value you want to set for the name-value pair. You also need to specify the length of the value in cbData. If the name-value is of type REG_SZ or REG_MULTI_SZ, you do not need to specify the size in cbData, just pass 0 in cbData, and the function will use the terminating NULL(double NULL for REG_MULTI_SZ) in the string pointed by lpData to calculate the length of the value.

To create a key(NOT a name-value pair), you should use RegCreateKey:

LSTATUS RegCreateKeyA(
  HKEY   hKey,
  LPCSTR lpSubKey,
  PHKEY  phkResult
);

hKey is the key you’ve already opened(or a predefined key), lpSubKey points to the string of the sub-key you want to create under hKey. If the call is successful, the created key is also opened and its handle is stored in the variable pointed by phkResult.

An opened key, if not a predefined key, need to be closed using RegCloseKey.

Knowing the syntax of registry handling functions, you cannot wait to play them around. If this is the first time you create a registry key using Windows API, you are almost certain to write the following code:

HKEY regkey;
RegCreateKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\myapplication", &regkey)

This will produce the following compiling error:

error: C2664: ‘LSTATUS RegCreateKeyW(HKEY,LPCWSTR,PHKEY)': cannot convert argument 2 from ‘const char [19]’ to ‘LPCWSTR’

In fact, RegCreateKey is a macro rather than a function. If defined UNICODE, the macro is expanded to RegCreateKeyW, which takes a LPCWSTR as its second parameter. If you cast the string literal to LPCWSTR forcefully as follows:

HKEY regkey;
RegCreateKey(HKEY_LOCAL_MACHINE, (LPCWSTR)"SOFTWARE\\myapplication", &regkey)

The program complies successfully, but the function call fails with the error:”The parameter is incorrect”. Let’s first see how to get this error message. RegCreateKey has a return value of type LSTATUS. This is basically an integer which gives you nothing about the actual error. To know what causes the failure of the function, you should convert the returned error code to an error message:

LSTATUS  result=RegCreateKey(HKEY_LOCAL_MACHINE, (LPCWSTR)"SOFTWARE\\myapplication", &regkey);
LPVOID message;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                            NULL, result, NULL,(LPTSTR) &message, 0, NULL );
QMessageBox::warning(this,"error",QString::fromWCharArray((wchar_t*)message));

 FormatMessage is another ugly and hard-to-understand API function.  But we do not need to understand this function thoroughly. All we need to do is pass the error code returned from RegCreateKey to the third parameter of FormatMessage. The converted message string is put in the memory pointed by message. Note that the fifth parameter of FormatMessage is a pointer to a pointer to a buffer that’s used to receive the converted result. The buffer is allocated by the API, not us, the programmer. To let the API allocate the buffer, you should set the FORMAT_MESSAGE_ALLOCATE_BUFFER flag in the first parameter of FormatMessage. You should also set the FORMAT_MESSAGE_FROM_SYSTEM flag to instruct the API function to search from the system message table for the error code. For the other parameters of FormatMessage, just set them to 0 or NULL. The error message we got is an array of wide chars. We need to convert it to QString for display, using QString::fromWCharArray.

We research the FormatMessage function a lot and finally get the error message “The parameter is incorrect” from the error code 87. But that message does not help a lot as to our problem. It seems all the parameters of RegCreateKey are good, not “incorrect”. The error actually hides in the second parameter of RegCreateKey.We forcefully cast the parameter to LPCWSTR but the string literal is of type const char[N]. String literals (the chars in quotes) in source code(typically UTF-8 encoded) are always char array type but  RegCreateKey requires a wide char array. We should use the standard prefix L(L is NOT a macro) to write a wide char string literal in source code:

LSTATUS  result=RegCreateKey(HKEY_LOCAL_MACHINE, L"SOFTWARE\\myapplication", &regkey);

Now the function should execute without problem.

If you develop your application in modern Windows such as Windows 10 that exerts UAC(user account control), you may encounter another problem. The RegCreateKey would fail with error “Access Denied.”. This is UAC in action. Even you are a user belong to the Administrators group, your program still runs with a standard token, not an administrator token. The token is used to decide if the program has the privileges to access certain resource like registry. This is different from UNIX system which uses the effective user to decide the access  privileges. Some privileges such as accessing to registry are filtered from the standard account token. To have the privileges of creating a key in registry, you need to elevate your program. You can do this by right-clicking on your .exe program, then clicking “Run as administrator” option in the context menu. A UAC dialog window will pop up saying “Do you want to allow the app from an unknown publisher to make changes to your device?”. Click “Yes”, and your program will run with the administrator  account token (but the user of the process remains unchanged, i.e., does not change to administrator) and thus can access the registry.

Knowing the above skill to run your application in administrative mode is not enough if you’re developing an app that’s used by many people. Your customers may not know this trick and your app won’t work. You may teach this method to your users in the manual or tutorial, but believe me, most of your customers won’t notice it and will continue to complain about your app.

The best way to deal with the uac issue is whenever a user double-clicks(or taps) on your app to run it, the uac prompt should appear to ask for permissions. The user must authorize the app in order to continue to use it. How to do that?

There are three methods.

The first method is to use a manifest file. If your application is named myapplication, you can create a manifest file myapplication.exe.manifest in the project directory with the following content:

 

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity version="1.0.0.0"  name="myapplication.exe" type="win32"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="requireAdministrator"/>
</requestedPrivileges>
</security>
</trustInfo>
</assembly>

After building the myapplication.exe, go to the build directory and run the following command:

"C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Bin\mt.exe" -manifest myapplication.exe.manifest -outputresource:myapplication.exe;#1

Now the manifest file is embedded into the exe file. You can see the icon of myapplication.exe has a shield on it, which means it will ask for privilege elevation when user runs it.

The second method eliminates the need for a manifest xml file. You can add the following line in the .pro file of your project:

QMAKE_LFLAGS += "/MANIFESTUAC:level='requireAdministrator'"

This way, a manifest fragment is also added into the exe file during link. Note that both the double quotes and the single quotes are required. Otherwise, you will see the following error when running the application:”The application has failed to start because its side-by-side configuration is incorrect. Please see the application event log or use the command-line sxstrace.exe tool for more detail.”

 

You may find another method in other places that uses a .rc to specify the .manifest file to avoid manual embedding the manifest file using mt.exe. The .rc file(myapplication.rc) would be:

#include <windows.h>
ID_ICON    ICON    DISCARDABLE    "myapplication.ico"
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "myapplication.exe.manifest"

Then you should include the resource file in the .pro file:

RC_FILE = myapplication.rc

You’ll get the following errors when building the application:

CVTRES:-1: error: CVT1100: duplicate resource.  type:MANIFEST, name:1, language:0x0409

:-1: error: LNK1123: failure during conversion to COFF: file invalid or corrupt

It turns out qt will generate and embed its own manifest file which conflicts with our manifest file. You can separate the default manifest file by adding the following line in the .pro file.

CONFIG -= embed_manifest_exe

It will disable embedding the default manifest file. Instead, it will embed our own manifest file into the final binary. The generated default manifest file will be put in the same directory as the .exe file.

With either method I introduced, you will be able to create an application that can run outside Qt Creator and will bring up the UAC prompt. But you still can not run your application in Qt creator. The error message is “Failed to start program. Path or permissions wrong?”. To run the app inside Qt Creator environment, you need to run Qt creator as administrator. You can do this by navigating to the shortcut of Qt Creator in start menu, right-clicking on it, clicking “More”, clicking “Run as administrator”. After Qt creator runs, you can open your project and run your application without problem. Your application will be started by Qt creator in administrative mode and won’t show the UAC prompt any more.

Another issue related to registry is if you are developing a 32-bit application and your application runs on 64-bit Windows, the register keys you create are actually stored under the WOW6432Node key. For example you want to create HKEY_LOCAL_MACHINE\SOFTWARE\myapplication, you are actually creating HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\myapplication. The registry reading/writing operation also have this mapping or redirecting so it is totally transparent to you, the programmer. You do not need to care the physical location of the keys.

 

 

Posted in

Leave a Reply