如何优雅地从 `syscall.Syscall()` 的使用中迁移?

IT小君   2022-11-11T08:51:05

syscall软件包已弃用。假设我有以下代码,我想将其迁移到未弃用的代码:

someGoObject := &struct{int; float32}{5, 45.4}
syscall.Syscall6(someProc.Addr(), 1, uintptr(unsafe.Pointer(someGoObject)), 0, 0, 0, 0, 0)

其中someProc是类型*syscall.LazyProcWindows)。

文档建议改用的sys subrepositorysyscall不再提供类似于 的功能syscall.Syscall如果那里没有实现所需的功能,人们可能会尝试通过以下方式解决问题,并相信工作已经完成:

someProc.Call(uintptr(unsafe.Pointer(someGoObject)))

其中 nowsomeProc属于*windows.LazyProc.

但是,如果我们这样做,我们将不会得到syscall.Syscall“(和朋友)的保证之一,因为LazyProc.Call() 它没有在 assembly 中实现

(4) 在调用 syscall.Syscall 时将指针转换为 uintptr。

syscall 包中的 Syscall 函数将它们的 uintptr 参数直接传递给操作系统,然后操作系统可能会根据调用的细节将其中的一些重新解释为指针。也就是说,系统调用实现隐式地将某些参数从 uintptr 转换回指针。

如果必须将指针参数转换为 uintptr 以用作参数,则该转换必须出现在调用表达式本身中:

syscall.Syscall(SYS_READ, uintptr(fd), uintptr(unsafe.Pointer(p)), uintptr(n))

编译器在调用汇编中实现的函数的参数列表中处理转换为 uintptr 的指针,方法是安排引用的已分配对象(如果有)在调用完成之前保留并且不移动,即使仅从类型中它也是如此在通话期间似乎不再需要该对象。

为了让编译器识别这种模式,转换必须出现在参数列表中:

// INVALID: uintptr cannot be stored in variable
// before implicit conversion back to Pointer during system call.
u := uintptr(unsafe.Pointer(p))
syscall.Syscall(SYS_READ, uintptr(fd), u, uintptr(n))

(取自https://golang.org/pkg/unsafe/

在我看来,唯一安全的方法是使用 C 分配内存。但是,这意味着我们需要复制数据并增加需要编写的代码量。

someObject := (*struct{int; float32})(C.calloc(1, unsafe.Sizeof(struct{int; float32}{}))) // Alloc
*someObject = struct{int; float32}{123, 456.789} // Fill with desired data
someProc.Call(uintptr(unsafe.Pointer(someObject))) // The actual call
C.free(unsafe.Pointer(someObject)) // Clean up

有没有更好的办法?

点击广告,支持我们为你提供更好的服务
评论(1)
IT小君

我认为处理这个问题的正确方法是使用mkwinsyscall工具。你可以像这样创建一个 Go 文件:

package main
//go:generate mkwinsyscall -output zmsi.go msi.go
//sys MsiOpenDatabase(dbPath string, persist int, db *windows.Handle) (e error) = msi.MsiOpenDatabaseW

然后运行go generate,你会得到一个这样的结果文件(为简洁起见,删除了一些部分):

func MsiOpenDatabase(dbPath string, persist int, db *windows.Handle) (e error) {
    var _p0 *uint16
    _p0, e = syscall.UTF16PtrFromString(dbPath)
    if e != nil {
        return
    }
    return _MsiOpenDatabase(_p0, persist, db)
}

func _MsiOpenDatabase(dbPath *uint16, persist int, db *windows.Handle) (e error) {
    r0, _, _ := syscall.Syscall(procMsiOpenDatabaseW.Addr(), 3, uintptr(unsafe.Pointer(dbPath)), uintptr(persist), uintptr(unsafe.Pointer(db)))
    if r0 != 0 {
        e = syscall.Errno(r0)
    }
    return
}

如您所见,它会自动处理字符串转换、系统调用代码,甚至是错误处理。

2022-11-11T08:51:07   回复