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

Rod*_*igo 2 unsafe-pointers go createfile

我正在尝试使用windows.CreateFile()函数创建文件(有关参考,请参阅https://godoc.org/golang.org/x/sys/windows#CreateFilehttps://docs.microsoft.com/en-us/windows/ win32/api/fileapi/nf-fileapi-createfilew ) 在 Golang 1.14 中。除了代码有效之外,我显然file NameCreateFile().

代码是:

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)
}
Run Code Online (Sandbox Code Playgroud)

我得到了一个用非 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
...
Run Code Online (Sandbox Code Playgroud)

此外,这个名称在每次运行中都不同,所以我认为我没有正确指向我的filename变量。任何的想法?(先感谢您)

kos*_*tix 6

问题

var filename string = "test_file"
strptr := &filename
fileNamePtr := (*uint16)(unsafe.Pointer(strptr))
Run Code Online (Sandbox Code Playgroud)

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

  1. Go 中的字符串是一个struct包含两个字段的类型化值:一个指向字符串数据第一个字节的指针和一个包含字符串长度(以字节为单位)的整数——基本上它的定义如下:

    type string struct {
        ptr *byte
        len int
    }
    
    Run Code Online (Sandbox Code Playgroud)

    因此,获取 Go 字符串变量的地址就是获取内存中包含指向字符串数据的指针的位置的地址(ptr上面的字段)。

    要获取字符串数据的第一个字节的地址可以执行&filename[0]. 但这在你的情况下仍然是不正确的——请耐心等待。

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

    Go 中有几个地方确实假设了 Go 字符串的特定编码——即UTF-8,这就是你在 Go 中的任何教程材料中都会读到的——但实际上它们可能包含不透明的字节,使用任何编码或不编码编码。
    这意味着必须根据具体情况决定将字符串重新编码为某种目标编码的方式——考虑源字符串的编码。

    幸运的是,您的特殊情况是最简单的情况。
    由于 Go 源代码文件被定义为以 UTF-8 编码,因此定义为字符串文字的 Go 字符串(并且您的filename变量被分配了由字符串文字定义的值)以 UTF-8 编码。

    UTF-8 是一种可变长度编码,每个编码的 Unicode 代码点使用 1 到 4 个字节,具体取决于其整数值。

    您打算调用的 Win32 API 函数需要一个以UTF-16编码的字符串。
    UTF-16 是一种固定长度的编码,它编码的每个 Unicode 代码点使用 2 个字节。

    我认为现在很明显,将指向 UTF-8 编码字符串的指针“重新解释”转换为指向 UTF-16 编码字符串的指针不会对该字符串的内容做任何事情:它们将保持以 UTF-8 编码。

解决方案

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

虽然您可以如上所述推出自己的实现,但 Go 已经在其内置syscall包中以

func UTF16FromString(s string) ([]uint16, error)
Run Code Online (Sandbox Code Playgroud)

功能。

所以你的代码应该变成这样

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

windows.CreateFile(&u16fname[0], ...)
Run Code Online (Sandbox Code Playgroud)

请注意,您可能会syscall通过阅读go doc syscall.

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

请注意,https: //golang.org/pkg/syscall 呈现了 的文档GOOS=linux,因此当您想使用特定于 Windows 的 stdlib 代码时,阅读它是没有用的。


如果您好奇,在您的情况下,当您将指针值的地址传递给 时CreateFileW,该函数开始将从 64 位指针值的第一个字节开始的原始内存解释为四个连续的 UTF-16-编码的字符然后它继续到包含该值的字符串值的长度字段0x0000000000000009- 字符串“test_file”的长度(以字节为单位) - 所以CreateFileW读取第一个0x0009,将其解释为 TAB 字符,然后停止,0x0000因为它是一个 UTF -16 编码的 NUL(终止“宽”Win32 API 中的字符串)。
它也可能设法提前停止——这取决于指针的实际值:如果它0x0000在其高位字中,则该值已用作 NUL 终止符。

  • @Rodrigo,我已经修复了代码示例:它应该是“windows.CreateFile(&amp;u16fname[0], ...)”——这样您就可以获得结果切片的第一个元素的地址。很抱歉造成混乱。(如果没有“[0]”位,您将获得切片值本身的地址,这与字符串非常相似,是带有指针和两个整数的“struct”类型。) (2认同)