1、VC2008中编写“Windows服务”(Windows Service)程序

vc2008下新建一个 ATL 项目-》 选择创建一个“服务”类型的ATL 项目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 : 调用CoInitializeSecurity 并为服务提供适当的
             // 安全设置
             // 建议- PKT 级别的身份验证、
             // RPC_C_IMP_LEVEL_IDENTIFY 的模拟级别
             // 以及适当的非NULL 安全说明符。
 
             return S_OK ;
      }
       //重写这个函数来启动任务啦
       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才能启动服务时走到这里
                         //可以在这里启动线程,或者什么其他东西来做自己的工作的啦
                         //这里是什么都没有做了,只输出一条信息
 
                         LogEvent(_T ("widebright 的服务启动咯,呵呵 "));
                         SetServiceStatus(SERVICE_RUNNING );
                  }
                   //进入消息循环,不停的处理消息,可能最后分发到 Handler去处理,调用了OnShutdown等函数的。
                   __super::RunMessageLoop ();
            }
             if (SUCCEEDED (hr))
            {
                   hr = __super ::PostMessageLoop();
            }
 
             //可以在适当的时候调用Uninstall函数来卸载掉服务
             //__super::Uninstall();
             return hr ;
      }
       //重写,服务退出处理
       void OnShutdown () throw()
      {
             LogEvent(_T ("TestService 的服务退出咯,一点都不好玩呵呵 "));
      }
 
};
 
CTestServiceModule _AtlModule;
 
 
 
//
extern "C" int WINAPI _tWinMain (HINSTANCE , HINSTANCE ,
                                LPTSTR  , int nShowCmd)
{
    return _AtlModule .WinMain( nShowCmd);
}
  我只要根据需要重写相应的函数来实现自己想要的功能就行了,比如你想创建的“服务”随系统启动,可以重写CAtlServiceModuleT   的Install函数,把里面的CreateService函数的参数修改一下,例如添加与用户交互可以使用 SERVICE_INTERACTIVE_PROCESS,具体可以去MSDN上查找CreateService这个API的说明。

如果想处理服务 停止和启动的动作,可以参考CAtlServiceModuleT 的源代码重写OnStop ()等函数。我上面简单到重写了Run函数,输出一条“事件”其实具体 工作是可以放到这里来完成的吧。

编译,生成程序之后就可以测试了,

执行“TestService -/Service” 就可以把服务注册到系统了,命令行参数其实是在CAtlServiceModuleT::ParseCommandLine 这个函数里面处理,可以去看一下,必要的话重写也是可以的,加上调用 UnInstall来删除服务的代码也很不错的吧。

注册后,就看用“sc start” 或者“net start” 等命令来操纵服务了。在“服务”控制器里面控制与可以:如图

这时候在Run函数中启动一个Notepad.exe,此时没有界面显示,在xp下可以使用下面的方法实现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 ;
}

这种方法的关键是OpenWindowStation、SetProcessWindowStation、OpenDesktop和SetThreadDesktop这四个函数。这种方法的思路是:当前进程所处于的Session必须有界面交互能力,这样才能显示出对话框。由于第一个交互式用户会登录到拥有WinSta0的Session 0,所以,强制性地把服务所在的进程与WinSta0关联起来,并且打开当前的桌面,把工作线程挂到该桌面上,就可以显示出对话框。

这种方法在WinXP和Windows2003下工作得不错,很遗憾,在Vista和Windows2008下,一旦执行到OpenWindowStation,试图代开WinSta0工作站时,程序就会出异常。

首先了解一下程序要具备怎样的条件才能与界面交互。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弹一个对话框呢?

幸运的是,还真可以这样做。
 
//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 ;
}
 
启动服务后显示了system权限的Notepad.exe,并且可以与用户进行交互

 
当然,在本例子启动进程的地方创建一个对话框也是可以
http://blog.sina.com.cn/s/blog_488cff5201017yug.html

