任务是使用Go查询Windows API并获取输入设备的系统名称.

当从Go程序调用GetRawInputDeviceInfA函数时,我得到错误"Invalid access to memory location".下面是终端消息:

错误:对内存位置的访问无效.类型:1描述符:65599

以下是完整的代码:

package main

import (
    "fmt"
    "syscall"
    "unsafe"
)

// defining an imitation RAWINPUTDEVICELIST
type rawInputDeviceList struct {
    DeviceHandle uint64
    Type         uint32
}

const RIDI_DEVICENAME uint32 = 0x20000007

var (
    user32                    = syscall.NewLazyDLL("user32.dll")
    getRawInputDeviceListProc = user32.NewProc("GetRawInputDeviceList")
    getRawInputDeviceInfoProc = user32.NewProc("GetRawInputDeviceInfoA")
)

func main() {
    dl := rawInputDeviceList{}
    size := uint32(unsafe.Sizeof(dl))

    var devCount uint32
    _ = getRawInputDeviceList(nil, &devCount, size)

    if devCount > 0 {
        devices := make([]rawInputDeviceList, size*devCount)

        for i := 0; i < int(devCount); i++ {
            devices[i] = rawInputDeviceList{}
        }

        err := getRawInputDeviceList(&devices[0], &devCount, size)
        if err != nil {
            fmt.Printf("Error: %v", err)
        }
        uiCommand := RIDI_DEVICENAME
        var pData uint32
        for i := 0; i < int(devCount); i++ {
            err := getRawInputDeviceInfo(&devices[i].DeviceHandle, &uiCommand, &pData, 0)
            if err != nil {
                fmt.Printf("Error: %v", err)
            }

            fmt.Printf("Type: %v Descriptor: %v \n", devices[i].Type, devices[i].DeviceHandle)

        }

    }
}

func getRawInputDeviceList(
    rawInputDeviceList *rawInputDeviceList,
    numDevices *uint32,
    size uint32,
) error {
    _, _, err := getRawInputDeviceListProc.Call(
        uintptr(unsafe.Pointer(rawInputDeviceList)),
        uintptr(unsafe.Pointer(numDevices)),
        uintptr(size))
    if err != syscall.Errno(0) {
        return err
    }

    return nil
}

// Get device info
func getRawInputDeviceInfo(
    deviceHandle *uint64,
    uiCommand *uint32,
    pData *uint32,
    size uint32,
) error {
    _, _, err := getRawInputDeviceInfoProc.Call(
        uintptr(unsafe.Pointer(deviceHandle)),
        uintptr(unsafe.Pointer(uiCommand)),
        uintptr(unsafe.Pointer(pData)),
        uintptr(size))
    if err != syscall.Errno(0) {
        return err
    }

    return nil
}

我认为句柄指的是真实设备的地址,并不理解是什么生成了这样的消息.有没有人能帮帮我?

附注: 我重写了函数头,出现了一条错误消息.

func getRawInputDeviceInfo(
    deviceHandle uintptr,
    uiCommand *uint32,
    pData uintptr,
    size uint32,
) error {
    _, _, err := getRawInputDeviceInfoProc.Call(
        deviceHandle,
        uintptr(unsafe.Pointer(uiCommand)),
        pData,
        uintptr(size))
    if err != syscall.Errno(0) {
        return err
    }

    return nil
}

函数调用

err := getRawInputDeviceInfo(devices[i].DeviceHandle, &uiCommand, pData, size)

现在错误信息是

Error: The parameter is incorrect.Type: 1 Descriptor: 65596
Error: The parameter is incorrect.Type: 0 Descriptor: 65594

推荐答案

好吧,你的更新代码仍然包含几个问题,我已经在我的 comments 中提到的问题的线程.

以下是修改后的工作版本:

package main

import (
    "fmt"
    "log"
    "syscall"
    "unsafe"
)

// defining an imitation RAWINPUTDEVICELIST
type rawInputDeviceList struct {
    DeviceHandle uintptr
    Type         uint32
}

const RIDI_DEVICENAME uint32 = 0x20000007

var (
    user32                     = syscall.NewLazyDLL("user32.dll")
    getRawInputDeviceListProc  = user32.NewProc("GetRawInputDeviceList")
    getRawInputDeviceInfoWProc = user32.NewProc("GetRawInputDeviceInfoW")
)

func main() {
    log.SetFlags(0)

    size := uint32(unsafe.Sizeof(rawInputDeviceList{}))

    var devCount uint32
    if err := getRawInputDeviceList(nil, &devCount, size); err != nil {
        log.Fatal(err)
    }

    if devCount == 0 {
        log.Fatal("no devices")
    }

    devices := make([]rawInputDeviceList, devCount)
    if err := getRawInputDeviceList(&devices[0], &devCount, size); err != nil {
        log.Fatal(err)
    }

    const RIDI_DEVICENAME uint32 = 0x20000007

    for di := range devices {
        var devName [256]uint16
        size := uint32(unsafe.Sizeof(devName))

        sizeOK, err := getRawInputDeviceInfo(
            devices[di].DeviceHandle,
            RIDI_DEVICENAME,
            unsafe.Pointer(&devName[0]),
            &size,
        )
        if err != nil {
            log.Fatal(err)
        }

        if !sizeOK {
            log.Fatalf("insufficient size; want %d\n", size)
        }

        fmt.Println(syscall.UTF16ToString(devName[:size]))
    }
}

