系列文章目录链接:
- windows 服务程序和桌面程序集成(一)概念介绍
- windows 服务程序和桌面程序集成(二)服务程序
- windows 服务程序和桌面程序集成(三)UDP监控工具
- windows 服务程序和桌面程序集成(四)桌面程序
- windows 服务程序和桌面程序集成(五)集成为一个EXE
- windows 服务程序和桌面程序集成(六)集成安装、启动、卸载功能
- 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 服务程序和桌面程序集成(四)桌面程序 中创建的程序一样。
通过菜单 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
3. 增加工作线程单元:uWorkerThread.pas,这个单元就是创建WIindows服务程序时创建的单元。
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服务程序的单元,将其到当前工程文件目录中。
添加完以上单元后,目前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 服务程序和桌面程序集成(六)集成安装、启动、卸载功能