Delphi 11.3 定时关机程序完全解析

前言

本文将深入分析一个使用 Delphi 11.3 编写的定时关机控制台程序。通过这个实例,我们将学习如何处理 Windows 系统权限、时间计算、用户交互以及控制台中文显示等关键技术。
“C:\delphisource\autoshutdown\temp_delphi_source\AutoShutdown.dproj”

程序概述

这是一个简洁实用的控制台应用程序,允许用户设置具体的关机时间(如 23:30),程序将显示倒计时并在指定时间自动关闭计算机。

核心功能

  • 用户输入目标关机时间(HH:MM 格式)
  • 智能日期判断(过期时间自动顺延至次日)
  • 实时倒计时显示
  • 获取系统关机权限
  • 执行强制关机

源码详细解析

1. 程序头部与引用单元

program AutoShutdown;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  System.DateUtils,
  Winapi.Windows;

关键点:

  • {$APPTYPE CONSOLE}:指定为控制台应用程序
  • System.SysUtils:提供基础系统工具函数
  • System.DateUtils:提供日期时间计算函数
  • Winapi.Windows:提供 Windows API 访问

2. 控制台编码设置

procedure SetConsoleUTF8;
begin
  SetConsoleOutputCP(CP_UTF8);
  SetConsoleCP(CP_UTF8);
end;

功能说明:

这个函数解决了 Windows 控制台中文乱码问题。Windows 控制台默认使用 GBK 编码(代码页 936),而 Delphi 现代版本默认使用 UTF-8 编码。

  • SetConsoleOutputCP(CP_UTF8):设置控制台输出编码为 UTF-8
  • SetConsoleCP(CP_UTF8):设置控制台输入编码为 UTF-8
  • CP_UTF8 是 Windows 定义的常量,值为 65001

为什么需要这个?
如果不设置,程序中的中文字符会显示为乱码,因为编码不匹配。

3. Windows 关机常量定义

const
  EWX_SHUTDOWN = $00000001;
  EWX_FORCE = $00000004;
  EWX_FORCEIFHUNG = $00000010;

这些是 Windows API 中 ExitWindowsEx 函数使用的标志位:

  • EWX_SHUTDOWN ($01):关闭计算机
  • EWX_FORCE ($04):强制关闭所有应用程序
  • EWX_FORCEIFHUNG ($10):强制关闭无响应的应用程序

位运算组合:

EWX_SHUTDOWN or EWX_FORCE

使用 or 运算符组合多个标志,实现"强制关机"效果。

4. 获取关机权限

function EnableShutdownPrivilege: Boolean;
var
  hToken: THandle;
  tkp: TTokenPrivileges;
  ReturnLength: Cardinal;
begin
  Result := False;
  if OpenProcessToken(GetCurrentProcess, TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, hToken) then
  begin
    try
      if LookupPrivilegeValue(nil, 'SeShutdownPrivilege', tkp.Privileges[0].Luid) then
      begin
        tkp.PrivilegeCount := 1;
        tkp.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED;
        Result := AdjustTokenPrivileges(hToken, False, tkp, 0, nil, ReturnLength);
      end;
    finally
      CloseHandle(hToken);
    end;
  end;
end;

深度解析:

这是程序最核心的部分之一。Windows 系统关机需要特殊权限,即使是管理员账户也需要显式启用。

执行流程:

  1. 打开进程令牌

    OpenProcessToken(GetCurrentProcess, TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, hToken)
    
    • GetCurrentProcess:获取当前进程句柄
    • TOKEN_ADJUST_PRIVILEGES:请求调整权限的权限
    • TOKEN_QUERY:请求查询权限的权限
    • hToken:返回的令牌句柄
  2. 查找关机权限

    LookupPrivilegeValue(nil, 'SeShutdownPrivilege', tkp.Privileges[0].Luid)
    
    • 'SeShutdownPrivilege':Windows 系统的关机权限名称
    • Luid:本地唯一标识符(Locally Unique Identifier)
  3. 启用权限

    tkp.PrivilegeCount := 1;
    tkp.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED;
    AdjustTokenPrivileges(hToken, False, tkp, 0, nil, ReturnLength);
    
    • 设置要调整的权限数量为 1
    • 设置权限属性为"启用"
    • 调用 API 应用更改
  4. 资源清理

    finally
      CloseHandle(hToken);
    end;
    

    使用 try-finally 确保令牌句柄被正确关闭,防止资源泄漏。

