现在的位置: 首页 > 综合 > 正文

封装控制面板函数 CCPApplet

2013年03月16日 ⁄ 综合 ⁄ 共 5275字 ⁄ 字号 评论关闭

封装控制面板函数 CCPApplet
 
编译 赵湘宁
 
Q 在编写一个Windows 98 和 Windows 2000 中使的MPEG播放器时,我想创建一个控制面板应用程序以便使用户可以改变播放器的基本配置。我知道应该创建一个DLL动态库,但接口是用C写的。如何利用C++和MFC实现?
A 不要忘了:C++就是C。所以,用C做的任何东西从某种意义上来说就是C++。但是如何解决你所遇到的问题呢?为此我将要说明用C++封装控制面板应用程序。我做了一个迷你型控制面板框架,利用它几乎不用改变控制面板扩展就可以解决问题。
控制面板扩展是一个带特殊输出接口的DLL动态库,这个接口就是CPlApplet函数,调用
它的语法如下:
LRESULT CPlApplet(HWND hwnd,
                  UINT msg,
                  LPARAM lp1,
                  LPARAM lp2);
控制面板的动态库有一个特殊的扩展名.cpl,而不是.dll。 当Windows的控制面板程序
(CONTROL.EXE)启动后,它在系统目录中寻找XXX.cpl文件,加载每一个DLL并以不同的消息调用其CPlApplet函数。例如,当控制面板第一次启动时,它用消息msg=CPL_INIT调用CPlApplet函数,当用户双击控制面板中的应用程序图标,它用消息msg=CPL_DBLCLK调用CPlApplet函数,然后控制面板应用程序显示其对话框,每个控制面板DLL能支持一个以上的图标或应用。通过对消息CPL_GETCOUNT的响应,可以让控制面板知道有多少个应用,通过发送CPL_INQUIRE 或 CPL_NEWINQUIRE消息,控制面板可以请求每一个应用的信息。
下图是一个跟踪的DUMP,可以查看控制面板发送消息的情况。
 
 
跟踪 Dump(TRACE Dump )
 
大多数控制面板和DLL之间的交互都是很普通的,可以被封装在一个框架里。我做了
两个类,CControlPanelApp 和 CCPApplet,实现了上述的封装。为了说明这两个类是如何工作的,我编写了自己的控制面板应用DLL:MyPanel。MyPanel 实现了两个控制面板应用,[如图一、图二、一个对话框(图三)和一个属性页(图四)]。MyPanel.cpp很象典型的MFC文档/视应用,只是它的APP类派生于CControlPanelApp,而不是CWinApp,文档模板中的InitInstance被重载,并且改名为OnInit,OnInit创建了三个应用(applets)

BOOL CMyControlPanelApp::OnInit()
{
     AddApplet(new CCPApplet(IDR_MYAPPLET1,
     RUNTIME_CLASS(CMyDialog1)));
     AddApplet(new CCPApplet(IDR_MYAPPLET2,
     RUNTIME_CLASS(CMyDialog2)));
     AddApplet(new CCPApplet(IDR_MYAPPLET3,
     RUNTIME_CLASS(CMyPropSheet)));
     return CControlPanelApp::OnInit();
}

对于MyPanel来说,CCPApplet类本身不必再派生新类,framework类直接在在外部运作。真正需要编码的部分是对话框本身。在这个例子中,MyPanel实现一个对话框和一个属性页CMyPropSheet。不管你相不相信,就这么简单,仅仅做一个对话框,并象上述那样重载CControlPanelApp::OnInit,framework照常工作。
到这里,我们的工作还没有完成,比如:图标、描述、CPlApplet函数在哪?那些个CPL消息怎么处理?不要着急,所有这些工作都由CControlPanelApp 和 CCPApplet来完成。CPanel.cpp有一个CPlApplet函数将CPL消息传递到虚拟函数。当控制面板以消息CPL_INIT 调用 CPlApplet时,CPlApplet再调用CControlPanelApp::OnCplMsg,依次将控制传到CControlPanelApp::OnInit,OnCplMsg类似于CWnd::WindowProc,OnInit 类似于消息处理器OnCreate。诸如CPL_INQUIRE、CPL_DBLCLK之类的CPL消息按应用号(applet number)(或者说索引)用参数lParam1传递。(记住:单个控制面板扩展能实现一个以上的图标/应用[icon/applet]),如果是那样的话,CControlPanelApp::OnCplMsg将消息传到CCPApplet类的虚拟函数,而非CControlPanelApp。

CControlPanelApp 和 CCPApplet 根据给定的信息实现了需要的处理。当你创建一个新的控制面板应用时,给构造器一个资源ID(IDR_MYAPPLET3)和一个MFC运行时类(CMyPropSheet)。

AddApplet(new CCPApplet(IDR_MYAPPLET3,
RUNTIME_CLASS(CMyPropSheet)));

这就是实现控制面板应用时,框架需要的全部信息。AddApplet将应用加到m_lsApplets列表。处理消息CPL_GETCOUNT,然后返回应用在列表中号码。当控制面板发送CPL_INQUIRE 或 CPL_NEWINQUIRE消息时,CCPApplet使用资源ID来获得应用的图标、名字和描述,名字和描述被解析为资源串中的子串。

STRINGTABLE PRELOAD DISCARDABLE
BEGIN
IDR_MYAPPLET3 "Intergalactic/n
Intergalactic settings for space cadets/n/n"
END

