从Win服务启动UI程序

从windows服务启动一个带UI程序的界面,这个需求在xp中是很随意的,从Vista开始似乎没有那么随意了,因为Vista中加入了Session的概念,那么什么是Session,我想这篇文章介绍的应该比我权威的多。Session隔离介绍

明白了Session的概念后,我将通过Win32 API来实现从windows服务启动一个带UI的界面(从Session 0中启动Session *的程序),这个实现过程是我从C++代码翻译过来的。

实现的思路

  1. 找到一个除Session 0之外的活动Session
  2. 通过Session ID获取用户Token
  3. 通过Token来启动UI程序

涉及的Win32 API

  1. WTSGetActiveConsoleSessionId获取活动的Session ID
  2. WTSQueryUserToken根据Session ID获取用户Token
  3. CreateProcessAsUser使用用户Token来启动UI程序
public class ProcessAsUser
{
    public struct SECURITY_ATTRIBUTES
    {
        public uint nLength;
        public uint lpSecurityDescriptor;
        public bool bInheritHandle;
    }

    public struct STARTUPINFO
    {
        public uint cb;
        public string lpReserved;
        public string lpDesktop;
        public string lpTitle;
        public uint dwX;
        public uint dwY;
        public uint dwXSize;
        public uint dwYSize;
        public uint dwXCountChars;
        public uint dwYCountChars;
        public uint dwFillAttribute;
        public uint dwFlags;
        public ushort wShowWindow;
        public ushort cbReserved2;
        public IntPtr lpReserved2;
        public IntPtr hStdInput;
        public IntPtr hStdOutput;
        public IntPtr hStdError;

    }
    public struct PROCESS_INFORMATION
    {
        public IntPtr hProcess;
        public IntPtr hThread;
        public uint dwProcessId;
        public uint dwThreadId;

    }

    [DllImport("kernel32.dll")]
    static extern uint WTSGetActiveConsoleSessionId();

    [DllImport("Wtsapi32.dll")]
    private static extern bool WTSQueryUserToken(uint SessionId, out uint hToken);

    [DllImport("Kernel32.dll")]
    private static extern uint GetLastError();

    [DllImport("kernel32.dll")]
    private static extern bool CloseHandle(IntPtr hSnapshot);

    [DllImport("advapi32.dll")]
    public extern static bool CreateProcessAsUser(IntPtr hToken,
                                            string lpApplicationName,
                                            string lpCommandLine,
                                            ref SECURITY_ATTRIBUTES lpProcessAttributes,
                                            ref SECURITY_ATTRIBUTES lpThreadAttributes,
                                            bool bInheritHandle,
                                            uint dwCreationFlags,
                                            uint lpEnvironment,
                                            string lpCurrentDirectory,
                                            ref STARTUPINFO lpStartupInfo,
                                            out PROCESS_INFORMATION lpProcessInformation);

    public static bool StartUIProcessFromService(string exePath)
    {
        //获取Session ID
        var sId=WTSGetActiveConsoleSessionId();
        if (sId == 0)
        {
            return false;
        }
        uint hToken;
        var isOk=WTSQueryUserToken(sId, out hToken);
        if (!isOk || hToken == 0)
        {
            return false;
        }
        var lpProcessAttr = new SECURITY_ATTRIBUTES();
        lpProcessAttr.nLength = (uint)Marshal.SizeOf(lpProcessAttr);

        var lpThreadAttr = new SECURITY_ATTRIBUTES();
        lpThreadAttr.nLength = (uint)Marshal.SizeOf(lpThreadAttr);

        var lpStratupInfo = new STARTUPINFO();
        lpStratupInfo.cb = (uint)Marshal.SizeOf(lpStratupInfo);
        lpStratupInfo.lpDesktop = @"winsta0\default";

        PROCESS_INFORMATION lpProcessInfo;
        isOk=CreateProcessAsUser((IntPtr)hToken,
                                    exePath,
                                    null,
                                    ref lpProcessAttr,
                                    ref lpThreadAttr,
                                    false,
                                    0,
                                    0,
                                    null,
                                    ref lpStratupInfo,
                                    out lpProcessInfo
                                );
        CloseHandle((IntPtr)hToken);
        return isOk;            
    }    
}
 

枚举活动Session ID

之前我们通过WTSGetActiveConsoleSessionId获取活动Session ID,当有多个用户登录时,Windows提供了WTSEnumerateSessions方法枚举多个Session ID。

主要涉及API

  1. WTSEnumerateSessions 检索在远程桌面会话主机 (RD 会话主机) 服务器上的会话的列表。
  2. WTSFreeMemory 释放由远程桌面服务函数分配的内存。

实现代码

[DllImport("Wtsapi32.dll")]
private static extern void WTSFreeMemory(IntPtr pSessionInfo);

[DllImport("Wtsapi32.dll")]
private extern static bool WTSEnumerateSessions(IntPtr hServer, uint reserved, uint version, out IntPtr ppSessionInfo, out uint pCount);
struct WTS_SESSION_INFO
{
    public uint SessionId;
    public string pWinStationName;
    public WTS_CONNECTSTATE_CLASS State;
}

