I Need A Suggestion For A Function

Background Story: Earlier today in the beginners section of the forum a newer user asked a question to the effect of "Do Visual Basic and C++ use different WinAPI's?". To the more experienced programmers the answer no seems obvious but the more I thought about this the more I realized that this isn't outright told to new programmers, and even for the seasoned ones it doesn't click until you are using multiple languages at a time. Then inspiration hit me I wanted needed to write a tutorial demonstrating that any exported function can be used in C\C++, and that this ability is not something that is exclusive to *nix users. I even had the perfect function ready, "UpdatePerUserSystemParameters()". For those who are unfamiliar with this one it is not documented anywhere on MSDN, nor is it declared in any of the WinAPI SDK header files but it is exported from User32.dll, and you can find dozens of examples of how to call it through the command line using the rundll32.exe shell1. It doesn't take any arguments and it's return type is irrelevant in this demonstration. What it does is it updates a users session with changes that were made to the registry with out the need to reboot, kind of like gpupdate. It would be perfect if I could just make a few registry changes to "HKEY_CURRENT_USER\Control Panel\Desktop" and have the users wallpaper change right before their eyes by using a function that, according to Microsoft, doesn't exist for use in either C or C++ (Inb4 use "SystemParametersInfo()" if you're thinking this then I salute your knowledge but you're missing the point).

My Problem: The function doesn't work anymore. Because of the way I'm calling it (see my code below) I have time to make sure the call to the function is on the stack using Process Explorer, I can send the address returned to me by "GetProcAddress()" to std::cout and compare it to the address of the function in the DLL using Nirsoft's DLL Export Viewer, even using Ollydbg I've watched it jump from my application into user32.dll to the address shown by both std::cout and DLL Export Viewer. Now I do get an error code from "GetLastError()" of 126 which means "Module Not Found" but I would be having a lot more trouble then that if user32 wasn't there, also I've verified a dozen ways that the DLL is loaded into my program AND the function call is on the stack.

If any of you could either suggest a function that meets at least most of the criteria I listed above or help me identify the problem with my code you would be my hero and I WILL mention your contribution in the article I plan to write. Thank you.

My Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
#include <iostream>
#include <string>

#include <windows.h>
#include <winbase.h>
#include <WinReg.h>

void pause()
{
    std::cin.sync();
    std::cin.ignore();
}


bool ChangeRegistryKey()
{
    HKEY RegKey; // Target Hive
    HKEY ControlPanel; // Target Registry Key
    DWORD RegKeySize; // Size Of Target Registry Key
    std::string RegSubKeyName = "Wallpaper"; // Name Of Subkey
    BYTE RegKeyData[MAX_PATH]; // Data In The Registry Keys
        ZeroMemory(&RegKeyData, sizeof(MAX_PATH)); 
    BYTE Data[] = "C:\\Picture.jpg\0";
    
    DWORD Index = 0;
    TCHAR KeyName[MAX_PATH]; // Subkeys In Target Registry Key
        ZeroMemory(&KeyName, sizeof(MAX_PATH));
    
    /* Open The Hive Containing The Target Registry Key In This Case HKEY_CURRENT_USER */
    if(RegConnectRegistry(NULL, HKEY_CURRENT_USER, &RegKey) != ERROR_SUCCESS)
    {
        std::cout << "FAILED TO GET HKEY_CURRENT_USER\n" << GetLastError() << std::endl;
        
        return false;
    }
    
    /* Open The Registry Key By Passing The Path Relative To The Key You Connected To With RegConnectRegistry Above */
    if(RegOpenKey(RegKey, TEXT("Control Panel\\Desktop\\"), &ControlPanel) != ERROR_SUCCESS)
    {
        std::cout << "FAILED TO OPEN CONTROL PANEL\\DESKTOP\n" << GetLastError() << std::endl;
        
        RegCloseKey(RegKey);
        return false;
    }
    
    /* The First Call To This Function Is Just To Get The Size Of The Key For Reading And Stores It In RegKeySize */
    RegQueryValueEx(ControlPanel, RegSubKeyName.c_str(), NULL, NULL, NULL, &RegKeySize);
    
    /* Now That We Know The Size We Can Tell The Function How Much To Expect */
    RegQueryValueEx(ControlPanel, RegSubKeyName.c_str(), NULL, NULL, RegKeyData, &RegKeySize);
    
    /* Display Data Currently Held By The Target Registry Key In Case You Are Interested */
    //std::cout << RegKeyData << std::endl;
    
    /* Verify The Contents Of The Registry Key If You Would Like */
    /* while(RegEnumKey(ControlPanel, Index, KeyName, MAX_PATH) != ERROR_NO_MORE_ITEMS)
    {
        Index++;
        
        std::cout << KeyName << std::endl;
        ZeroMemory(&KeyName, sizeof(MAX_PATH));
    }
    
    std::cout << "\nTHATS ALL OF THE KEYS HERE\n" << Index << std::endl;
    pause(); */
    
    /*Use RegSetKey To Change The Target Registry Key */
    if(RegSetValueEx(ControlPanel, "Wallpaper", 0, REG_SZ, Data, sizeof(Data)) != ERROR_SUCCESS)
    {
        std::cout << "FAILED TO SET WALLPAPER\n" << GetLastError() << std::endl;
        
        RegCloseKey(RegKey);
        RegCloseKey(ControlPanel);
        return false;
    }
    
    if(RegSetValueEx(ControlPanel, "OriginalWallpaper", 0, REG_SZ, Data, sizeof(Data)) != ERROR_SUCCESS)
    {
        std::cout << "FAILED TO SET WALLPAPER\n" << GetLastError() << std::endl;
        
        RegCloseKey(RegKey);
        RegCloseKey(ControlPanel);
        return false;
    }
    
    if(RegSetValueEx(ControlPanel, "ConvertedWallpaper", 0, REG_SZ, Data, sizeof(Data)) != ERROR_SUCCESS)
    {
        std::cout << "FAILED TO SET WALLPAPER\n" << GetLastError() << std::endl;
        
        RegCloseKey(RegKey);
        RegCloseKey(ControlPanel);
        return false;
    }
    
    
    RegCloseKey(RegKey);
    RegCloseKey(ControlPanel);
    
    return true;
}


