下面介紹使用VC++開發Windows服務程式。
首先運行VC++6,選擇建立工程,在出現的下面視窗中選擇”ATL COM AppWizard“,並選擇工程置放位置和相應的工程名,然後選擇確定按鈕。
此時出現如視窗介面,在此介面中選擇”服務(EXE)“,然後選擇完成按鈕。
在接下來的視窗中選擇確定按鈕。
則VC完成嚮導並產生相應代碼(效果如下)。
程式的進入點是全域函數_tWinMain, 仔細看一下這個函數,我們會發現當我們運行程式時,可以加上參數,例如: winsvr /RegServer 或者 winsvr -RegServer,這個是用來本機伺服器註冊(Register as Local S Register as Service erver)。
其中winsvr / Service 或者 winsvr -Service,這個是服務的註冊(Register as Service);winsvr /UnRegServer 或者 winsvr -UnRegServer ,這個是服務的刪除。
所以,當我們寫好了服務程式,只要啟動並執行時候加上參數 Service ,這個時候在SCM中就會看到我們的服務了。
每次編碼後測試都要在命令列中加參數運行服務才可以在SCM中列出來這樣很麻煩,因此可以採用如下方式來處理:選擇VC IDE的菜單工程 -> 設定, 再選擇自訂群組建面板(如所示)
在"$(TargetPath)" /RegServer的下面加上:"$(TargetPath)" /Service,這樣當我們每次編碼後編譯器,就不用再在命令列中去加參數執行我們的服務程式完成服務的註冊了。
同時通過介面我們也看到,嚮導為我們建立了一個類:CServiceModule,全域變數_Module就是這個類的執行個體。
Init():這個函數用於完成一些初始化工作;
Run():這個函數就是服務開始運行後的內容,我們接下來要修改的內容也就是從這裡入手。
Install():有如下一段代碼
SC_HANDLE hService = ::CreateService(
hSCM, m_szServiceName, m_szServiceName,
SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
szFilePath, NULL, NULL, _T("RPCSS\0"), NULL, NULL);
注意:如果服務中啟動的程式具有視窗(即具有互動功能則要求使用如下代碼)
SC_HANDLE hService = ::CreateService(
hSCM, m_szServiceName, m_szServiceName,
SERVICE_ALL_ACCESS, SERVICE_WIN32_SHARE_PROCESS | SERVICE_INTERACTIVE_PROCESS,
SERVICE_AUTO_START, SERVICE_ERROR_NORMAL,
szFilePath, NULL, NULL, _T("RPCSS\0"), NULL, NULL);
這個CreateService函數原先如下:
SC_HANDLE CreateService(
SC_HANDLE hSCManager, // handle to SCM database
LPCTSTR lpServiceName, // name of service to start
LPCTSTR lpDisplayName, // display name
DWORD dwDesiredAccess, // type of access to service
DWORD dwServiceType, // type of service
DWORD dwStartType, // when to start service
DWORD dwErrorControl, // severity of service failure
LPCTSTR lpBinaryPathName, // name of binary file
LPCTSTR lpLoadOrderGroup, // name of load ordering group
LPDWORD lpdwTagId, // tag identifier
LPCTSTR lpDependencies, // array of dependency names
LPCTSTR lpServiceStartName, // account name
LPCTSTR lpPassword // account password
);
第六個參數是服務的啟動類型。
SERVICE_DEMAND_START是手動啟動,SERVICE_AUTO_START是自動啟動。
第十一個參數是服務的依存關係,比如說服務的啟動想要依存SQL Server的啟動,那我們可以把這個參數寫成:
_T("MSSQLSERVER\0");
如果我們寫的服務不依存於其他的任何服務,那我們就將此參數設定為NULL就可以了。
接下來我們實現我們需要實現的業務。
首先,我們在類CServiceModule中找到Run函數,並在Run函數中找到以下代碼:
MSG msg;
while (GetMessage(&msg, 0, 0, 0))
DispatchMessage(&msg);
並在此代碼前加入自己的代碼,我這裡加入的代碼:CustomFunc1()。
這裡對應不同應用有不同寫法:
1、如果要啟動一個視窗進行互動,則代碼如下:
定義兩個成員函數CustomFunc1和CustomFunc2
void CServiceModule::CustomFunc1()
{
TCHAR szFilePath[MAX_PATH + 1];
GetModuleFileName(NULL, szFilePath, MAX_PATH);
(_tcsrchr(szFilePath, _T('\\')))[1] = 0; //刪除檔案名稱,只獲得路徑
CString str_url = szFilePath;
str_url=str_url+"B.exe";
LogEvent("運行程式:"+str_url);
UINT rtn=WinExec(str_url,SW_SHOW );
//UINT rtn=WinExec("c:\\windows\\system32\\cmd.exe",SW_SHOWNORMAL);
if(rtn>31){
LogEvent("調用成功");
}
else{
if(rtn==0){
LogEvent("記憶體不足");
}
else{
if(rtn==ERROR_BAD_FORMAT){ //ERROR_BAD_FORMAT = 11
LogEvent("EXE 檔案無效");
}
else{
if(rtn==ERROR_FILE_NOT_FOUND){ //ERROR_FILE_NOT_FOUND = 2
LogEvent("檔案名稱錯誤");
}
else{
if(rtn==ERROR_PATH_NOT_FOUND){ //ERROR_PATH_NOT_FOUND = 3
LogEvent("路徑名錯誤");
}
else{
LogEvent("發生其它錯誤");
}
}
}
}
}
}
BOOL CServiceModule::CustomFunc2()
{
HDESK hdeskCurrent;
HDESK hdesk;
HWINSTA hwinstaCurrent;
HWINSTA hwinsta;
hwinstaCurrent = GetProcessWindowStation();
if (hwinstaCurrent == NULL){
LogEvent(_T("get window station err"));
return FALSE;
}
hdeskCurrent = GetThreadDesktop(GetCurrentThreadId());
if (hdeskCurrent == NULL){
LogEvent(_T("get window desktop err"));
return FALSE;
}
//開啟winsta0
hwinsta = OpenWindowStation("winsta0", FALSE,
WINSTA_ACCESSCLIPBOARD |
WINSTA_ACCESSGLOBALATOMS |
WINSTA_CREATEDESKTOP |
WINSTA_ENUMDESKTOPS |
WINSTA_ENUMERATE |
WINSTA_EXITWINDOWS |
WINSTA_READATTRIBUTES |
WINSTA_READSCREEN |
WINSTA_WRITEATTRIBUTES);
if (hwinsta == NULL){
LogEvent(_T("open window station err"));
return FALSE;
}
if (!SetProcessWindowStation(hwinsta)){
LogEvent(_T("Set window station err"));
return FALSE;
}
//開啟desktop
hdesk = OpenDesktop("default", 0, FALSE,
DESKTOP_CREATEMENU |
DESKTOP_CREATEWINDOW |
DESKTOP_ENUMERATE |
DESKTOP_HOOKCONTROL |
DESKTOP_JOURNALPLAYBACK |
DESKTOP_JOURNALRECORD |
DESKTOP_READOBJECTS |
DESKTOP_SWITCHDESKTOP |
DESKTOP_WRITEOBJECTS);
if (hdesk == NULL){
LogEvent(_T("Open desktop err"));
return FALSE;
}
SetThreadDesktop(hdesk);
CustomFunc1();
if (!SetProcessWindowStation(hwinstaCurrent))
return FALSE;
if (!SetThreadDesktop(hdeskCurrent))
return FALSE;
if (!CloseWindowStation(hwinsta))
return FALSE;
if (!CloseDesktop(hdesk))
return FALSE;
return TRUE;
}
2、無互動視窗,則代碼如下:
void CServiceModule::CustomFunc1(){
LogEvent(_T("Custorm Function Invoked")); //這裡可以改成任何你需要的代碼,當然不能有顯示視窗之內的代碼,要顯示視窗之內代碼請採用上面方法1。
}
現在可以編譯並執行程式了。
此時會編譯時間會報告一個錯誤:'CString' : undeclared identifier。
這時需要查看工程的一些設定:
菜單工程->設定 ,常規面板,預設的設定是:使用MFC作為靜態串連庫。如果是這個設定則做如下工作:
然後我們開啟StdAfx.h檔案,並找到#include <atlbase.h>位置,並在它之前加入#include <afx.h>。重新編譯可以了。
如果需要修改出現在scm中的服務名,可以在工程中找到資源檔中的IDS_SERVICENAME項的內容就可以了。
注意:編譯好了的程式需要在控制台中輸入如下指令
1、註冊服務
winsvr /regserver
winsvr /service //此條指令十分重要,如果不執行則在scm中是看不到此服務的
2、登出服務
winsvr /unregserver