65.9K
CodeProject 正在变化。 阅读更多。
Home

我什么时候可以登录 Windows?

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.33/5 (5投票s)

2010年9月12日

CPOL

9分钟阅读

viewsIcon

22381

downloadIcon

197

简要解释如何解释 USER_INFO 结构中的“登录小时数”成员。

引言

在我的一台 Windows 7 机器上,我偶然发现了家长控制使用的时间限制对话框。

这引起了我的好奇心,想知道如何获取这些信息。我很快找到了 NetUserGetInfo() 函数。该函数将九种不同的 USER_INFO_x 结构之一填充为有关服务器上特定用户帐户的各种信息。然而,每次我找到结构成员外观的示例时,它们都以相同的方式处理 usriX_logon_hours 成员:作为 21 个独立的字节。使用 %s%d 格式化打印其他结构成员是可以的,但我发现从 %x 格式化查看该成员一无所获。因此,我的任务开始了……

读取数组

MSDN 告诉我们,usriX_logon_hours 成员是一个 168 位数组(布局从 0 到 167),其中每一位代表一天中的一小时。我将一个用户帐户设置为允许的时间为:周日 13-20;周一至周四、周六 9-20;周五 9-21。如果将此数组布置成看起来像典型的每周 24 小时,您将得到以下结果(交替的灰色阴影显示了字节边界)。

0:00 1:00 2:00 3:00 4:00 5:00 6:00 7:00 8:00 9:00 10:00 11:00 12:00 13:00 14:00 15:00 16:00 17:00 18:00 19:00 20:00 21:00 22:00 23:00
周日 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 0 0 0 0
周一 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0
周二 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0
周三 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0
周四 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0
周五 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0
周六 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0

那么,如何读取 21 字节的数组,使其看起来像上面那样呢?从数组读取足够简单,但我们处理的是位而不是字节。我发现将数组看作 1x168 位数组比 1x21 字节或 7x24 位数组更容易。因此,首要任务是“转换”这 21 个字节为 168 位。我用类似以下的方式完成了这个任务:

LPBYTE lpLogonHours = lpUserInfo->usri2_logon_hours;
int nBits[7 * 24];

for (int x = 0; x < 21; x++)
{
    // get bit 8, bit 7, bit 6, etc
    for (int y = 7; y >= 0; y--)
        nBits[z++] = (*lpLogonHours >> y) & 0x01;

    lpLogonHours++; 
}

这会生成一个 168 位数组,排列成熟悉的 7x24 表,看起来像:

0:00 1:00 2:00 3:00 4:00 5:00 6:00 7:00 8:00 9:00 10:00 11:00 12:00 13:00 14:00 15:00 16:00 17:00 18:00 19:00 20:00 21:00 22:00 23:00
周日 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 0 0 0
周一 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1
周二 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1
周三 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1
周四 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1
周五 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1
周六 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1

嗯,这看起来并不像我们想要的。位似乎有某种统一性,但有些东西并不完全对齐。例如,我本来期望周日包含连续的七个 1,对应于 13:00-20:00 的时间段。

MSDN 告诉我们,位必须根据我们的时区进行调整(即移位)。例如,对于 UTC±0,第一个字节的第一个位是周日,0:00 到 0:59;第二个位是周日,1:00 到 1:59;依此类推,如上所示。好的,但是由于我在 UTC-6 时区,对我来说,起始位是 6(第一个字节)。那将是我的周日从 0:00 到 0:59;我的周日从 1:00 到 1:59 将是位 7;依此类推。而且,由于我的周日从位 6 开始,位 0-5 就是周六的最后 6 个小时(这种“环绕”会变得更加明显)。前 24 小时现在看起来像:

周六 18:00 19:00 20:00 21:00 22:00 23:00 周日 0:00 1:00 2:00 3:00 4:00 5:00 6:00 7:00 8:00 9:00 10:00 11:00 12:00 13:00 14:00 15:00 16:00 17:00
0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 0 0 0

它改变了,但看起来并没有更好。让我们看看还有什么需要调整的。

反向读取每个字节

查看第一个表,我应该在周六的 18:00-19:00 时间段看到一个 1,但我看到的是一个 0。我确实在那个字节的另一端看到了两个 1。此外,我应该在周日的 13:00-17:00 时间段看到 1(最后五个小时),但我却在 10:00-14:00 时间段看到了它们(前五个小时)。在这两种情况下,每个字节的位似乎都是反向的。当将字节转换为位时,似乎我们应该简单地以相反的顺序迭代位,例如:

LPBYTE lpLogonHours = lpUserInfo->usri2_logon_hours;
int nBits[7 * 24];

for (int x = 0; x < 21; x++)
{
    // get bit 1, bit 2, bit 3, etc
    for (int y = 0; y < 8; y++)
        nBits[z++] = (*lpLogonHours >> y) & 0x01;

    lpLogonHours++; 
}

现在该表看起来如下。请注意,由于周日 0:00-0:59 的时间段已从数组开头偏移了 6 小时,因此所有后续时间段都已偏移,周六的最后 6 个小时已环绕回到数组开头。

周六 18:00 19:00 20:00 21:00 22:00 23:00 周日 0:00 1:00 2:00 3:00 4:00 5:00 6:00 7:00 8:00 9:00 10:00 11:00 12:00 13:00 14:00 15:00 16:00 17:00
1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1

周日 18:00 19:00 20:00 21:00 22:00 23:00 周一 0:00 1:00 2:00 3:00 4:00 5:00 6:00 7:00 8:00 9:00 10:00 11:00 12:00 13:00 14:00 15:00 16:00 17:00
1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1

周一 18:00 19:00 20:00 21:00 22:00 23:00 周二 0:00 1:00 2:00 3:00 4:00 5:00 6:00 7:00 8:00 9:00 10:00 11:00 12:00 13:00 14:00 15:00 16:00 17:00
1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1

周二 18:00 19:00 20:00 21:00 22:00 23:00 周三 0:00 1:00 2:00 3:00 4:00 5:00 6:00 7:00 8:00 9:00 10:00 11:00 12:00 13:00 14:00 15:00 16:00 17:00
1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1

周三 18:00 19:00 20:00 21:00 22:00 23:00 周四 0:00 1:00 2:00 3:00 4:00 5:00 6:00 7:00 8:00 9:00 10:00 11:00 12:00 13:00 14:00 15:00 16:00 17:00
1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1

周四 18:00 19:00 20:00 21:00 22:00 23:00 周五 0:00 1:00 2:00 3:00 4:00 5:00 6:00 7:00 8:00 9:00 10:00 11:00 12:00 13:00 14:00 15:00 16:00 17:00
1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1

周五 18:00 19:00 20:00 21:00 22:00 23:00 周六 0:00 1:00 2:00 3:00 4:00 5:00 6:00 7:00 8:00 9:00 10:00 11:00 12:00 13:00 14:00 15:00 16:00 17:00
1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1

好多了!

查找数组中的正确起始点

当我们创建本文顶部的表格时,只需找到数组中正确的位即可开始读取。通过查看此表中的每个时区和起始偏移量,我们可以看到我们应该从哪里开始读取,并且我们还可以看到一个模式。

如果 TZ 是…… 那么数组偏移量是……
-12 12
-11 11
-10 10
-9 9
-8 8
-7 7
-6 6
-5 5
-4 4
-3 3
-2 2
-1 1
0 0
1 167
2 166
3 165
4 164
5 163
6 162
7 161
8 160
9 159
10 158
11 157
12 156
13 155

实现起始偏移量的方法首先需要我们知道协调世界时 (UTC) 和本地时间之间有多少小时的差距。

TIME_ZONE_INFORMATION tzi;
GetTimeZoneInformation(&tzi);

int nOffset = tzi.Bias / -60;

其次,我们然后向前或向后调整该值,例如:

nOffset = (168 - nOffset) % 168;

现在我们知道从哪里开始读取数组,我们可以简单地迭代所有 168 位。但是当我们到达数组末尾但尚未读取所有 168 位时会发生什么?答案:只需环绕回到 0。一种方法是:

nOffset = nOffset + 1;
if (168 == nOffset)
    nOffset = 0;

或者,如果您追求简洁,一行解决方案将是:

nOffset = (nOffset + 1) % 168;

结语

虽然我在上面的代码片段中使用了硬编码的值,如 24 和 168,但这只是为了使文本更易于阅读。然而,在附带的示例中,我使用了 lmaccess.h 文件中找到的 #define 宏。

本文的目的不是要替换 Vista 和 Windows 7 使用的时间限制对话框,我也不想创建一个功能齐全的“限制”应用程序。其中已有几个。我只想展示如何读取信息。我将“写入”功能留给感兴趣的读者。

尽情享用!

© . All rights reserved.