Level Extreme platform
Subscription
Corporate profile
Products & Services
Support
Legal
Français
Articles
Search: 

Inside the Control Panel
Roberto C. Ianni, May 1, 2005
Windows Control Panel holds the configuration from several of the operating system's applications, and from some other applications that provide an applet to appear there. Learn how to build this kind of applets to give your application a professional configuration mechanism.
Summary
Windows Control Panel holds the configuration from several of the operating system's applications, and from some other applications that provide an applet to appear there. Learn how to build this kind of applets to give your application a professional configuration mechanism.
Description

Today we are going to delve into a Pandora's box, this little black box which we know is there, but which almost nobody uses. Windows Control Panel has the configurations of diverse operating system applications, and of some applications that generate an applet for it. We, as VFP programmers, are accustomed to place the application's configuration within the application itself, and to call it from a menu, or to create an external application that we call from the Windows startup menu; this is really valid and works correctly, but for my taste, we aren't giving our applications a really professional look.

As hinted above, we will have to develop an applet for the Windows Control Panel. To achieve this, we will have to roll up our sleeves and use VC++, so if you are easily excited, or have heart problems, please continue directly with the section on how to use it (this is a joke).

Objectives

As an objective, I propose we develop a CPL library which, through configuration, is capable of dynamically placing applets into the Control Panel, with the purpose that, when a user enters the Windows Control Panel, he can execute the configuration of our applications. For this purpose, we have to develop the CPL library and the EXE applications that it will call.

The points we will develop are:

  1. The basics.
  2. How to develop a CPL?
  3. Parameterizing our applet.
  4. Conclusions.

The basics

The CPL applet is no longer a simple Win32 DLL. When the Windows Control Panel opens, it searches all the files "*.CPL" found in the Win32 directory, or defined in the "control.ini" file. Then it loads the DLL, or rather the CPL files, into memory, and searches for a specific function CPlApplet", which has to be exported. If it finds this function, the Control Panel understands that it is a true applet, and starts to dialog with it.

The first thing the Control Panel does is to send the CPL_INIT message which makes the applet initialize and do its work, especially requesting memory and such stuff. Once the applet finishes its initialization work, it replies to the Control Panel, which automatically sends it the message CPL_GETCOUNT to ask about the number of applications associated with the applet. This shows us that a single CPL can have "n" associated applets; as we will see later, these can be static or dynamic.

The exported "CPlApplet" function looks like this:

LRESULT APIENTRY CPlApplet(HWND hwnd, UINT msg, LPARAM lp1, LPARAM lp2)

With these parameters, the Control Panel has a relationship with the CPL, to inform it about the actions which the applet executes and the parameters that are passed back and forth between the two.

How to develop a CPL?

To develop a CPL, we are going to use VC++, so, as I said, if you are easily excited... forget this point.

The steps within VC++ will be, create a new project of type "Win32 Dynamic-Link Library" and give it a name in the library, as shown in the figure.

Figure 1: Creating the new project

Then we tell the project assistant that we want it to generate a simple project; thus, we have the basic framework generated and configured, as shown in the figure.

Figure 2: First step

Once we completed these steps, VC++ gives us the starting code to start our work. It should look more or less like this:

#include "stdafx.h"

BOOL APIENTRY DllMain( HANDLE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                )
{
    return TRUE;
}

In this basic DLL function, we take the handle of the library instance, since we are going to use it further on.

{
   m_hInstance = hModule;
    return TRUE;
}

As the next, and last, configuration step, we have to tell the compiler that when it compiles, it should generate a file with an extension of CPL instead of DLL. We do this by going to the menu "Project/Settings", where we change the extension to CPL, as shown in the figure.

Figure 2: Project configuration

All that is missing now is to do the actual programming! What we have to do is define the function CPlApplet so that it can be used by the Control Panel, and consider the actions which the Control Panel wants us to do. We could implement it as follows:

LRESULT APIENTRY CPlApplet(HWND hwnd, UINT msg, LPARAM lp1, LPARAM lp2)
{

   LPCPLINFO lpCPlInfo;
   int i = (int) lp1;      // number of applet

   switch( msg )
   {
        case CPL_INIT :      // first message, sent once
         return TRUE;
         break;
        case CPL_GETCOUNT:    // second message, sent once
         break;

        case CPL_INQUIRE:    // third message, sent once per application
         break;

        case CPL_NEWINQUIRE:
         break;

        case CPL_DBLCLK:     // application icon double-clicked
            break;

        case CPL_STOP:       // sent once per application before CPL_EXIT
            break;

        case CPL_EXIT:      // sent once before FreeLibrary is called
            break;
        default:
            break;
    }
    return 0;
}

