在服务程序中弹出对话框

http://blog.csdn.net/linux7985/article/details/5694613

先说说在WinXP和Windows2003下用的方法。 

第一种方法是在服务进程中启动一个子进程。用该子进程弹出对话框。.NET的C#代码大致如下: 

[c-sharp]  view plain  copy
  1. public static void Show( string msg, string cap, MessageBoxButtons buttons, MessageBoxIcon icon )  
  2. {  
  3.     try  
  4.     {  
  5.   
  6.         Process proc = new Process();  
  7.         proc.StartInfo.FileName = EXE_NAME;  
  8.         proc.StartInfo.Arguments = string.Format( ARG_FMT, msg, cap, buttons, icon );  
  9.         proc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;  
  10.   
  11.         proc.Start();  
  12.     }  
  13.     catch { }  
  14. }  

这段代码是启动一个进程。该进程唯一的目的是显示一个对话框,大致的代码如下:

 

[c-sharp]  view plain  copy
  1. static class Program  
  2. {  
  3.   
  4.     ///    
  5.     /// The main entry point for the application.   
  6.     ///    
  7.     [STAThread]  
  8.     static void Main( string[] args )  
  9.     {  
  10.         try  
  11.         {  
  12.             string strMsg = args[0];  
  13.             string strCap = args[1];  
  14.             MessageBoxButtons btn = ( MessageBoxButtons )Enum.Parse( typeof( MessageBoxButtons ), args[2] );  
  15.             MessageBoxIcon icon = ( MessageBoxIcon )Enum.Parse( typeof( MessageBoxIcon ), args[3] );  
  16.   
  17.             MessageBox.Show( strMsg, strCap, btn, icon, MessageBoxDefaultButton.Button1, MessageBoxOptions.ServiceNotification );  
  18.         }  
  19.         catch { }  
  20.     }  
  21. }  

 

这里的做法是指定子进程的线程模型为STAThread。这就意味着线程的消息是靠一个隐含的窗口来分发。也正因为有这个隐含的窗口,该子进程获得了交互能力,可以弹出对话框。 
不幸的是,在Vista和Windows2008下,用这种方法时,子进程无法成功创建。

 

还有另一种方法,可以不创建子进程,直接在服务进程中显示对话框。代码大致如下,由于主要是调用API,这里直接展示C++形式的代码: 

[cpp]  view plain  copy
  1. HDESK hdeskCurrent;  
  2. HDESK hdesk;  
  3. HWINSTA hwinstaCurrent;  
  4. HWINSTA hwinsta;  
  5. hwinstaCurrent = GetProcessWindowStation();  
  6. hdeskCurrent = GetThreadDesktop(GetCurrentThreadId());  
  7. //Open winsta0  
  8. hwinsta = OpenWindowStation("winsta0", FALSE, WINSTA_ACCESSCLIPBOARD | WINSTA_ACCESSGLOBALATOMS | WINSTA_CREATEDESKTOP | WINSTA_ENUMDESKTOPS | WINSTA_ENUMERATE | WINSTA_EXITWINDOWS | WINSTA_READATTRIBUTES | WINSTA_READSCREEN | WINSTA_WRITEATTRIBUTES);  
  9.   
  10. SetProcessWindowStation(hwinsta);  
  11.   
  12. //Open default desktop  
  13. hdesk = OpenDesktop("default", 0, FALSE, DESKTOP_CREATEMENU | DESKTOP_CREATEWINDOW | DESKTOP_ENUMERATE | DESKTOP_HOOKCONTROL | DESKTOP_JOURNALPLAYBACK | DESKTOP_JOURNALRECORD | DESKTOP_READOBJECTS | DESKTOP_SWITCHDESKTOP | DESKTOP_WRITEOBJECTS);  
  14.   
  15. SetThreadDesktop(hdesk);  
  16.   
  17. // Show the dialog CMsgDlg dlgMsg;  
  18. 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  copy
  1. public static bool Show( string msg, string cap, MessageBoxButtons buttons, MessageBoxIcon icon )  
  2. {  
  3.     try  
  4.     {  
  5.         Int32 sessionId = WTSGetActiveConsoleSessionId();  
  6.         Int32 result = 0;  
  7.         bool bSuccess = WTSSendMessage( ( IntPtr )0, sessionId, cap, cap.Length, msg, msg.Length, Convert.ToInt32( buttons ) + Convert.ToInt32( icon ), 0, ref result, true );  
  8.         return bSuccess;  
  9.     }  
  10.     catch { return false; }  
  11. }   

你可能感兴趣的:(c#)