我正在尝试使用Golang 1.14中的windows.CreateFile()
函数创建文件(有关引用,请参阅https://godoc.org/golang.org/x/sys/windows#CreateFile和https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-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
变量。任何的想法? (先感谢您)
最佳答案
问题
var filename string = "test_file"
strptr := &filename
fileNamePtr := (*uint16)(unsafe.Pointer(strptr))
在多个级别上不正确:
struct
类型的值,包含两个字段:指向字符串数据第一个字节的指针和一个包含字符串长度(以字节为单位)的整数-基本上是这样定义的:type string struct {
ptr *byte
len int
}
因此,采用Go的字符串变量的地址就是采用内存中包含指向字符串数据的指针的位置的地址(上述
ptr
字段)。要获取字符串数据的第一个字节的地址,可以执行
&filename[0]
。但这在您的情况下仍然是不正确的-与我同在。 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/