Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Override Windows Creation Parameters with a Win32 Service

0.00/5 (No votes)
19 Dec 2004 1  
Provide a way to override the creation parameters for any window with a service application.

Introduction

The full source code can be divided into a few parts:

  • Command line processing
  • Win32 service
  • Windows hook
  • Configuration file interpreter

This demo project runs as a Windows service application to override creation parameters of any specified window. As part of the service code, it reads entries from a configuration file. Each entry should have a window class name and its desired creation parameters. In the beginning of its execution, it installs a global hook procedure of type WH_SHELL to monitor shell events. Whenever a top level window is created, it receives notification about it and adjusts the window according to the set of parameters defined by the user.

Some examples of what this demo program can do:

Making only 'Notepad' to be transparent:

Notepad

Making only 'ExploreWClass' to be transparent:

Explorer

Making 'SciCalc' to always start at the upper left corner of the desktop:

Scientific Calculator

All the above can be done with the following few lines in the configuration file:

ExploreWClass -1 -1 -1 -1 200
Notepad -1 -1 -1 -1 200
SciCalc 10 10 260 0 -1

The above examples are trivial, much more can be done with this simple application. The configuration file interpreter within the service application can be reprogrammed to understand complex scripting language. For example, we can add clock and date information to an application's title with the same type of hook, or we can disable certain annoying pop-ups by destroying them every time they are created.

Command Line Processing

Files:

  • Main.cpp
  • CCmdLineArgs.cpp
  • CCmdLineArgs.h

The program handles command line arguments in a very similar way to how GNU programs do. Arguments can be options or non-options. Option arguments begin with a '-'. For example:

helloworld.exe -i -r c:\hello\world\helloworld.exe
  • helloworld.exe is the program file name.
  • -i and -r are option arguments.
  • c:\hello\world\helloworld.exe is a non-option argument.

Each short option can have a corresponding long option and vice versa. Long names can sometimes be easier to remember. Long options begin with a '--'. For example:

helloworld.exe --install --run
  • helloworld.exe is the program file name.
  • --install and --run are long options.

The way CCmdLineArgs works is to call Parse repeatedly with argc and argv until it returns CLA_DONE. Each time Parse is called, one argument is processed. If the argument is a short option, a unique character representing the option is returned; if the argument is a long option, a character can be similarly returned or a variable can be set.

The function Parse is defined as follows:

static char Parse(int argc, char *const argv[], 
            const char *optstr, const lopt_entry *lopts);

The string optstr defines all short options and the table lopt_entry should hold information about all long options.

optstr contains a string of characters with each of them representing a valid short option. A character in optstr can be appended with a single colon, ':' or a double colon, '::'. Single colon means the option requires an argument, whereas double colon means the option can be with or without an argument. An argument to an option and the option itself must be written together. For example:

helloworld.exe -ic:\hello\world\helloworld.exe
  • helloworld.exe is the program file name.
  • -i is an option and c:\hello\world\helloworld.exe is the argument to the option -i.

lopt_entry is defined as follows:

typedef struct lopt_entry_
{
    const char *name;
    int has_arg;
    int *flag;
    int val;
} lopt_entry;
  • name is the long option name.
  • has_arg determines whether the long option requires an argument, and has three possible values, CLA_ARG, CLA_NOARG and CLA_OPTARG.
  • flag is the variable to set by Parse. If NULL, Parse will return val as a character, otherwise the variable *flag will be set to val.

Examples:

If you want your program helloworld.exe to accept the following few arguments:

helloworld.exe -1 -2 -3 -4 -5 -6 -7 -8hello

the Parse function should be something like the following:

char ret = Parse(argc, argv, "12345678:", NULL);

Win32 Service

Files:

  • Main.cpp
  • CWinServ.cpp
  • CWinServ.cpp

The program is capable of installing and uninstalling itself as a service, and executing service code when the Service Control Manager starts it.

  • To install, use the options -i or --install.
  • To uninstall, use the options -u or --uninstall.
  • To run, do not specify any arguments or use the options -r or --run.

There are two excellent articles that explain how a simple service application can be created.

I assume readers have read through the above articles and now have a good understanding of the construction of a simple service application. The implementation in CWinServ.cpp and CWinServ.h is similar, except that it is done with a static class.

I have made the service to accept the following few user controls:

SERVICE_CONTROL_PAUSE The list of target classes and their parameters will be cleared, overriding will be paused.
SERVICE_CONTROL_CONTINUE The list will be reconstructed, overriding will continue.
SERVICE_CONTROL_STOP Service will terminate.
SERVICE_CONTROL_SHUTDOWN Service will terminate.

Windows Hook

Files:

  • DllMain.cpp
  • DllMain.h

To monitor the system for events, we need to install a hook procedure. The events being monitored can be associated to either a specific thread or all threads in the same desktop as the calling thread.

