如何在Golang中传递*uint16指针到windows.CreateFile()?

问题描述 投票:0回答:1

我试图用 windows.CreateFile() 功能(参考请看 https:/godoc.orggolang.orgxsyswindows#CreateFile。https:/docs.microsoft.comen-uswindowswin32apifileapinf-fileapi-createfilew。 ),除了代码可以正常工作外,我显然是给 file Name 的属性 CreateFile().

代码是。

package main

import (
    "unsafe"

    "golang.org/x/sys/windows"
)

func main() {
    var (
        nullHandle windows.Handle
        filename   string = "test_file"
    )

    strptr := &filename
    fileNamePtr := (*uint16)(unsafe.Pointer(strptr))
    dwShareMode := uint32(windows.FILE_SHARE_READ | windows.FILE_SHARE_WRITE | windows.FILE_SHARE_DELETE)
    dwFlagsAndAttributes := uint32(windows.FILE_FLAG_DELETE_ON_CLOSE)

    windows.CreateFile(fileNamePtr, windows.GENERIC_WRITE, dwShareMode, nil, windows.CREATE_NEW, dwFlagsAndAttributes, nullHandle)
}

我得到了一个用非ascii字符创建的文件(在本例中是指 庡R)

Directory of C:\Users\rodrigo\src\delete_on_close

04/30/2020  03:15 PM    <DIR>          .
04/30/2020  03:15 PM    <DIR>          ..
04/30/2020  03:12 PM               715 main.go
04/30/2020  03:14 PM         2,698,240 __debug_bin
04/30/2020  03:15 PM                 0 庡R
               3 File(s)      2,698,955 bytes
...

此外,这个名字在每次运行中都会有所不同,所以我认为我没有正确地指向我的 filename 变量。有什么办法吗?(先谢谢你)

go createfile unsafe-pointers
1个回答
5
投票

问题

var filename string = "test_file"
strptr := &filename
fileNamePtr := (*uint16)(unsafe.Pointer(strptr))

在多个层面上是不正确的。

  1. 在围棋中,一个字符串是一个 struct类型的值,包含两个字段:一个指向字符串数据的第一个字节的指针和一个包含字符串长度的整数(以字节为单位)--基本上是这样定义的。

    type string struct {
        ptr *byte
        len int
    }
    

    基本上它的定义是这样的: 取一个围棋的字符串变量的地址就是取内存中那个字符串数据指针所在的位置的地址(the ptr 字段)。)

    要获取字符串的第一个字节的地址,必须在字符串的 数据。 会的 &filename[0]. 但在你的情况下,这仍然是不正确的--请耐心听我说。

  2. Go的字符串包含不透明的字节。

    在围棋中,有几个地方 假设围棋字符串的特定编码--即。UTF-8这就是你在任何围棋教程中读到的内容,但实际上它们可能包含不透明的字节,使用任何编码或根本没有编码。这意味着将一个字符串重新编码为某种目标编码的方法必须根据具体情况来决定--同时考虑到源字符串的编码。

    幸运的是,您的特殊情况是最简单的。由于围棋源代码文件被定义为UTF-8编码,所以被定义为字符串字面的围棋字符串(以及您的 filename 变量被分配一个由字符串文字定义的值)都是以UTF-8编码的。

    UTF-8是一个 长短不一 编码,每个编码的Unicode码点使用1到4个字节--取决于其整数值。

    你想调用的Win32 API函数希望字符串用以下方式编码。UTF-16. UTF-16是一种固定长度的编码,每个Unicode码点使用2个字节。

    我想现在应该很清楚,把指向UTF-8编码字符串的指针 "重新解释 "为指向UTF-16编码字符串的指针,不会对 其内容 的字符串:它们将保持UTF-8编码。

解决方案

因此,你首先需要进行适当的转换:计算源字符串中包含的Unicode码点("符文")的数量,为新的字符串分配两倍的字节数,然后逐个遍历源字符串中的符文,将每个符文正确地编码到目标字符串中(Windows使用UTF-16的小字节格式)。

虽然您可以按照上面的描述推出您自己的实现,但 Go 已经在其内置的 syscall 的形式的包。

func UTF16FromString(s string) ([]uint16, error)

函数。

所以你的代码应该是这样的

u16fname, err := syscall.UTF16FromString(filename)
if err != nil {
  // fail
}

windows.CreateFile(&u16fname[0], ...)

请注意,你可能会看到,在 syscall 包的输出,读取 go doc syscall.

如果你不在目标操作系统上,请执行 GOOS=windows go doc syscall.

并注意到 https:/golang.orgpkgsyscall。 的文件。GOOS=linux所以当你想使用Windows特有的stdlib代码时,读它是没有用的。


如果你很好奇,在你的情况下,当你把一个指针值的地址传给了 CreateFileW该函数从64位指针值的第1个字节开始,将原始内存解释为4个连续的UTF-16编码字符,然后转到字符串值的长度字段,该字段包含的值是 0x0000000000000009 - 字符串 "test_file "的长度,以字节为单位,-所以 CreateFileW 初读 0x0009,将其解释为TAB字符,然后停在了 0x0000 因为它是一个UTF-16编码的NUL(在 "宽 "的Win32 API中终止字符串)。它也可能会设法提前停止--取决于指针的实际值:如果它有 0x0000 在其上位词中,该值曾作为一个NUL-终结符。


2
投票

参照这个...

在Windows中,一些接受字符串参数的程序有两种变体:一种是ANSI编码的,另一种是UTF-16编码的字符串。无论您选择哪一种,这两种字符串类型都不能直接与围棋字符串兼容。为了使用它们,你需要构建兼容的字符串。

你可以使用类似这样的方法将围棋字符串转换为空端UTF-16字符串。

func StringToUTF16Ptr(str string) *uint16 {
    wchars := utf16.Encode([]rune(str + "\x00"))    
    return &wchars[0]
}

警告(摘自Rob Pike的 "Go Proverbs")。

有了不安全的包袱就没有保障。

© www.soinside.com 2019 - 2024. All rights reserved.