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
失?。ú⑶覂H適用于“DOS 驅動器”掛載)。
MSDN 對這些問題保持沉默,所以我只是不知道它應該如何表現。如果我是你,我可能會在 Windows 機器上進行一些測試;-)
- 2 回答
- 0 關注
- 91 瀏覽
添加回答
舉報