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. 深入Lazy<T>——.NET Framework 4.0

    .NET Framework 4 在一次次跳票中终于发布了,在一次偶然的机会,我看到了 Anytao 的 [你必须知道的.NET]第三十三回,深入.NET 4.0之,Lazy<T>点滴 . ...

  2. 【21.67%】【codeforces 727B】Bill Total Value

    time limit per test1 second memory limit per test256 megabytes inputstandard input outputstandard ou ...

  3. 于CentOS 6.5编译器安装Git 1.8

    yum install git版本号是太老,它是1.7.1. 在这里,我们将介绍如何编译和安装Git 1.8 yum install curl-devel expat-devel gettext-de ...

  4. jQuery怎么选择两个class属性

    Jquery选择多个Class属性: $('.className1,.className2,.className4,.className5')

  5. Fast-tracking approach for building routing topologies in fast-moving networks

    In one embodiment, a local node in a communication network determines a set of its neighbor nodes, a ...

  6. Linux经常使用的命令(两) - cd

    Linux cd 命令能够说是Linux中最主要的命令语句,其它的命令语句要进行操作,都是建立在使用 cd 命令上的.所以,学习Linux 经常使用命令.首先就要学好 cd 命令的用法技巧. 1. 命 ...

  7. 机器学习: t-Stochastic Neighbor Embedding 降维算法 (一)

    Introduction 在计算机视觉及机器学习领域,数据的可视化是非常重要的一个应用,一般我们处理的数据都是成百上千维的,但是我们知道,目前我们可以感知的数据维度最多只有三维,超出三维的数据是没有办 ...

  8. JDBC学习笔记——增删改查

    1.数据库准备  要用JDBC操作数据库,第一步当然是建立数据表: ? 1 2 3 4 5 6 CREATE TABLE `user` (   `id` int(11) NOT NULL AUTO_I ...

  9. WPF UpdateSourceTrigger的使用

    <Window x:Class="XamlTest.Window8"        xmlns="http://schemas.microsoft.com/winf ...

  10. WPF 数据模板DataType属性的使用,不用指定ItemTemplate

    <Window x:Class="CollectionBinding.MainWindow"        xmlns="http://schemas.micros ...