Windows服务(system权限)程序显示界面与用户交互,Session0通知Session1里弹出对话框(真的很牛) good的更多相关文章

  1. Windows服务System权限下在当前用户桌面创建快捷方式C#实例程序

    Windows服务一般运行在System权限下,这样权限比较高,方便执行一些高权限的操作. 但是,Environment.GetFolderPath等函数获取的也是System用户下的,而不是当前用户 ...

  2. Windows服务器SYSTEM权限Webshell无法添加3389账户情况突破总结

    转自:http://bbs.blackbap.org/thread-2331-1-1.html 近好多Silic的朋友在Windows下SYSTEM权限的php webshell下添加账户,但是却无法 ...

  3. Windows服务 System.ServiceProcess.ServiceBase类

    一.Windows服务 1.Windows服务应用程序是一种需要长期运行的应用程序,它适合服务器环境. 2.无用户界面,任何消息都会写进Windows事件日志. 3.随计算机启动而启动,不需要用户一定 ...

  4. 制作Windows服务和安装程序(C#版)

    http://blog.sina.com.cn/s/blog_5f4ffa170100vt2b.html 1.创建服务项目: 打开VS 2005 编程环境,在C#中新建Windows服务程序 2.将安 ...

  5. vs 2010创建Windows服务定时timer程序

    vs 2010创建Windows服务定时timer程序: 版权声明:本文为搜集借鉴各类文章的原创文章,转载请注明出处:  http://www.cnblogs.com/2186009311CFF/p/ ...

  6. 在没有界面的类中,实现弹出UIAlertView || 在没有界面的类中,刷新程序界面 思路

    +(DisplayErrorMsg *)sharedDisplayErrorMsg { static DisplayErrorMsg *instance = nil; @synchronized(in ...

  7. Selenium(八):其他操作元素的方法、冻结界面、弹出对话框、开发技巧

    1. 其他操作元素的方法 之前我们对web元素做的操作主要是:选择元素,然后点击元素或者输入字符串. 还有没有其他的操作了呢?有. 比如:比如鼠标右键点击.双击.移动鼠标到某个元素.鼠标拖拽等. 这些 ...

  8. 调整弹出对话框在ASP.NET应用程序的大小

    调整弹出对话框在ASP.NET应用程序的大小 #region 调整弹出对话框在ASP.NET应用程序的大小    protected void PopupWindowControl_Customize ...

  9. 为C# Windows服务添加安装程序

    最近一直在搞Windows服务,也有了不少经验,感觉权限方面确定比一般程序要受限很多,但方便性也很多.像后台运行不阻塞系统,不用用户登录之类.哈哈,扯远了,今天讲一下那个怎么给Windows服务做个安 ...

随机推荐

  1. [Android]使用化名(alias)功能防止相同资源的重复

    在为一个应用匹配不同资源文件的时候,有时可能需要在不同适配类型的资源路径下使用相同的资源文件,这时使用alias方法可以防止相同资源文件的重复,提高效率.以下摘自Android开发文档http://d ...

  2. WPF 插拔触摸设备触摸失效

    原文:WPF 插拔触摸设备触摸失效 最近使用 WPF 程序,在不停插拔触摸设备会让 WPF 程序触摸失效.通过分析 WPF 源代码可以找到 WPF 触摸失效的原因. 在 Windows 会将所有的 H ...

  3. MySQL旧版本ORDER BY 方法

    MySQL 的order by 它涉及到三个参数:A. sort_buffer_size 排序缓存.B. read_rnd_buffer_size 第二次排序缓存.C. max_length_for_ ...

  4. 《Head First 设计模式》学习笔记——命令模式

    在软件系统,"行为请求者"与"行为实施者"通常存在一个"紧耦合".但在某些场合,比方要对行为进行"记录.撤销/重做.事务" ...

  5. Spring MVC【入门】一篇!

    MVC 设计概述 在早期 Java Web 的开发中,统一把显示层.控制层.数据层的操作全部交给 JSP 或者 JavaBean 来进行处理,我们称之为 Model1:     出现的弊端: JSP ...

  6. 深刻认识OpenStack

    OpenStack 1 OpenStack简单介绍                                                          Openstack archite ...

  7. hudson搭建经验总结(二)

    作者:朱金灿 来源:http://blog.csdn.net/clever101 继续部署hudson,发现从google上的一个开源工程上:http://code.google.com/p/huds ...

  8. 3-2 从降级的例子 认识Polly套路

    1 没有返回值的降级 Policy policy = Policy.Handle<Exception>().Fallback(() => { Console.WriteLine(&q ...

  9. 机器学习、深度学习实战细节(batch norm、relu、dropout 等的相对顺序)

    cost function,一般得到的是一个 scalar-value,标量值: 执行 SGD 时,是最终的 cost function 获得的 scalar-value,关于模型的参数得到的: 1. ...

  10. 031 二进制1的数量(keep it up, 看到这个问题,刚开始有点蒙)

    剑指offer在标题中:http://ac.jobdu.com/problem.php?pid=1513 题目描写叙述: 输入一个整数,输出该数二进制表示中1的个数.当中负数用补码表示. 输入: 输入 ...