zoukankan      html  css  js  c++  java
  • Delphi制作关不掉的程序

    问题的提出
    最近要编写一个考试系统,使得考生能自主地取得试卷并进入考试,考试期间系统自动计时并在考试结束后自动回卷到服务器。取卷、回卷功能都已实现,但在整个考试过程中能成功地准确计时,必须使得程序不在中途被关闭掉。退一步来说,即使程序中途被关闭,也要自动地重新起动,并以之前已保存的时间记录继续工作。
     
    解决的思路
    对于这种监控用的程序除了程序的交互界面要做得不能让用户退出外,更应该注意到用户使用诸如“任务管理器”等“暴力”的手段来终止程序。而对付这种终止程序的方法一般来说有以下两种方法:
    第一种方法,把监控程序的进程伪装起来。例如把进程的名称改为与系统进程相仿的名字,让用户不敢终止或忽略这个进程,更甚者直接把进程的名称也隐藏起来。以上的方法已在很多计算机病毒中得到了实现。但现在要编写的是“考试系统”,不同于病毒启动的隐蔽性,用户是自主地进入程序(自主地取得试卷),因此用户很容易就知道进程的名字。从而也很容易就用任务管理器把相应的进程结束掉。
    另外的一种方法是在加载程序产生工作进程后,这个工作进程又再加载另外一个程序产生另外一个进程(以下称“影子进程”),两个进程之间相互监控。如每隔200ms就探测一下对方存不存在,如果不存在就马上加载它。因为Windows自带的任务管理器是不能在同一时间终止两个进程的,因此,只要保证探测的时间间隔足够的短(比点击4、5次鼠标短就行了),就可以保证这个程序能及时的重生。
    对于第二种方法,按一般的思路要写两个程序,一个用于产生工作进程,另一个用于产生影子进程。但这样的也存在一个漏洞:如果一旦工作原理被他人了解,很容易就被另一个假的影子程序所替代,使得这种方法失效。有鉴于此,让同一程序根据不同的启动方式(本例使用不同的命令行参数),分别产生工作进程及影子进程,然后再进行相互监控,这样就能有效地堵塞了以上漏洞。
     
    程序的实现
    示例程序用Delphi编码。在Delphi中新建一个Application,把它保存为ShadowTest.dpr。把工程默认的Unit保存为WorkerUnit.pas,接着多加一个Form到工程中,并保存为ShadowUnit.pas。把WorkerUnit中的Form改名为为frmWorker,同时也把ShadowUnit中的Form改名为frmShadow,并在两个Form中都各加入一个定时器Timer1,设置Interval值为 200。在两个Form的声明中加入
    protected
    procedure CreateParams(var Params: TCreateParams); override;
    在frmWorker中加入一个按钮,命名为btnStopMe并击活其OnClick事件。
    在frmShadow加入一个函数
    procedure StopMe(var i : integer); message WM_APP + 517;
     
    打开 WorkerUnit.pas,并加入以下代码
    var
      frmWorker: TfrmWorker;
      GWorkerClass, GShadowClass : string;
    implementation
    {$R *.dfm}
     
    procedure TfrmWorker.CreateParams(var Params: TCreateParams);
    var
      i, k : integer;
    begin
      inherited;
      k := Length (GWorkerClass);
      for i := 1 to k do
        Params.WinClassName[i - 1] := GWorkerClass[i];
      Params.WinClassName[k] := Chr(0);
    end;
     
    procedure TfrmWorker.Timer1Timer(Sender: TObject);
    var
      hApp : integer;
    begin
      hApp := FindWindow(PChar(GShadowClass), nil);
      if hApp > 0 then
        exit
      else
        WinExec (PChar(Application.ExeName + ' -shadow'), SW_SHOW);
    end;
     
    procedure TfrmWorker.btnStopMeClick(Sender: TObject);
    var
      hApp : integer;
    begin
      Timer1.Enabled := false;
      hApp := FindWindow(PChar(GShadowClass), nil);
      if hApp > 0 then
        PostMessage(hApp, WM_STOPME, 0, 0);
      Close;
    end;
    程序说明:
    1.    程序中重载CreateParams过程,旨在是设定工作进程对应的WinClassName。这样就可以为影子进程中的FindWindow找到工作进程的句柄做好准备。
    2.       Timer1Timer的作用是定时以影子进程的WinClassName查找相应的进程句柄,若找不到就起动它。
    3.       btnStopMeClick的作用在于停止程序。当真的要停止进程时,通过进程间PostMessage机制,从工作进程中发一个终止运行的信息到影子进程,使得其退出,然后马上终止自身进程。
     
    打开 ShadowUnit.pas,并加入以下代码
    procedure TfrmShadow.CreateParams(var Params: TCreateParams);
    var
      i, k : integer;
    begin
      inherited;
      k := Length (GShadowClass);
      for i := 1 to k do
        Params.WinClassName[i - 1] := GShadowClass[i];
      Params.WinClassName[k] := Chr(0);
    end;
     
    procedure TfrmShadow.StopMe(var i: integer);
    begin
      Timer1.Enabled := false;
      Close;
    end;
     
    procedure TfrmShadow.Timer1Timer(Sender: TObject);
    var
      hApp : integer;
    begin
      hApp := FindWindow(PChar(GWorkerClass), nil);
      if hApp > 0 then
        exit
      else
        WinExec (PChar(Application.ExeName), SW_SHOW);
    end;
     
    程序说明:
    StopMe函数的作用在于响应工作进程发来的退出消息。其它部份与WorkerUnit.pas中的内容相仿。
     
    接着打开ShadowTest.dpr,改写里面的内容
     
    program ShadowTest;
    uses
      Forms,  Windows,
      WorkerUnit in 'WorkerUnit.pas' {frmWorker},
      ShadowUnit in 'ShadowUnit.pas' {frmShadow};
     
    {$R *.res}
    type
      TAppID = (aiWorker, aiShadow);
     
    var
      hApp : integer;
      AppID : TAppID ;
    begin
      GShadowClass := 'FlexShadow_WinClass';
      GWorkerClass := 'FlexWorker_WinClass';
     
      AppID := aiWorker;
      if ParamCount = 1 then
        if ParamStr(1) = '-shadow' then AppID := aiShadow;
     
      case AppID of
        aiWorker :
          if FindWindow(PChar(GWorkerClass), nil) > 0 then exit;
        aiShadow :
          if FindWindow(PChar(GShadowClass), nil) > 0 then exit;
      end;
      Application.Initialize;
      case AppID of
        aiWorker :
          Application.CreateForm(TfrmWorker, frmWorker);
        aiShadow :
          begin
            Application.ShowMainForm := false;
            ShowWindow(Application.Handle, SW_HIDE);
            Application.CreateForm(TfrmShadow, frmShadow);
          end;
      end;
      Application.Run;
    end.
    ShadowTest.dpr是整个程序的关键,它的运行步骤如下:
    1.    通过命令行参数( -shadow)来识别应该处于工作进程还是影子进程模式。然后检测有没有与自身相同模式下已运行的进程,若有则退出。这是为了保证在同一时间只有一个工作进程和一个影子进程运行。
    2.    按VCL框架要求调用Application.Initialize;进行初始化进程。
    3.    通过AppID识别工作模式,然后初始化对应的主窗口。如果是在影子模式下,还应该把程序的主窗口通过ShowMainForm := False; 语句隐藏起来,同时通过ShowWindow(Application.Handle, SW_HIDE); 把任务栏上的任务也隐藏起来。
  • 相关阅读:
    PHP7还没学明白,PHP8就要来了, 能有多快?
    Linux ab 压力测试
    大公司为什么都有API网关?没你想的那么简单!
    mac安装的vagrant访问laraval欢迎页面,执行时间15秒,安装nfs挂载点(亲测可行)
    PHP操作Elasticsearch
    PHP OpenSSL扩展 对称加密
    为什么 select count(*) from t,在 InnoDB 引擎中比 MyISAM 慢?
    Redis哨兵机制
    未来三五年,社会上什么工作会更吃香呢?这几方面
    自己的 Doxyfile 模板
  • 原文地址:https://www.cnblogs.com/94YY/p/2043491.html
Copyright © 2011-2022 走看看