以下內容是我這幾天學寫 Service 的筆記,共享一下,剛好讓想寫 Service 的朋友一起探討(當然,這翻譯很爛請不要笑話):
· 一個 Service 程序包括三個部分
第一個部分是控制模塊,主要是與服務管理程序溝通,進行服務程序的安裝和刪除。
第二個模塊是主模塊,也就是服務程序運行過程中要做的工作,應該是一個循環(如果退出了該循環,是否需要通知操作系統?)。
第三個模塊是與服務管理程序控制處理模塊,主要處理 Service啟動,Service停止等事件的。并通知服務管理程序當前的狀態。
了解過程
· 如何安裝一個 Service 程序,以及如何刪除一個 Service 程序。
· 如何處理系統的啟動 Service 和停止 Service 的事件
· Service 程序運行的主模塊應該放在那里?以及如何組織?
MSDN 節選內容
Service Control Manager 簡稱 SCM (或 SCMgr),是當系統啟動的時候自動運行的遠程調用
·維護已安裝 Service 程序的數據庫(List)
·在系統啟動時或當需要時啟動 Service 或 驅動型 Services 。
·列舉已經安裝的 Service 和 Service 驅動
·獲取運行中的 Service 和 Service 驅動的狀態信息
·給運行中的 Service 發送控制要求
·鎖定 (或解除) Service 數據庫。
The main Function
Service 程序 通常會被寫成 控制臺程序, main 函數將會是控制臺程序的入口,得到來自注冊表內預定的參數。
當 SCM 啟動 Service 程序,它會等待 調用 StartServiceCtrlDispatcher 函數
SERVICE_WIN32_OWN_PROCESS 類型的 Service 會立即從它的主線程里調用 StartServiceCtrlDispatcher 。你可以在 ServiceMain 函數內執行一些初始化操作,當 Service 啟動以后。
StartServiceCtrlDispatcher 函數有一個 SERVICE_TABLE_ENTRY 作為參數,里面指定了 Service 的名字和入口點。
如果 StartServiceCtrlDispatcher 函數調用成功,被調用線程不會在 Service 進程結束以前返回。
·當新的 Service 啟動時,創建一個新的線程并調用對應的入口點
·調用適當的處理函數去處理 Service 控制要求
The ServiceMain Function
ServiceMain 函數是 Service 的入口點。
當 Service 控制程序需要一個新的 Service 運行。 SCM 開始 Service 并發送啟動要求給控制傳送者,然后由它創建新的線程以運行 Service 的 ServiceMain 函數。
ServiceMain 函數需要執行一下任務:
馬上調用 RegisterServiceCtrlHandlerEx 去注冊一個 處理函數(HandlerEx) 去處理 Service 的 控制要求。返回值是通知 SCM 的 Service 狀態處理的 handle 。
執行初始化單元,如果初始化代碼的執行時間少于1秒或很短,可以在 ServiceMain 里執行。
如果 初始化時間太長了,最好先調用 SetServiceStatus 函數,指定狀態為 SERVICE_START_PENDING (啟動中),最好是經常作 SetServiceStatus 報告現在的進度,這樣可以比較好的進行 Debug 。
當初始化完成后,調用 SetServiceStatus 并指定 SERVICE_RUNNING 標示現在的狀態。
執行 Service 的任務,或者返回(如果沒有任務的)
如果 初始化 或 運行 過程中出現了錯誤,應該調用 SetServiceStatus 并指定 SERVICE_STOP_PENDING(正在停止) 。完成清場工作后,再指定現在狀態為 SERVICE_STOPPED, 記得在 SERVICE_STATUS 結構中指定成員 dwServiceSpecificExitCode 和 dwWin32ExitCode 的值,用來指定錯誤的類型。
The Control Handler Function
每一個 Service 都有控制處理 (HandlerEx) 函數,它會被控制發送機制調用 當 Service 進程從Service 控制程序那里獲得控制要求,因此,這個函數是在 control dispatcher 的線程中執行的。
當處理函數被調用,Service 必須要調用 SetServiceStatus 函數來向 SCM 匯報當前狀態,不管當前的狀態是否又改變了。
Service 控制程序可以使用 ControlService 函數來發送控制要求,所有的 Service 都必須接受并處理 SERVICE_CONTROL_INTERROGATE (控制詢問),你可以打開或者禁止接受其他的控制代碼,使用 SetServiceStatus 來完成。如果要接收 SERVICE_CONTROL_DEVICEEVENT (控制事件驅動) 代碼,你必須調用 RegisterDeviceNotification 函數,Service 還可以處理其他的用戶自定義的代碼。
控制處理函數必須在30秒內返回,否則 SCM 會返回錯誤, 如果 Service 需要做更多的工作來處理,那么最好是創建一個新的線程,比方說要處理 停止服務的 要求,可以創建另外一個線程去做處理,然后在處理函數里面調用 SetServiceStatus 設置 SERVICE_STOP_PENDING 來返回。
當使用者關閉操作系統時,所有的控制處理過程都會因為被調用 SetServiceStatus 設置了 SERVICE_ACCEPT_SHUTDOWN 而 接收到 SERVICE_CONTROL_SHUTDOWN 控制信號,它們會按照安裝順序而被排隊通知該信號,按照默認,每個 Service 可以有大約20秒的清場時間趕在系統關閉之前完成,你可以設置注冊表的 WaitToKillServiceTimeout 鍵值去改變系統等待 Service 關閉的計時,該鍵值在以下地方設置。
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control
如何寫 Service 程序的 main 函數
Servcie 的 main 函數調用 StartServiceCtrlDispatcher 函數去連接 SCM 并開始控制發送器的線程,該線程開始并等待控制請求的到來(當然,是經過過濾的),這個函數是不會返回的的,直到有錯誤發生或者進程內所有的服務都完成了。 SCM 會發送控制請求以告訴關閉線程,才會從 StartServiceCtrlDispatcher 函數里返回,然后進程就關閉了。
以下的例子是一個服務進程里只包含一個服務的情況。(省略。。。)
SERVICE_STATUS MyServiceStatus;
SERVICE_STATUS_HANDLE MyServiceStatusHandle;
VOID MyServiceStart (DWORD argc, LPTSTR *argv);
VOID MyServiceCtrlHandler (DWORD opcode);
DWORD MyServiceInitialization (DWORD argc, LPTSTR *argv, DWORD *specificError);
void main( )
{
SERVICE_TABLE_ENTRY DispatchTable[] =
{
{ "MyService", MyServiceStart },
{ NULL, NULL }
};
if (!StartServiceCtrlDispatcher( DispatchTable))
{
SvcDebugOut(" [MY_SERVICE] StartServiceCtrlDispatcher error = %d\n", GetLastError());
}
}
VOID SvcDebugOut(LPSTR String, DWORD Status)
{
CHAR Buffer[1024];
if (strlen(String) < 1000)
{
sprintf(Buffer, String, Status);
OutputDebugStringA(Buffer);
}
}
如果你的 Service 程序支持多個服務的, 那么 main 函數將會有輕微的不同, 更多的服務名稱將會被加入 dispatch table 以可以被 dispatcher 線程監控。
如何寫 ServiceMain 函數
以下的那個例子里面 MyServiceStart 函數是 Service 的入口, MyServiceStart 使用 命令行參數,就好像 Console 程序的 main 函數一樣。 第一個參數包含共有幾個參數被傳送到 Service 。所以,那總是至少有一個參數,第二個參數是一個指針的,指向字串指針的數組。數組第一個項一定是 Service 的名字。
MyServiceStart 首先填充 SERVICE_STATUS 結構,包含了控制代號可以被接受的,盡管這個 Service 可以接受 SERVICE_CONTROL_PAUSE (暫停) 和 SERVICE_CONTROL_CONTINUE (繼續) 的,被告知 暫停 是沒有意義的,SERVICE_ACCEPT_PAUSE_CONTINUE 標志的包含只是為了說明為目的。如果 暫停 在你的 Service 里面并沒有意義,那么就不要支持它。
MyServiceStart 函數然后調用 RegisterServiceCtrlHandler 函數去注冊 MyService 當成 Service 的處理函數,并開始初始化的工作。 下列的初始化例子程序 MyServiceInitialization 只是用來說明,并沒有任何實際的初始化代碼,和創建任何的線程。如果你的初始化代碼可能會很長,那么你的代碼最好周期性的調用 SetServiceStatus 來發送等待信息和初始化的進度信息。
當初始化完成后,例子會調用 SetSercviceStatus 設置 SERVICE_RUNNING 并繼續它的工作,如果在初始化期間出了錯, MyServiceStart 報告 SERVICE_STOPPED 后返回。
因為這個例子并沒有完成什么真正的任務,MyServiceStart 簡單的返回控制給調用者。 無論如何,你的 Service 應該使用這個線程去完成本來想要設計做的東西。如果你的 Service 并不需要運行什么東西(只是操作 RPC 請求而已), ServiceMain 函數應當返回到調用者那里。這個比調用 ExitThread 好的多,因為我們要給機會調用程序做清理現場的工作,比方申請的內存和數組。
如果要輸出 debug 信息, 可以調用 SvcDebugOut ,該函數的源代碼已經在《如何寫 Servcie 程序的 main 函數》那里給出
SERVICE_STATUS MyServiceStatus;
SERVICE_STATUS_HANDLE MyServiceStatusHandle;
void MyServiceStart (DWORD argc, LPTSTR *argv)
{
DWORD status;
DWORD specificError;
MyServiceStatus.dwServiceType = SERVICE_WIN32;
MyServiceStatus.dwCurrentState = SERVICE_START_PENDING;
MyServiceStatus.dwControlsAclearcase/" target="_blank" >ccepted = SERVICE_ACCEPT_STOP |
SERVICE_ACCEPT_PAUSE_CONTINUE;
MyServiceStatus.dwWin32ExitCode = 0;
MyServiceStatus.dwServiceSpecificExitCode = 0;
MyServiceStatus.dwCheckPoint = 0;
MyServiceStatus.dwWaitHint = 0;
MyServiceStatusHandle = RegisterServiceCtrlHandler(
"MyService",
MyServiceCtrlHandler);
if (MyServiceStatusHandle == (SERVICE_STATUS_HANDLE)0)
{
SvcDebugOut(" [MY_SERVICE] RegisterServiceCtrlHandler failed %d\n", GetLastError());
return;
}
// Initialization code goes here.
status = MyServiceInitialization(argc,argv, &specificError);
// Handle error condition
if (status != NO_ERROR)
{
MyServiceStatus.dwCurrentState = SERVICE_STOPPED;
MyServiceStatus.dwCheckPoint = 0;
MyServiceStatus.dwWaitHint = 0;
MyServiceStatus.dwWin32ExitCode = status;
MyServiceStatus.dwServiceSpecificExitCode = specificError;
SetServiceStatus (MyServiceStatusHandle, &MyServiceStatus);
return;
}
// Initialization complete - report running status.
MyServiceStatus.dwCurrentState = SERVICE_RUNNING;
MyServiceStatus.dwCheckPoint = 0;
MyServiceStatus.dwWaitHint = 0;
if (!SetServiceStatus (MyServiceStatusHandle, &MyServiceStatus))
{
status = GetLastError();
SvcDebugOut(" [MY_SERVICE] SetServiceStatus error %ld\n",status);
}
// This is where the service does its work.
SvcDebugOut(" [MY_SERVICE] Returning the Main Thread \n",0);
return;
}
// Stub initialization function.
DWORD MyServiceInitialization(DWORD argc, LPTSTR *argv, DWORD *specificError)
{
argv;
argc;
specificError;
return(0);
}
如何寫 控制處理 函數
在下面的例子里 MyServiceCtrlHandler 是處理函數,當這個函數被 dispatcher 線程調用,它會處理來自 OPcode 參數控制代碼的然后調用 SetServiceStatus 更新當前的 Service 狀態。 每次當處理函數接收到控制信號,它會適當的處理并返回狀態,而不管 Service 是否還在處置控制信息。
當接收到暫停的控制信號。 MyServiceCtrlHandler 簡單的設置當前 SERVICE_STATUS 里的 dwCurrentState 為失敗于 SERVICE_PAUSED 里。同樣的,如果接收到繼續的信息,則設置 SERVICE_RUNNING 。所以,該例子并不是一個好的用來處理 暫停和繼續 的例子程序。其實如果你的 Service 曾經聲明不支持 暫停和繼續,那么 SCM 是不會發送這些信息到處理函數的。
SERVICE_STATUS MyServiceStatus;
SERVICE_STATUS_HANDLE MyServiceStatusHandle;
VOID MyServiceCtrlHandler (DWORD Opcode)
{
DWORD status;
switch(Opcode)
{
case SERVICE_CONTROL_PAUSE:
// Do whatever it takes to pause here.
MyServiceStatus.dwCurrentState = SERVICE_PAUSED;
break;
case SERVICE_CONTROL_CONTINUE:
// Do whatever it takes to continue here.
MyServiceStatus.dwCurrentState = SERVICE_RUNNING;
break;
case SERVICE_CONTROL_STOP:
// Do whatever it takes to stop here.
MyServiceStatus.dwWin32ExitCode = 0;
MyServiceStatus.dwCurrentState = SERVICE_STOPPED;
MyServiceStatus.dwCheckPoint = 0;
MyServiceStatus.dwWaitHint = 0;
if (!SetServiceStatus (MyServiceStatusHandle, &MyServiceStatus))
{
status = GetLastError();
SvcDebugOut(" [MY_SERVICE] SetServiceStatus error %ld\n",status);
}
SvcDebugOut(" [MY_SERVICE] Leaving MyService \n",0); return;
case SERVICE_CONTROL_INTERROGATE:
// Fall through to send current status.
break;
default:
SvcDebugOut(" [MY_SERVICE] Unrecognized opcode %ld\n", Opcode);
}
// Send current status.
if (!SetServiceStatus (MyServiceStatusHandle, &MyServiceStatus))
{
status = GetLastError();
SvcDebugOut(" [MY_SERVICE] SetServiceStatus error %ld\n",status);
}
return;
}
完畢,如果我寫了什么可以參考的服務程序例子,我再拿到這里來,以權作參考。
原文轉自:http://www.anti-gravitydesign.com