为什么需要这个?
Windows 安全模型要求即使是管理员也必须显式请求某些敏感操作的权限。这是纵深防御策略的一部分。

5. 执行关机操作

procedure PerformShutdown;
begin
  if EnableShutdownPrivilege then
  begin
    Writeln('正在关机...');
    ExitWindowsEx(EWX_SHUTDOWN or EWX_FORCE, 0);
  end
  else
    Writeln('错误: 无法获取关机权限!');
end;

执行逻辑:

  1. 先尝试获取关机权限
  2. 如果成功,调用 ExitWindowsEx API 执行关机
  3. 如果失败,显示错误信息

参数说明:

  • 第一个参数:EWX_SHUTDOWN or EWX_FORCE(强制关机)
  • 第二个参数:0(关机原因,0 表示其他原因)

6. 时间解析函数

function ParseTimeInput(const Input: string; out ShutdownTime: TDateTime): Boolean;
var
  Hour, Minute: Integer;
  Parts: TArray<string>;
begin
  Result := False;
  Parts := Input.Split([':']);
  
  if Length(Parts) <> 2 then
    Exit;
  
  if not TryStrToInt(Parts[0], Hour) or not TryStrToInt(Parts[1], Minute) then
    Exit;
  
  if (Hour < 0) or (Hour > 23) or (Minute < 0) or (Minute > 59) then
    Exit;
  
  ShutdownTime := EncodeTime(Hour, Minute, 0, 0);
  ShutdownTime := Date + ShutdownTime;
  
  // 如果设定时间已过,则设为明天
  if ShutdownTime <= Now then
    ShutdownTime := IncDay(ShutdownTime, 1);
  
  Result := True;
end;

功能详解:

这个函数负责将用户输入的字符串(如"23:30")转换为实际的日期时间值。

处理步骤:

  1. 字符串分割

    Parts := Input.Split([':']);
    

    使用冒号分割输入,得到小时和分钟部分。

  2. 格式验证

    if Length(Parts) <> 2 then Exit;
    

    确保分割后正好有两部分。

  3. 数值转换

    if not TryStrToInt(Parts[0], Hour) or not TryStrToInt(Parts[1], Minute) then Exit;
    

    使用 TryStrToInt 安全地转换字符串为整数,失败返回 False。

  4. 范围检查

    if (Hour < 0) or (Hour > 23) or (Minute < 0) or (Minute > 59) then Exit;
    

    验证小时(0-23)和分钟(0-59)的合法性。

  5. 构建时间

    ShutdownTime := EncodeTime(Hour, Minute, 0, 0);
    ShutdownTime := Date + ShutdownTime;
    
    • EncodeTime:创建时间部分(时、分、秒、毫秒)
    • Date:获取今天的日期
    • 相加得到完整的日期时间
  6. 智能日期处理

    if ShutdownTime <= Now then
      ShutdownTime := IncDay(ShutdownTime, 1);
    

    如果设定时间已经过去,自动顺延到明天同一时间。

设计亮点:

  • 使用 out 参数返回解析结果
  • 返回布尔值表示解析是否成功
  • 多重验证确保数据有效性
  • 智能处理跨日期情况

7. 倒计时显示函数

procedure ShowCountdown(TargetTime: TDateTime);
var
  Remaining: Int64;
  Hours, Minutes, Seconds: Integer;
