Windows 服务程序、窗口界面、桌面交互、与远程桌面

时间:2021-11-19 06:25:59

昨天用c写了一个windows服务(服务内部带一个gui窗口+系统托盘),在windows xp sp3上测试,启动服务后,系统托盘显示正常。

但在另一台windows 2003 sp2 上测试(通过远程桌面登录),晕了,服务是启动了(在进程管理器中能看到),但系统托盘看不到,也就是在桌面的右下角看不到系统托盘的图标。

到网上找原因,找到这么几篇:

http://blog.s135.com/windows_mstsc/

http://chenjava.blog.51cto.com/374566/80250

http://www.sldd.cn/web/bbsxp/ShowPost.asp?id=45110

http://hi.baidu.com/since2006bitlove/blog/item/81555e4cdc52c5f3d62afc71.html

http://topic.csdn.net/u/20090320/17/84ac2e8e-cdff-4ca8-908a-fe7e6854deb6.html

一开始,我参照:http://topic.csdn.net/u/20090320/17/84ac2e8e-cdff-4ca8-908a-fe7e6854deb6.html 在c的代码中显示窗口的前、后面加了这么一段代码:

HDESK   hdeskCurrent;
    HDESK   hdesk;
    HWINSTA hwinstaCurrent;
    HWINSTA hwinsta;

hwinstaCurrent = GetProcessWindowStation();
    if (hwinstaCurrent == NULL)
    {
        //LogEvent(_T("get window station err"));
        return;
    }

hdeskCurrent = GetThreadDesktop(GetCurrentThreadId());
    if (hdeskCurrent == NULL)
    {
        //LogEvent(_T("get window desktop err"));
        return;
    }

//打开winsta0
    hwinsta = OpenWindowStation("winsta0", FALSE,
                                WINSTA_ACCESSCLIPBOARD   |
                                WINSTA_ACCESSGLOBALATOMS |
                                WINSTA_CREATEDESKTOP     |
                                WINSTA_ENUMDESKTOPS      |
                                WINSTA_ENUMERATE         |
                                WINSTA_EXITWINDOWS       |
                                WINSTA_READATTRIBUTES    |
                                WINSTA_READSCREEN        |
                                WINSTA_WRITEATTRIBUTES);
    if (hwinsta == NULL)
    {
        //LogEvent(_T("open window station err"));

return;
    }

if (!SetProcessWindowStation(hwinsta))
    {
        //LogEvent(_T("Set window station err"));

return;
    }

//打开desktop
    hdesk = OpenDesktop("default", 0, FALSE,
                        DESKTOP_CREATEMENU |
                        DESKTOP_CREATEWINDOW |
                        DESKTOP_ENUMERATE    |
                        DESKTOP_HOOKCONTROL  |
                        DESKTOP_JOURNALPLAYBACK |
                        DESKTOP_JOURNALRECORD |
                        DESKTOP_READOBJECTS |
                        DESKTOP_SWITCHDESKTOP |
                        DESKTOP_WRITEOBJECTS);
    if (hdesk == NULL)
    {
        //LogEvent(_T("Open desktop err"));

return;
    }

SetThreadDesktop(hdesk);

//到这一步,我们获取了和用户交互(如显示窗口)的权利

//显示窗口的代码写在这里

.....................................

SetProcessWindowStation(hwinstaCurrent);
    SetThreadDesktop(hdeskCurrent);
    CloseWindowStation(hwinsta);
    CloseDesktop(hdesk);

编译后,在另一台 windows 2003 sp2 上启动服务,然后在 windows xp sp3 上,运行  mstsc /console,吊用没有。

为什么没效果呢?

接着搜索,找到一篇:

远程桌面mstsc /console(/admin) 的运用 -> http://lcq225.blog.163.com/blog/static/16978498201171252623326/

原来:如果系统是WINXP SP3,是不支持 /console这个参数的,需要使用mstsc /admin。经测试,vista 也不支持 mstsc  /console模式,看了一下帮助,是没有/console这个参数的。

windows xp升级到sp3后,命令换成mstsc /admin即可实现winXp2中MSTSC /console的功能。

我试着使用 mstsc /admin 登录,在Java 6 环境,发现系统托盘区还是没有显示图标。

但是,如果手动启动服务,系统托盘就能显示图标了。

我试着使用 mstsc /admin 登录,在Java 7 环境,发现系统托盘区可以正常显示图标。

难道不成,java 6 与 java 7在系统托盘(SystemTray)方面的底层实现有所不同???

 

我接着把上面的代码删除,再启动服务,系统托盘也会显示图标,看来上面的代码是不需要了。

当然,windows服务需要与桌面交互,还是需要设置一下服务的属性:

打开控制面板->服务,查看服务的属性->[登录]-[允许服务与桌面交互],打上钩后,系统托盘就能显示在任务栏。

至于为什么不能显示系统托盘的原因,看了这个介绍才明白

