生活随笔
收集整理的這篇文章主要介紹了
Delphi 写服务程序
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
?
如何用 Delphi 創建系統服務程序?
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,并且把這個窗口設置為手工創建.完成后的代碼如下:
?
view sourceprint?
| 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); |
| 17 | { Private declarations } |
| 19 | function GetServiceController: TServiceController; override; |
| 20 | { Public declarations } |
| 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; |
| 77 | Svcmgr.Application.CreateForm(TFrmMain, FrmMain); |
| 82 | procedure TDelphiService.ServiceStop(Sender: TService; |
?
主窗口單元如下:
?
view sourceprint?
| 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); |
| 021 | { Private declarations } |
| 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; |
| 028 | { Public declarations } |
| 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 |
| 084 | else inherited; // 執行默認動作 |
| 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)關于服務程序與桌面交互,還有種動態切換方法.大概單元如下:
view sourceprint?
| 05 | function InitServiceDesktop: boolean; |
| 06 | procedure DoneServiceDeskTop; |
| 10 | uses Windows, SysUtils; |
| 13 | DefaultWindowStation = WinSta0; |
| 14 | DefaultDesktop = Default; |
| 20 | function InitServiceDesktop: boolean; |
| 24 | dwThreadId := GetCurrentThreadID; |
| 25 | // Ensure connection to service window station and desktop, and |
| 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; |
| 62 | // Restore window station and desktop. |
| 63 | SetThreadDesktop(hdeskSave); |
| 64 | SetProcessWindowStation(hwinstaSave); |
| 65 | if hwinstaUser <> 0 then |
| 66 | CloseWindowStation(hwinstaUser); |
| 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實現的話,單元如下:
view sourceprint?
| 009 | // Service config info levels |
| 011 | SERVICE_CONFIG_DESCRIPTION = 1; |
| 012 | SERVICE_CONFIG_FAILURE_ACTIONS = 2; |
| 014 | // DLL name of imported functions |
| 016 | AdvApiDLL = advapi32.dll; |
| 019 | // Service description string |
| 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; |
| 045 | // Actions to take on service failure |
| 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; |
| 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; |
| 094 | // API Function Prototypes |
| 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; |
| 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; |
| 173 | //eg:InstallService(服務名稱,顯示名稱,描述信息,服務文件); |
| 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; |
| 215 | //ss : TServiceStatus; |
| 219 | srvdesc : PServiceDescription; |
| 223 | lpServiceArgVectors:pchar; |
| 227 | //SrvType := SERVICE_WIN32_OWN_PROCESS and SERVICE_INTERACTIVE_PROCESS; |
| 228 | hSCM:=OpenSCManager(nil,nil,SC_MANAGER_ALL_ACCESS);//連接服務數據庫 |
| 229 | if hSCM=0 then Exit;//MessageBox(hHandle,Pchar(SysErrorMessage(GetLastError)),服務程序管理器,MB_ICONERROR+MB_TOPMOST); |
| 232 | hSCS:=CreateService( //創建服務函數 |
| 234 | Pchar(strServiceName), // 服務名稱 |
| 235 | Pchar(strDisplayName), // 顯示的服務名稱 |
| 236 | SERVICE_ALL_ACCESS, // 存取權利 |
| 237 | SERVICE_WIN32_OWN_PROCESS or SERVICE_INTERACTIVE_PROCESS,// 服務類型 SERVICE_WIN32_SHARE_PROCESS |
| 238 | SERVICE_AUTO_START, // 啟動類型 |
| 239 | SERVICE_ERROR_IGNORE, // 錯誤控制類型 |
| 240 | Pchar(strFilename), // 服務程序 |
| 246 | if hSCS=0 then Exit;//MessageBox(hHandle,Pchar(SysErrorMessage(GetLastError)),Pchar(Application.Title),MB_ICONERROR+MB_TOPMOST); |
| 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 //啟動服務 |
| 263 | Exit; //MessageBox(hHandle,Pchar(SysErrorMessage(GetLastError)),Pchar(Application.Title),MB_ICONERROR+MB_TOPMOST); |
| 264 | CloseServiceHandle(hSCS); //關閉句柄 |
| 268 | procedure UninstallService(strServiceName:string); |
| 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工具箱"的功能?首先,根據進程名稱來殺死進程是用以下函數:
?
view sourceprint?
| 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權限即可:
?
view sourceprint?
| 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); |
?
使用方法:
view sourceprint?
| 1 | EnableDebugPrivilege;//提升權限 |
| 3 | KillTask(xxxx.exe);//關閉該服務程序. |
?
?
轉載于:https://www.cnblogs.com/ywangzi/archive/2011/10/24/2222850.html
總結
以上是生活随笔為你收集整理的Delphi 写服务程序的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。