int main(int argc, char *argv[])
{
    /* Load The Library That Contains The Target Function Using The Windows "TEXT()" Macro */
    HMODULE User32 = LoadLibrary("C:\\WINDOWS\\system32\\user32.dll");
    
    std::cout << "1 ERROR CODE AT THIS POINT: " << GetLastError() << std::endl;
    
    /* Make Sure The DLL Loaded Properly */
    if(User32 == NULL)
    {
        std::cout << "FAILED TO LOAD DLL";
        pause();
        
        return 1;
    }
    
    std::cout << "2 ERROR CODE AT THIS POINT: " << GetLastError() << std::endl;
    
    /* Call Our Function */
    if(!ChangeRegistryKey())
    {
        std::cout << "FAILED TO CONNECT TO REGISTRY\n" << GetLastError() << std::endl;
        
        pause();
        return 2;
    }
    
    std::cout << "3 ERROR CODE AT THIS POINT: " << GetLastError() << std::endl;
    
    /* Get Exported Functions Address */
    LPVOID UpdatePerUserSystemParameters = (LPVOID) GetProcAddress(User32, "UpdatePerUserSystemParameters");
    
    if(UpdatePerUserSystemParameters == NULL)
    {
        std::cout << "FAILED TO GET FUNCTION ADDRESS\n" << GetLastError() << std::endl;
        pause();
        
        return 3;
    }
    
    
    /* You Can Verify The Adderss That The Pointer Holds With DLL Export Viewer By Nirsoft */
    //std::cout << UpdatePerUserSystemParameters;
    
    /* Be Lazy, Launch The Function With CreateThread Instead Of Dealing With Function Pointers That Have Unknown Data Types */
    HANDLE MyThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) UpdatePerUserSystemParameters, NULL, CREATE_SUSPENDED, NULL);
    
    /* Remind User That The Thread Was Created In Suspended Mode */
    std::cout << "Press ENTER To Launch\n";
    pause();
    
    /* Resume Thread And Wait For It To Finish */
    ResumeThread(MyThread);
    WaitForSingleObject(MyThread, INFINITE);
    
    std::cout << "\"MyThread\" Completed\n";
    
    /* Clean Up */
    FreeLibrary(User32);
    CloseHandle(MyThread);
    
    pause();
    return EXIT_SUCCESS;
}


The commenting on this is meant to be verbose by the way, I plan on making this into an article here on this site. Thank you to anyone who tried.