With this framework we could already have a working applet; we only need to export the function so that the C++ compiler may export it. For this purpose, we create an empty file which we call "FoxiControl.def". In this file, we create the following content:

; FoxiControl.def : Declares the module parameters for the DLL.

LIBRARY      "FoxiControl"
DESCRIPTION  'FoxiControl Windows Dynamic Link Library'

EXPORTS
    CPlApplet @1; Explicit exports can go here

With these simple steps we already have a CPL library, ready to be compiled and to run, but really it does nothing at all. So now we have to program the actions requested by the Control Panel. For this case, we will treat the applet as dynamic; as you can imagine, this is the most difficult part. We will later see what changes have to be done to make it static.

      case CPL_INIT :      // first message, sent once
      {
         LoadInfo();

         return TRUE;
         break;
      }

Now, the function LoadInfo has to open an .INI file, which will contain all the information of the applets which the DLL will contain, and which have to be loaded at runtime. Our function, then, might look something like this:

void LoadInfo()
{
   char cFileName[MAX_PATH];
   if ( ::GetModuleFileName( (HMODULE)m_hInstance, cFileName, MAX_PATH ) > 0 )
   {
      char drive[_MAX_DRIVE],  dir[_MAX_DIR],
          fname[_MAX_FNAME],  ext[_MAX_EXT],
          sLoadFileINI[MAX_PATH];
      char sIconFile[MAX_PATH], sProceso[32], sFile[MAX_PATH], sInfo[64], sHelFile[128];
      char sNameIconFile[MAX_PATH], sNameProceso[32],
         sNameFile[MAX_PATH], sNameInfo[64], sNameHelFile[128];

      _splitpath( cFileName, drive, dir, fname, ext );
      sprintf( sLoadFileINI, "%s%s%s.ini",
         drive, dir, fname );

      m_nCantProcesos = ::GetPrivateProfileInt( 
         "General", "CantidadProcesos", 0, sLoadFileINI );

      for( int nI = 0; nI < m_nCantProcesos; nI++ )
      {
         sprintf( sIconFile, "Icono%i", nI+1 );      // Icono1..... Icono255
         sprintf( sProceso,  "Proceso%i", nI+1 );    // Proceso1... Proceso255
         sprintf( sFile,       "FileExe%i", nI+1 );    // FileExe1... FileExe255
         sprintf( sInfo,       "Info%i", nI+1 );       // Info1...... Info255
         sprintf( sHelFile,  "HelpFile%i", nI+1 );   // HelpFile1.. HelpFile255

         ::GetPrivateProfileString( "Procesos", sIconFile, "",
            sNameIconFile, MAX_PATH, sLoadFileINI );
         ::GetPrivateProfileString( "Procesos", sProceso,  "",
            sNameProceso, 32, sLoadFileINI );
         ::GetPrivateProfileString( "Procesos", sFile,  "",
            sNameFile, 32, sLoadFileINI );
         ::GetPrivateProfileString( "Procesos", sInfo,     "",
            sNameInfo, 64, sLoadFileINI );
         ::GetPrivateProfileString( "Procesos", sHelFile,  "",
            sNameHelFile, 128, sLoadFileINI );

         m_ListaCPL[nI].hIcon =  (HICON)::LoadImage( 
            NULL, sNameIconFile, IMAGE_ICON, 0, 0, LR_LOADFROMFILE );
         strcpy( m_ListaCPL[nI].szFileExe,  sNameFile );
         strcpy( m_ListaCPL[nI].szName,     sNameProceso );
         strcpy( m_ListaCPL[nI].szInfo,     sNameInfo );
         strcpy( m_ListaCPL[nI].szHelpFile, sNameHelFile );

      }

      for( nI = m_nCantProcesos; nI < NUM_SUBPROGS; nI++ )
      {
         if( m_ListaCPL[nI].hIcon )
            ::DestroyIcon( m_ListaCPL[nI].hIcon );

         m_ListaCPL[nI].hIcon =  NULL;
         strcpy( m_ListaCPL[nI].szName,     "" );
         strcpy( m_ListaCPL[nI].szInfo,     "" );
         strcpy( m_ListaCPL[nI].szHelpFile, "" );
      }
   }
}

Well, to avoid an infarct, we will analyze the function one part at a time. The first thing it does is to call the API GetModuleFileName which returns the name of the executing library; in this case it will simply be FoxiContro.dll, but if the project has another name it would be called differently. With the library name and the path were it is located, we now form a string where there has to be a file with the same name as the library, but with an INI extension, which contains the configuration; in our case, this will be FoxiControl.ini.