enum WTS_CONNECTSTATE_CLASS
{
    WTSActive,
    WTSConnected,
    WTSConnectQuery,
    WTSShadow,
    WTSDisconnected,
    WTSIdle,
    WTSListen,
    WTSReset,
    WTSDown,
    WTSInit
}

private static uint EnumerateActiveSession()
{
    uint dwSessionID = 0xFFFFFFFF;
    uint dwCount = 0;
    IntPtr intPtr = IntPtr.Zero;
    try
    {
        IntPtr hServer = IntPtr.Zero;
        if (WTSEnumerateSessions(hServer, 0, 1, out intPtr, out dwCount))
        {
            var tmp = intPtr;
            for (var i = 0; i < dwCount; ++i)
            {
                var pSessionInfo = (WTS_SESSION_INFO)Marshal.PtrToStructure(tmp, typeof(WTS_SESSION_INFO));

                if (WTS_CONNECTSTATE_CLASS.WTSActive == pSessionInfo.State)
                {
                    dwSessionID = pSessionInfo.SessionId;
                    break;
                }
                if (WTS_CONNECTSTATE_CLASS.WTSConnected == pSessionInfo.State)
                {
                    dwSessionID = pSessionInfo.SessionId;
                }
                tmp += Marshal.SizeOf(typeof(WTS_SESSION_INFO));
            }
            WTSFreeMemory(intPtr);
        }
        var eCode = GetLastError();
    }
    catch (Exception ex)
    {
        var eCode = GetLastError();
    }
    return dwSessionID;
}

 

 

使用命令行启动WP8模拟器

用管理员模式启动CMD,输入一下命令即可

"C:\Program Files (x86)\Microsoft XDE\8.0\XDE.exe" /vhd "C:\Program Files (x86)\Microsoft SDKs\Windows Phone\v8.0GDR3\Emulation\Images\Flash.vhd" /video "768x1280" /memsize 1024 /language 804 /creatediffdisk "H:\dd.768x1280.1024.vhd" /fastShutdown

更多参数帮助可以输入

"C:\Program Files (x86)\Microsoft XDE\8.0\XDE.exe" /?

新浪微博Win8 APP启动秒退Bug的分析

自从更新Win8 RTM后,应用程序商店里面的新浪客户端就不能正常运行,多次卸载重装都不行,于是祭出ILSpy把那个APP反编译了。

经过多次调试发现是以下这段代码报错

private async void GetDeviceInfo()
{
    string[] array = new string[]
    {
        "System.Devices.ModelName",
        "System.Devices.Manufacturer"
    };
    string text = "System.Devices.LocalMachine:=System.StructuredQueryType.Boolean#True";
    PnpObjectCollection pnpObjectCollection = await PnpObject.FindAllAsync(PnpObjectType.DeviceContainer, array, text);
    if (pnpObjectCollection != null && ((IReadOnlyCollection<PnpObject>)pnpObjectCollection).Count > 0)
    {
        PnpObject pnpObject = ((IReadOnlyList<PnpObject>)pnpObjectCollection)[0];
        WeiboConstant.DeviceModel = pnpObject.get_Properties()[array[0]].ToString();
        WeiboConstant.DeviceManufacturer = pnpObject.get_Properties()[array[1]].ToString();
    }
    PackageVersion version = Package.Current.Id.Version;
    WeiboConstant.UAValue = string.Format("{0}__weibo__{1}.{2}.{3}__win8pad", new object[]
    {
        WeiboConstant.DeviceModel,
        version.Major,
        version.Minor,
        version.Build
    });
}

报错的地方为:

WeiboConstant.DeviceModel = pnpObject.get_Properties()[array[0]].ToString();
WeiboConstant.DeviceManufacturer = pnpObject.get_Properties()[array[1]].ToString();

这两句抛出了没有处理的null异常

原因很简单,由于笔者用得不是品牌电脑,所以没有制造商信息,故属性值为null,然后代码把null值ToString()的时候就会抛出null异常