这类似于MFC使用IDR_MAINFRAME处理串资源,如应用程序名、文当类型、COM ProgID等。只要按规范定义图标和资源串,就不必实现OnInqure 或者 OnNewInqure。另外,CPL_NEWINQUIRE是一个改进,一般说来,一个应用只要实现OnInqure,但如果应用的信息要从一个SESSION改变到另一个SESSION(似乎有点不可思议),就只需实现OnNewInquire。如果是那样,应将CCPApplet::m_bDynamic赋值为TRUE;告诉框架从CPL_INQUIRE返回FALIED,从而使控制面板处理CPL_NEWINQUIRE消息。有点不明白的地方是为什么能忽略那么多细节,只使用资源串就把问题搞定。

当用户双击控制面板中的应用图标时,Windows发送CPL_DBLCK消息。 这个消息被映射到CCPApplet::OnLaunch,此函数用对话框或者属性页的运行时类来创建一个实例,并调用DoModal。

LRESULT CCPApplet::OnLaunch(CWnd* pWndCpl, LPCSTR lpCmdLine)
{
    CWnd* pw = (CWnd*)m_pDialogClass->CreateObject();
    if (pw)
    {
        if (pw->IsKindOf(RUNTIME_CLASS(CPropertySheet)))
        {
            CPropertySheet* ps = (CPropertySheet*)pw;
            ps->SetActivePage(lpCmdLine ? atoi(lpCmdLine) : 0);
            ps->DoModal();
        }
        else
        {
            if (pw->IsKindOf(RUNTIME_CLASS(CDialog)))
            {
               CDialog* pd = (CDialog*)pw;
               pd->DoModal();
            }
        }
        return pw==NULL;
     }
}
一定要用DECLARE_DYNCREATE来声明对话框类和属性页类。如果不这样做会出错,而且可能还会有MFC的TRACE诊断错。除此之外,还要记住重载对话框和属性页的OnPostNcDestroy函数,加入代码“delete this”。 通常是用下面的代码stack上创建一个对话框:
     CMyDialog dlg;
     dlg.DoModal();

所以不必delete。而CPApplet是在heap上创建的对话框和属性页,在对话框和属性页被destroy掉以后,必须要delete它们,否则造成内存溢出。对于普通的对话框或者基于属性页的应用,为了添加应用,只需要重载CControlPanelApp::OnInit。Table 列出了各种CPL消息及framework如何处理它们;代码
最后,讲几个注意事项(技巧和诀窍),当你建立了控制面板扩展后,不要忘记将名字改为.cpl,然后将它拷到/WINDOWS/SYSTEM目录。你可以在“Project Settings”菜单中加一个Post-Build来省略拷贝步骤。如果你喜欢将动态库放在自己的目录,那在/WINDOWS/CONTROL.INI文件的“MMCPL section”中加上下面一行:

[MMCPL]
MyPanel=c:/utils/MyPanel/MyPanel.cpl

再就是当你编译链接控制面板程序时,可能遇到下列问题:增加另一个应用、或者改变名字或图标--而这个更改没有反映在控制面板中。那是因为控制面板第一次读取(CPL_INQUIRE)信息时遇到了你的DLL,然后将信息缓冲到磁盘。使得控制面板重读新信息的方法是改DLL名字。你也可以试一下在控制面板中按F5(Refresh),但这不是每回都奏效。在开发期间,你可以将CCPApplet::m_bDynamic 设置为 TRUE,让它告诉框架使用CPL_NEWINQUIRE(不缓冲)代替 CPL_INQUIRE (缓冲)。当你调试完发行时,再将CCPApplet::m_bDynamic设置回(缺省值)FALSE。

说到调试,这里涉及控制面板应用程序调试问题,有两种方法:一种是在调试器状态下启动控制面板;另外一种较繁琐的方法是使用rundll32,在命令行或者VC的Debug Settings中敲入:

rundll32 shell32.dll,Control_RunDLL mypanel.cpl

Control_RunDLL是shell32.dll中的一个特别的函数,运行一个控制面板应用;rundll32.exe是在DLL中调用函数的一个程序。为了在DLL中运行第一个应用,输入:

rundll32 shell32.dll,Control_RunDLL mypanel.cpl,@n
 n 是基于零的应用索引。如果你末尾加一个串,它被传递到CPL_STARTWPARAMS,象一个一般windows应用的命令行,通常,这个串被用于启动一个基于属性页的控制面板应用,然后定位在特定的属性页上。例如:为了调出计算机显示器的“设置”属性页标签,敲入:

rundll32 shell32.dll,Control_RunDLL desk.cpl,,3
如果你以前不知道有些应用程序是怎么启动特定的控制面板应用的,现在你应该知道了。如果你使用我在这里提供的框架(framework),自己就不必写任何解析参数的代码;如果你的应用是一个属性页,CCPApplet自动翻译超出的“arg”为一个页号。

LRESULT CCPApplet::OnLaunch(CWnd* pWndCpl, LPCSTR lpCmdLine)
{
   CWnd* pw = (CWnd*)m_pDialogClass->CreateObject();
   if (pw) {
     if (pw->IsKindOf(RUNTIME_CLASS(CPropertySheet))) {
         CPropertySheet* ps = (CPropertySheet*)pw;
         ps->SetActivePage(lpCmdLine ? atoi(lpCmdLine) : 0);
         ps->DoModal();
     } else if (pw->IsKindOf(RUNTIME_CLASS(CDialog))) {
                CDialog* pd = (CDialog*)pw;
                pd->DoModal();
            }
   }
   return pw==NULL;
}
 
  

抱歉!评论已关闭.