Windows服務(wù)編寫(Windows Service,system權(quán)限)程序顯示界面與用戶交互(xp,win7通用)
1、VC2008中編寫“Windows服務(wù)”(Windows Service)程序
源碼資源下載:/201604/yuanma/TestService_jb51.rar
vc2008下新建一個(gè) ATL 項(xiàng)目-》 選擇創(chuàng)建一個(gè)“服務(wù)”類型的ATL 項(xiàng)目TestService,將生成如下代碼,
class CTestServiceModule : public CAtlServiceModuleT< CTestServiceModule, IDS_SERVICENAME > { public : DECLARE_LIBID(LIBID_TestServiceLib ) DECLARE_REGISTRY_APPID_RESOURCEID (IDR_TESTSERVICE, "{1FF78006-B225-4CC0-A7DE-E0C9D31C9937}" ) HRESULT InitializeSecurity () throw() { // TODO : 調(diào)用CoInitializeSecurity 并為服務(wù)提供適當(dāng)?shù)? // 安全設(shè)置 // 建議- PKT 級(jí)別的身份驗(yàn)證、 // RPC_C_IMP_LEVEL_IDENTIFY 的模擬級(jí)別 // 以及適當(dāng)?shù)姆荖ULL 安全說(shuō)明符。 return S_OK ; } //重寫這個(gè)函數(shù)來(lái)啟動(dòng)任務(wù)啦 HRESULT Run (int nShowCmd = SW_HIDE ) throw() { HRESULT hr = S_OK; hr = __super ::PreMessageLoop( nShowCmd); if (hr == S_OK) { if (m_bService ) { //需要定義#define _ATL_NO_COM_SUPPORT才能啟動(dòng)服務(wù)時(shí)走到這里 //可以在這里啟動(dòng)線程,或者什么其他東西來(lái)做自己的工作的啦 //這里是什么都沒有做了,只輸出一條信息 LogEvent(_T ("widebright 的服務(wù)啟動(dòng)咯,呵呵 ")); SetServiceStatus(SERVICE_RUNNING ); } //進(jìn)入消息循環(huán),不停的處理消息,可能最后分發(fā)到 Handler去處理,調(diào)用了OnShutdown等函數(shù)的。 __super::RunMessageLoop (); } if (SUCCEEDED (hr)) { hr = __super ::PostMessageLoop(); } //可以在適當(dāng)?shù)臅r(shí)候調(diào)用Uninstall函數(shù)來(lái)卸載掉服務(wù) //__super::Uninstall(); return hr ; } //重寫,服務(wù)退出處理 void OnShutdown () throw() { LogEvent(_T ("TestService 的服務(wù)退出咯,一點(diǎn)都不好玩呵呵 ")); } }; CTestServiceModule _AtlModule; // extern "C" int WINAPI _tWinMain (HINSTANCE , HINSTANCE , LPTSTR , int nShowCmd) { return _AtlModule .WinMain( nShowCmd); }
2、我只要根據(jù)需要重寫相應(yīng)的函數(shù)來(lái)實(shí)現(xiàn)自己想要的功能就行了
比如你想創(chuàng)建的“服務(wù)”隨系統(tǒng)啟動(dòng),可以重寫CAtlServiceModuleT 的Install函數(shù),把里面的CreateService函數(shù)的參數(shù)修改一下,例如添加與用戶交互可以使用 SERVICE_INTERACTIVE_PROCESS,具體可以去MSDN上查找CreateService這個(gè)API的說(shuō)明。
如果想處理服務(wù) 停止和啟動(dòng)的動(dòng)作,可以參考CAtlServiceModuleT 的源代碼重寫OnStop ()等函數(shù)。我上面簡(jiǎn)單到重寫了Run函數(shù),輸出一條“事件”其實(shí)具體 工作是可以放到這里來(lái)完成的吧。
編譯,生成程序之后就可以測(cè)試了,
執(zhí)行“TestService -/Service” 就可以把服務(wù)注冊(cè)到系統(tǒng)了,命令行參數(shù)其實(shí)是在CAtlServiceModuleT::ParseCommandLine 這個(gè)函數(shù)里面處理,可以去看一下,必要的話重寫也是可以的,加上調(diào)用 UnInstall來(lái)刪除服務(wù)的代碼也很不錯(cuò)的吧。
注冊(cè)后,就看用“sc start” 或者“net start” 等命令來(lái)操縱服務(wù)了。在“服務(wù)”控制器里面控制與可以:如圖
3、這時(shí)候在Run函數(shù)中啟動(dòng)一個(gè)Notepad.exe,此時(shí)沒有界面顯示,在xp下可以使用下面的方法實(shí)現(xiàn)notepad與用戶的交互:
//for xp system DWORD _stdcall LaunchAppIntoSession0( LPTSTR lpCommand ) { ////////////////////////////////////////////system show dlg//////////////////// HDESK hdeskCurrent ; HDESK hdesk ; HWINSTA hwinstaCurrent ; HWINSTA hwinsta ; hwinstaCurrent = GetProcessWindowStation (); if (hwinstaCurrent == NULL) { return FALSE ; } hdeskCurrent = GetThreadDesktop (GetCurrentThreadId()); if (hdeskCurrent == NULL){ return FALSE ; } //打開winsta0 //打開winsta0 hwinsta = OpenWindowStation (L"Winsta0" , FALSE, WINSTA_ALL_ACCESS); // WINSTA_ACCESSCLIPBOARD| // WINSTA_ACCESSGLOBALATOMS | // WINSTA_ENUMDESKTOPS | // WINSTA_CREATEDESKTOP | // WINSTA_CREATEDESKTOP | // WINSTA_ENUMERATE | // WINSTA_EXITWINDOWS | // WINSTA_READATTRIBUTES | // WINSTA_READSCREEN | // WINSTA_WRITEATTRIBUTES); if (hwinsta == NULL){ return FALSE ; } if (!SetProcessWindowStation (hwinsta)) { return FALSE ; } //打開desktop hdesk = OpenDesktop (L"default" , 0, FALSE, DESKTOP_CREATEMENU | DESKTOP_CREATEWINDOW | DESKTOP_ENUMERATE| DESKTOP_HOOKCONTROL| DESKTOP_JOURNALPLAYBACK | DESKTOP_JOURNALRECORD | DESKTOP_READOBJECTS | DESKTOP_SWITCHDESKTOP | DESKTOP_WRITEOBJECTS); if (hdesk == NULL){ return FALSE ; } SetThreadDesktop(hdesk ); ////////////////////////////////////////////end of system show dlg//////////////////// STARTUPINFO si = { sizeof( si) }; SECURITY_ATTRIBUTES saProcess , saThread; PROCESS_INFORMATION piProcessB , piProcessC; // Prepare to spawn Process B from Process A. // The handle identifying the new process // object should be inheritable. saProcess.nLength = sizeof( saProcess); saProcess.lpSecurityDescriptor = NULL; saProcess.bInheritHandle = TRUE; // The handle identifying the new thread // object should NOT be inheritable. saThread.nLength = sizeof( saThread); saThread.lpSecurityDescriptor = NULL; saThread.bInheritHandle = FALSE; CreateProcess(NULL , lpCommand, & saProcess, &saThread , FALSE, 0, NULL , NULL, & si, &piProcessB ); if (!SetProcessWindowStation (hwinstaCurrent)) return FALSE ; if (!SetThreadDesktop (hdeskCurrent)) return FALSE ; if (!CloseWindowStation (hwinsta)) return FALSE ; if (!CloseDesktop (hdesk)) return FALSE ; return TRUE ; }
這種方法的關(guān)鍵是OpenWindowStation、SetProcessWindowStation、OpenDesktop和SetThreadDesktop這四個(gè)函數(shù)。這種方法的思路是:當(dāng)前進(jìn)程所處于的Session必須有界面交互能力,這樣才能顯示出對(duì)話框。由于第一個(gè)交互式用戶會(huì)登錄到擁有WinSta0的Session 0,所以,強(qiáng)制性地把服務(wù)所在的進(jìn)程與WinSta0關(guān)聯(lián)起來(lái),并且打開當(dāng)前的桌面,把工作線程掛到該桌面上,就可以顯示出對(duì)話框。
4、這種方法在WinXP和Windows2003下工作得不錯(cuò),很遺憾,在Vista和Windows2008下,一旦執(zhí)行到OpenWindowStation,試圖代開WinSta0工作站時(shí),程序就會(huì)出異常。
首先了解一下程序要具備怎樣的條件才能與界面交互。Windows提供了三類對(duì)象:用戶界面對(duì)象(User Interface)、GDI對(duì)象和內(nèi)核對(duì)象。內(nèi)核對(duì)象有安全性,而前兩者沒有。為了對(duì)前兩者提供安全性,通過(guò)工作站對(duì)象(Window station)和桌面對(duì)象(Desktop)來(lái)管理用戶界面對(duì)象,因?yàn)楣ぷ髡緦?duì)象和桌面對(duì)象有安全特性。簡(jiǎn)單說(shuō)來(lái),工作站是一個(gè)帶有安全特性的對(duì)象,它與進(jìn)程相關(guān)聯(lián),包含了一個(gè)或多個(gè)桌面對(duì)象。當(dāng)工作站對(duì)象被創(chuàng)建時(shí),它被關(guān)聯(lián)到調(diào)用進(jìn)程上,并且被賦給當(dāng)前Session。交互式工作站W(wǎng)inSta0,是唯一一個(gè)可以顯示用戶界面,接受用戶輸入的工作站。它被賦給交互式用戶的登錄Session,包含了鍵盤、鼠標(biāo)和顯示設(shè)備。所有其他工作站都是非交互式的,這就意味著它們不能顯示用戶界面,不能接受用戶的輸入。當(dāng)用戶登錄到一臺(tái)啟用了終端服務(wù)的計(jì)算機(jī)上時(shí),每個(gè)用戶都會(huì)啟動(dòng)一個(gè)Session。每個(gè)Session都會(huì)與自己的交互式工作站相聯(lián)系。桌面是一個(gè)帶有安全特性的對(duì)象,被包含在一個(gè)窗口工作站對(duì)象中。一個(gè)桌面對(duì)象有一個(gè)邏輯的顯示區(qū)域,包含了諸如窗口、菜單、鉤子等等這樣的用戶界面對(duì)象。
在Vista之前,之所以可以通過(guò)打開Winsta0和缺省桌面顯示對(duì)話框,是因?yàn)椴还苁欠?wù)還是第一個(gè)登錄的交互式用戶,都是登錄到Session 0中。因此,服務(wù)程序可以通過(guò)強(qiáng)制打開WinSta0和桌面來(lái)獲得交互能力。
然而,在Vista和Windows2008中,Session 0專用于服務(wù)和其他不與用戶交互的應(yīng)用程序。第一個(gè)登錄進(jìn)來(lái),可以進(jìn)行交互式操作的用戶被連到Session 1上。第二個(gè)登錄進(jìn)行的用戶被分配給Session 2,以此類推。Session 0完全不支持要與用戶交互的進(jìn)程。如果采取在服務(wù)進(jìn)程中啟動(dòng)子進(jìn)程來(lái)顯示對(duì)話框,子對(duì)話框?qū)o(wú)法顯示;如果采取用OpenWindowStation系統(tǒng)API打開WinSta0的方法,函數(shù)調(diào)用會(huì)失敗。總之,Vista和Windows2008已經(jīng)堵上了在Session 0中產(chǎn)生界面交互的路。這就是原因所在。
那么,是否真的沒法在服務(wù)中彈出對(duì)話框了呢?對(duì)于服務(wù)進(jìn)程自身來(lái)說(shuō),確實(shí)如此,操作系統(tǒng)已經(jīng)把這條路堵上了。但是,我們想要的并不是“在服務(wù)進(jìn)程中彈出對(duì)話框”,我們想要的不過(guò)是“當(dāng)服務(wù)出現(xiàn)某些狀況的時(shí)候,在桌面上彈出對(duì)話框”。既然在Session 0中無(wú)法彈出對(duì)話框,而我們看到的桌面是Session X,并非Session 0,很自然的一個(gè)想法是:能不能讓Session 0通知其他的Session,讓當(dāng)前桌面正顯示著的Session彈一個(gè)對(duì)話框呢?
幸運(yùn)的是,還真可以這樣做。
//for win7 DWORD _stdcall LaunchAppIntoDifferentSession( LPTSTR lpCommand ) { DWORD dwRet = 0; PROCESS_INFORMATION pi ; STARTUPINFO si ; DWORD dwSessionId ; HANDLE hUserToken = NULL; HANDLE hUserTokenDup = NULL; HANDLE hPToken = NULL; HANDLE hProcess = NULL; DWORD dwCreationFlags ; HMODULE hInstKernel32 = NULL; typedef DWORD (WINAPI * WTSGetActiveConsoleSessionIdPROC)(); WTSGetActiveConsoleSessionIdPROC WTSGetActiveConsoleSessionId = NULL; hInstKernel32 = LoadLibrary (L"Kernel32.dll" ); if (!hInstKernel32 ) { return FALSE ; } OutputDebugString(L "LaunchAppIntoDifferentSession 1\n" ); WTSGetActiveConsoleSessionId = (WTSGetActiveConsoleSessionIdPROC )GetProcAddress( hInstKernel32,"WTSGetActiveConsoleSessionId" ); // Log the client on to the local computer. dwSessionId = WTSGetActiveConsoleSessionId (); do { WTSQueryUserToken( dwSessionId ,&hUserToken ); dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE; ZeroMemory( &si , sizeof( STARTUPINFO ) ); si.cb = sizeof( STARTUPINFO ); si.lpDesktop = L"winsta0\\default" ; ZeroMemory( &pi , sizeof( pi) ); TOKEN_PRIVILEGES tp ; LUID luid ; if( !::OpenProcessToken ( GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY | TOKEN_ADJUST_SESSIONID | TOKEN_READ | TOKEN_WRITE , &hPToken ) ) { dwRet = GetLastError (); break; } else; if ( !LookupPrivilegeValue ( NULL, SE_DEBUG_NAME, &luid ) ) { dwRet = GetLastError (); break; } else; tp.PrivilegeCount =1; tp.Privileges [0].Luid = luid; tp.Privileges [0].Attributes = SE_PRIVILEGE_ENABLED; if( !DuplicateTokenEx ( hPToken, MAXIMUM_ALLOWED, NULL , SecurityIdentification , TokenPrimary, & hUserTokenDup ) ) { dwRet = GetLastError (); break; } else; //Adjust Token privilege if( !SetTokenInformation ( hUserTokenDup,TokenSessionId ,(void*)& dwSessionId,sizeof (DWORD) ) ) { dwRet = GetLastError (); break; } else; if( !AdjustTokenPrivileges ( hUserTokenDup, FALSE, &tp , sizeof(TOKEN_PRIVILEGES ), (PTOKEN_PRIVILEGES) NULL, NULL ) ) { dwRet = GetLastError (); break; } else; LPVOID pEnv =NULL; if( CreateEnvironmentBlock ( &pEnv, hUserTokenDup, TRUE ) ) { dwCreationFlags|=CREATE_UNICODE_ENVIRONMENT ; } else pEnv =NULL; // Launch the process in the client's logon session. if( CreateProcessAsUser ( hUserTokenDup, // client's access token NULL, // file to execute lpCommand, // command line NULL, // pointer to process SECURITY_ATTRIBUTES NULL, // pointer to thread SECURITY_ATTRIBUTES FALSE, // handles are not inheritable dwCreationFlags,// creation flags pEnv, // pointer to new environment block NULL, // name of current directory & si, // pointer to STARTUPINFO structure & pi // receives information about new process ) ) { } else { dwRet = GetLastError (); break; } } while( 0 ); //Perform All the Close Handles task if( NULL != hUserToken ) { CloseHandle( hUserToken ); } else; if( NULL != hUserTokenDup) { CloseHandle( hUserTokenDup ); } else; if( NULL != hPToken ) { CloseHandle( hPToken ); } else; return dwRet ; }
5、啟動(dòng)服務(wù)后顯示了system權(quán)限的Notepad.exe,并且可以與用戶進(jìn)行交互
當(dāng)然,在本例子啟動(dòng)進(jìn)程的地方創(chuàng)建一個(gè)對(duì)話框也是可以顯示對(duì)話框的。
- Windows10下安裝Docker的步驟圖文教程
- 淺談Windows平臺(tái)上Docker安裝與使用
- 在windows下的安裝Docker的教程
- Windows7下通過(guò)命令行獲得System權(quán)限問(wèn)題解決方法
- Windows下MongoDB配置用戶權(quán)限實(shí)例
- Windows下Apache應(yīng)用環(huán)境塔建安全設(shè)置(目錄權(quán)限設(shè)置)
- Windows 2003服務(wù)器權(quán)限詳細(xì)配置方案
- Windows Docker 安裝 Gitlab Volume權(quán)限問(wèn)題解決方案
相關(guān)文章
c#使用EPPlus封裝excel表格導(dǎo)入功能的問(wèn)題
這篇文章主要介紹了c#使用EPPlus封裝excel表格導(dǎo)入功能的問(wèn)題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-04-04C#?VB.NET?實(shí)現(xiàn)在Word中嵌入多媒體(視頻、音頻)文件
Word中可將Office、PDF、txt等文件作為OLE對(duì)象插入到文檔中,雙擊該對(duì)象可直接訪問(wèn)或編輯該文件,除了以上常見的文件格式對(duì)象,也可以插入多媒體文件,如視頻、音頻等。本篇文章介紹了通過(guò)C#實(shí)現(xiàn)在Word中插入多媒體文件。感興趣的可以學(xué)習(xí)一下2021-12-12C#使用ADO.Net部件來(lái)訪問(wèn)Access數(shù)據(jù)庫(kù)的方法
數(shù)據(jù)庫(kù)的訪問(wèn)是所有編程語(yǔ)言中最重要的部分,C#提供了ADO.Net部件用于對(duì)數(shù)據(jù)庫(kù)進(jìn)行訪問(wèn)。本文從最簡(jiǎn)單易用的微軟Access數(shù)據(jù)庫(kù)入手討論在C#中對(duì)數(shù)據(jù)庫(kù)的訪問(wèn)。2015-09-09用 C# 編寫一個(gè)停放在任務(wù)欄上的圖標(biāo)程序
用 C# 編寫一個(gè)停放在任務(wù)欄上的圖標(biāo)程序...2007-03-03C#通過(guò)yield實(shí)現(xiàn)數(shù)組全排列的方法
這篇文章主要介紹了C#通過(guò)yield實(shí)現(xiàn)數(shù)組全排列的方法,以實(shí)例形式較為詳細(xì)的分析了全排列的概念及C#的實(shí)現(xiàn)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-03-03