先說說在WinXP和Windows2003下用的方法。 第一種方法是在服務進程中啟動一個子進程。用該子進程彈出對話框。.NET的C#代碼大致如下:
[c-sharp] view plain copypublic static void Show( string msg, string cap, MessageBoxButtons buttons, MessageBoxIcon icon ) { try { PRocess proc = new Process(); proc.StartInfo.FileName = EXE_NAME; proc.StartInfo.Arguments = string.Format( ARG_FMT, msg, cap, buttons, icon ); proc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; proc.Start(); } catch { } }這段代碼是啟動一個進程。該進程唯一的目的是顯示一個對話框,大致的代碼如下:
[c-sharp] view plain copystatic class Program { /// <summary> /// The main entry point for the application. /// </summary> [STAThread] static void Main( string[] args ) { try { string strMsg = args[0]; string strCap = args[1]; MessageBoxButtons btn = ( MessageBoxButtons )Enum.Parse( typeof( MessageBoxButtons ), args[2] ); MessageBoxIcon icon = ( MessageBoxIcon )Enum.Parse( typeof( MessageBoxIcon ), args[3] ); MessageBox.Show( strMsg, strCap, btn, icon, MessageBoxDefaultButton.Button1, MessageBoxOptions.ServiceNotification ); } catch { } } }
這里的做法是指定子進程的線程模型為STAThread。這就意味著線程的消息是靠一個隱含的窗口來分發。也正因為有這個隱含的窗口,該子進程獲得了交互能力,可以彈出對話框。 不幸的是,在Vista和Windows2008下,用這種方法時,子進程無法成功創建。
還有另一種方法,可以不創建子進程,直接在服務進程中顯示對話框。代碼大致如下,由于主要是調用API,這里直接展示C++形式的代碼:
[cpp] view plain copyHDESK hdeskCurrent; HDESK hdesk; HWINSTA hwinstaCurrent; HWINSTA hwinsta; hwinstaCurrent = GetProcessWindowStation(); hdeskCurrent = GetThreadDesktop(GetCurrentThreadId()); //Open winsta0 hwinsta = OpenWindowStation("winsta0", FALSE, WINSTA_accessCLipBOARD | WINSTA_ACCESSGLOBALATOMS | WINSTA_CREATEDESKTOP | WINSTA_ENUMDESKTOPS | WINSTA_ENUMERATE | WINSTA_EXITWINDOWS | WINSTA_READATTRIBUTES | WINSTA_READSCREEN | WINSTA_WRITEATTRIBUTES); SetProcessWindowStation(hwinsta); //Open default desktop hdesk = OpenDesktop("default", 0, FALSE, DESKTOP_CREATEMENU | DESKTOP_CREATEWINDOW | DESKTOP_ENUMERATE | DESKTOP_HOOKCONTROL | DESKTOP_JOURNALPLAYBACK | DESKTOP_JOURNALRECORD | DESKTOP_READOBJECTS | DESKTOP_SWITCHDESKTOP | DESKTOP_WRITEOBJECTS); SetThreadDesktop(hdesk); // Show the dialog CMsgDlg dlgMsg; dlgMsg.DoModal();可以看到,這種方法的關鍵是OpenWindowStation、SetProcessWindowStation、OpenDesktop和SetThreadDesktop這四個函數。這種方法的思路是:當前進程所處于的session必須有界面交互能力,這樣才能顯示出對話框。由于第一個交互式用戶會登錄到擁有WinSta0的Session 0,所以,強制性地把服務所在的進程與WinSta0關聯起來,并且打開當前的桌面,把工作線程掛到該桌面上,就可以顯示出對話框。
這種方法在WinXP和Windows2003下工作得不錯,很遺憾,在Vista和Windows2008下,一旦執行到OpenWindowStation,試圖代開WinSta0工作站時,程序就會出異常。
為什么會這樣?看來還有一些深層的東西在制約著Vista和Windows2008,使得服務程序無法顯示對話框。
首先了解一下程序要具備怎樣的條件才能與界面交互。Windows提供了三類對象:用戶界面對象(User Interface)、GDI對象和內核對象。內核對象有安全性,而前兩者沒有。為了對前兩者提供安全性,通過工作站對象(Window station)和桌面對象(Desktop)來管理用戶界面對象,因為工作站對象和桌面對象有安全特性。簡單說來,工作站是一個帶有安全特性的對象,它與進程相關聯,包含了一個或多個桌面對象。當工作站對象被創建時,它被關聯到調用進程上,并且被賦給當前Session。交互式工作站WinSta0,是唯一一個可以顯示用戶界面,接受用戶輸入的工作站。它被賦給交互式用戶的登錄Session,包含了鍵盤、鼠標和顯示設備。所有其他工作站都是非交互式的,這就意味著它們不能顯示用戶界面,不能接受用戶的輸入。當用戶登錄到一臺啟用了終端服務的計算機上時,每個用戶都會啟動一個Session。每個Session都會與自己的交互式工作站相聯系。桌面是一個帶有安全特性的對象,被包含在一個窗口工作站對象中。一個桌面對象有一個邏輯的顯示區域,包含了諸如窗口、菜單、鉤子等等這樣的用戶界面對象。
在Vista之前,之所以可以通過打開Winsta0和缺省桌面顯示對話框,是因為不管是服務還是第一個登錄的交互式用戶,都是登錄到Session 0中。因此,服務程序可以通過強制打開WinSta0和桌面來獲得交互能力。 然而,在Vista和Windows2008中,Session 0專用于服務和其他不與用戶交互的應用程序。第一個登錄進來,可以進行交互式操作的用戶被連到Session 1上。第二個登錄進行的用戶被分配給Session 2,以此類推。Session 0完全不支持要與用戶交互的進程。如果采取在服務進程中啟動子進程來顯示對話框,子對話框將無法顯示;如果采取用OpenWindowStation系統API打開WinSta0的方法,函數調用會失敗。總之,Vista和Windows2008已經堵上了在Session 0中產生界面交互的路。這就是原因所在
那么,是否真的沒法在服務中彈出對話框了呢?對于服務進程自身來說,確實如此,操作系統已經把這條路堵上了。但是,我們想要的并不是“在服務進程中彈出對話框”,我們想要的不過是“當服務出現某些狀況的時候,在桌面上彈出對話框”。既然在 Session 0 中無法彈出對話框,而我們看到的桌面是 Session X ,并非 Session 0 ,很自然的一個想法是:能不能讓 Session 0 通知其他的 Session ,讓當前桌面正顯示著的 Session 彈一個對話框呢? 幸運的是,還真可以這樣做。一個 Session 中的進程可以用 WTSSendMessage ,讓另一個 Session 彈出對話框。 WTSSendMessage 的一個參數是 SessionID ,目的是指定要彈出對話框的 Session 。為了獲得當前顯示的桌面所在的 SessionID ,可以用 WTSGetActiveConsoleSessionId 得到這個 SessionID 。代碼大致類似于:
[c-sharp] view plain copypublic static bool Show( string msg, string cap, MessageBoxButtons buttons, MessageBoxIcon icon ) { try { Int32 sessionId = WTSGetActiveConsoleSessionId(); Int32 result = 0; bool bSuccess = WTSSendMessage( ( IntPtr )0, sessionId, cap, cap.Length, msg, msg.Length, Convert.ToInt32( buttons ) + Convert.ToInt32( icon ), 0, ref result, true ); return bSuccess; } catch { return false; } }
|
新聞熱點
疑難解答