慕斯709654
好的,我决定抛开我的 Win32 API 编程技能,准备一个解决方案。基于您提到的线程的la脚方法的解决方案如下:package mainimport ( "errors" "fmt" "log" "syscall" "unsafe")var ( kernel32 = syscall.NewLazyDLL("kernel32.dll") getDriveTypeWProc = kernel32.NewProc("GetDriveTypeW"))func getDriveType(rootPathName []uint16) (int, error) { rc, _, _ := getDriveTypeWProc.Call( uintptr(unsafe.Pointer(&rootPathName[0])), ) dt := int(rc) if dt == driveUnknown || dt == driveNoRootDir { return -1, driveTypeErrors[dt] } return dt, nil}var ( errUnknownDriveType = errors.New("unknown drive type") errNoRootDir = errors.New("invalid root drive path") driveTypeErrors = [...]error{ 0: errUnknownDriveType, 1: errNoRootDir, })const ( driveUnknown = iota driveNoRootDir driveRemovable driveFixed driveRemote driveCDROM driveRamdisk)func getFixedDOSDrives() ([]string, error) { var drive = [4]uint16{ 1: ':', 2: '\\', } var drives []string for c := 'A'; c <= 'Z'; c++ { drive[0] = uint16(c) dt, err := getDriveType(drive[:]) if err != nil { if err == errNoRootDir { continue } return nil, fmt.Errorf("error getting type of: %s: %s", syscall.UTF16ToString(drive[:]), err) } if dt != driveFixed { continue } drives = append(drives, syscall.UTF16ToString(drive[:])) } return drives, nil}func main() { drives, err := getFixedDOSDrives() if err != nil { log.Fatal(err) } for _, drive := range drives { log.Println(drive) }}按盒子运行(在 Wine 4.0 下)我得到:tmp$ GOOS=windows go build drvs.go tmp$ wine64 ./drvs.exe0009:fixme:process:SetProcessPriorityBoost (0xffffffffffffffff,1): stub2020/07/06 21:06:02 C:\2020/07/06 21:06:02 D:\2020/07/06 21:06:02 X:\2020/07/06 21:06:02 Z:\(所有驱动器都使用 映射winecfg。)这种方法的问题是:即使系统中存在的 DOS 驱动器的数量远小于 ASCII 大写字母的数量,它也会执行 26 次系统调用。在当今的 Windows 系统上,驱动器可能会映射到常规目录下——很像在 POSIX 系统上,因此它根本没有 DOS 驱动器号。Eryk Sun 准确地暗示了对此应该采取的措施。我将尝试提供一个考虑到这些因素的适当(尽管更复杂)的解决方案。这是代码的要点。GetDriveTypeW文档。希望这会让您对 Win32 API 如何工作以及如何从 Go 使用它感兴趣。试着syscall在你的 Go 安装中查看包的来源——再加上 MSDN 文档,这应该是有启发性的。
Cats萌萌
基于 Eryk Sun 在评论中建议的改进方法。编码package mainimport ( "errors" "log" "strings" "syscall" "unsafe")func main() { mounts, err := getFixedDriveMounts() if err != nil { log.Fatal(err) } for _, m := range mounts { log.Println("volume:", m.volume, "mounts:", strings.Join(m.mounts, ", ")) }}var ( kernel32 = syscall.NewLazyDLL("kernel32.dll") findFirstVolumeWProc = kernel32.NewProc("FindFirstVolumeW") findNextVolumeWProc = kernel32.NewProc("FindNextVolumeW") findVolumeCloseProc = kernel32.NewProc("FindVolumeClose") getVolumePathNamesForVolumeNameWProc = kernel32.NewProc("GetVolumePathNamesForVolumeNameW") getDriveTypeWProc = kernel32.NewProc("GetDriveTypeW"))const guidBufLen = syscall.MAX_PATH + 1func findFirstVolume() (uintptr, []uint16, error) { const invalidHandleValue = ^uintptr(0) guid := make([]uint16, guidBufLen) handle, _, err := findFirstVolumeWProc.Call( uintptr(unsafe.Pointer(&guid[0])), uintptr(guidBufLen*2), ) if handle == invalidHandleValue { return invalidHandleValue, nil, err } return handle, guid, nil}func findNextVolume(handle uintptr) ([]uint16, bool, error) { const noMoreFiles = 18 guid := make([]uint16, guidBufLen) rc, _, err := findNextVolumeWProc.Call( handle, uintptr(unsafe.Pointer(&guid[0])), uintptr(guidBufLen*2), ) if rc == 1 { return guid, true, nil } if err.(syscall.Errno) == noMoreFiles { return nil, false, nil } return nil, false, err}func findVolumeClose(handle uintptr) error { ok, _, err := findVolumeCloseProc.Call(handle) if ok == 0 { return err } return nil}func getVolumePathNamesForVolumeName(volName []uint16) ([][]uint16, error) { const ( errorMoreData = 234 NUL = 0x0000 ) var ( pathNamesLen uint32 pathNames []uint16 ) pathNamesLen = 2 for { pathNames = make([]uint16, pathNamesLen) pathNamesLen *= 2 rc, _, err := getVolumePathNamesForVolumeNameWProc.Call( uintptr(unsafe.Pointer(&volName[0])), uintptr(unsafe.Pointer(&pathNames[0])), uintptr(pathNamesLen), uintptr(unsafe.Pointer(&pathNamesLen)), ) if rc == 0 { if err.(syscall.Errno) == errorMoreData { continue } return nil, err } pathNames = pathNames[:pathNamesLen] break } var out [][]uint16 i := 0 for j, c := range pathNames { if c == NUL && i < j { out = append(out, pathNames[i:j+1]) i = j + 1 } } return out, nil}func getDriveType(rootPathName []uint16) (int, error) { rc, _, _ := getDriveTypeWProc.Call( uintptr(unsafe.Pointer(&rootPathName[0])), ) dt := int(rc) if dt == driveUnknown || dt == driveNoRootDir { return -1, driveTypeErrors[dt] } return dt, nil}var ( errUnknownDriveType = errors.New("unknown drive type") errNoRootDir = errors.New("invalid root drive path") driveTypeErrors = [...]error{ 0: errUnknownDriveType, 1: errNoRootDir, })const ( driveUnknown = iota driveNoRootDir driveRemovable driveFixed driveRemote driveCDROM driveRamdisk driveLastKnownType = driveRamdisk)type fixedDriveVolume struct { volName string mountedPathnames []string}type fixedVolumeMounts struct { volume string mounts []string}func getFixedDriveMounts() ([]fixedVolumeMounts, error) { var out []fixedVolumeMounts err := enumVolumes(func(guid []uint16) error { mounts, err := maybeGetFixedVolumeMounts(guid) if err != nil { return err } if len(mounts) > 0 { out = append(out, fixedVolumeMounts{ volume: syscall.UTF16ToString(guid), mounts: LPSTRsToStrings(mounts), }) } return nil }) if err != nil { return nil, err } return out, nil}func enumVolumes(handleVolume func(guid []uint16) error) error { handle, guid, err := findFirstVolume() if err != nil { return err } defer func() { err = findVolumeClose(handle) }() if err := handleVolume(guid); err != nil { return err } for { guid, more, err := findNextVolume(handle) if err != nil { return err } if !more { break } if err := handleVolume(guid); err != nil { return err } } return nil}func maybeGetFixedVolumeMounts(guid []uint16) ([][]uint16, error) { paths, err := getVolumePathNamesForVolumeName(guid) if err != nil { return nil, err } if len(paths) == 0 { return nil, nil } var lastErr error for _, path := range paths { dt, err := getDriveType(path) if err == nil { if dt == driveFixed { return paths, nil } return nil, nil } lastErr = err } return nil, lastErr}func LPSTRsToStrings(in [][]uint16) []string { if len(in) == 0 { return nil } out := make([]string, len(in)) for i, s := range in { out[i] = syscall.UTF16ToString(s) } return out}(这是此代码的要点。)在具有 4 个驱动器的 Wine 4.0(使用`winecfg 配置)下,我有:tmp$ GOOS=windows go build fvs.gotmp$ wine64 ./fvs.exe 0009:fixme:process:SetProcessPriorityBoost (0xffffffffffffffff,1): stub2020/07/09 22:48:25 volume: \\?\Volume{00000000-0000-0000-0000-000000000043}\ mounts: C:\2020/07/09 22:48:25 volume: \\?\Volume{00000000-0000-0000-0000-000000000044}\ mounts: D:\2020/07/09 22:48:25 volume: \\?\Volume{00000000-0000-0000-0000-00000000005a}\ mounts: Z:\2020/07/09 22:48:25 volume: \\?\Volume{169203c7-20c7-4ca6-aaec-19a806b9b81e}\ mounts: X:\代码滚动如下:枚举系统中的所有卷(不是 DOS“驱动器”)。对于每个卷,它会查询挂载该卷的路径名列表(如果有)。对于每个这样的路径名,它会尝试获取它的类型——看看它是否是固定的。好的一面正如我在另一个答案中所述,这种方法的好处是,该代码应该能够检测到非驱动器安装——也就是说,作为目录安装的卷,而不是 DOS 设备。缺点代码比较复杂。结果,它实际上产生了一个两级结构(深度为 2 的树):一个固定卷的列表,每个卷都包含其挂载的列表。唯一明显的问题是它可能更难处理,但你可以自由地伪造它,以便它返回一个平面的坐骑列表。可能的问题刚才我看到了两个,不幸的是我没有容易访问的运行 Windows 的机器来检查。第一个问题是我希望调用kernel32!GetDriveTypeW对卷名起作用——那些\\?\Volume{169203c7-20c7-4ca6-aaec-19a806b9b81e}\由卷枚举 API 调用返回的样式的东西,但事实并非如此——总是返回DRIVE_UNKNOWN错误代码。因此,在我的代码中,我什至没有尝试在卷名上调用它,而是直接通过 查询卷的安装kernel32!GetVolumePathNamesForVolumeNameW,然后尝试获取它们的驱动器类型。我不知道为什么它会这样工作。可能——只是可能——这是我用来测试的 Wine 4.0 中的一个错误,但不太可能。另一个问题是我不知道如何在Wine下创建一个新的非驱动卷挂载,老实说我没有时间去了解。因此,目录挂载也可能kernel32!GetDriveTypeW失败(并且仅适用于“DOS 驱动器”挂载)。MSDN 对这些问题保持沉默,所以我只是不知道它应该如何表现。如果我是你,我可能会在 Windows 机器上进行一些测试;-)