BSOD Saver - 蓝屏死机屏幕保护程序
模拟蓝屏死机和 Windows XP 重启的屏幕保护程序
引言
BSOD 是在发生严重系统错误时显示的错误消息。

来自维基百科:“蓝屏死机(也称为停止错误、BSoD、蓝屏或厄运蓝屏)是某些操作系统(最著名的是 Microsoft Windows)在遇到可能导致系统关闭以防止对系统完整性造成不可逆转损害的严重系统错误后显示的错误屏幕的通俗说法。它旨在提供操作系统发出 Bug 检查时收集的用于诊断目的的信息。”
在 Windows XP 之前,BSOD 经常出现。如果运行的某个应用程序出了问题,您就会遇到 BSOD!自 Windows XP 以来,这种情况就不那么频繁了,或者说,很少见,但屏幕的蓝色仍然会让人们脸色苍白或通红!
背景
历史上那些臭名昭著的东西经常被用来制造乐趣。BSOD 也是如此。不久前,我遇到了一个来自 sysinternals.com 的屏保应用程序。
这是一个模拟 BSOD 并带有 PC 重启的屏保应用程序。由于源代码不可用,我创建了一个具有相同行为的新应用程序。该应用程序还提供了有关如何使用 MFC 创建屏保应用程序的详细信息。它既可以作为屏保运行,也可以作为独立应用程序运行,具体取决于传递的参数。
BSOD 内部原理
每当 Windows 操作系统遇到系统崩溃(由于某些应用程序错误或其他原因)时,就会出现 BSOD 屏幕并显示错误消息。由于系统崩溃,屏幕分辨率会更改为 640X480,并以白色文本在蓝色背景上显示文本消息。
典型的 BSOD 图像可以从 Google 图片搜索获得。
显示此消息时使用的字体是 Lucida Console(记事本字体)。我通过打印不同字体的消息并与 BSOD 屏幕进行比较来找到此信息。后来,我发现这个信息已经在维基百科上提到了 J。
模拟 BSOD
根据上述收集的信息,我们可以通过以下方式模拟 BSOD:
- 将屏幕分辨率更改为 640x480。
- 使用 Lucida Console 字体显示文本消息,背景色为蓝色。
如果这项工作在一个屏保应用程序中完成,我们就会得到一个 BSOD 屏保。
模拟 PC 重启
本文仅讨论模拟Windows XP重启。对于其他 Windows 版本,需要在应用程序中进行更改。
当我们在 Windows XP 电脑上启动时,在 POST 过程之后,Windows 徽标和特殊的进度条会以 640x480 的分辨率显示。在后台,驱动程序被加载,当加载完成后,Explorer.exe 被启动,加载桌面。这只是一个概述,还有许多其他重要任务在幕后完成。要了解更多信息,请单击此处。
如果我们能在 640x480 分辨率的窗口中显示相同的徽标和进度条,我们就可以模拟 PC 启动过程。为了获得真实的徽标和进度条,我在虚拟机(Virtual box)上启动 Windows XP 时使用 PrintScreen。我提取了滚动条的滑块图像,并为其添加了一些动画来模拟进度条。
最终应用程序方法
- 使用向导创建了一个基于 MFC 对话框的应用程序。该对话框没有边框,并且在任务栏中隐藏。
- Windows 徽标保存为资源文件,并在图片查看控件中显示。
- 默认的 BSOD 错误消息以
string
的形式存储在程序中。为了能够动态更改消息,它也从应用程序目录中的ErrorMessage.txt文件中读取。如果应用程序路径中没有ErrorMessage.txt文件,则仅显示默认消息。 - 创建了一个名为
CProgressBar
的类来绘制自定义滚动条控件。该类从资源加载滚动滑块,并根据计时器事件对其进行滚动。 - 在控件初始化期间,应用程序会保存当前分辨率,并将分辨率更改为 640X480。为了更改分辨率,我重用了 CodeProject 文章 - 以编程方式更改显示分辨率,作者是 Cristian Amarie 的代码。
- 该应用程序会显示 BSOD 消息并以循环方式可视化 PC 重启。此循环由另一个计时器维护。错误消息、Windows 启动和启动后持续时间可以通过修改
m_ErrorMsgDuration
、m_WindowStartDuration
和m_AfterStartDuration
变量值来控制。
应用程序作为屏保
要将应用程序作为屏保运行,请将扩展名从 exe 更改为 scr,然后将其复制到c:\Windows\System32文件夹。现在可以使用屏保设置对话框进行配置。
屏保详情
屏保是一个全屏模式下运行的应用程序,其窗口位于最顶层。屏保应用程序的扩展名是 scr 而不是 exe。要安装屏保,可以将可执行文件手动复制到windows\system32文件夹,或者用户可以选择右键菜单中的安装选项。
Windows 启动屏保时会传递以下三种命令线选项之一:
/s – 此选项用于以全屏模式启动屏保。在这种情况下,应用程序正常启动以全屏显示输出。
/c – 此选项用于显示配置设置对话框。在这种情况下,应用程序会显示一个配置对话框来修改启动时间和其它设置。
/p #### – 此选项用于使用指定的窗口句柄 #### 显示屏保的预览。与 /p 传递的值是屏保设置对话框中预览窗口句柄的十进制等效值。使用此选项时,应用程序应在后台运行,并将此窗口用作渲染窗口。在这种情况下,应用程序的输出应显示在启动应用程序时传递其句柄的窗口中。
屏保通常不直接在预览窗口中渲染,而是创建一个以预览窗口为父窗口的新窗口。新窗口的位置和大小与预览窗口相同,应用程序在新窗口中渲染其输出。由于新窗口的大小和位置与预览窗口相同,因此看起来就像输出显示在预览窗口中一样。
为了简单起见,我在 BSOD 应用程序中省略了预览模式代码。 Lucian Wischik 有一个完整的网站,其中包含创建具有所有选项的屏保的详细信息。您可以访问他的网站了解更多详情。
关于代码
本文的代码非常简单。此应用程序的方法已在上面描述,代码是按照该方法编写的。
以下是此项目中的类列表:
CBsodSaverApp
:从
CWinApp
派生的主应用程序类CBsodSaverDlg
:从
CDialog
派生的主对话框类CChangeRes
:用于更改屏幕分辨率的类
CConfigDlg
:用于更改应用程序设置的对话框类
CMyStatic
:用于设置
static
控件的背景和前景颜色的类。CProgressBar
:模拟 Windows 启动进度条的类。
在应用程序启动时,将在 CBsodSaverApp::InitInstance()
函数中检查其参数,并根据提供的参数显示配置对话框或主应用程序对话框。
CString strOption = CmdLine.Right (CmdLine.GetLength() - CmdLine.ReverseFind('/') - 1);
switch(strOption[0])
{
case 'c':
case 'C':
{
CConfigDlg dlg;
m_pMainWnd = &dlg;
INT_PTR nResponse = dlg.DoModal();
}
break;
case 'p':
case 'P':
{
//Code to create a new preview window and show the output in that window
}
break;
case 's':
case 'S':
default://Launch the application in full screen mode otherwise.
{
CBsodSaverDlg dlg;
m_pMainWnd = &dlg;
INT_PTR nResponse = dlg.DoModal();
}
}
CBsodSaverDlg
类包含以下成员变量:
CMyStatic m_BlueText; //Static box to display Error Message.
CBrush m_BkgrndBrush; //Solid Brush to change the dialog color to m_BkColor.
COLORREF m_BkColor; //Dialog background color.
CStatic m_WindowsLogo; //Static Box (Image Type) to show the Windows Logo.
CProgressBar m_ScrollbarBox; //Windows XP startup progressbar.
CPoint m_OldResolution; //Initial desktop resolution.
int m_TimerCount; //Keeps a count for error/startup & light blue desktop
CString m_ErrorMessage; //Default Error Message.
UINT m_ErrorMsgDuration; //Time taken to show the error message (in seconds).
UINT m_WindowStartDuration; //Time taken to show Windows Startup Logo(in seconds).
UINT m_AfterStartDuration; //Time taken to show the Initial desktop (light blue)
// (in seconds).
在 OnInitDialog()
函数中启动一个计时器,并根据 m_ErrorMsgDuration
、m_WindowStartDuration
和 m_AfterStartDuration
值隐藏/显示 UI 对象。
RepositionControls()
函数用于将控件重新定位到正确的位置。
CProgressBar
类维护自己的计时器,用于在给定的矩形区域内重新定位滚动条滑块图像。宽度、高度和速度由宏定义。
#define SCROLL_BAR_HEIGHT 9
#define SCROLLING_SPEED 40
关注点
如上所述,在预览模式下,屏保将以预览窗口句柄作为其第 2 个参数启动。该句柄以十进制参数传递,屏保应用程序将其用作渲染窗口。
这意味着,如果将窗口句柄与 /p
参数传递给屏保,它将仅渲染到该窗口。要验证这一点,请启动记事本应用程序,并使用 Visual Studio 附带的 SPY++ 应用程序获取其主窗口句柄。使用计算器将十六进制窗口句柄值转换为十进制。
在我的机器上,记事本的窗口句柄是 – 000F05F6,其十进制等效值为 984566。现在要启动屏保,请打开命令提示符并转到c:\windows\System32文件夹。使用 dir
命令搜索可用的屏保。我正在使用 Windows 7,并且有 Bubbles 屏保。
要在记事本中启动 Bubbles 屏保,请按如下方式运行命令:
c:\Windows\System32>Bubbles.scr /p 984566
哇!!!
屏保正在记事本中运行。

我还在开发一个真正的 BSOD 应用程序,它将在用户每次运行时导致系统崩溃。如果有人有任何想法,请分享。
历史
- 初始版本