这个在微软官方论坛上得到了证实(http://social.msdn.microsoft.com/Forums/zh-CN/metroappzhcn/thread/c7397740-4b30-4324-bf95-3c2bf84fc60d

笔者和新浪微博Win8 APP的其中一位开发人员取得了联系,反馈了这个Bug并拿到了修正Bug后的离线包,已经正常用上了眨眼

离线包安装需要最少开发者越狱,和一定的系统知识这里我就先不发了,各位看官还是等应用市场更新吧

另:目测 WeiboConstant.DeviceModel 应该就是日后Win8品牌机尾巴识别用的了,先在这备注一下,日后修改尾巴的时候可以用到吐舌鬼脸

微软正式启动Windows Azure移动服务

好消息!微软今天为开发者启动了Windows Azure移动服务。
该服务能使开发者非常轻松地将移动客户端与云端对接。现在开发者可以将应用的数据库、认证用户以及推送消息等在几分钟内放入云端。
此外,Windows 8应用开发者现在就可体验,而Windows Phone、iPhone以及Android的用户也将在近期尝到这份甜头。
如果有兴趣,现在就可以尝试一下,简直太棒了!
具体介绍和使用方法链接:http://www.windowsazure.com/en-us/develop/mobile/

《暗黑3》台服封测正式启动

《暗黑破坏神3》台版封测于4月25日正式启动,Blizzard将陆续发出《暗黑破坏神3》台版封测邀请通知,并且进行为期约一周的封测。同时 Blizzard 也同时释出封测的常见问答,让关注这项游戏重要动态的玩家,更了解整个台板封测细节。

这些拥有封测资格的测试者是如何从测试许可被选上的?

参与测试许可的玩家中随机挑选部分玩家参与此次封测,由于是封闭测试,故受邀参与此次封测的玩家有限。

我没有被抽中封测,还有那些办法玩到《暗黑破坏神3》呢?

我们和部分的网咖有合作,到这些网咖可以玩到《暗黑破坏神3》繁体中文版,点这裡看看那些网咖提供试玩。

我该如何知道我抽中《暗黑破坏神3》台版封测资格封测资格?

有两种方式可以确认:

• E-mail:我们会寄一封电子邮件至每一位被选中的玩家的Battle.net帐号的电子邮件地址,由于不同测试者收到信件的时间可能有异,请定期注意电子邮件信箱确认是否有收到封测通知。

• Battle.net 帐号:如果你登入Battle.net 帐号管理页面中有看到《暗黑破坏神3》封测标志在游戏中,就表示你已经抽中《暗黑破坏神3》封测资格。

收到封测邀请后,我该做什么?

• 前往 tw.battle.net 并登入拥有《暗黑破坏神3》封测资格的 Battle.net 帐号。

• 至游戏下载页面下载《暗黑破坏神3》繁体中文封测程式、安装并进行游戏。

我的Battle.net帐号已经有《暗黑破坏神3》美版封测资格,请问我还可以参加这次的《暗黑破坏神3》台版封测吗?

只要拥有《暗黑破坏神3》封测资格的玩家,都可以下载《暗黑破坏神3》繁体中文封测程式进行测试美洲及亚洲服务器。

你们打算邀请多少玩家参与这次的测试活动?

由于是封闭测试,故受邀参与此次封测的玩家有限,我们会视测试的需要来决定邀请人数。

这次测试中包含了哪些游戏内容?

此次玩家测试的是《暗黑破坏神3》繁体中文客户端。玩家不仅能以五种职业进行游戏,还能体验从一开始到骷髅王阶段的初期故事内容。在新崔斯特姆,你可以 和这次新加入的人物,以及二代的旧面孔互动,同时与在受到诅咒的崔斯特姆大教堂中复甦、不断涌向玩家的死者战斗。另外,玩家也能体验到《暗黑破坏神3》中各种随机元素,及将《暗黑破坏神》的设计核心提升至崭新境界的许多新系统设计。

这次封测的主要测试方向是什么?

这次的封测我们会测试通讯锁在亚洲服务器上的运作以及服务器的诸多测试。我们鼓励将来欲使用或已使用通讯锁的封测受邀玩家协助测试通讯锁功能。请记住,在封测期间,玩家可能会遭遇不预警的排队、服务中断、延迟或断线。

封测会维持多久?

封测期间大约会持续 1 周,接近结束时我们于官网中公告通知参加封测的玩家。

《暗黑破坏神III》台版现金拍卖场会在这次封测中测试吗?

《暗黑破坏神3》亚洲服务器封测期间仅测试金币拍卖场功能。

亚洲服务器会开放公测吗?

我们在美洲服务器的公测中获得相当多的资讯,了解玩家于公测中遭遇的游戏问题,目前这些已知问题正在修正与排除,这些修正同样适用于亚洲服务器,我们感谢玩家参与上周末的美洲服务器公测。未来游戏上市后这些问题都将排除,目前我们不会有亚洲服务器的公测。

我有《暗黑破坏神3》台版封测的相关问题,我该如何寻求协助?

由于封测支援的人力有限,如果你有任何执行游戏的问题,我们鼓励你到官方讨论区的台版封测错误回报讨论区中发言,遇到问题时也请先确认讨论串中的已知问题清单。

Mac 电脑的用户也能参与这次测试活动吗?

我们仅提供PC的用户端,目前没有规划 MAC 的用户端。

Android模拟器启动慢怎么办?

这个基本没有解决办法,Android模拟器启动慢是这个模拟器的一大特色,提升PC的硬件配置也是杯水车薪

一个好的做法是开电脑后第一时间打开模拟器,完成完常规工作后模拟器也差不多也启动完了
工作中不要关闭你的AVD,直到你完成了你一天的工作。
当你的模拟器启动后,Eclipse自动地安装并运行你的应用程序。