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

标签 go createfile unsafe-pointers

我正在尝试使用Golang 1.14中的windows.CreateFile()函数创建文件(有关引用,请参阅https://godoc.org/golang.org/x/sys/windows#CreateFilehttps://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew)。
除了代码工作之外,我显然为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)
}

我正在使用非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变量。任何的想法? (先感谢您)

最佳答案

问题

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

在多个级别上不正确:
  • Go中的字符串是一个struct类型的值,包含两个字段:指向字符串数据第一个字节的指针和一个包含字符串长度(以字节为单位)的整数-基本上是这样定义的:

    type string struct {
        ptr *byte
        len int
    }
    

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

    要获取字符串数据的第一个字节的地址,可以执行&filename[0]。但这在您的情况下仍然是不正确的-与我同在。
  • Go 字符串包含不透明字节。

    Go中有几个地方确实采用了Go字符串的特定编码(即UTF-8,这就是您在Go中的任何教程资料中都会读到的),但实际上它们可能包含不透明字节,使用任何编码或不使用编码进行编码全部。
    这意味着将字符串重新编码为某种目标编码的方式必须视情况而定-考虑到源字符串的编码。

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

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

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

    我认为到现在应该很明显,对指向UTF-8编码的字符串的指针进行“重新解释”强制转换为指向UTF-16编码的字符串的指针不会对该字符串的内容产生任何作用:它们将保持UTF-8编码。

  • 解决方案

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

    尽管您可以如上所述滚动自己的实现,但是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.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终止符。

    关于go - 如何在Golang中将* uint16指针传递给windows.CreateFile(),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61524403/

    相关文章:

    javascript - 使用 JS 和 html5 从字符串创建文本文件

    file - FILE_FLAG_POSIX_SEMANTICS 有什么作用?

    c指针递减安全/不安全?

    Swift 4.1 取消初始化和解除分配(容量 :) deprecated

    ios - 在 Swift 中从 NSData 读取时的 EXC_ARM_DA_ALIGN

    乱码 : Join with changeable 'where' conditions

    go - Gitlab-CI 运行器 : ignore self-signed certificate

    go - 与多个包共享全局定义的 db conn

    windows - 是否存在函数 CreateFile 返回 INVALID_HANDLE_VALUE 而 GetLastError() 返回 ERROR_ALREADY_EXISTS 的情况

    linux - 从 TCP 连接获取 HTTP header