1: You would normally enter RUNDLL32.EXE user32.dll,UpdatePerUserSystemParameters into the command line to use this function.
Last edited on
In ChangeRegistryKey(), you connect to your local registry with handle RegKey (which is unncessary by the way), then use that to open a key with ControlPanel. Then you close RegKey before closing ControlPanel. That's bad.

Why are you calling UpdatePerUserSystemParameters() in a separate thread? Why not just call it?

Although it may feel pretty cool to call undocumented functions, you should realise that they are subject to change between minor releases of the OS. So you really should avoid then. There is a message that you can broadcast that has the same effect, but I can't remember it right now and don't have the time to look it up. If no one can find it, I'll try to find it.
Why are you calling UpdatePerUserSystemParameters() in a separate thread? Why not just call it?

Trouble shooting, I don't blame you if you glanced over my obnoxiuos wall of text but I created the thread in suspended mode so that I could look at the stack in Process Explorer before I executed it. This code is all in the pre beta phase, that pause function is eventually getting the boot and I'll be using the RAII concept from the beginners sticky to hold the window open.

In ChangeRegistryKey(), you connect to your local registry with handle RegKey (which is unncessary by the way), then use that to open a key with ControlPanel. Then you close RegKey before closing ControlPanel. That's bad.

I'm willing to try it but it really shouldn't matter, HKEYs aren't actual objects they're just void pointers.

Although it may feel pretty cool to call undocumented functions, you should realise that they are subject to change between minor releases of the OS. So you really should avoid then

I meant for this to be a demonstration of how if something is doable in one language using the winapi the it's doable in c/c++. I didn't think about people being encouraged to use it in production code but that's an interesting point. I'm not ready to abandon it yet but I'll be sure to make that point in the article.
Last edited on
Sorry, after thinking more about your post I can see why you're calling that function. It's a direct comparison between VB and C++. But you should probably go for something more known like MessageBox.

Windows uses handles for almost everything, so don't be fooled by the handle for a registry object, it maps onto an underlying OS object. Anyway, you only need to call RegConnectRegistry when connecting to a different computer. You can pass HKEY_CURRENT_USER to RegOpenKeyEx. Forget RegOpenKey, it's a hang over from WIN16.

Anyway, I've taken a look at that function. It takes two parameters as far as I can tell. Here's my code.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <Windows.h>
#include <tchar.h>

typedef LPVOID WINAPI UpdatePerUserSystemParameters_Type(int, BOOL);

int main(int argc, char *argv[])
{
    if (HMODULE hUser32 = LoadLibrary(_TEXT("user32.dll")))
    {
        UpdatePerUserSystemParameters_Type* UpdatePerUserSystemParameters =
            (UpdatePerUserSystemParameters_Type*)GetProcAddress(hUser32, "UpdatePerUserSystemParameters");

        if (UpdatePerUserSystemParameters)
        {
            UpdatePerUserSystemParameters(1, TRUE);
        }

        FreeLibrary(hUser32);
    }

    return 0;
}
Last edited on
kbw said:
Anyway, I've taken a look at that function. It takes two parameters as far as I can tell.

I would love for you to go into more detail on how you did this. I'm self taught and I've always been under the impression that without documentation from the author of the API you're SOL in regards to using an exported function. What made this function "the perfect choice" was that all of the examples with RUNDLL32.exe called for no arguments.

Anyway, you only need to call RegConnectRegistry when connecting to a different computer...

The potential for non-destructive mischief is part of the allure to this idea. Besides it would still be up the the reader to deal with the ACLs, user accounts and connecting to the correct HKEY_USER hive if they wanted to be a pest, so I think I'll leave that part in and just not comment on it. I was thinking about including a function with "OpenSCManager()" and showing them how to start Remote Registry with "OpenService()" and "StartService()" so that it would work on later versions of Windows but I don't want to make it too obvious for script kiddies.
I would love for you to go into more detail on how you did this.
In all honesty, I had never seen UpdatePerUserSystemParameters, and will almost certainly never use it myself. All the searches showed it being called as:
 
RunDll32.exe USER32.DLL,UpdatePerUserSystemParameters ,1 ,True

And that is calling UpdatePerUserSystemParameters with two parameters, so I copied that in the C++ example. It seemed to refresh the desktop when run on XP.
As of right now I can't get the function to properly refresh the screen. If there's no visible result then I know the article isn't going to be as "cool" as I wanted it to be so I think this is going to be another project destined for that eternal back burner. As usual thank you for all of your help kbw, but it's like you said these functions should be avoided for a reason.
Topic archived. No new replies allowed.