Windows 2000/XP和2003等支持一种叫做"服务程序"的东西.程序作为服务启动有以下几个好处:
(1)不用登陆进系统即可运行.
(2)具有SYSTEM特权.所以你在进程管理器里面是无法结束它的.
笔者在2003年为一公司开发机顶盒项目的时候,曾经写过课件上传和媒体服务,下面就介绍一下如何用Delphi7创建一个Service程序.
运行Delphi7,选择菜单File-->New-->Other--->Service Application.将生成一个服务程序的框架.将工程保存为ServiceDemo.dpr和Unit_Main.pas,然后回到主框架.我们注意到,Service有几个属性.其中以下几个是我们比较常用的:
(1)DisplayName:服务的显示名称
(2)Name:服务名称.
我们在这里将DisplayName的值改为"Delphi服务演示程序",Name改为"DelphiService".编译这个项目,将得到 ServiceDemo.exe.这已经是一个服务程序了!进入CMD模式,切换致工程所在目录,运行命令"ServiceDemo.exe /install",将提示服务安装成功!然后"net start DelphiService"将启动这个服务.进入控制面版-->管理工具-->服务,将显示这个服务和当前状态.不过这个服务现在什么也干不了,因为我们还没有写代码:)先"net stop DelphiService"停止再"ServiceDemo.exe /uninstall"删除这个服务.回到Delphi7的IDE.
我们的计划是为这个服务添加一个主窗口,运行后任务栏显示程序的图标,双击图标将显示主窗口,上面有一个按钮,点击该按钮将实现Ctrl+Alt+Del功能.
实际上,服务程序莫认是工作于Winlogon桌面的,可以打开控制面板,查看我们刚才那个服务的属性-->登陆,其中"允许服务与桌面交互 "是不打钩的.怎么办?呵呵,回到IDE,注意那个布尔属性:Interactive,当这个属性为True的时候,该服务程序就可以与桌面交互了.
File-->New-->Form为服务添加窗口FrmMain,单元保存为Unit_FrmMain,并且把这个窗口设置为手工创建.完成后的代码如下:
06 |
Windows, Messages, SysUtils, Classes, Graphics, Controls, SvcMgr, Dialogs, Unit_FrmMain; |
09 |
TDelphiService = class (TService) |
10 |
procedure ServiceContinue(Sender: TService; var Continued: Boolean ); |
11 |
procedure ServiceExecute(Sender: TService); |
12 |
procedure ServicePause(Sender: TService; var Paused: Boolean ); |
13 |
procedure ServiceShutdown(Sender: TService); |
14 |
procedure ServiceStart(Sender: TService; var Started: Boolean ); |
15 |
procedure ServiceStop(Sender: TService; var Stopped: Boolean ); |
19 |
function GetServiceController: TServiceController; override; |
24 |
DelphiService: TDelphiService; |
30 |
procedure ServiceController(CtrlCode: DWord); stdcall; |
32 |
DelphiService . Controller(CtrlCode); |
35 |
function TDelphiService . GetServiceController: TServiceController; |
37 |
Result := ServiceController; |
40 |
procedure TDelphiService . ServiceContinue(Sender: TService; |
41 |
var Continued: Boolean ); |
43 |
while not Terminated do |
46 |
ServiceThread . ProcessRequests( False ); |
50 |
procedure TDelphiService . ServiceExecute(Sender: TService); |
52 |
while not Terminated do |
55 |
ServiceThread . ProcessRequests( False ); |
59 |
procedure TDelphiService . ServicePause(Sender: TService; |
65 |
procedure TDelphiService . ServiceShutdown(Sender: TService); |
73 |
procedure TDelphiService . ServiceStart(Sender: TService; |
74 |
var Started: Boolean ); |
77 |
Svcmgr . Application . CreateForm(TFrmMain, FrmMain); |
82 |
procedure TDelphiService . ServiceStop(Sender: TService; |
83 |
var Stopped: Boolean ); |
主窗口单元如下:
006 |
Windows, Messages, SysUtils, Variants, Classes, ShellApi, Graphics, Controls, Forms, |
007 |
Dialogs, ExtCtrls, StdCtrls; |
010 |
WM_TrayIcon = WM_USER + 1234 ; |
012 |
TFrmMain = class (TForm) |
015 |
procedure FormCreate(Sender: TObject); |
016 |
procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean ); |
017 |
procedure FormDestroy(Sender: TObject); |
018 |
procedure Timer1Timer(Sender: TObject); |
019 |
procedure Button1Click(Sender: TObject); |
022 |
IconData: TNotifyIconData; |
023 |
procedure AddIconToTray; |
024 |
procedure DelIconFromTray; |
025 |
procedure TrayIconMessage( var Msg: TMessage); message WM_TrayIcon; |
026 |
procedure SysButtonMsg( var Msg: TMessage); message WM_SYSCOMMAND; |
038 |
procedure TFrmMain . FormCreate(Sender: TObject); |
040 |
FormStyle := fsStayOnTop; |
041 |
SetWindowLong(Application . Handle, GWL_EXSTYLE, WS_EX_TOOLWINDOW); |
043 |
Timer1 . Interval := 1000 ; |
044 |
Timer1 . Enabled := True ; |
047 |
procedure TFrmMain . FormCloseQuery(Sender: TObject; var CanClose: Boolean ); |
049 |
CanClose := gbCanClose; |
056 |
procedure TFrmMain . FormDestroy(Sender: TObject); |
058 |
Timer1 . Enabled := False ; |
062 |
procedure TFrmMain . AddIconToTray; |
064 |
ZeroMemory(@IconData, SizeOf(TNotifyIconData)); |
065 |
IconData . cbSize := SizeOf(TNotifyIconData); |
066 |
IconData . Wnd := Handle; |
068 |
IconData . uFlags := NIF_MESSAGE or NIF_ICON or NIF_TIP; |
069 |
IconData . uCallbackMessage := WM_TrayIcon; |
070 |
IconData . hIcon := Application . Icon . Handle; |
071 |
IconData . szTip := Delphi服务演示程序; |
072 |
Shell_NotifyIcon(NIM_ADD, @IconData); |
075 |
procedure TFrmMain . DelIconFromTray; |
077 |
Shell_NotifyIcon(NIM_DELETE, @IconData); |
080 |
procedure TFrmMain . SysButtonMsg( var Msg: TMessage); |
082 |
if (Msg . wParam = SC_CLOSE) or |
083 |
(Msg . wParam = SC_MINIMIZE) then Hide |
087 |
procedure TFrmMain . TrayIconMessage( var Msg: TMessage); |
089 |
if (Msg . LParam = WM_LBUTTONDBLCLK) then Show(); |
092 |
procedure TFrmMain . Timer1Timer(Sender: TObject); |
097 |
procedure SendHokKey;stdcall; |
101 |
HDesk_WL := OpenDesktop (Winlogon, 0 , False , DESKTOP_JOURNALPLAYBACK); |
102 |
if (HDesk_WL <> 0 ) then |
103 |
if (SetThreadDesktop (HDesk_WL) = True ) then |
104 |
PostMessage(HWND_BROADCAST, WM_HOTKEY, 0 , MAKELONG (MOD_ALT or MOD_CONTROL, VK_DELETE)); |
107 |
procedure TFrmMain . Button1Click(Sender: TObject); |
111 |
CreateThread( nil , 0 , @SendHokKey, nil , 0 , dwThreadID); |
补充:
(1)关于更多服务程序的演示程序,请访问以下 http://www.torry.net/pages.php?id=226 ,上面包含了多个演示如何控制和管理系统服务的代码.
(2)请切记:Windows实际上存在多个桌面.例如屏幕传输会出现白屏,可能有两个原因:一是系统处于锁定或未登陆桌面,二是处于屏幕保护桌面.这时候要将当前桌面切换到该桌面才能抓屏.
(3)关于服务程序与桌面交互,还有种动态切换方法.大概单元如下:
05 |
function InitServiceDesktop: boolean ; |
06 |
procedure DoneServiceDeskTop; |
10 |
uses Windows, SysUtils; |
13 |
DefaultWindowStation = WinSta0; |
14 |
DefaultDesktop = Default; |
20 |
function InitServiceDesktop: boolean ; |
24 |
dwThreadId := GetCurrentThreadID; |
27 |
hwinstaSave := GetProcessWindowStation; |
28 |
hdeskSave := GetThreadDesktop(dwThreadId); |
31 |
hwinstaUser := OpenWindowStation(DefaultWindowStation, FALSE , MAXIMUM_ALLOWED); |
32 |
if hwinstaUser = 0 then |
34 |
OutputDebugString( PChar (OpenWindowStation failed + SysErrorMessage(GetLastError))); |
39 |
if not SetProcessWindowStation(hwinstaUser) then |
41 |
OutputDebugString(SetProcessWindowStation failed); |
46 |
hdeskUser := OpenDesktop(DefaultDesktop, 0 , FALSE , MAXIMUM_ALLOWED); |
49 |
OutputDebugString(OpenDesktop failed); |
50 |
SetProcessWindowStation(hwinstaSave); |
51 |
CloseWindowStation(hwinstaUser); |
55 |
Result := SetThreadDesktop(hdeskUser); |
57 |
OutputDebugString( PChar (SetThreadDesktop + SysErrorMessage(GetLastError))); |
60 |
procedure DoneServiceDeskTop; |
63 |
SetThreadDesktop(hdeskSave); |
64 |
SetProcessWindowStation(hwinstaSave); |
65 |
if hwinstaUser <> 0 then |
66 |
CloseWindowStation(hwinstaUser); |
67 |
if hdeskUser <> 0 then |
68 |
CloseDesktop(hdeskUser); |
更详细的演示代码请参看: http://www.torry.net/samples/samples/os/isarticle.zip
(4)关于安装服务如何添加服务描述.有两种方法:一是修改注册表.服务的详细信息都位于HKEY_LOCAL_MACHINE\SYSTEM\ ControlSet001\Services\下面,例如我们刚才那个服务就位于HKEY_LOCAL_MACHINE\SYSTEM\ ControlSet001\Services\DelphiService下.第二种方法就是先用QueryServiceConfig2函数获取服务信息,然后ChangeServiceConfig2来改变描述.用Delphi实现的话,单元如下:
005 |
uses Windows, WinSvc; |
011 |
SERVICE_CONFIG_DESCRIPTION = 1 ; |
012 |
SERVICE_CONFIG_FAILURE_ACTIONS = 2 ; |
016 |
AdvApiDLL = advapi32 . dll; |
021 |
PServiceDescriptionA = ^TServiceDescriptionA; |
022 |
PServiceDescriptionW = ^TServiceDescriptionW; |
023 |
PServiceDescription = PServiceDescriptionA; |
024 |
{$EXTERNALSYM _SERVICE_DESCRIPTIONA} |
025 |
_SERVICE_DESCRIPTIONA = record |
026 |
lpDescription : PAnsiChar ; |
028 |
{$EXTERNALSYM _SERVICE_DESCRIPTIONW} |
029 |
_SERVICE_DESCRIPTIONW = record |
030 |
lpDescription : PWideChar ; |
032 |
{$EXTERNALSYM _SERVICE_DESCRIPTION} |
033 |
_SERVICE_DESCRIPTION = _SERVICE_DESCRIPTIONA; |
034 |
{$EXTERNALSYM SERVICE_DESCRIPTIONA} |
035 |
SERVICE_DESCRIPTIONA = _SERVICE_DESCRIPTIONA; |
036 |
{$EXTERNALSYM SERVICE_DESCRIPTIONW} |
037 |
SERVICE_DESCRIPTIONW = _SERVICE_DESCRIPTIONW; |
038 |
{$EXTERNALSYM SERVICE_DESCRIPTION} |
039 |
SERVICE_DESCRIPTION = _SERVICE_DESCRIPTIONA; |
040 |
TServiceDescriptionA = _SERVICE_DESCRIPTIONA; |
041 |
TServiceDescriptionW = _SERVICE_DESCRIPTIONW; |
042 |
TServiceDescription = TServiceDescriptionA; |
047 |
{$EXTERNALSYM _SC_ACTION_TYPE} |
048 |
_SC_ACTION_TYPE = (SC_ACTION_NONE, SC_ACTION_RESTART, SC_ACTION_REBOOT, SC_ACTION_RUN_COMMAND); |
049 |
{$EXTERNALSYM SC_ACTION_TYPE} |
050 |
SC_ACTION_TYPE = _SC_ACTION_TYPE; |
052 |
PServiceAction = ^TServiceAction; |
053 |
{$EXTERNALSYM _SC_ACTION} |
055 |
aType : SC_ACTION_TYPE; |
058 |
{$EXTERNALSYM SC_ACTION} |
059 |
SC_ACTION = _SC_ACTION; |
060 |
TServiceAction = _SC_ACTION; |
062 |
PServiceFailureActionsA = ^TServiceFailureActionsA; |
063 |
PServiceFailureActionsW = ^TServiceFailureActionsW; |
064 |
PServiceFailureActions = PServiceFailureActionsA; |
065 |
{$EXTERNALSYM _SERVICE_FAILURE_ACTIONSA} |
066 |
_SERVICE_FAILURE_ACTIONSA = record |
067 |
dwResetPeriod : DWORD; |
071 |
lpsaActions : ^SC_ACTION; |
073 |
{$EXTERNALSYM _SERVICE_FAILURE_ACTIONSW} |
074 |
_SERVICE_FAILURE_ACTIONSW = record |
075 |
dwResetPeriod : DWORD; |
076 |
lpRebootMsg : LPWSTR; |
079 |
lpsaActions : ^SC_ACTION; |
081 |
{$EXTERNALSYM _SERVICE_FAILURE_ACTIONS} |
082 |
_SERVICE_FAILURE_ACTIONS = _SERVICE_FAILURE_ACTIONSA; |
083 |
{$EXTERNALSYM SERVICE_FAILURE_ACTIONSA} |
084 |
SERVICE_FAILURE_ACTIONSA = _SERVICE_FAILURE_ACTIONSA; |
085 |
{$EXTERNALSYM SERVICE_FAILURE_ACTIONSW} |
086 |
SERVICE_FAILURE_ACTIONSW = _SERVICE_FAILURE_ACTIONSW; |
087 |
{$EXTERNALSYM SERVICE_FAILURE_ACTIONS} |
088 |
SERVICE_FAILURE_ACTIONS = _SERVICE_FAILURE_ACTIONSA; |
089 |
TServiceFailureActionsA = _SERVICE_FAILURE_ACTIONSA; |
090 |
TServiceFailureActionsW = _SERVICE_FAILURE_ACTIONSW; |
091 |
TServiceFailureActions = TServiceFailureActionsA; |
096 |
TQueryServiceConfig2 = function (hService : SC_HANDLE; dwInfoLevel : DWORD; lpBuffer : pointer ; |
097 |
cbBufSize : DWORD; var pcbBytesNeeded) : BOOL; stdcall; |
098 |
TChangeServiceConfig2 = function (hService : SC_HANDLE; dwInfoLevel : DWORD; lpInfo : pointer ) : BOOL; stdcall; |
102 |
LibLoaded : boolean ; |
105 |
OSVersionInfo : TOSVersionInfo; |
107 |
{$EXTERNALSYM QueryServiceConfig2A} |
108 |
QueryServiceConfig2A : TQueryServiceConfig2; |
109 |
{$EXTERNALSYM QueryServiceConfig2W} |
110 |
QueryServiceConfig2W : TQueryServiceConfig2; |
111 |
{$EXTERNALSYM QueryServiceConfig2} |
112 |
QueryServiceConfig2 : TQueryServiceConfig2; |
114 |
{$EXTERNALSYM ChangeServiceConfig2A} |
115 |
ChangeServiceConfig2A : TChangeServiceConfig2; |
116 |
{$EXTERNALSYM ChangeServiceConfig2W} |
117 |
ChangeServiceConfig2W : TChangeServiceConfig2; |
118 |
{$EXTERNALSYM ChangeServiceConfig2} |
119 |
ChangeServiceConfig2 : TChangeServiceConfig2; |
124 |
OSVersionInfo . dwOSVersionInfoSize := SizeOf(OSVersionInfo); |
125 |
GetVersionEx(OSVersionInfo); |
126 |
if (OSVersionInfo . dwPlatformId = VER_PLATFORM_WIN32_NT) and (OSVersionInfo . dwMajorVersion >= 5 ) then |
130 |
hDLL:=GetModuleHandle(AdvApiDLL); |
134 |
hDLL := LoadLibrary(AdvApiDLL); |
141 |
@QueryServiceConfig2A := GetProcAddress(hDLL, QueryServiceConfig2A); |
142 |
@QueryServiceConfig2W := GetProcAddress(hDLL, QueryServiceConfig2W); |
143 |
@QueryServiceConfig2 := @QueryServiceConfig2A; |
144 |
@ChangeServiceConfig2A := GetProcAddress(hDLL, ChangeServiceConfig2A); |
145 |
@ChangeServiceConfig2W := GetProcAddress(hDLL, ChangeServiceConfig2W); |
146 |
@ChangeServiceConfig2 := @ChangeServiceConfig2A; |
151 |
@QueryServiceConfig2A := nil ; |
152 |
@QueryServiceConfig2W := nil ; |
153 |
@QueryServiceConfig2 := nil ; |
154 |
@ChangeServiceConfig2A := nil ; |
155 |
@ChangeServiceConfig2W := nil ; |
156 |
@ChangeServiceConfig2 := nil ; |
160 |
if (hDLL <> 0 ) and LibLoaded then |
170 |
Windows,WinSvc,WinSvcEx; |
172 |
function InstallService( const strServiceName,strDisplayName,strDescription,strFilename: string ): Boolean ; |
174 |
procedure UninstallService(strServiceName: string ); |
177 |
function StrLCopy(Dest: PChar ; const Source: PChar ; MaxLen: Cardinal ): PChar ; assembler; |
208 |
function StrPCopy(Dest: PChar ; const Source: string ): PChar ; |
210 |
Result := StrLCopy(Dest, PChar (Source), Length(Source)); |
213 |
function InstallService( const strServiceName,strDisplayName,strDescription,strFilename: string ): Boolean ; |
219 |
srvdesc : PServiceDescription; |
223 |
lpServiceArgVectors: pchar ; |
228 |
hSCM:=OpenSCManager( nil , nil ,SC_MANAGER_ALL_ACCESS); |
234 |
Pchar (strServiceName), |
235 |
Pchar (strDisplayName), |
237 |
SERVICE_WIN32_OWN_PROCESS or SERVICE_INTERACTIVE_PROCESS, |
239 |
SERVICE_ERROR_IGNORE, |
248 |
if Assigned(ChangeServiceConfig2) then |
250 |
desc := Copy(strDescription, 1 , 1024 ); |
251 |
GetMem(srvdesc,SizeOf(TServiceDescription)); |
252 |
GetMem(srvdesc^.lpDescription,Length(desc) + 1 ); |
254 |
StrPCopy(srvdesc^.lpDescription, desc); |
255 |
ChangeServiceConfig2(hSCS,SERVICE_CONFIG_DESCRIPTION,srvdesc); |
257 |
FreeMem(srvdesc^.lpDescription); |
261 |
lpServiceArgVectors := nil ; |
262 |
if not StartService(hSCS, 0 , lpServiceArgVectors) then |
264 |
CloseServiceHandle(hSCS); |
268 |
procedure UninstallService(strServiceName: string ); |
270 |
SCManager: SC_HANDLE; |
272 |
Status: TServiceStatus; |
274 |
SCManager := OpenSCManager( nil , nil , SC_MANAGER_ALL_ACCESS); |
275 |
if SCManager = 0 then Exit; |
277 |
Service := OpenService(SCManager, Pchar (strServiceName), SERVICE_ALL_ACCESS); |
278 |
ControlService(Service, SERVICE_CONTROL_STOP, Status); |
279 |
DeleteService(Service); |
280 |
CloseServiceHandle(Service); |
282 |
CloseServiceHandle(SCManager); |
(5)如何暴力关闭一个服务程序,实现我们以前那个"NT工具箱"的功能?首先,根据进程名称来杀死进程是用以下函数:
03 |
function KillTask(ExeFileName: string ): Integer ; |
05 |
PROCESS_TERMINATE = 01 ; |
08 |
FSnapshotHandle: THandle; |
09 |
FProcessEntry32: TProcessEntry32; |
12 |
FSnapshotHandle := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0 ); |
13 |
FProcessEntry32 . dwSize := SizeOf(FProcessEntry32); |
14 |
ContinueLoop := Process32First(FSnapshotHandle, FProcessEntry32); |
16 |
while Integer (ContinueLoop) <> 0 do |
18 |
if ((UpperCase(ExtractFileName(FProcessEntry32 . szExeFile)) = |
19 |
UpperCase(ExeFileName)) or (UpperCase(FProcessEntry32 . szExeFile) = |
20 |
UpperCase(ExeFileName))) then |
21 |
Result := Integer (TerminateProcess( |
22 |
OpenProcess(PROCESS_TERMINATE, |
24 |
FProcessEntry32 . th32ProcessID), |
26 |
ContinueLoop := Process32Next(FSnapshotHandle, FProcessEntry32); |
28 |
CloseHandle(FSnapshotHandle); |
但是对于服务程序,它会提示"拒绝访问".其实只要程序拥有Debug权限即可:
01 |
function EnableDebugPrivilege: Boolean ; |
02 |
function EnablePrivilege(hToken: Cardinal ; PrivName: string ; bEnable: Boolean ): Boolean ; |
07 |
TP . PrivilegeCount := 1 ; |
08 |
LookupPrivilegeValue( nil , pchar (PrivName), TP . Privileges[ 0 ].Luid); |
10 |
TP . Privileges[ 0 ].Attributes := SE_PRIVILEGE_ENABLED |
11 |
else TP . Privileges[ 0 ].Attributes := 0 ; |
12 |
AdjustTokenPrivileges(hToken, False , TP, SizeOf(TP), nil , Dummy); |
13 |
Result := GetLastError = ERROR_SUCCESS; |
19 |
OpenProcessToken(GetCurrentProcess, TOKEN_ADJUST_PRIVILEGES, hToken); |
20 |
result:=EnablePrivilege(hToken, SeDebugPrivilege, True ); |
使用方法: