windows 服务程序和桌面程序集成(五)集成为一个EXE

windows 服务程序和桌面程序集成(五)集成为一个EXE_第1张图片

系列文章目录链接:

  1. windows 服务程序和桌面程序集成(一)概念介绍
  2. windows 服务程序和桌面程序集成(二)服务程序
  3. windows 服务程序和桌面程序集成(三)UDP监控工具
  4. windows 服务程序和桌面程序集成(四)桌面程序
  5. windows 服务程序和桌面程序集成(五)集成为一个EXE
  6. windows 服务程序和桌面程序集成(六)集成安装、启动、卸载功能
  7. windows 服务程序和桌面程序集成(七)效果演示及源程序下载
     

通过前四节的介绍,相信大家已经对windows服务程序和桌面程序开发流程等很清楚了,前面讲的windows服务程序和桌面程序都还是传统的独立的EXE程序。对于windows服务程序我们仍然无法进行调试,开发的流程必须是编译成EXE文件、安装服务、启动服务、停止服务、卸载服务,检查日志,然后再根据日志(UDP消息)修改代码继续上述步骤迭代,直至服务程序满足要求。

本节我们将介绍这个系列文章的核心,就是将windows服务程序和桌面程序集成为一个EXE文件,这个EXE文件可以通过 /insall 将程序安装为服务,也可以直接双击EXE文件,启动为普通的桌面程序。

事实上,解决这个问题也比较简单,就是将windows服务程序和桌面程序启动程序合并起来,然后通过运行时是否携带 /install 或者 /uninstall 参数来判断是运行windows服务程序还是桌面程序,如果有/install 或者 /uninstall 参数则肯定是运行windows服务程序,否则就是运行桌面程序。

Windows中,安装服务程序必须是管理员权限运行 CMD才能安装,否则无法成功安装,所以我们需要程序判断当前是否是管理员权限,如果不是管理员权限,提示用户无法安装。判断当前程序是否再管理员权限下运行需要使用到uProcess_UserCode.pas单元。

下面详细介绍集成实现步骤:

一、创建一个VCL桌面程序:Service_Application_Demo.dproj

其实可以直接使用 windows 服务程序和桌面程序集成(四)桌面程序 中创建的程序来进行修改!

命名主Form单元为:uMainForm_Application.pas

修改主界面属性:

序号 属性 内容
1 Caption 桌面程序主界面
2 Name Form_Application

增加4个按键:创建线程并启动、暂停线程运行、继续线程运行、停止并释放线程。也就是和windows 服务程序和桌面程序集成(四)桌面程序 中创建的程序一样。

windows 服务程序和桌面程序集成(五)集成为一个EXE_第2张图片

 通过菜单 Project -> View Source 打开工程启动文件

program Service_Application_Demo;

uses
  Vcl.Forms,
  uMainForm_Application in 'uMainForm_Application.pas' {Form_Application};

{$R *.res}

begin
  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TForm_Application, Form_Application);
  Application.Run;
end.

二、给上面的VCL桌面程序增加windows服务程序功能

1. 修改通过菜单 Project -> View Source 打开工程启动文件如下:

修改前:

program Service_Application_Demo;

uses
  Vcl.Forms,
  uMainForm_Application in 'uMainForm_Application.pas' {Form_Application};

{$R *.res}

begin
  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TForm_Application, Form_Application);
  Application.Run;
end.

修改后:

program Service_Application_Demo;

uses
  Vcl.SvcMgr,
  Vcl.Forms,
  System.SysUtils,
  Winapi.Windows,
  uMainForm_Application in 'uMainForm_Application.pas' {Form_Application},
  uProcess_UserCode in '..\Public\uProcess_UserCode.pas',
  uWorkerThread in '..\Public\uWorkerThread.pas',
  uWindows_Service in 'uWindows_Service.pas' {Service1: TService};

{$R *.res}
var
  bIsGUI: Boolean;   //增加的变量,用来判断是windows服务程序还是桌面GUI程序

begin
  //判断是否是GUI模式
  bIsGUI := NOT is_SYSTEM_Account;   //判断是否是 SYSTEM用户?
  bIsGUI := bIsGUI AND (NOT FindCmdLineSwitch('INSTALL', ['/'], True));
  bIsGUI := bIsGUI AND (NOT FindCmdLineSwitch('UNINSTALL', ['/'], True));

  FbIsGUI := bIsGUI; //注意这个变量,在输出UDP消息的时候判断是GUI还是服务程序

  //如果有INSTALL 或者 UNINSTALL等参数,但是不是管理员权限,也是不能注册的
  if (not bIsGUI) and (not IsUserAnAdmin) then
    begin
      Application.MessageBox(PWideChar('如果携带参数: INSTALL或者UNINSTALL, 系统必须以管理员身份运行!'),'出错啦',MB_OK + MB_ICONERROR);
      Exit;
    end;

  if bIsGUI then
     begin
        Application.Initialize;
        Application.MainFormOnTaskbar := True;
        Application.CreateForm(TForm_Application, Form_Application);
        Application.CreateForm(TService1, Service1);
        Application.Run;
     end
  else
     begin
       if not Vcl.SvcMgr.Application.DelayInitialize or Vcl.SvcMgr.Application.Installing then
          Vcl.SvcMgr.Application.Initialize;
       Vcl.SvcMgr.Application.CreateForm(TService1, Service1);
       Vcl.SvcMgr.Application.Run;
     end;
end.

修改完这个文件后,程序将会出现错误,有些地方显示红色波浪线,这是因为有些单元我们还没有增加进去。

