Skip to content

Commit 8579f72

Browse files
committed
windows_sdk.zig: Reinstate COM ISetupEnumInstances logic
The C++ version of this code used this logic, and it turns out it is able to find some setups that the current registry/Vs7 methods cannot. For example, if only the "Build Tools for Visual Studio" are installed but not Visual Studio itself, then only the ISetupEnumInstances method seems to find it. Follow up to ziglang#15657, fixes a regression caused by moving from the C++ version to the Zig version
1 parent 282cb5e commit 8579f72

File tree

3 files changed

+290
-25
lines changed

3 files changed

+290
-25
lines changed

lib/std/os/windows.zig

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2496,6 +2496,8 @@ pub const LPCWSTR = [*:0]const WCHAR;
24962496
pub const PVOID = *anyopaque;
24972497
pub const PWSTR = [*:0]WCHAR;
24982498
pub const PCWSTR = [*:0]const WCHAR;
2499+
/// Allocated by SysAllocString, freed by SysFreeString
2500+
pub const BSTR = [*:0]WCHAR;
24992501
pub const SIZE_T = usize;
25002502
pub const UINT = c_uint;
25012503
pub const ULONG_PTR = usize;
@@ -3253,6 +3255,7 @@ pub const KF_FLAG_SIMPLE_IDLIST = 256;
32533255
pub const KF_FLAG_ALIAS_ONLY = -2147483648;
32543256

