關於使用BCB6編寫Windows服務的問題

來源:互聯網
上載者:User

 前日因為系統遺留問題,不得不重新開啟已經N久沒有使用的Borland C++ Builder 6,編寫一個Windows服務。最初設想是編寫一個能夠根據指定參數,設定諸如服務名稱、顯示名稱、描述、設定檔路徑的東西,以一個服務程式作為多種不同服務內容的外殼,能夠在Windows的服務管理員中分別控制。由於BCB的服務範本未考慮定製的情況,所以需要費點周折。

 

BCB中使用者實現的服務物件繼承自TService,這裡暫訂為TMyService,當使用全域對象Svrmgr::Application(TServiceApplication)來建立服務物件時,使用者的服務物件自動成為Application的組件之一。

通常這樣來建立服務物件:

  1. Application->CreateForm(__classid(TMyService), &MyService);

BCB的SvrMgr.hpp其實是Delphi實現的申明。查看SvrMgr.pas,可以見到建立服務的代碼:

  1. Svc := CreateService( SvcMgr, 
  2.                       PChar(Name), //< TMyService->Name用作服務名稱
  3.                       PChar(DisplayName), //< TMyService->DisplayName用作顯示名稱
  4.                       SERVICE_ALL_ACCESS,
  5.                       GetNTServiceType,
  6.                       GetNTStartType,
  7.                       GetNTErrorSeverity,
  8.                       PChar(Path),
  9.                       PChar(LoadGroup),
  10.                       PTag,
  11.                       PChar(GetNTDependencies),
  12.                       PSSN,
  13.                       PChar(Password));

這裡可以看到,當使用/install參數來註冊服務時,BCB的實現是使用TMyService的Name屬性作為服務名稱,DisplayName作為服務顯示名稱。如果要定製這兩個名稱,我們可以增加額外的參數,設為/name和/displayname,分別用於指定服務名稱和服務顯示名稱。樣本如下:

 

MyService /install /name:"ThisIsAnotherService" /displayname:"顯示名稱"

 

/name和/displayname後面跟一個":"來分隔後繼的字串,在參數解析時,系統會將雙引號中的字串作為一個整體對待。不瞭解這一點和對引號有不同看法的請參考Windows提供的協助:命令提示字元。

 

BCB只處理/install和/uninstall參數,增加的參數需要自己處理,這裡要用到兩個BCB提供的函數:ParamCount()和ParamStr(),具體使用方法請參考BCB的Help。如下是範例程式碼:

 

 

 

  1. bool isInstall = FindCmdLineSwitch("install", true); // 安裝服務標章
  2. bool isUninstall = FindCmdLineSwitch("uninstall", true); // 卸載服務標章
  3. AnsiString serviceName; // 服務名稱
  4. AnsiString serviceDispName; // 服務顯示名稱
  5. if (ParamCount() > 1 && (isInstall || isUninstall)) // 指定了參數以及安裝、卸載標誌
  6. {
  7.     // 先建立之
  8.     Application->CreateForm(__classid(TMyService), &MyService);
  9.     if ( isInstall || isUninstall ) // 對於安裝或卸載情況
  10.     {
  11.         for(int i = 1; i <= ParamCount(); i++) // 迴圈處理所有參數
  12.         {
  13.             AnsiString param = ParamStr(i).LowerCase(); // 為便於比較,先轉換成小寫
  14.             int pos; // 參數位置
  15.             if (param.Pos("/name:")) // 匹配服務名稱: "/name:xxxxx"
  16.             {
  17.                 pos = param.Pos(":");
  18.                 serviceName = param.SubString(pos + 1, param.Length() - pos);
  19.                 if ((pos = serviceName.Pos(" ")) > 0) // 刪除空格,這個其實不必要,系統會指出名稱非法
  20.                 {
  21.                     serviceName.Delete(pos, 1);
  22.                 }
  23.             }
  24.             else
  25.             if (param.Pos("/dispname:")) // 匹配服務顯示名稱: "/serviceDispName:xxxxx"
  26.             {
  27.                 pos = param.Pos(":");
  28.                 serviceDispName = param.SubString(pos + 1, param.Length() - pos);
  29.             }
  30.         }
  31.         if (serviceName.Length() > 0)
  32.             Application->Components[0]->Name = serviceName; // 使用指定的服務名稱
  33.         else
  34.             serviceName = Application->Components[0]->Name; // 未指定服務名稱,會使用TMyService的預設名稱:MyService
  35.         if (serviceDispName.Length() == 0)
  36.             serviceDispName = serviceName;  // 如果未指定顯示名稱,使用服務名稱來代替
  37.             
  38.         ((TService *)Application->Components[0])->DisplayName = serviceDispName; // 設定顯示名稱
  39.     }
  40. }
  41. Application->Run(); //< 服務開始運行

現在,編譯並註冊我們的服務,可以看到它按指定的方式顯示在服務列表中,讓我們試著啟動它。。。。。。等等。。。。。啟動失敗!!!!OOOOOOOH! SHIT!!!!!