In this example application, we want windows of specific classes to be created with our own window creation parameters. The WH_SHELL hook can be used for this purpose. This type of Windows hook enables us to monitor shell events such as creation and destruction of top-level windows. More information can be found in the Platform SDK Documentation, search for the keyword "ShellProc".

This global hook procedure must be in a separate DLL. Since the hook-installing application must have the handle to the DLL module, the installing and uninstalling functions are placed within the same DLL as the hook procedure. Otherwise, a shared variable can be exported from the DLL to hold the handle to the DLL module.

Window Stations and Desktops

Service applications, by default, are running in a different window station from the user's. To find out what window stations are currently running, use EnumWindowStations. In my case, there are:

  • WinSta0
  • Service-0x0-3e7$
  • Service-0x0-3e4$
  • Service-0x0-3e5$
  • SAWinSta
  • __X78B95_89_lW

For every window station, there are desktops. To find out what they are, use EnumDesktops. The following desktops are in my "WinSta0" window station.

  • Default
  • Disconnect
  • Winlogon

As pointed out earlier, the user's window station and desktop are "WinSta0" and "Default" respectively. Recall that a global hook procedure receives only notification about events happening in the same desktop. Therefore, we have to switch the window station and desktop of our service thread to the same as the user's. The following portion of the source code does exactly that:

/* -- Open user's window station "WinSta0". -- */
HWINSTA hWinStaUser = OpenWindowStation("WinSta0", FALSE, MAXIMUM_ALLOWED); 
/* -- Set process window station to "WinSta0", this enables the process to 
      access objects in the window station such as desktops. -- */
if (SetProcessWindowStation(hWinStaUser)) {
    /* -- Open user's desktop "Default". -- */
    HDESK hDeskUser = OpenDesktop("Default", 0, FALSE, MAXIMUM_ALLOWED);
    /* -- Set thread desktop to "Default". -- */ 
    if (SetThreadDesktop(hDeskUser)) {
        /* -- Now install hook procedure to monitor events associated 
              with threads in the user's desktop. -- */
        if (InstallHook()) {
            ......
            ......
        /* -- Service should terminate, uninstall hook procedure. -- */
            UninstallHook();
            /* -- No error. -- */
            serv_stat.dwWin32ExitCode = 0;
        }
    }
    if (hDeskUser) {
        CloseDesktop(hDeskUser); 
    }
}
if (hWinStaUser) {
    CloseWindowStation(hWinStaUser);
}

At any point in time, we have no idea where our hook procedure is in the hook chain. If an application decides to install the same type of hook, it is likely to be after our service code, which is during startup. Therefore, our hook procedure has a high chance of always staying at the end of the hook chain. Although hook procedures should always call CallNextHookEx, they can always choose not to. To ensure that the service always works, we should always check that our hook procedure is called whenever a shell event occurs. This, however is not implemented in this sample application.

Configuration File Format

For the sake of this sample application, the file format has been designed to be trivially simple. Each line in the configuration file should correspond one-to-one with each member in the structure below:

typedef struct SHWP_STRUCT_ {
    char szClassName[256];
    int x, y, cx, cy, alpha;
} SHWP_STRUCT, *LPSHWP_STRUCT;

A valid line in the configuration file is:

ExploreWClass -1 -1 -1 -1 200

ExploreWClass will be the class name. A value of -1 means the parameter should be ignored. The last one sets the desired alpha value to 200.

Note that the implementation does not incorporate any error-checking mechanism to detect errors in the configuration file.

Others

The main part of the service code is the following:

while (serv_stat.dwCurrentState != SERVICE_STOPPED) {
    /* -- Run -- */
    if (serv_stat.dwCurrentState == SERVICE_RUNNING) {
        Sleep(5000);
        RefreshEntries();
    }
}

The configuration file is read every five seconds. Therefore, lines added to or removed from the file will take effect after five seconds in the worst case.

Note that by default each process using a DLL has its own copy of all global and static variables. Here, at least two processes are linked to the DLL, one being the service and the other being the thread that calls our hook procedure whenever a shell event occurs. Of course, our configuration entries read from file must be shared across these instances. For that, I used the data_seg pragma to define a shared data segment. The permission for the segment is set with the linker to be /SECTION:.SHARED,RWS.

#pragma data_seg(".SHARED")
SHWP_STRUCT entries[NUM_OF_ENTRIES] = { 0 };
#pragma data_seg()

References

  1. Arguments, Options, and the Environment.
  2. Window Stations and Desktops Overview, Microsoft Platform SDK Documentation.
  3. Hook Overview, Microsoft Platform SDK Documentation.
  4. Creating a Simple Win32 Service in C++ by Thompson Nigel.
  5. Five Steps to Writing Windows Services in C by Yevgeny Menaker.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here