關於 Service 設計初步(MSDN節選翻譯) (轉)

amyz發表於2007-11-12
關於 Service 設計初步(MSDN節選翻譯) (轉)[@more@]

  以下內容是我這幾天學寫 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__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 報告現在的進度,這樣可以比較好的進行 De 。

當初始化完成後,呼叫 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_MACHINESYSTEMCurrentControlSetControl

 

如何寫 Service 程式的 main 函式

  Servc的 main 函式呼叫 StartServiceCtrlDispatcher 函式去連線 SCM 並開始控制傳送器的執行緒,該執行緒開始並等待控制請求的到來(當然,是經過過濾的),這個函式是不會返回的的,直到有錯誤發生或者程式內所有的服務都完成了。 SCM 會傳送控制請求以告訴關閉執行緒,才會從 StartServiceCtrlDispatcher 函式里返回,然後程式就關閉了。

  以下的例子是一個服務程式裡只包含一個服務的情況。(省略。。。)

 

SERVICE_STATUS  MyServiceStatus;
SERVICE_STATUS_HANDLE  MyServiceStatusHandle;
 
VOID  MyServiceStart (D 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))
  {
  SebugOut(" [MY_SERVICE] StartServiceCtrlDispatcher error = %dn", 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.dwControlsAccepted  = 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 %dn", 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 %ldn",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  %ldn",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 %ldn",  Opcode);

  }

 

  // Send current status.

  if (!SetServiceStatus (MyServiceStatusHandle,  &MyServiceStatus))

  {

  status = GetLastError();

  SvcDebugOut(" [MY_SERVICE] SetServiceStatus error %ldn",status);

  }

  return;

}

  完畢,如果我寫了什麼可以參考的服務程式例子,我再拿到這裡來,以權作參考。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752019/viewspace-981882/,如需轉載,請註明出處,否則將追究法律責任。

相關文章