feature. See also
. The project being documented here (as the example) is the Zig library itself.
windows.getCpuInfoFromRegistry
fn getCpuInfoFromRegistry(core: usize, args: anytype) !void
File
Code
fn getCpuInfoFromRegistry(core: usize, args: anytype) !void {
const ArgsType = @TypeOf(args);
const args_type_info = @typeInfo(ArgsType);
if (args_type_info != .@"struct") {
@compileError("expected tuple or struct argument, found " ++ @typeName(ArgsType));
}
const fields_info = args_type_info.@"struct";
// would sequentially visit each CPU#d subkey in the registry and pull the value of interest into
// a buffer, however, NT seems to be expecting a single buffer per each table meaning we would
// end up pulling only the last CPU core info, overwriting everything else.
// If anyone can come up with a solution to this, please do!
const table_size = 1 + fields_info.field_names.len;
var table: [table_size + 1]std.os.windows.RTL_QUERY_REGISTRY_TABLE = undefined;
const topkey = std.unicode.utf8ToUtf16LeStringLiteral("\\Registry\\Machine\\HARDWARE\\DESCRIPTION\\System\\CentralProcessor");
const max_cpu_buf = 4;
var next_cpu_buf: [max_cpu_buf]u8 = undefined;
const next_cpu = try std.fmt.bufPrint(&next_cpu_buf, "{d}", .{core});
var subkey: [max_cpu_buf + 1]u16 = undefined;
const subkey_len = try std.unicode.utf8ToUtf16Le(&subkey, next_cpu);
subkey[subkey_len] = 0;
table[0] = .{
.QueryRoutine = null,
.Flags = std.os.windows.RTL_QUERY_REGISTRY_SUBKEY | std.os.windows.RTL_QUERY_REGISTRY_REQUIRED,
.Name = subkey[0..subkey_len :0],
.EntryContext = null,
.DefaultType = .NONE,
.DefaultData = null,
.DefaultLength = 0,
};
var tmp_bufs: [fields_info.field_names.len][max_value_len]u8 align(@alignOf(std.os.windows.UNICODE_STRING)) = undefined;
inline for (fields_info.field_names, 0..) |field_name, i| {
const ctx: *anyopaque = blk: {
switch (@field(args, field_name).value_type) {
.SZ,
.EXPAND_SZ,
.MULTI_SZ,
=> {
comptime assert(@sizeOf(std.os.windows.UNICODE_STRING) % 2 == 0);
const unicode: *std.os.windows.UNICODE_STRING = @ptrCast(&tmp_bufs[i]);
unicode.* = .{
.Length = 0,
.MaximumLength = max_value_len - @sizeOf(std.os.windows.UNICODE_STRING),
.Buffer = @ptrCast(tmp_bufs[i][@sizeOf(std.os.windows.UNICODE_STRING)..]),
};
break :blk unicode;
},
.DWORD,
.DWORD_BIG_ENDIAN,
.QWORD,
=> break :blk &tmp_bufs[i],
else => unreachable,
}
};
var key_buf: [max_value_len / 2 + 1]u16 = undefined;
const key_len = try std.unicode.utf8ToUtf16Le(&key_buf, @field(args, field_name).key);
key_buf[key_len] = 0;
table[i + 1] = .{
.QueryRoutine = null,
.Flags = std.os.windows.RTL_QUERY_REGISTRY_DIRECT | std.os.windows.RTL_QUERY_REGISTRY_REQUIRED,
.Name = key_buf[0..key_len :0],
.EntryContext = ctx,
.DefaultType = .NONE,
.DefaultData = null,
.DefaultLength = 0,
};
}
table[table_size] = .{
.QueryRoutine = null,
.Flags = 0,
.Name = null,
.EntryContext = null,
.DefaultType = .NONE,
.DefaultData = null,
.DefaultLength = 0,
};
const res = std.os.windows.ntdll.RtlQueryRegistryValues(
std.os.windows.RTL_REGISTRY_ABSOLUTE,
topkey,
&table,
null,
null,
);
switch (res) {
.SUCCESS => {
inline for (fields_info.field_names, 0..) |field_name, i| switch (@field(args, field_name).value_type) {
.SZ,
.EXPAND_SZ,
.MULTI_SZ,
=> {
var buf = @field(args, field_name).value_buf;
const entry: *const std.os.windows.UNICODE_STRING = @ptrCast(table[i + 1].EntryContext);
const len = try std.unicode.utf16LeToUtf8(buf, entry.slice());
buf[len] = 0;
},
.DWORD,
.DWORD_BIG_ENDIAN,
.QWORD,
=> {
const entry: [*]const u8 = @ptrCast(table[i + 1].EntryContext);
switch (@field(args, field_name).value_type) {
.DWORD, .DWORD_BIG_ENDIAN => {
@memcpy(@field(args, field_name).value_buf[0..4], entry[0..4]);
},
.QWORD => {
@memcpy(@field(args, field_name).value_buf[0..8], entry[0..8]);
},
else => unreachable,
}
},
else => unreachable,
};
},
else => return error.Unexpected,
}
}