使用Win32 SDK开发屏幕保护程序

版权声明:© Jie Zhuang 署名-非商用使用-禁止演绎 (CC BY-NC-ND 2.5) https://blog.csdn.net/jez/article/details/85760049

暮鼓集    行走集

1.引言

屏幕保护程序(Screen Saver)的历史几乎与视窗操作系统的历史一样悠久,设计它的目的是为了保护CRT显示器使其的使用寿命更长。但随着技术的进步,新型的CRT显示器及液晶显示器已经无须这样做了,不过屏幕保护程序并未消失,因为其绚丽的画面成了人们彰显个性的方式。

现在,逼真的水族箱,浩瀚的太空以及更吸引眼球的屏幕保护程序不段被开发出来。

如果你有兴趣开发自己的屏幕保护程序,并且具有Windows程序的开发经验,请深入阅读本文,本文将提供使用Win32 Platform SDK开发屏幕保护程序的指引及范例。

2.屏幕保护程序是一种应用程序

屏幕保护程序虽然变化多端,但对开发者而言,它仍然是一种Windows应用程序,虽然屏幕保护程序的扩展文件名是scr,但在文件格式上与exe可执行文件是完全一样的。

那么,是不是只要是Windows应用程序都可以作为屏幕保护程序呢?

我们做个一个实验,将Windows自带的“扫雷”程序文件winmine.exe改成winmine.scr,接着我们打开显示器设定,选择屏幕保护程序选项卡,可以发现winmine.scr已经被当成一个屏幕保护程序出现在列表里。我们选中它,看看发生了甚么--扫雷程序运行起来了!但是,随后无论我们如何摆弄键盘和鼠标,程序不会象其他屏幕保护程序那样自动关闭。

所以,虽然屏幕保护程序一种应用程序,但是它与普通应用程序在人机界面上有所不同,我们可以归纳出以下几点特征:

操作系统自动运行程序 全屏幕的窗口 用户输入即退出 因此,只要我们在一个应用程序中实现这些特征,那么这个应用程序即可做为屏幕保护程序。

3.实现屏幕保护程序的基本特征

3.1 操作系统自动运行程序

这实际上由系统完成,只要用户将程序设定为系统的屏幕保护程序,那么当用户没有输入一段时间后,系统就会自动调用这个程序。因此,这不需要开发者操心。

3.2. 创建全屏幕的窗口

我们在创建Window时设置其坐标及长宽参数即可实现这个目的:

CreateWindow( WNDCLASS_SSFRAME,
                         NULL,
                         WS_POPUP|WS_VISIBLE,
                         0,
                         0,
                         GetSystemMetrics( SM_CXSCREEN ),
                         GetSystemMetrics( SM_CYSCREEN ),
                         HWND_DESKTOP,
                         NULL,
                         hInst,
                         NULL);

注意,在这里函数参数中的父窗口句柄传入的是HWND_DESKTOP。

3.3. 用户输入即退出

用户输入的主要途径是通过键盘或鼠标,在Windows系统中,当键盘或鼠标事件发生时,系统会产生一系列的消息通知应用程序,具体有如下几个:

WM_LBUTTONDOWN   按下鼠标左键
WM_MBUTTONDOWN 按下鼠标中键   
WM_RBUTTONDOWN 按下鼠标右键
WM_MOUSEMOVE 鼠标移动
WM_KEYDOWN 按下键盘键
WM_KEYUP 抬起键盘键
WM_SYSKEYDOWN 按下键盘系统键

  (注: 系统键指Windows系统定义的用来激活菜单的F10键,以及按下Alt与其它键的组合键)。

因此,当屏幕保护程序收到这些消息时,应立刻退出运行,在消息处理函数添加如下的代码:

INT g_nOrigin_X = -1, g_nOrigin_Y = -1;

switch( message )
{
 ...
case WM_LBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
    PostQuitMessage( 0 ); // 退出程序
    break;  
case WM_MOUSEMOVE:
    {
        INT nNew_X = LOWORD(lParam);
        INT nNew_Y = HIWORD(lParam);
        if( g_nOrigin_X == -1 && g_nOrigin_Y == -1 )
        {
           g_nOrigin_X = nNew_X ;
           g_nOrigin_Y = nNew_Y ;
         }
         else if( g_nOrigin_X != nNew_X  &&  g_nOrigin_Y != nNew_Y )
            PostQuitMessage( 0 );
    }
...
}

请注意,程序对WM_MOUSEMOVE的消息的处理有些特殊。

当应用程序开始之行时,系统会将当前的鼠标坐标用WM_MOUSEMOVE消息通知应用程序。如果采用收到这个消息就退出处理方法,就会导致程序刚运行就会退出。因此,通过使用两个全局变量g_nOrigin_X和g_nOrigin_Y来纪录下初始化时的鼠标位置值。当再次收到这一消息时,通过比较当前鼠标位置与纪录的位置值可检测出鼠标有没有被移动过,若检测到则退出运行。

3.4 实现屏幕保护程序的画面

这与普通应用程序是完全一样的,在WM_PAINT里运用GDI API实现你的想法。在本例中,我们什么也不做,这就是简单实现黑屏的效果。

按照上面的方法生成的屏幕保护程序已经有模有样了。

4.成为真正的屏幕保护程序

尽管上述的应用程序已经很像一个屏幕保护程序了,但是,如果我们深入使用它,就会发现一些问题。

当我们把这个应用程序的扩展名改成scr并且设定它为系统的屏幕保护程序后,再次在控制面板中选择显示器设定并进入屏幕保护程序选项卡,发现及时我们没有“测试”,它却已经运行了。

这是因为,Windows系统会通过不同的命令行参数,以三种方式运行屏幕保护程序:

方式 命令行参数
正常(由系统自动执行,全屏幕显示 /s
配置(屏幕保护程序选项卡中的设定) /c
预览(显示在屏幕保护程序选项卡的小窗口中) /p

当我们一进入显示器设定的屏幕保护程序选项卡,系统就会自动在小窗口中预览当前选中的屏幕保护程序,即在命令行中传入/p参数运行了程序。而我们的程序中并未处理命令行参数,所以出现了上述的问题。

4.1 处理命令行参数

程序如下

if( __argc > 1 )
{
 if( strstr(__argv[1], "/p"))
 {
  if( __argc == 3 )
  {
   hFrameWnd = (HWND)atoi( __argv[2] );

  GetClientRect( hFrameWnd, &rect );
  CreateWindow( "ScreenSaverDraw", NULL,
    WS_CHILD|WS_VISIBLE,
    rect.left, rect.top,
    rect.right, rect.bottom,
    hFrameWnd, NULL,
    hInst, NULL);

  return 0;
  }
 }
 else if(strchr(__argv[1], 'c') )
 {
  MessageBox( NULL, "This screen saver doesn't have configuration",
   "Blank Screen Saver", MB_OK );
  // Preview mode
  return 0;
 }
}

我们重点要处理的是/p和/c参数。

当系统传入/p参数时,一定会将预览窗口的句柄通过第二个命令行参数一并传入。因此在程序中,可以通过

hFrameWnd = (HWND)atoi( __argv[2] );

来获知预览窗口句柄,此后可以在此窗口中显示预览界面。

当系统传入/c参数时,通常我们要创建一个对话框,并在其中提供一些设定让用户对屏幕保护程序的外观进行定制而使其更具个性。在本程序中,为了简化,所以只其弹出一个对话框,告知用户"This screen saver doesn’t have configuration”。

4.2 密码验证

如果在设置屏幕保护程序的时候选择了密码保护选项,则当屏幕保护程序退出前,应该显示密码验证窗口。

应用程序如何获知用户有没有选择密码保护? Windows会通过发送消息SCRM_VERIFYPW消息来通知应用程序来进行保护。

我们可以定制一个对话框要求用户输入指定的密码。 也可以通过Windows的Shell API使用共用的密码界面,这会更加简单可靠。

处理SCRM_VERIFYPW的代码:

case SCRM_VERIFYPW:
 if( VerifyPassword(hWnd) )
  PostQuitMessage(0);
 break;

函数VerifyPassword的代码:

BOOL VerifyPassword( HWND hwnd )
{
    // Under NT, we return TRUE immediately. This lets the saver quit,
    // and the system manages passwords. Under '95, we call VerifyScreenSavePwd.
    // This checks the appropriate registry key and, if necessary,
    // pops up a verify dialog

    OSVERSIONINFO osv;
    HINSTANCE hpwdcpl;
    typedef BOOL (WINAPI *VERIFYSCREENSAVEPWD)(HWND hwnd);
    VERIFYSCREENSAVEPWD VerifyScreenSavePwd;
    BOOL bRet;

    osv.dwOSVersionInfoSize = sizeof(osv);
 
    GetVersionEx( &osv );

    if( osv.dwPlatformId == VER_PLATFORM_WIN32_NT )
         return TRUE;

    hpwdcpl = LoadLibrary( "PASSWORD.CPL" );

    if( hpwdcpl == NULL )
        return TRUE;

    VerifyScreenSavePwd=(VERIFYSCREENSAVEPWD)GetProcAddress(hpwdcpl,"VerifyScreenSavePwd");
 
    if (VerifyScreenSavePwd==NULL)
    {
        FreeLibrary( hpwdcpl );
        return TRUE;
    }

    bRet = VerifyScreenSavePwd( hwnd );
    FreeLibrary(hpwdcpl);

    return bRet;
}

4.3 处理其它消息

屏幕保护程序还必须处理如下系统消息

WM_ACTIVATE

WM_ACTIVATEAPP

WM_NCACTIVATE   

这是因为我们没有处理WM_SYSCOMMAND消息。当我们选择最大化,最小化按钮,选择系统菜单时等等,或者是应用程序就会收到这个消息,

前三个消息指示当程序窗口被系统或其它应用程序解除活动状态时,应退出。(注,wParam为FALSE表示收到这是一个DEACTIVATE消息。WM_SYSCOMMAND是系统通知消息,wParam标示参数类型,SC_SCREENSAVE和SC_CLOSE表示系统要求执行预定的屏幕保护程序和结束程序,这两种情况下,也应该结束程序的执行。

代码如下:

switch( message )
{
 ...  
case WM_ACTIVATE:
case WM_ACTIVATEAPP:
case WM_NCACTIVATE :
    if( wParam == FALSE )
         PostQuitMessage( 0 );
     break;
 
case WM_SYSCOMMAND:
     if( wParam == SC_SCREENSAVE || wParam == SC_CLOSE )
         PostQuitMessage( 0 );
    break;
 ...
}

4.4 隐藏光标

由于屏幕保护程序运行过程中,不需要与用户互动,因此鼠标光标没有必要显示出来。隐藏鼠标的光标可以通过处理WM_SETCURSOR消息来实现。

case WM_SETCURSOR:  
  SetCursor( NULL );  
  return FALSE;

5.让开发更简单-Screen Saver程序库

使用上述的方法所开发的应用程序已经具有屏幕保护程序的典型特征了。我们可以了解,任何屏幕保护程序为了实现其共有特征,必须对键盘,鼠标和部分系统消息进行相同的处理。

Microsoft考虑到了这一点,它对这些处理过程进行封装,这就是Screen Saver程序库scrnsave.lib。

在这个程序库中,Mircosoft封装了包括WinMain,注册Window Class(窗口类),建立消息循环等过程。开发者要做的只是完成名为”ScreenSaverProc”的消息处理函数。

下面是用Screen Saver程序库改写的”全屏”程序的完整代码。

#include <windows.h>
#include <scrnsave.h>

LRESULT CALLBACK ScreenSaverProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
    switch( message )
    {
        case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc;
            RECT rect;
   
            GetClientRect( hWnd, &rect );
   
            hdc = BeginPaint(hWnd, &ps);
   
            hBrush = CreateSolidBrush( RGB(0,0,0) );
            FillRect( hdc, &rect, hBrush );
   
            DeleteObject( hBrush );
            EndPaint( hWnd, &ps );
        }
        break;
 

    case SCRM_VERIFYPW:
        ScreenSaverChangePassword( hWnd );
        break;
    }
 
    defScreenSaverProc(hWnd, message, wParam, lParam);
}

 

BOOL WINAPI RegisterDialogClasses (HANDLE hInst)
{
    return TRUE;
}

 

BOOL WINAPI ScreenSaverConfigureDialog (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch( message )
    {
       case WM_INITDIALOG:
           return FALSE;
    }
    return 0;
}

6.结束语

通过阅读本文,相信读者已经掌握了屏幕保护程序的开发方法。对于开发一般的屏幕保护程序,我建议使用Screen Saver程序库进行开发,这样,开发者可以将精力放在屏幕保护程序的内容的开发上,从而提高开发的效率。

展开阅读全文

没有更多推荐了,返回首页