begin
  Remaining := SecondsBetween(TargetTime, Now);
  if Remaining > 0 then
  begin
    Hours := Remaining div 3600;
    Minutes := (Remaining mod 3600) div 60;
    Seconds := Remaining mod 60;
    Write(Format(#13'距离下次关机: %2.2d:%2.2d:%2.2d', [Hours, Minutes, Seconds]));
  end;
end;

技术要点:

  1. 时间差计算

    Remaining := SecondsBetween(TargetTime, Now);
    

    SecondsBetween 返回两个时间点之间的秒数差。

  2. 时分秒转换

    Hours := Remaining div 3600;        // 总秒数除以3600得到小时
    Minutes := (Remaining mod 3600) div 60;  // 余数除以60得到分钟
    Seconds := Remaining mod 60;        // 再取余得到秒数
    

    经典的时间单位转换算法。

  3. 原地刷新显示

    Write(Format(#13'距离下次关机: %2.2d:%2.2d:%2.2d', [Hours, Minutes, Seconds]));
    
    • #13:回车符(CR),使光标返回行首
    • Write(而非 Writeln):不换行输出
    • %2.2d:格式化为两位数字,不足补零
    • 效果:倒计时在同一行不断刷新

显示效果:

距离下次关机: 01:30:45

数字会每秒更新,但不产生新行。

8. 主程序逻辑

var
  ShutdownTime: TDateTime;
  UserInput: string;
  
begin
  try
    SetConsoleUTF8;
    
    Writeln('========================================');
    Writeln('     定时关机程序 v1.0');
    Writeln('========================================');
    Writeln;
    
    // 获取用户输入的关机时间
    Write('请输入关机时间(格式 HH:MM,如 23:30): ');
    Readln(UserInput);
    
    if not ParseTimeInput(UserInput, ShutdownTime) then
    begin
      Writeln('错误: 请输入有效的时间格式(HH:MM)!');
      Writeln('按回车键退出...');
      Readln;
      Exit;
    end;
    
    Writeln;
    Writeln(Format('已设置关机时间: %s', [FormatDateTime('yyyy-mm-dd hh:nn:ss', ShutdownTime)]));
    Writeln('按 Ctrl+C 可随时取消');
    Writeln('========================================');
    Writeln;
    
    // 倒计时循环
    while Now < ShutdownTime do
    begin
      ShowCountdown(ShutdownTime);
      Sleep(1000); // 每秒更新一次
    end;
    
    Writeln;
    Writeln;
    
    // 执行关机
    PerformShutdown;
    
  except
    on E: Exception do
    begin
      Writeln('发生错误: ', E.Message);
      Writeln('按回车键退出...');
      Readln;
    end;
  end;
end.

程序流程分析:

  1. 初始化

    • 设置 UTF-8 编码
    • 显示程序标题
  2. 用户输入

    • 提示输入格式
    • 读取用户输入
    • 解析并验证
  3. 错误处理

    if not ParseTimeInput(UserInput, ShutdownTime) then
    begin
      Writeln('错误: 请输入有效的时间格式(HH:MM)!');
      Writeln('按回车键退出...');
      Readln;
      Exit;
    end;
    

    输入无效时给出清晰提示并等待用户确认退出。

  4. 倒计时循环

    while Now < ShutdownTime do
    begin
      ShowCountdown(ShutdownTime);
      Sleep(1000);
    end;
    
    • 每秒检查一次时间
    • 调用显示函数更新倒计时
    • Sleep(1000):暂停1秒,避免CPU满负荷
  5. 执行关机
    时间到达后调用 PerformShutdown 执行关机。

  6. 全局异常处理

    except
      on E: Exception do
      begin
        Writeln('发生错误: ', E.Message);
        Writeln('按回车键退出...');
        Readln;
      end;
    end;
    

    捕获所有未处理的异常,显示错误信息并优雅退出。

技术亮点总结

1. 安全性设计

  • 权限检查和获取机制
  • 多重输入验证
  • 完善的错误处理

2. 用户体验

  • 清晰的界面提示
  • 实时倒计时反馈
  • 智能的日期处理

3. 代码质量

  • 结构清晰,职责分明
  • 资源管理规范(try-finally)
  • 异常处理完善

4. Windows API 集成

  • 进程令牌操作
  • 权限管理
  • 系统关机控制

使用注意事项

必须以管理员权限运行

即使代码中获取了关机权限,程序本身也必须以管理员身份运行才能成功。

原因:

  • OpenProcessToken 需要进程有足够权限
  • 普通用户权限无法调整令牌权限

如何运行:

  1. 右键点击 exe 文件
  2. 选择"以管理员身份运行"
  3. 或在项目属性中设置需要管理员权限

时间格式严格

  • 必须使用 HH:MM 格式
  • 小时:00-23(24小时制)
  • 分钟:00-59
  • 必须有冒号分隔

强制关机说明

程序使用 EWX_FORCE 标志,会:

  • 强制关闭所有程序
  • 不保存未保存的工作
  • 不给用户确认机会

建议:
测试时可以将关机代码注释,用输出语句代替。

可能的改进方向

1. 功能增强

  • 添加取消关机的热键(非 Ctrl+C)
  • 支持休眠、重启等其他操作
  • 添加日志记录
  • 支持配置文件

2. 用户界面

  • 添加倒计时结束前的警告提示
  • 支持多种时间输入格式
  • 显示更详细的状态信息

3. 安全改进

  • 添加密码保护
  • 记录操作日志
  • 支持远程取消

4. 代码优化

// 可以添加取消机制
function CheckCancelKey: Boolean;
var
  InputRec: TInputRecord;
  NumRead: Cardinal;
begin
  Result := False;
  if PeekConsoleInput(GetStdHandle(STD_INPUT_HANDLE), InputRec, 1, NumRead) then
  begin
    if (NumRead > 0) and (InputRec.EventType = KEY_EVENT) then
      Result := InputRec.Event.KeyEvent.wVirtualKeyCode = VK_ESCAPE;
  end;
end;

学习价值

通过这个项目,我们学到了:

  1. Windows API 编程

    • 进程和令牌管理
    • 权限操作
    • 系统控制
  2. Delphi 编程技巧

    • 控制台应用开发
    • 字符串处理
    • 日期时间操作
  3. 编程最佳实践

    • 错误处理
    • 资源管理
    • 用户体验设计
  4. 系统编程知识

    • Windows 安全模型
    • 权限提升
    • 系统调用

运行结果

在这里插入图片描述

带开环升压转换器和逆变器的太阳能光伏系统 太阳能光伏系统驱动开环升压转换器和SPWM逆变器提供波形稳定、设计简单的交流电的模型 Simulink模型展示了一个完整的基于太阳能光伏的直流到交流电力转换系统,该系统由简单、透明、易于理解的模块构建而成。该系统从配置为提供真实直流输出电压的光伏阵列开始,然后由开环DC-DC升压转换器进行处理。升压转换器将光伏电压提高到适合为单相全桥逆变器供电的稳定直流链路电平。 逆变器使用正弦PWM(SPWM)开关来产生干净的交流输出波形,使该模型成为研究直流-交流转换基本操作的理想选择。该设计避免了闭环和MPPT的复杂性,使用户能够专注于光伏接口、升压转换和逆变器开关的核心概念。 此模型包含的主要功能: •太阳能光伏阵列在标准条件下产生~200V电压 •具有固定占空比操作的开环升压转换器 •直流链路电容器,用于平滑和稳定转换器输出 •单相全桥SPWM逆变器 •交流负载,用于观察实际输出行为 •显示光伏电压、升压输出、直流链路电压、逆变器交流波形和负载电流的组织良好的范围 •完全可编辑的结构,适合分析、实验和扩展 该模型旨在为太阳能直流-交流转换提供一个干净高效的仿真框架。布局简单明了,允许用户快速了解信号流,检查各个阶段,并根据需要修改参数。 系统架构有意保持模块化,因此可以轻松扩展,例如通过添加MPPT、动态负载行为、闭环升压控制或并网逆变器概念。该模型为进一步开发或整合到更大的可再生能源模拟中奠定了坚实的基础。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值