點解!?!?

 

再來查看SvrMgr.pas,BCB(或Delphi?)是這樣啟動服務滴:

 

 

 

  1. // .....
  2. begin
  3.     Forms.Application.OnException := OnExceptionHandler; // 異常控制代碼
  4.     ServiceCount := 0; // 服務物件(TXXXService)數量
  5.     for i := 0 to ComponentCount - 1 do // Application包含的組件數量即服務物件數量
  6.       if Components[i] is TService then Inc(ServiceCount);
  7.     SetLength(ServiceStartTable, ServiceCount + 1); // 設定服務啟動表的尺寸
  8.     FillChar(ServiceStartTable[0], SizeOf(TServiceTableEntry) * (ServiceCount + 1), 0); // 清零
  9.     J := 0;
  10.     for i := 0 to ComponentCount - 1 do // 填充服務入口表
  11.       if Components[i] is TService then
  12.       begin
  13.         ServiceStartTable[J].lpServiceName := PChar(Components[i].Name); // 這裡使用的是Name屬性!
  14.         ServiceStartTable[J].lpServiceProc := @ServiceMain; // 關於ServiceMain入口函數,請參閱Windows SDK help
  15.         Inc(J);
  16.       end;
  17.     StartThread := TServiceStartThread.Create(ServiceStartTable); // 啟動服務
  18.     // .....
  19. // TServiceStartThread的線程函數實現
  20. procedure TServiceStartThread.Execute;
  21. begin
  22.   if StartServiceCtrlDispatcher(FServiceStartTable[0]) then // 使用服務入口表啟動服務
  23.     ReturnValue := 0
  24.   else
  25.     ReturnValue := GetLastError;
  26. end;

Windows API StartServiceCtrlDispatcher()使用服務入口表啟動服務,該操作是名稱相關的,而我們已經使用了指定的名稱安裝服務,所以當系統(TServiceApplication)使用原有的名稱(這裡是MyService)來啟動服務時,會找不到名為MyService的服務(我們已經指定其名稱為ThisIsAnotherService),導致啟動失敗。由於系統在啟動服務時,沒有提供關於服務名稱的上下文,因此我們需要作一點手腳,創造這個上下文。簡單的方法是:在安裝服務時,修改服務的啟動路徑記錄,添加服務名稱作為參數。在服務啟動時,解析這個參數,並使用該參數修改TMyService->Name,這樣服務應能順利啟動。程式碼範例如下:

 

先添加額外的參數,儲存服務名稱:

 

  1. // .......
  2. Application->Run();
  3. // 需要在Run以後,此時服務已經成功安裝
  4. if (isInstall)
  5. {
  6.     // 最簡單的方法是Hack註冊表
  7.     TRegistry* reg = new TRegistry();
  8.     AnsiString key = "//System//CurrentControlSet//Services//" + serviceName; // 註冊表路徑
  9.     reg->RootKey = HKEY_LOCAL_MACHINE;
  10.     if (reg->OpenKey(key, false)) // 開啟薦
  11.     {
  12.         AnsiString imagePath = reg->ReadString("ImagePath"); // 介就系程式映像的路徑
  13.         reg->WriteString("ImagePath", imagePath + " -" + serviceName); // 我們要做的是添加額外的參數:服務名稱
  14.     }
  15.     reg->CloseKey();
  16.     delete reg;
  17. } // if isInstall then hack registry

再添加對參數的處理:

  1. if (paramCount() > 1 && (isInstall || isUninstall))
  2. {
  3.     //......
  4. }
  5. else // 非安裝,即啟動
  6. {
  7.     // 檢查參數個數
  8.     if (ParamCount() > 0)
  9.     {
  10.         AnsiString extraParam = ParamStr(1).LowerCase(); // 額外參數,轉換為小寫
  11.         AnsiString specifiedServiceName;
  12.         if (extraParam.Pos("-")) // 定位"-"
  13.         {
  14.             int pos = extraParam.Pos("-");
  15.             specifiedServiceName = extraParam.SubString(pos + 1, extraParam.Length() - pos); // 解析服務名稱
  16.         }
  17.         if (!specifiedServiceName.Length()) // 如果名稱無效,則使用預設名稱:MyService
  18.         {
  19.             Application->CreateForm(__classid(TMyService), &MyService); // 使用預設名稱
  20.         }
  21.         else
  22.         {
  23.             Application->CreateForm(__classid(TMyService), &MyService);
  24.             MyService->Name = specifiedServiceName; // 使用指定的名稱
  25.         }
  26.     } // 
  27. } // if ... else
  28. // ...
  29. Application->Run();
  30. // ...

 

現在,編譯器,重新安裝服務,再試著啟動一下。。。。。。

如果沒有錯誤的話,服務應該能夠順利啟動。(廢話)

 

BCB沒有提供服務的描述屬性,這也可以通過修改註冊表的方法實現,操作很簡單,這裡不在多言。

 

此乃末技。。。。。

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.