32553257
pub const S_OK = 0;
3258+
pub const S_FALSE = 0x00000001;
32563259
pub const E_NOTIMPL = @as(c_long, @bitCast(@as(c_ulong, 0x80004001)));
32573260
pub const E_NOINTERFACE = @as(c_long, @bitCast(@as(c_ulong, 0x80004002)));
32583261
pub const E_POINTER = @as(c_long, @bitCast(@as(c_ulong, 0x80004003)));
@@ -3563,15 +3566,11 @@ pub const RTL_RUN_ONCE = extern struct {
35633566

35643567
pub const RTL_RUN_ONCE_INIT = RTL_RUN_ONCE{ .Ptr = null };
35653568

3566-
pub const COINIT_APARTMENTTHREADED = COINIT.COINIT_APARTMENTTHREADED;
3567-
pub const COINIT_MULTITHREADED = COINIT.COINIT_MULTITHREADED;
3568-
pub const COINIT_DISABLE_OLE1DDE = COINIT.COINIT_DISABLE_OLE1DDE;
3569-
pub const COINIT_SPEED_OVER_MEMORY = COINIT.COINIT_SPEED_OVER_MEMORY;
3570-
pub const COINIT = enum(c_int) {
3571-
COINIT_APARTMENTTHREADED = 2,
3572-
COINIT_MULTITHREADED = 0,
3573-
COINIT_DISABLE_OLE1DDE = 4,
3574-
COINIT_SPEED_OVER_MEMORY = 8,
3569+
pub const COINIT = struct {
3570+
pub const APARTMENTTHREADED = 2;
3571+
pub const MULTITHREADED = 0;
3572+
pub const DISABLE_OLE1DDE = 4;
3573+
pub const SPEED_OVER_MEMORY = 8;
35753574
};
35763575

35773576
pub const MEMORY_BASIC_INFORMATION = extern struct {

lib/std/os/windows/ole32.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ const HRESULT = windows.HRESULT;
88
pub extern "ole32" fn CoTaskMemFree(pv: LPVOID) callconv(WINAPI) void;
99
pub extern "ole32" fn CoUninitialize() callconv(WINAPI) void;
1010
pub extern "ole32" fn CoGetCurrentProcess() callconv(WINAPI) DWORD;
11+
pub extern "ole32" fn CoInitialize(pvReserved: ?LPVOID) callconv(WINAPI) HRESULT;
1112
pub extern "ole32" fn CoInitializeEx(pvReserved: ?LPVOID, dwCoInit: DWORD) callconv(WINAPI) HRESULT;

src/windows_sdk.zig

Lines changed: 281 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -550,7 +550,6 @@ pub const ZigWindowsSDK = struct {
550550

551551
const msvc_lib_dir: ?[]const u8 = MsvcLibDir.find(allocator) catch |err| switch (err) {
552552
error.MsvcLibDirNotFound => null,
553-
error.PathTooLong => null,
554553
error.OutOfMemory => return error.OutOfMemory,
555554
};
556555
errdefer allocator.free(msvc_lib_dir);
@@ -576,6 +575,112 @@ pub const ZigWindowsSDK = struct {
576575
};
577576

578577
const MsvcLibDir = struct {
578+
// https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.setup.configuration
579+
fn findViaCOM(allocator: std.mem.Allocator) error{ OutOfMemory, PathNotFound }![]const u8 {
580+
switch (windows.ole32.CoInitializeEx(null, windows.COINIT.MULTITHREADED)) {
581+
windows.S_OK, windows.S_FALSE => {},
582+
windows.E_OUTOFMEMORY => return error.OutOfMemory,
583+
else => return error.PathNotFound,
584+
}
585+
// > To close the COM library gracefully on a thread, each successful
586+
// > call to CoInitialize or CoInitializeEx, including any call that
587+
// > returns S_FALSE, must be balanced by a corresponding call to CoUninitialize.
588+
// https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-coinitializeex
589+
defer windows.ole32.CoUninitialize();
590+
591+
var setup_config: *ISetupConfiguration = undefined;
592+
switch (CoCreateInstance(
593+
SetupConfiguration.CLSID,
594+
null,
595+
CLSCTX.INPROC_SERVER | CLSCTX.INPROC_HANDLER,
596+
ISetupConfiguration.IID,
597+
@ptrCast(&setup_config),
598+
)) {
599+
windows.S_OK => {},
600+
windows.E_OUTOFMEMORY => return error.OutOfMemory,
601+
else => return error.PathNotFound,
602+
}
603+
defer _ = setup_config.vtable.unknown.Release(setup_config);
604+
605+
var all_instances: *IEnumSetupInstances = undefined;
606+
switch (setup_config.vtable.setup_configuration.EnumInstances(setup_config, &all_instances)) {
607+
windows.S_OK => {},
608+
windows.E_OUTOFMEMORY => return error.OutOfMemory,
609+
else => return error.PathNotFound,
610+
}
611+
defer _ = all_instances.vtable.unknown.Release(all_instances);
612+
613+
while (true) {
614+
var cur: *ISetupInstance = undefined;
615+
switch (all_instances.vtable.enum_setup_instances.Next(all_instances, 1, &cur, null)) {
616+
windows.S_OK => {},
617+
windows.S_FALSE => break,
618+
windows.E_OUTOFMEMORY => return error.OutOfMemory,
619+
else => return error.PathNotFound,
620+
}
621+
defer _ = cur.vtable.unknown.Release(cur);
622+
623+
var installation_path_bstr: windows.BSTR = undefined;
624+
switch (cur.vtable.setup_instance.GetInstallationPath(cur, &installation_path_bstr)) {
625+
windows.S_OK => {},
626+
windows.E_OUTOFMEMORY => return error.OutOfMemory,
627+
else => continue,
628+
}
629+
defer SysFreeString(installation_path_bstr);
630+
631+
const installation_path_w = std.mem.span(installation_path_bstr);
632+
const lib_dir_path = libDirFromInstallationPath(allocator, installation_path_w) catch |err| switch (err) {
633+
error.OutOfMemory => |e| return e,
634+
error.PathNotFound => continue,
635+
};
636+
errdefer allocator.free(lib_dir_path);
637+
638+
return lib_dir_path;
639+
}
640+
return error.PathNotFound;
641+
}
642+
643+
fn libDirFromInstallationPath(allocator: std.mem.Allocator, installation_path_w: []const u16) error{ OutOfMemory, PathNotFound }![]const u8 {
644+
// Each UTF-16LE code unit may be expanded to 3 UTF-8 bytes.
645+
var lib_dir_buf = try std.ArrayList(u8).initCapacity(allocator, installation_path_w.len * 3);
646+
errdefer lib_dir_buf.deinit();
647+
648+
lib_dir_buf.items.len = std.unicode.utf16leToUtf8(lib_dir_buf.unusedCapacitySlice(), installation_path_w) catch {
649+
return error.PathNotFound;
650+
};
651+
652+
if (!std.fs.path.isSep(lib_dir_buf.getLast())) {
653+
try lib_dir_buf.append('\\');
654+
}
655+
const installation_path_with_trailing_sep_len = lib_dir_buf.items.len;
656+
657+
try lib_dir_buf.appendSlice("VC\\Auxiliary\\Build\\Microsoft.VCToolsVersion.default.txt");
658+
var default_tools_version_buf: [512]u8 = undefined;
659+
const default_tools_version_contents = std.fs.cwd().readFile(lib_dir_buf.items, &default_tools_version_buf) catch {
660+
return error.PathNotFound;
661+
};
662+
var tokenizer = std.mem.tokenizeAny(u8, default_tools_version_contents, " \r\n");
663+
const default_tools_version = tokenizer.next() orelse return error.PathNotFound;
664+
665+
lib_dir_buf.shrinkRetainingCapacity(installation_path_with_trailing_sep_len);
666+
try lib_dir_buf.appendSlice("VC\\Tools\\MSVC\\");
667+
try lib_dir_buf.appendSlice(default_tools_version);
668+
const folder_with_arch = "\\Lib\\" ++ comptime switch (builtin.target.cpu.arch) {
669+
.x86 => "x86",
670+
.x86_64 => "x64",
671+
.arm, .armeb => "arm",
672+
.aarch64 => "arm64",
673+
else => |tag| @compileError("MSVC lib dir cannot be detected on architecture " ++ tag),
674+
};
675+
try lib_dir_buf.appendSlice(folder_with_arch);
676+
677+
if (!verifyLibDir(lib_dir_buf.items)) {
678+
return error.PathNotFound;
679+
}
680+
681+
return lib_dir_buf.toOwnedSlice();
682+
}
683+
579684
// https://learn.microsoft.com/en-us/visualstudio/install/tools-for-managing-visual-studio-instances?view=vs-2022#editing-the-registry-for-a-visual-studio-instance
580685
fn findViaRegistry(allocator: std.mem.Allocator) error{ OutOfMemory, PathNotFound }![]const u8 {
581686

@@ -661,6 +766,10 @@ const MsvcLibDir = struct {
661766
};
662767
errdefer allocator.free(msvc_dir);
663768

769+
if (!verifyLibDir(msvc_dir)) {
770+
return error.PathNotFound;
771+
}
772+
664773
return msvc_dir;
665774
}
666775

@@ -720,36 +829,192 @@ const MsvcLibDir = struct {
720829
};
721830
try base_path.appendSlice(folder_with_arch);
722831

832+
if (!verifyLibDir(base_path.items)) {
833+
return error.PathNotFound;
834+
}
835+
723836
const full_path = try base_path.toOwnedSlice();
724837
return full_path;
725838
}
726839

840+
fn verifyLibDir(lib_dir_path: []const u8) bool {
841+
std.debug.assert(std.fs.path.isAbsolute(lib_dir_path)); // should be already handled in `findVia*`
842+
843+
var dir = std.fs.openDirAbsolute(lib_dir_path, .{}) catch return false;
844+
defer dir.close();
845+
846+
const stat = dir.statFile("vcruntime.lib") catch return false;
847+
if (stat.kind != .file)
848+
return false;
849+
850+
return true;
851+
}
852+
727853
/// Find path to MSVC's `lib/` directory.
728854
/// Caller owns the result.
729-
pub fn find(allocator: std.mem.Allocator) error{ OutOfMemory, MsvcLibDirNotFound, PathTooLong }![]const u8 {
730-
const full_path = MsvcLibDir.findViaRegistry(allocator) catch |err1| switch (err1) {
855+
pub fn find(allocator: std.mem.Allocator) error{ OutOfMemory, MsvcLibDirNotFound }![]const u8 {
856+
const full_path = MsvcLibDir.findViaCOM(allocator) catch |err1| switch (err1) {
731857
error.OutOfMemory => return error.OutOfMemory,
732-
error.PathNotFound => MsvcLibDir.findViaVs7Key(allocator) catch |err2| switch (err2) {
858+
error.PathNotFound => MsvcLibDir.findViaRegistry(allocator) catch |err2| switch (err2) {
733859
error.OutOfMemory => return error.OutOfMemory,
734-
error.PathNotFound => return error.MsvcLibDirNotFound,
860+
error.PathNotFound => MsvcLibDir.findViaVs7Key(allocator) catch |err3| switch (err3) {
861+
error.OutOfMemory => return error.OutOfMemory,
862+
error.PathNotFound => return error.MsvcLibDirNotFound,
863+
},
735864
},
736865
};
737866
errdefer allocator.free(full_path);
738-
std.debug.assert(std.fs.path.isAbsolute(full_path)); // should be already handled in `findVia*`
739867

740-
var dir = std.fs.openDirAbsolute(full_path, .{}) catch |err| switch (err) {
741-
error.NameTooLong => return error.PathTooLong,
742-
else => return error.MsvcLibDirNotFound,
868+
return full_path;
869+
}
870+
};
871+
872+
const IUnknown = extern struct {
873+
vtable: *VTable(IUnknown),
874+
875+
const IID_Value = windows.GUID.parse("{00000000-0000-0000-c000-000000000046}");
876+
pub const IID = &IID_Value;
877+
878+
pub fn VTable(comptime T: type) type {
879+
return extern struct {
880+
QueryInterface: *const fn (
881+
self: *T,
882+
riid: ?*const windows.GUID,
883+
ppvObject: ?*?*anyopaque,
884+
) callconv(windows.WINAPI) windows.HRESULT,
885+
AddRef: *const fn (
886+
self: *T,
887+
) callconv(windows.WINAPI) u32,
888+
Release: *const fn (
889+
self: *T,
890+
) callconv(windows.WINAPI) u32,
743891
};
744-
defer dir.close();
892+
}
893+
};
745894

746-
const stat = dir.statFile("vcruntime.lib") catch |err| switch (err) {
747-
error.NameTooLong => return error.PathTooLong,
748-
else => return error.MsvcLibDirNotFound,
895+
const ISetupConfiguration = extern struct {
896+
vtable: *extern struct {
897+
unknown: IUnknown.VTable(ISetupConfiguration),
898+
setup_configuration: VTable(ISetupConfiguration),
899+
},
900+
901+
const IID_Value = windows.GUID.parse("{42843719-db4c-46c2-8e7c-64f1816efd5b}");
902+
pub const IID = &IID_Value;
903+
904+
pub fn VTable(comptime T: type) type {
905+
return extern struct {
906+
EnumInstances: *const fn (
907+
self: *T,
908+
ppEnumInstances: **IEnumSetupInstances, // [out]
909+
) callconv(windows.WINAPI) windows.HRESULT,
910+
GetInstanceForCurrentProcess: *const fn (
911+
self: *T,
912+
ppInstance: **ISetupInstance, // [out]
913+
) callconv(windows.WINAPI) windows.HRESULT,
914+
GetInstanceForPath: *const fn (
915+
self: *T,
916+
wzPath: windows.LPCWSTR, // [in]
917+
ppInstance: **ISetupInstance, // [out]
918+
) callconv(windows.WINAPI) windows.HRESULT,
749919
};
750-
if (stat.kind != .file)
751-
return error.MsvcLibDirNotFound;
920+
}
921+
};
752922

753-
return full_path;
923+
const IEnumSetupInstances = extern struct {
924+
vtable: *extern struct {
925+
unknown: IUnknown.VTable(IEnumSetupInstances),
926+
enum_setup_instances: VTable(IEnumSetupInstances),
927+
},
928+
929+
const IID_Value = windows.GUID.parse("{6380bcff-41d3-4b2e-8b2e-bf8a6810c848}");
930+
pub const IID = &IID_Value;
931+
932+
pub fn VTable(comptime T: type) type {
933+
return extern struct {
934+
/// Returns S_OK if the number of elements were fetched,
935+
/// S_FALSE if nothing was fetched (at end of enumeration),
936+
/// E_INVALIDARG if `celt` is greater than 1 and pceltFetched is NULL,
937+
/// or E_OUTOFMEMORY if an ISetupInstance could not be allocated.
938+
Next: *const fn (
939+
self: *T,
940+
/// The number of product instances to retrieve
941+
celt: windows.ULONG, // [in]
942+
/// A pointer to an array of ISetupInstance
943+
rgelt: **ISetupInstance, // [out]
944+
/// A pointer to the number of product instances retrieved.
945+
/// If `celt` is 1 this paramter may be NULL
946+
pceltFetched: ?*windows.ULONG,
947+
) callconv(windows.WINAPI) windows.HRESULT,
948+
Skip: *const fn (
949+
self: *T,
950+
/// The number of product instances to skip
951+
celt: windows.ULONG, // [in]
952+
) callconv(windows.WINAPI) windows.HRESULT,
953+
Reset: *const fn (
954+
self: *T,
955+
) callconv(windows.WINAPI) void,
956+
Clone: *const fn (
957+
self: *T,
958+
ppenum: **IEnumSetupInstances, // [out]
959+
) callconv(windows.WINAPI) windows.HRESULT,
960+
};
754961
}
755962
};
963+
964+
const ISetupInstance = extern struct {
965+
vtable: *extern struct {
966+
unknown: IUnknown.VTable(ISetupInstance),
967+
setup_instance: VTable(ISetupInstance),
968+
},
969+
970+
const IID_Value = windows.GUID.parse("{b41463c3-8866-43b5-bc33-2b0676f7f42e}");
971+
pub const IID = &IID_Value;
972+
973+
pub fn VTable(comptime T: type) type {
974+
return extern struct {
975+
GetInstanceId: *const fn (
976+
self: *T,
977+
pbstrInstanceId: *windows.BSTR, // [out]
978+
) callconv(windows.WINAPI) windows.HRESULT,
979+
GetInstallDate: *const fn (
980+
self: *T,
981+
pInstallDate: *windows.FILETIME, // [out]
982+
) callconv(windows.WINAPI) windows.HRESULT,
983+
GetInstallationName: *const fn (
984+
self: *T,
985+
pbstrInstallationName: *windows.BSTR, // [out]
986+
) callconv(windows.WINAPI) windows.HRESULT,
987+
GetInstallationPath: *const fn (
988+
self: *T,
989+
pbstrInstallationPath: *windows.BSTR, // [out]
990+
) callconv(windows.WINAPI) windows.HRESULT,
991+
GetInstallationVersion: *const fn (
992+
self: *T,
993+
pbstrInstallationVersion: *windows.BSTR, // [out]
994+
) callconv(windows.WINAPI) windows.HRESULT,
995+
GetDisplayName: *anyopaque,
996+
GetDescription: *anyopaque,
997+
ResolvePath: *anyopaque,
998+
};
999+
}
1000+
};
1001+
1002+
const SetupConfiguration = extern struct {
1003+
const CLSID_Value = windows.GUID.parse("{177f0c4a-1cd3-4de7-a32c-71dbbb9fa36d}");
1004+
pub const CLSID = &CLSID_Value;
1005+
};
1006+
1007+
extern "ole32" fn CoCreateInstance(
1008+
rclsid: ?*const windows.GUID, // [in]
1009+
pUnkOuter: ?*IUnknown, // [in]
1010+
dwClsContext: windows.DWORD, // [in]
1011+
riid: ?*const windows.GUID, // [in]
1012+
ppv: **anyopaque, // [out]
1013+
) callconv(windows.WINAPI) windows.HRESULT;
1014+
1015+
extern "oleaut32" fn SysFreeString(bstrString: ?windows.BSTR) callconv(windows.WINAPI) void;
1016+
1017+
const CLSCTX = struct {
1018+
const INPROC_SERVER = 0x1;
1019+
const INPROC_HANDLER = 0x2;
1020+
};

0 commit comments

Comments
 (0)