2 回答
TA贡献1840条经验 获得超5个赞
好的,我决定抛开我的 Win32 API 编程技能,准备一个解决方案。
基于您提到的线程的la脚方法的解决方案如下:
package main
import (
"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.exe
0009:fixme:process:SetProcessPriorityBoost (0xffffffffffffffff,1): stub
2020/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 文档,这应该是有启发性的。
TA贡献1805条经验 获得超9个赞
基于 Eryk Sun 在评论中建议的改进方法。
编码
package main
import (
"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 + 1
func 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.go
tmp$ wine64 ./fvs.exe
0009:fixme:process:SetProcessPriorityBoost (0xffffffffffffffff,1): stub
2020/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 机器上进行一些测试;-)
- 2 回答
- 0 关注
- 72 浏览
添加回答
举报