func getRawInputDeviceList(
    rawInputDeviceList *rawInputDeviceList,
    numDevices *uint32,
    size uint32,
) error {
    r1, _, err := getRawInputDeviceListProc.Call(
        uintptr(unsafe.Pointer(rawInputDeviceList)),
        uintptr(unsafe.Pointer(numDevices)),
        uintptr(size))
    if r1 == uintptr(^uint32(0)) {
        return err
    }
    return nil
}

func getRawInputDeviceInfo(
    deviceHandle uintptr,
    uiCommand uint32,
    pData unsafe.Pointer,
    size *uint32,
) (bool, error) {
    r1, _, err := getRawInputDeviceInfoWProc.Call(
        uintptr(deviceHandle),
        uintptr(uiCommand),
        uintptr(pData),
        uintptr(unsafe.Pointer(size)))

    if err != syscall.Errno(0) {
        return true, err
    }

    return r1 > 0, nil
}

它打印(在Wine下):

\\?\HID#VID_845E&PID_0001#0&0000&0&0#{378de44c-56ef-11d1-bc8c-00a0c91405dd}
\\?\HID#VID_845E&PID_0002#0&0000&0&0#{884b96c3-56ef-11d1-bc8c-00a0c91405dd}

要点:

  • HANDLE‘S类型改为uintptr(Win32API将其定义为LPVOID).
  • 修复了getRawInputDeviceInfopDatasize的类型.
  • 修复了存储多个设备上的信息的切片大小的计算:GO切片的长度是其元素的计数,而不是它们总共占用的字节数.
  • 使用GetRawInputDeviceInfoW而不是*A版本来简化到Go-Native字符串的转换:它们以UTF-8编码,FooA函数返回使用当前活动代码页编码的字符串;尽管设备ID应该是全ASCII的,但使用返回UTF-16编码的字符串的"宽"调用更容易.它还使Windows不必执行重新编码这些字符串来将它们返回给我们.
  • 修复了对系统调用返回值的处理(有关详细信息,请参阅go doc syscall.Proc.Call).

请注意,我不处理getRawInputDeviceInfo生成超过255个字符的名称的情况.

一般来说,请参考有关实际调用的MSDN文档,并确保您的包装器遵循这些调用的类型规则,错误处理也是如此(Win32 API以其不一致的返回值规则而闻名).


一个不太明显的注意事项:never使用uintptr来存储指向内存的指针,直到您很好地理解发生了什么.它们的问题在于,它们不能算作对内存的实时引用--因为它们不是指针.这意味着一个看起来很天真的代码,就像

func Foo(ptr *Whatever) {
  var u uintptr = uintptr(unsafe.Pointer(ptr))
  // (1)
  someWin32Proc.Call(u)
}

可能会在运行时崩溃,因为在点"(1)",GC可能会启动并释放ptr所指向的Whatever的实例,如果这个变量是对它的最后一次引用,并且syscall将接收释放内存的地址-一个classic 的"释放后使用"问题.

因此,通常情况下,您只需在为调用计算参数的位置将GO指针转换为uintptr:GO有特殊的规则,确保在这种情况下源指针被视为活动的.

尽管如此,使用uintptrstoreHANDLE是可以的,因为它们引用了非GO内存.

Go相关问答推荐

Go PQ驱动程序无法使用默认架构进行查询

如何描述OpenAPI规范中围棋的数据类型.JSON?

带有一个新变量的Go冒号等于运算符

如何在Golang中获取mp3文件的持续时间?

如何使用工作区方法扩展克隆的Golang库

如何在VSCode中为特定的.go文件创建调试配置?

死锁 - 所有 goroutine 都处于睡眠状态(即使使用等待组)

如何测试 Zerolog 记录器引发类型错误的日志(log)事件?

Go安装成功但没有输出简单的Hello World

如何从 Go Lambda 函数返回 HTML?

当我的 go build 成功时,如何修复我的 docker build 失败? Dockerfile 包括 go mod 下载

gopacket:IP-in-IP 数据包上的解码层

无法从主域访问子域:无Access-Control-Allow-Origin

设置 graphql 的最大文件上传大小(golang)

AddE 上的 Apache Tinkerpop gremlin-go 驱动程序 Next() 返回E0903:没有剩余结果

Golang模板无法访问embedFS中的文件

无法访问 Go 模块导入的远程存储库

使用 Golang SQL 驱动程序连接到snowflake

每 N 秒运行一次函数,上下文超时

如何优雅地映射到 Go 中返回可变长度数组的方法?