我什么时候可以登录 Windows?






4.33/5 (5投票s)
简要解释如何解释 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 使用的时间限制对话框,我也不想创建一个功能齐全的“限制”应用程序。其中已有几个。我只想展示如何读取信息。我将“写入”功能留给感兴趣的读者。
尽情享用!