我们的软件在Windows NT/2000/XP/Vista 系统中安装了一个系统服务,这个服务负责以 SYSTEM 权限启动我们的主程序。我们的主程序启动后会在系统托盘添加一个图标,点击此图标可以弹出控制菜单,通过这个菜单也可以激活配置程序首选项的对话框。在 Windows NT/2000/XP 下我们的程序都可以正常工作。哦不,当 XP 具备了快速用户切换功能的时候我们的问题已经出现了。XP 启动后我们以用户 A 登录,我们的图标出现在系统托盘,一切工作都正常,可当我们使用快速用户切换,切换到用户B后(用户A此时也是已登录状态,并没有注销),虽然用户B已经 是本地控制台会话(Session 属性为 Console)但我们的图标已经无法出现了,自然菜单和对话框更无从谈起了。我们的程序是和本机控制台桌面相关的,这种情况无疑是个缺陷。再来看一下在 Vista 平台是怎么样吧,系统启动后以用户A登录,我们的图标更本就没有出现,查看进程管理器中的进程列表发现我们的程序已经启动了,当我们从远端检查我们的服 务,发现已经正常工作,尝试远程登录我们的服务,Vista 会在本机控制台弹出一个消息框,提示有交互式服务消息,是否查看这个消息,点击立刻查看发现切换到另外一个桌面去了。
于是开始分析这种情况发生的原因。在 Windows NT/2000 中系统服务进程和本机控制台交互式登录的用户都运行于Session0 中,默认用户桌面运行于 WinSta0 窗口站,所以我们的程序由服务程序启动时依然是和本机用户处于同一个Session中,即使在某些情况下出现不能弹出对话框或者无法添加系统托盘图标的情 况也只需要修改一下进程桌面到 WinSta0\Default 就可以了(可以参考 MSDN 中 OpenInputDesktop, SetThreadDesktop 等API的说明)。
XP为我们带来了快速用户切换,也让我们所采用的软件架构问题浮现出来。当我们快速切 换到用户B的时候,用户A仍然在会话中(Session0),而用户B则处于新启动的会话中(Session1或者其他),此时服务程序和本机控制台程序 就不在处于同一会话了,OpenInputDesktop,SetThreadDesktop 等API的工作范围仅限于本Session,用户A没有退出,Session0也依然存在但是已经是 Disconnected 状态,当进程所处的Session是 Disconnected 状态的时候调用 OpenInputDesktop 会返回错误“无效的API”。进程及线程所属的Session 是由他们的Token 结构中的 TokenSessionId 决定的(参见MSDN中SetTokenInformation 和 TOKEN_INFORMATION_CLASS的说明),我尝试以微软提供的相关API修改运行中的进程和线程的TokenSessionId 信息从而达到修改桌面环境的目的,到目前还没有成功过(或许可以尝试参考RootKit 技术,不过即使修改成功到底能不能实现我们的需求也不确定)。我们的进程无法跨越Session的界限,自然无法与当前活动的另外一个Session中的 桌面交互了, 。
Vista中又是如何的一番景象呢?处于安全方面及其他因素的考虑,Vista以及将 所有的服务程序置于Session0中,而为本机第一个交互登录的用户创建了Session1,快速切换到用户B后则是 Session2,无论是本机登录的用户,快速切换后的用户,还是远程桌面登录的用户再也没有谁和服务进程处于同一个Session中了,我们的程序还运 行在Session0中,自然我们的托盘图标是没有用户能看到了。事实上这个图标还是可以出现的。Session0因为不是一个交互式会话所以没有象其他 用户环境初始化的时候一样启动Explorer程序,但是我们开始可以手工启动他,在Session0中启动 Explorer 后任务栏出现后我们还是看到了我们的图标(具体启动Explorer的方法我们不在此文中讨论),菜单、对话框也可以使用。
既然我们的程序必须运行在Session0而我们又没有办法把我们的图标、对话框一下 子就抛到隔壁Session的用户桌面上去,只能想其他的办法了。微软也不提倡我们这种服务程序直接提供GUI与用户直接交互的方式,而他们建议使用 C/S架构,Client/Server之间用Socket/Pipe/RPC等方式通讯,这样我们只要把Client整个进程放到用户Session去 和用户交互,然后将配置信息等内容通过上述途径传递给Server,服务端在作出相应的响应即可。
把GUI分离出来并不是那么困难,然后在以前直接调用的地方加上一个通过Pipe通讯的接口,这样GUI(Client)的运行就可以灵活的掌握了。
最初我想把用户界面程序放到 Startup(启动)中随用户登录自动启动。这样当用户A和B都登录后将有两个用户界面程序在运行,而我们的服务只是和当前活动的控制台登录用户交互,所以这样并不符合需求。
接下来我们需要看看如何判定当前的活动Session是哪个,然后如何在这个活动Session中启动我们的用户界面程序了。

2012-01-21