2. 增加判断是否是管理员权限单元:uProcess_UserCode.pas

windows 服务程序和桌面程序集成(五)集成为一个EXE_第3张图片

 3. 增加工作线程单元:uWorkerThread.pas,这个单元就是创建WIindows服务程序时创建的单元。

windows 服务程序和桌面程序集成(五)集成为一个EXE_第4张图片

uWorkerThread.pas单元代码:

unit uWorkerThread;

interface
uses
  System.Classes,
  IdUDPClient,
  IdGlobal,
  System.SysUtils;
  //Winapi.Windows;

type
  //实际工作线程类
  TWorkThread = Class(TThread)
     private
       FPaused : Boolean;   //
     protected
       constructor Create;
       procedure Execute; override;
     public
       procedure Pause;
       procedure Continue;
  End;
//服务执行的函数,UDP发送消息函数
procedure Send_UDP_Info(str : string);

var
  //工作线程变量
  WorkThread : TWorkThread;
  FbIsGUI    : Boolean;        //是否是桌面程序

implementation

procedure Send_UDP_Info(str : string);
var
  UDPClient: TIdUDPClient;
  B : TBytes;
begin
  UDPClient := TIdUDPClient.Create(nil);
  try
    UDPClient.BroadcastEnabled := True;
    if FbIsGUI then
       str := ' (桌面程序) ' + str
    else
       str := ' (服务程序) ' + str;

    B := TEncoding.UTF8.GetBytes(str);
    //只给本机发送,这个地方只需要给本机发送广播消息即可 2023-03-04
    UDPClient.Broadcast(TidBytes(B),8192,'127.0.0.1');  //端口号
    //广播到任何地方
    //UDPClient.Broadcast(TidBytes(B),G_UDPPort);  //端口号
  finally
    UDPClient.Free;
  end;
end;


{ TWorkThread }

procedure TWorkThread.Continue;
begin
  FPaused := False;
  Send_UDP_Info('服务继续工作....');
end;

constructor TWorkThread.Create;
begin
  FPaused := False;
end;

procedure TWorkThread.Execute;
var
  S : string;
begin
  inherited;
  while not Terminated do
  begin
    if not FPaused then
       begin
         S := FormatDateTime('YYYY-MM-DD hh:mm:ss',Now);
         Send_UDP_Info(S);
       end;
    TThread.Sleep(1000);
  end;

  Send_UDP_Info('********** 服务终止工作 **********');
end;

procedure TWorkThread.Pause;
begin
  FPaused := True;
  Send_UDP_Info('服务暂停工作!!!');
end;

end.

 4. 增加windows服务程序主单元:uWindows_Service.pas 。这个单元就直接使用前面节中创建的独立windows服务程序的单元,将其到当前工程文件目录中。

windows 服务程序和桌面程序集成(五)集成为一个EXE_第5张图片

 添加完以上单元后,目前Service_Application_Demo.exe已经是一个双模程序了,既可以当windows服务程序,也可以是普通的桌面程序。

三、完善桌面程序功能

1. 在uMainForm_Application.pas中引用 uWorkerThread.pas单元;

2. 实现 创建线程并启动、暂停线程运行、继续线程运行、停止并释放线程 四个按键的功能;

uMainForm_Application.pas单元完善后代码:

unit uMainForm_Application;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TForm_Application = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form_Application: TForm_Application;

implementation

uses
  uWorkerThread;

{$R *.dfm}

procedure TForm_Application.Button1Click(Sender: TObject);
begin
   //创建工作线程
  if WorkThread = nil then
     WorkThread := TWorkThread.Create;
  WorkThread.FreeOnTerminate := True;    //完成后直接释放
end;

procedure TForm_Application.Button2Click(Sender: TObject);
begin
  WorkThread.Pause;
end;

procedure TForm_Application.Button3Click(Sender: TObject);
begin
  WorkThread.Continue;
end;

procedure TForm_Application.Button4Click(Sender: TObject);
begin
  WorkThread.Terminate;
  while not WorkThread.Finished do
    begin
      Sleep(200);
    end;
  WorkThread := nil;
end;

end.

四、双模程序的使用

经过前面三步,我们已经成功的创建了一个windows服务程序和桌面程序于一体的双模程序,单独的EXE文件,既是windows服务程序,也是普通的桌面程序。

作为windows服务程序,我们可以使用 /install 和 /uninstall安装和卸载,作为桌面程序,我们可以双击EXE文件直接运行。

程序的主要功能是uWorkerThread.pas单元实现,这个单元的工作在windows服务程序和桌面程序都有引用。我们以后开发的双模程序就可以按照这个模板,把程序的功能集中在共享单元uWorkerThread.pas中,调试的时候可以按照桌面程序进行单步跟踪,调试完成满足要求后,可以按照windows服务程序来安装,作为一个真正的windows服务程序来使用。这样就真正解决了windows服务程序调试问题,也真正实现了双模程序。

虽然我们的双模程序已经完美实现了,但是当作为windows服务程序使用时需要安装,启动,卸载等,必须使用CMD(当然也可以使用其他工具),总是要使用其他工具,能否在我们的桌面程序的界面上实现windows服务程序的安装、启动、停止、卸载呢?

答案是可以的,继续看下一节....

下一篇:windows 服务程序和桌面程序集成(六)集成安装、启动、卸载功能

你可能感兴趣的:(windows,Delphi,线程,Delphi,windows服务程序,服务程序集成桌面程序,双模程序)