如何编写控制面板程序
控制面板程序是用户用于配置Windows环境的一些特殊动态连接库(DLL)。Windows已经为我们提供了一些标准的控制面板程序,如字体、键盘、鼠标设置等;如图1所示。我们也可以根据实际的需要创建自己的控制面板程序,以便让用户来查看或者修改特定软硬件的参数设置。
控制面板的功能和操作方式
控制面板程序的主要功能是显示一个对话框让用户完成特定的任务。但是,与其它应用程序不同的是,控制面板程序并不是一个独立的应用程序,它不提供标准菜单或者其它方法让用户直接去操作这些对话框,而是在其他应用程序(如控制面板)的控制下操作和显示它们的对话框的。
控制面板程序通常是由一个Windows的系统工具(即控制面板)所控制,从而让用户来运行的。但是,其它应用程序只要发送控制面板程序所需要的消息并且处理它们的返回值同样可以加载并且管理这些控制面板程序,为了陈述简单,下文中我们将这类程序统称为控制面板。
大多数的控制面板程序仅显示并且维护一个对话框让用户来控制系统某一个部件的参数设置以及操作方式,但事实上控制面板程序可以提供多个对话框来控制多个系统部件(这些单个的对话框通常被称为Applet小程序)。为了区分这些对话框,控制面板程序通常为每个对话框提供一个图标。控制面板则把这些图标显示出来,供用户选择不同的对话框进行配置。
程序入口函数
前面提到,控制面板程序是特殊的动态连接库(DLL),在每一个控制面板程序中必须提供一个标准的函数入口 CPlApplet ,控制面板程序通过这个函数来接收控制面板发送的消息,从而执行相应的功能,如初始化程序、显示并且管理对话框以及关闭程序等。
当控制面板首次加载控制面板程序时取得CPlApplet函数的地址,通过该地址去调用控制面板程序的函数以及给它发送消息。
控制面板可以向控制面板程序发送的消息名称及其意义如下表所示:
消息名称
含义
CPL_DBLCLK
该消息通知CPlApplet函数用户双击了某对话框相关联的图标。CPlApplet应该显示相应的对话框并且完成用户指定的任务。
CPL_EXIT
该消息在最后一条CPL_STOP消息之后,并且控制面板调用FreeLibrary 函数释放包含控制面板程序的动态连接库DLL 之前调用。CPlApplet应该释放所用资源准备关闭。
CPL_GETCOUNT
该消息在发送了CPL_INIT消息之后发送,CPlApplet应该返回一个整数指明它支持多少个对话框。
CPL_INIT
该消息在控制面板动态连接库DLL程序首次加载时调用, CPlApplet在此可以执行一些初始化过程,包括内存的分配。
CPL_INQUIRE
该消息在CPL_GETCOUNT消息之后发送,让CPlApplet提供特定对话框的信息。CPlApplet的lParam2参数指向一个CPLINFO结构。
CPL_NEWINQUIRE
该消息在CPL_GETCOUNT消息之后发送,让CPlApplet提供指定对话框的信息。参数lParam2是指向NEWCPLINFO结构的指针,在Windows 95 and Windows NT version 4.0,为了提高性能,控制面板程序应该处理CPL_INQUIRE消息,而不是CPL_NEWINQUIRE消息。
CPL_STOP
在控制面板关闭前向每一个对话框发送该消息。CPlApplet应该释放与给定对话框相关联的内存资源。
消息处理
回调函数CPlApplet处理所有由控制面板发送给控制面板程序的上述消息,这些消息是按照特定的顺序来发送并且处理的。
当控制面板首次加载控制面板程序的动态连接库时,CPlApplet收到CPL_INIT消息。CPlAppet在处理该消息的程序中应该执行一些初始化的工作,如分配内存等,并且返回非0值;如果CPlApplet初始化失败,则应该返回0,控制面板将停止向CPlApplet发送消息并且释放已经加载的动态连接库DLL。
当CPL_INIT消息处理成功之后,CPlApplet函数将收到CPL_GETCOUNT消息,CPlApplet函数必须返回该控制面板程序所支持的对话框的数目。
对应控制面板所支持的每一个对话框,CPlApplet函数收到由控制面板发送的一条CPL_INQUIRE消息和一条CPL_NEWINQUIRE消息。函数需要用控制面板程序的信息填充CPLINFO或者NEWCPLINFO结构体,如名称、图标以及描述字符串等。多数控制面板程序仅需处理CPL_INQUIRE消息而将CPL_NEWINQUIRE消息忽略。CPL_INQUIRE通过一个控制面板能够缓存的结构存放信息从而提高性能。CPL_NEWINQUIRE仅当您需要根据计算机的状态来改变控制面板程序的图标和显示字符串时有用。
当用户在控制面板中双击某个对话框所对应的图标时, CPlApplet函数将收到一条CPL_DBLCLK消息,CPlApplet函数可以收到该消息多次。该消息包含了对话框的标志和一个lData数据,CPlApplet函数应该显示相应的对话框并且处理用户的输入。
当控制面板关闭时,对应控制面板程序支持的每一个对话框, CPlApplet函数将收到一条CPL_STOP消息。该消息包含了对话框的标志以及lData数据,CPlApplet函数应该释放此对话框所占用的资源。
在最后一条CPL_STOP消息之后, CPlApplet将收到一条CPL_EXIT消息。此时应该释放此控制面板程序所占用的所有资源。在该消息返回之后,控制面板将调用FreeLibrary 来释放控制面板程序动态连接库。
控制面板程序的安装
在编译连接输出文件名选项中将输出文件扩展名改为.CPL,编译连接成功后将其拷贝到windows的SYSTEM目录即可。
示例程序:
该示例程序将显示一个对话框,并且根据用户的输入改变系统注册表中相应的数据.按上小节所述的安装方法安装控制面板示例程序后,再次启动控制面板,在控制面板中将出现示例程序的图标,双击该图标后将弹出一个对话框。如图2所示。
该程序使用Visual C++开发,从File菜单选择New,再选择MFC AppWizard,工程名称为CPL,如图3所示。单击OK之后再选择MFC Extention DLL,如图4所示。这样,Visual C++将为我们创建一个包含资源文件,DEF文件的动态连接库工程。
在Visual C++ AppWizard为我们创建的工程中,加入一个图标资源和字符串资源,ID分别为ID_ICON1和ID_STRING1,然后再加入一个图2所示对话框,其中有一个CheckBox,ID为ID_CHECK1。并且将工程的输出文件名改为CDAUTO.CPL,方法是选择Project菜单的Settings...项,如图5所示。以下是程序的清单。
程序清单:
// CPL.cpp : Defines the initialization routines for the DLL.
//
#include "stdafx.h"
#include <afxdllx.h>
#include <CPL.h>
#include <winreg.h>
#include "resource.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
static AFX_EXTENSION_MODULE CPLDLL = { NULL, NULL };
HINSTANCE hinst = NULL;
BOOL FAR PASCAL CPLDlg(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);
file://控制面板接口函数CPlApplet
extern "C" int APIENTRY CPlApplet(HWND hwndCPL, UINT uMsg, LPARAM lParam1, LPARAM lParam2);
extern "C" int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
// Remove this if you use lpReserved
UNREFERENCED_PARAMETER(lpReserved);
if (dwReason == DLL_PROCESS_ATTACH)
{
TRACE0("CPL.DLL Initializing!\n");
// Extension DLL one-time initialization
if (!AfxInitExtensionModule(CPLDLL, hInstance))
return 0;
// Insert this DLL into the resource chain
// NOTE: If this Extension DLL is being implicitly linked to by
// an MFC Regular DLL (such as an ActiveX Control)
// instead of an MFC application, then you will want to
// remove this line from DllMain and put it in a separate
// function exported from this Extension DLL. The Regular DLL
// that uses this Extension DLL should then explicitly call that
// function to initialize this Extension DLL. Otherwise,
// the CDynLinkLibrary object will not be attached to the
// Regular DLL's resource chain, and serious problems will
// result.
new CDynLinkLibrary(CPLDLL);
}
else if (dwReason == DLL_PROCESS_DETACH)
{
TRACE0("CPL.DLL Terminating!\n");
// Terminate the library before destructors are called
AfxTermExtensionModule(CPLDLL);
}
hinst = hInstance;
return 1; // ok
}
int APIENTRY CPlApplet(HWND hwndCPL, UINT uMsg, LPARAM lParam1, LPARAM lParam2)
{
int i;
LPCPLINFO lpCPlInfo;
i = (int) lParam1;
switch (uMsg) {
case CPL_INIT: // first message, sent once
return TRUE;
case CPL_GETCOUNT: // second message, sent once
return 1;
break;
case CPL_INQUIRE: // third message, sent once per application
lpCPlInfo = (LPCPLINFO) lParam2;
lpCPlInfo->lData = 0;
lpCPlInfo->idIcon =IDI_ICON1;
lpCPlInfo->idName = IDS_STRING1;
lpCPlInfo->idInfo = IDS_STRING1;
break;
case CPL_DBLCLK: // application icon double-clicked
FARPROC lpProcDlg;
lpProcDlg = (FARPROC)MakeProcInstance(CPLDlg,hinst);
DialogBox(hinst,MAKEINTRESOURCE(IDD_DIALOG1),hwndCPL,(DLGPROC)lpProcDlg);
FreeProcInstance(lpProcDlg);
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;
}
BOOL FAR PASCAL CPLDlg(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
DWORD dataType;
BYTE data[100];
DWORD reserved;
DWORD size;
HKEY hKey = HKEY_CURRENT_USER;
char ValueName[]="Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoDriveTypeAutoRun";
switch(message)
{
case WM_INITDIALOG:
size = 4;
if(RegQueryValueEx(HKEY_CURRENT_USER,ValueName,&reserved,&dataType,data,&size)
== ERROR_SUCCESS )
{
if( data[0] == 0x95 )
SendDlgItemMessage(hDlg,IDC_CHECK1,BM_SETCHECK,1,0L);
else
SendDlgItemMessage(hDlg,IDC_CHECK1,BM_SETCHECK,0,0L);
}
return TRUE;
case WM_COMMAND:
switch(wParam)
{
case IDOK:
if(SendDlgItemMessage(hDlg,IDC_CHECK1,BM_GETCHECK,0,0L) == BST_CHECKED)
data[0] = 0x95;
else
data[0] = 0xb5;
data[1] =data[2] = data[3] = 0;
size =4;
reserved = 0;
dataType = REG_BINARY;
RegSetValueEx(HKEY_CURRENT_USER,ValueName,reserved,dataType,(BYTE*)data,size);
EndDialog(hDlg,TRUE);
break;
case IDCANCEL:
EndDialog(hDlg,FALSE);
break;
}
break;
}
return FALSE;
}