一直感覺VC++太複雜了,但昨天看了汪蒲陽編著的網際網路應用程式設計,其中寫到背景服務程式的編寫,論述的非常詳細,而且邏輯清晰,看了之後感覺明白不少,故拿來與需要之人共享,并更正了原程式的一些錯誤,補充了一些材料。另外還有一種用C++編寫背景服務程式的思路(不算.NET上服務程式開發模型),以後整理好了再發上來。
在2000/XP等基于NT 的作業系統中,有一個服務管理器,它管理的背景程序被稱為 service。
服務是一種應用程式類型,它在背景運作,與 UNIX 背景應用程式類似。服務應用程式通常可以
在本地和通過網絡為使用者提供一些功能,例如用戶端/伺服器應用程式、Web 伺服器、資料庫服
務器以及其他基于伺服器的應用程式。
背景服務 程式是在背景悄悄運作的。我們通過将自己的程式登記為服務,可以使自己的程式不出現
在任務管理器中,并且随系統啟動而最先運作,随系統關閉而最後停止。
服務控制管理器是一個RPC 伺服器,它顯露了一組應用程式設計接口,程式員可以友善的編寫程式來配置
服務和控制遠端伺服器中服務程式。
服務程式通常編寫成控制台類型的應用程式,總的來說,一個遵守服務控制管理程式接口要求的程式
包含下面三個函數:
1、服務程式主函數(main):調用系統函數 StartServiceCtrlDispatcher 連接配接程式主線程到服務控制管理程式。
2、服務入口點函數(ServiceMain):執行服務初始化任務,同時執行多個服務的服務程序有多個服務入口函數。
3、控制服務處理程式函數(Handler):在服務程式收到控制請求時由控制分發線程引用。(此處是Service_Ctrl)。
另外在系統運作此服務之前需要安裝登記服務程式:installService 函數。删除服務程式則需要先删除服務安裝登記:removeService 函數。
服務類型:
類型
說明
SERVICE_FILE_SYSTEM_DRIVER=2
檔案系統驅動服務。
SERVICE_KERNEL_DRIVER=1
驅動服務。
SERVICE_WIN32_OWN_PROCESS=16
獨占一個程序的服務。
SERVICE_WIN32_SHARE_PROCESS=32
與其他服務共享一個程序的服務。
建立WIN32控制台程式, 其源檔案名為service.cpp 。我用的開發工具是VC++.NET。
1.服務程式主函數
服務控制管理程式啟動服務程式後,等待服務程式主函數調用系統函StartServiceCtrlDispatcher。一個SERVICE_WIN32_OWN_PROCESS 類型的服務應該立即調用 StartServiceCtrlDispatcher 函數,可以在服務啟動後讓服務入口點函數完成初始化工作。對于 SERVICE_WIN32_OWN_PROCESS 類型的服務和程式中所有服務共同的初始化工作可以在主函數中完成,但不要超過30秒。否則必須建立另外的線程完成這些共同的初始化工作,進而保證服務程式主函數能及時地調用 StartServiceCtrlDispatcher 函數。
[cpp]
view plain
copy
1. //服務程式主函數。
2.
3. #include "stdafx.h"
4.
5. #include "Windows.h"
6.
7. #define SZAPPNAME "serverSample" //服務程式名
8.
9. #define SZSERVICENAME "serviceSample" //辨別服務的内部名
10.
11.
12.
13. //内部變量
14.
15. bool bDebugServer=false;
16.
17. SERVICE_STATUS ssStatus;
18.
19. SERVICE_STATUS_HANDLE
20.
21. DWORD
22.
23. TCHAR
24.
25.
26.
27. //下面的函數由程式實作
28.
29. void WINAPI Service_Main(DWORD dwArgc, LPTSTR
30.
31. void WINAPI Service_Ctrl(DWORD
32.
33. void
34.
35. void
36.
37. void debugService(int argc,char** argv);
38.
39. bool ReportStatusToSCMgr(DWORD dwCurrentState,DWORD dwWin32ExitCode,DWORD
40.
41. void AddToMessageLog(LPTSTR
42.
43.
44.
45. int _tmain(int
46.
47. {
48.
49. SERVICE_TABLE_ENTRY dispatchTable[]=
50.
51. {
52.
53. {TEXT(SZSERVICENAME),(LPSERVICE_MAIN_FUNCTION)Service_Main},
54.
55. { NULL,NULL}
56.
57. };
58.
59. if((argc>1)&&((*argv[1]=='-')||(argv[1]=="/")))
60.
61. {
62.
63. if(_stricmp("install",argv[1]+1)==0)
64.
65. {
66.
67. installService();
68.
69. }
70.
71. else if(_stricmp("remove",argv[1]+1)==0)
72.
73. {
74.
75. removeService();
76.
77. }
78.
79. else if(_stricmp("debug",argv[1]+1)==0)
80.
81. {
82.
83. true;
84.
85. debugService(argc,argv);
86.
87. }
88.
89. else
90.
91. //如果未能和上面的如何參數比對,則可能是服務控制管理程式來啟動該程式。立即調用
92.
93. //StartServiceCtrlDispatcher 函數。
94.
95. "%s - install to install the service /n",SZAPPNAME);
96.
97. "%s - remove to remove the service /n",SZAPPNAME);
98.
99. "%s - debug to debug the service /n",SZAPPNAME);
100.
101. "/n StartServiceCtrlDispatcher being called./n");
102.
103. "This may take several seconds.Please wait./n");
104.
105. if(!StartServiceCtrlDispatcher(dispatchTable))
106.
107. "StartServiceCtrlDispatcher failed."));
108.
109. else
110.
111. "StartServiceCtrlDispatcher OK."));
112.
113. }
114.
115. exit(0);
116.
117. }
118.
119. return
120.
121. }
2.服務入口點函數
服務入口點函數 service_main 首先調用系統函數 RegisterServiceCtrlHandler 注冊服務控制處理函數 service_ctrl,然後調用 ReportStatusToSCMgr 函數,它通過系統函數 SetServiceStatus 更新服務的狀态,然後調用特定的服務初始化入口函數 ServiceStart 完成具體的初始化工作。
[c-sharp]
view plain
copy
1. //服務入口點函數
2.
3. void ServiceStart(DWORD dwArgc,LPTSTR* lpszArgv);//具體服務的初始化入口函數
4. void
5.
6. {
7.
8. //注冊服務控制處理函數
9.
10. sshStatusHandle=RegisterServiceCtrlHandler(TEXT(SZSERVICENAME),Service_Ctrl);
11.
12. //如果注冊失敗
13.
14. if(!sshStatusHandle)
15.
16. {
17.
18. goto
19.
20. return;
21.
22. }
23.
24. //初始化 SERVICE_STATUS 結構中的成員
25.
26. ssStatus.dwServiceType=SERVICE_WIN32_OWN_PROCESS;
27.
28. ssStatus.dwServiceSpecificExitCode=0;
29.
30. //更新服務狀态
31.
32. if(!ReportStatusToSCMgr(
33.
34. //服務狀态,The service is starting.
35.
36. //退出碼
37.
38. //等待時間
39.
40. goto cleanup; //更新服務狀态失敗則轉向 cleanup
41.
42. ServiceStart(dwArgc,lpszArgv);
43.
44. return;
45.
46. cleanup:
47.
48. //把服務狀态更新為 SERVICE_STOPPED,并退出。
49.
50. if(sshStatusHandle)
51.
52. void)ReportStatusToSCMgr(SERVICE_STOPPED,dwErr,0);
53.
54. }
3.控制處理程式函數
函數 Service_Ctrl 是服務的控制處理程式函數,由主函數線程的控制分發程式引用。在處理控制請求碼時,應該在确定的時間間隔内更新服務狀态檢查點,避免發生服務不能響應的錯誤。
[c-sharp]
view plain
copy
1. //控制處理程式函數
2.
3. void
4.
5. {
6.
7. //處理控制請求碼
8.
9. switch(dwCtrlCode)
10.
11. {
12.
13. //先更新服務狀态為 SERVICDE_STOP_PENDING,再停止服務。
14.
15. case
16.
17. ReportStatusToSCMgr(SERVICE_STOP_PENDING,NO_ERROR,500);
18.
19. //由具體的服務程式實作
20.
21. return;
22.
23. //暫停服務
24.
25. case
26.
27. ReportStatusToSCMgr(SERVICE_STOP_PENDING,NO_ERROR,500);
28.
29. //由具體的服務程式實作
30.
31. ssStatus.dwCurrentState=SERVICE_PAUSED;
32.
33. return;
34.
35. //繼續服務
36.
37. case
38.
39. ReportStatusToSCMgr(SERVICE_STOP_PENDING,NO_ERROR,500);
40.
41. //由具體的服務程式實作
42.
43. ssStatus.dwCurrentState=SERVICE_RUNNING;
44.
45. return;
46.
47. //更新服務狀态
48.
49. case
50.
51. break;
52.
53. //無效控制碼
54.
55. default:
56.
57. break;
58.
59. }
60.
61. ReportStatusToSCMgr(ssStatus.dwCurrentState,NO_ERROR,0);
62.
63. }
64.
除了系統定義的五種控制碼外(還有一種是:SERVICE_CONTROL_SHUTDOWN),使用者還可自定義控制碼,其取值範圍是128-255。使用者可以通過控制台中的服務項向特定服務程式的控制處理函數發送控制碼,程式員可以調用系統函數 ControlService 直接向服務程式的控制處理函數發送控制碼。其函數原型如下:
[c-sharp]
view plain
copy
1. BOOL ControlService(
2.
3. SC_HANDLE hService,
4.
5. DWORD dwControl,
6.
7. LPSERVICE_STATUS lpServiceStatus
8.
9. );
hService :函數 OpenService or CreateService 傳回的服務程式句柄。
dwControl :控制碼,不能是SERVICE_CONTROL_SHUTDOWN。
lpServiceStatus:傳回最後收到的服務狀态資訊。
4.安裝服務程式
每個已安裝服務程式在 HKEY_LOCAL_MACHINE/SYSTE/CurrentControlSet/Services 下都有一個服務名的關鍵字,程式員可以調用系統函數 CreateService 安裝服務程式,并指定服務類型,服務名等。這個函數建立一個服務對象,并将其增加到相關的服務控制管理器資料庫中。
下面是函數原型:
[cpp]
view plain
copy
1.
2.
3. SC_HANDLE
4.
5. SC_HANDLE hSCManager, //服務控制管理程式維護的登記資料庫的句柄,由系統函數OpenSCManager 傳回
6.
7. LPCTSTR lpServiceName, //以NULL 結尾的服務名,用于建立登記資料庫中的關鍵字
8.
9. LPCTSTR lpDisplayName, //以NULL 結尾的服務名,用于使用者界面辨別服務
10.
11. DWORD dwDesiredAccess, //指定服務傳回類型
12.
13. DWORD dwServiceType, //指定服務類型
14.
15. DWORD dwStartType, //指定何時啟動服務
16.
17. DWORD dwErrorControl, //指定服務啟動失敗的嚴重程度
18.
19. LPCTSTR lpBinaryPathName, //指定服務程式二進制檔案的路徑
20.
21. LPCTSTR lpLoadOrderGroup, //指定順序裝入的服務組名
22.
23. LPDWORD lpdwTagId, //忽略,NULL
24.
25. LPCTSTR lpDependencies, //指定啟動該服務前必須先啟動的服務或服務組
26.
27. LPCTSTR lpServiceStartName, //以NULL 結尾的字元串,指定服務帳号。如是NULL,則表示使用LocalSystem 帳号
28.
29. LPCTSTR lpPassword //以NULL 結尾的字元串,指定對應的密碼。為NULL表示無密碼。但使用LocalSystem時填NULL
30.
31. );
對于一個已安裝的服務程式,可以調用系統函數 OpenService 來擷取服務程式的句柄
下面是其函數原型:
[c-sharp]
view plain
copy
1. SC_HANDLE OpenService(
2.
3. SC_HANDLE hSCManager,
4.
5. LPCTSTR lpServiceName,
6.
7. DWORD dwDesiredAccess
8.
9. );
hSCManager :服務控制管理程式微服的登記資料庫的句柄。由函數 OpenSCManager function 傳回 這個句柄。
lpServiceName :将要打開的以NULL 結尾的服務程式的名字,和 CreateService 中的 lpServiceName 相對應。
dwDesiredAccess :指定服務的通路類型。服務響應請求時,首先檢查通路類型。
用CreateService 或OpenService 打開的服務程式句柄使用完畢後必須用CloseServiceHandle 關閉。
OpenSCManager打開的服務管理資料庫句柄也必須用它來關閉。
[cpp]
view plain
copy
1. //安裝服務程式
2.
3. void
4.
5. {
6.
7. SC_HANDLE
8.
9. SC_HANDLE
10.
11. TCHAR
12.
13. //得到程式磁盤檔案的路徑
14.
15. if(GetModuleFileName(NULL,szPath,512)==0)
16.
17. {
18.
19. "Unable to install %s - %s /n"),
20.
21. TEXT(SZAPPNAME),
22.
23. //@1擷取調用函數傳回的最後錯誤碼
24.
25. return;
26.
27. }
28.
29. //打開服務管理資料庫
30.
31. schSCManager=OpenSCManager(
32.
33. //本地計算機
34.
35. //預設的資料庫
36.
37. //要求所有的通路權
38.
39. );
40.
41. if(schSCManager)
42.
43. {
44.
45. //登記服務程式
46.
47. schService=CreateService(
48.
49. //服務管理資料庫句柄
50.
51. //服務名
52.
53. //用于顯示服務的辨別
54.
55. //響應所有的通路請求
56.
57. //服務類型
58.
59. //啟動類型
60.
61. //錯誤控制類型
62.
63. //服務程式磁盤檔案的路徑
64.
65. //服務不屬于任何組
66.
67. //沒有tag辨別符
68.
69. //啟動服務所依賴的服務或服務組,這裡僅僅是一個空字元串
70.
71. //LocalSystem 帳号
72.
73. NULL);
74.
75. if(schService)
76.
77. {
78.
79. "%s installed. /n"),TEXT(SZAPPNAME));
80.
81. CloseServiceHandle(schService);
82.
83. }
84.
85. else
86.
87. {
88.
89. "CreateService failed - %s /n"),GetLastError());
90.
91. }
92.
93. CloseServiceHandle(schSCManager);
94.
95. }
96.
97. else
98.
99. "OpenSCManager failed - %s /n"),GetLastError());
100.
101. }
102.
主函數處理了三中指令行參數:- install,- remove,- debug,分别用于安裝,删除和調試服務程式。如果不帶參數運作,則認為是服務控制管理出現啟動該服務程式。參數不正确則給出提示資訊。
StartServiceCtrlDispatcher 函數負責把程式主線程連接配接到服務控制管理程式。具體描述如下:
BOOL StartServiceCtrlDispatcher(
const LPSERVICE_TABLE_ENTRY lpServiceTable);
lpServiceStartTable 指向 SERVICE_TABLE_ENTRY 結構類型的數組,他包含了調用程序所提供的每個服務的入口函數和字元串名。表中的最後一個元素必須為 NULL,指明入口表結束。SERVICE_TABLE_ENTRY 結構具體描述如下:
typedef struct _SERVICE_TABLE_ENTRY { LPTSTR lpServiceName; LPSERVICE_MAIN_FUNCTION lpServiceProc;
} SERVICE_TABLE_ENTRY, *LPSERVICE_TABLE_ENTRY;
lpServiceName 是一個以 NULL 結尾的字元串,辨別服務名。如果是 SERVICE_WIN32_OWN_PROCESS 類型的服務,這個字元串會被忽略。