Once we have the directory and the name of the INI file, we have to use the function GetPrivateProfileInt to obtain the number of processes defined in the INI. With this information the file is scanned for the definitions, trying to get the icon name, the process name, the EXE file which it has to execute, the help information that will be shown in the Control Panel. All this is loaded into an array for later use.

Now, after initializing the Control Panel applet, we have to send another message to ask how many applets the library has.

      case CPL_GETCOUNT:   // second message, sent once
         return m_nCantProcesos;
         break;

The next message we are going to send to the Control Panel will be CPL_INQUIRE, where we will be consulted once for every applet which we informed it that will be contained within the library. At this point there is a crossroads; here we must inform whether our applets are static or dynamic. In case our applet is dynamic, we have to inform about the structure of the value "CPL_DYNAMIC_RES", otherwise, we will inform the corresponding values; see the example.

Dynamic:

case CPL_INQUIRE: // third message, sent once per application
   lpCPlInfo = (LPCPLINFO) lp2;
   lpCPlInfo->lData = 0;
   lpCPlInfo->idIcon = CPL_DYNAMIC_RES;
   lpCPlInfo->idName = CPL_DYNAMIC_RES;
   lpCPlInfo->idInfo = CPL_DYNAMIC_RES;
   break;

Static:

case CPL_INQUIRE: // third message, sent once per application
   lpCPlInfo = (LPCPLINFO) lp2;
   lpCPlInfo->lData = 0;
   lpCPlInfo->idIcon = IDI_ICON1;
   lpCPlInfo->idName = IDS_PRIMER_ITEM;
   lpCPlInfo->idInfo = IDS_PRIMER_INFO;
   break;

The example of the static applet is very simple, since we only inform about the values of the resources that are compiled within the library, and that is all. With the dynamic case, on the other hand, we have to inform a specific value in a structure, and then receive another message where we have to specify the dynamic values.

Then, when our applet is dynamic, after the message CPL_INQUIRE we immediately receive the message CPL_NEWINQUIRE, were we have to inform everything again.

case CPL_NEWINQUIRE:
   NEWCPLINFO *lpNewWinQuire;
   lpNewWinQuire = ((NEWCPLINFO*)lp2);

   lpNewWinQuire->dwSize = sizeof(NEWCPLINFO);
   lpNewWinQuire->dwFlags = 0;
   lpNewWinQuire->dwHelpContext = 0;
   lpNewWinQuire->lData = (LONG)m_hInstance;
   lpNewWinQuire->hIcon =  m_ListaCPL[i].hIcon;

   strcpy(lpNewWinQuire->szName,     m_ListaCPL[i].szName );
   strcpy(lpNewWinQuire->szInfo,     m_ListaCPL[i].szInfo );
   strcpy(lpNewWinQuire->szHelpFile, m_ListaCPL[i].szHelpFile);
   break;

So, what we do is return to the Control Panel a pointer with all the dynamic information of the applet, which we can't inform as static, since it isn't compiled within our library.

At this point, the Control Panel will already be able to obtain all the information it requires, and show our icons on its desktop, but when the user double-clicks on one of our items, this will send us the message CPL_DBLCLK, to inform us that we have to do something. In our case, the CPL is only in charge of executing external applications, therefore we might write, describing our message as follows:

case CPL_DBLCLK:    // application icon double-clicked
   WinExec( m_ListaCPL[i].szFileExe, SW_NORMAL );
   break;

As we can see, the only thing we are doing is to use the API function WinExec, which is in charge of executing an application, in order to run the application defined in the INI file.

And finally, when the Control Panel sends the message to exit, we destroy the icons that we loaded into memory, so that memory is not lost, and can be used by other programs.

case CPL_EXIT:    // sent once before FreeLibrary is called
{
   for( int nI = 0; nI < NUM_SUBPROGS; nI++ )
      ::DestroyIcon( m_ListaCPL[nI].hIcon );

   break;
}

Parameterizing our applet.

Now, we will make our library work. Once we have it compiled, we have to create our configuration file, which should look as follows:

[General]
CantidadProcesos = 2

[Procesos]
Icono1    = C:\FoxLook\FoxLookConfig.ico
Proceso1  = FoxLook Config
FileExe1  = C:\FoxLook\FoxLookConfig.exe
Info1     = Configuración de FoxLook

Icono2    = C:\MyApp\MyApp.ico
Proceso2  = Config App
FileExe2  = C:\MyApp\MyConfig.exe
Info2     = My application config.

In the entry "General" we define the number of processes which the library will have, and in the entry "Procesos" we define all the processes that we want the Control Panel to show when it starts.

Now the only thing we have to do is compile the two files FoxiControl.cpl and FoxiControl.ini into the Windows Win32 directory; then we have to enter the Control Panel and see our applications within it; obviously we can execute them.

I want to point out that when copy the file and enter the Control Panel, the operating system will keep the file FoxiControl.clp open. Therefore, if you try to erase it, this won't be allowed, because the file will be in use. So, if you want to erase it, you have to close your session, to force Windows to close the library, and when you start the session, proceed directly to erase it, without going to the Control Panel. The same happens if you need to change something in the INI configuration – you will also have to close the session and restart after doing the change.

Our applet, then, would look like this, in the Control Panel, if we have at least one applications configured in our INI file:

Figure 4: Our available applet

Conclussion

As we can see, adding an applet to the Control Panel isn't really a Pandora's Box; there is some complexity in using VC++, but as we can see, it isn't too complicated either. With the library FoxiControl.cpl we can not introduce our applications into the Control Panel without having to write C++, since it is completely encapsulated, and being dynamic, it allows us to add diverse applications, thus having a certain degree of control over the Windows Control Panel. We can also give our applications a more professional look.

I hope that you have liked this, and that nobody got an infarct ;) and as many of you know, if you have questions I will answer them as far as I am able to, but I will always try to answer them.

Source Code

Roberto C. Ianni, Banco Hipotecario
Roberto Ianni (Buenos Aires, Argentina) is a professional software developer for the last 4 years. He has programmed in C++, VFP, VC++, C#, and has lots of experience in VFP and VC++. He currently works in the Banco Hipotecario as an Analyst-Developer.
More articles from this author
Roberto C. Ianni, December 1, 2004
Today we are going to talk about data compression. This is something we do frequently, something which is useful for many different tasks.
Roberto C. Ianni, June 1, 2004
The "EventLog" or "Event viewer" is something we use on a daily basis, to obtain information about what happened to our computer at a certain moment; for example, when an application generates an error, most of us will intuitively look at the EventLog to see what information it left for us.
Roberto C. Ianni, May 1, 2004
The "EventLog" or "Event viewer" is something we use every day, to obtain information about what happened to our computer at a certain moment. For example, when an application generates an error, most of us instinctively go to the EventLog to see what information it left for us.
Roberto C. Ianni, July 1, 2004
One of the great limitations I find in VFP is the lack of low-level manipulation I can obtain from it. For many, this might not be an inconvenience, but several times I have encountered a problem which can't be solved with Fox, and it is then that I use a DLL or an FLL.
Roberto C. Ianni, August 1, 2004
As an objective for this issue, I want to propose the following points, to complete the first part of the development of an FLL. Advanced development with an FLL; Accessing VFP data; Executing VFP commands from a FLL; MultiThreading; FLL with VFP 9.0; Executing Assembler from VFP.
Roberto C. Ianni, October 1, 2004
This article is the continuation of "Sending email through Visual FoxPro without additional components", published last March. Back when I wrote this article, I didn't think it would get such a huge response, but it did, hence, due to the mails coming from everywhere I decided to write again on that...
Roberto C. Ianni, November 1, 2004
In this second part we will learn how to: Implement MIME, Attach binary files, the final implementation of the WSendMail class, and the use of the WSendMail class.
Roberto C. Ianni, April 1, 2005
The intention of this article is to finish with the use of POP3 (Post Office Protocol 3), implementing everything we have seen so far.
Roberto C. Ianni, January 1, 2005
This article is the beginning of receiving email from VFP; this is complemented with the articles you have already seen about sending email from VFP. To be able to do this, we will have to use the POP3 protocol (Post Office Protocol 3).
Roberto C. Ianni, February 1, 2005
We will continue some of the points mentioned in the previous article, in order to be able to implement message reception. The points to be developed are: POP3 Authentication (both Flat and MD5 authentication methods) and EMail parsing.
Roberto C. Ianni, March 1, 2004
Many of us send email using third-party tools such as Outlook Express or Microsoft Outlook, among others, and most likely have encountered registration problems in the client, licencing problems, OLE errors that only occurr at the client's site but not in our developer environment. Also problems if ...
Roberto C. Ianni, August 1, 2006
The present article is one of those that Martín Salías calls "low level fox", the general idea is to develop a service for the operative system whose only goal will be to run a VFP application in charge of handling the business logic.