Как передать указатель * uint16 на windows .CreateFile () в Golang - PullRequest
2 голосов
/ 30 апреля 2020

Я пытаюсь создать файл, используя функцию windows.CreateFile() (для справки см. https://godoc.org/golang.org/x/sys/windows#CreateFile и https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew) в Golang 1.14. Помимо кода работает, я явно передаю неверный параметр для 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 chars (в данном случае 庡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. Есть идеи? (заранее спасибо)

Ответы [ 2 ]

5 голосов
/ 30 апреля 2020

Проблема

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

неверна на нескольких уровнях:

  1. Строка в Go - это значение типа struct, содержащее два поля: указатель на первый байт данных строки и целое число, содержащее длину строки (в байтах) - в основном это определяется так:

    type string struct {
        ptr *byte
        len int
    }
    

    Следовательно, беря адрес Go Строковая переменная принимает адрес ячейки памяти, в которой содержится этот указатель на данные строки (поле ptr выше).

    Чтобы получить адрес первого байта данных строки можно было бы сделать &filename[0]. Но это все равно неправильно в вашем случае - потерпите меня.

  2. Go строки содержат непрозрачные байты.

    В Go есть несколько мест, которые do предполагает определенную кодировку Go строк, а именно UTF-8 , и это то, что вы прочитали бы в любом учебном материале в Go, но на самом деле они могут содержать непрозрачные байты , закодированный с использованием любой кодировки или вообще без кодирования.
    Это означает, что способ перекодирования строки в некоторую целевую кодировку должен решаться в каждом конкретном случае с учетом кодирования исходной строки.

    К счастью, ваш конкретный случай является самым простым.
    Так как Go файлы исходного кода определены для кодирования в UTF-8, Go строки, которые были определены как строковые литералы (и ваш filename переменной присваивается значение, определенное строковым литералом), кодируются в кодировке UTF-8.

    UTF-8 представляет собой кодировку переменной длины , которая использует от 1 до 4 байтов на кодированный Кодовая точка Unicode - dep заканчивая его целочисленным значением.

    Функция Win32 API, которую вы намереваетесь вызывать, хочет, чтобы строка была закодирована в UTF-16 .
    UTF-16 - это кодировка фиксированной длины, которая использует 2 байт на кодовую точку Unicode, которую он кодирует.

    Думаю, к настоящему времени должно быть очевидно, что создание «переинтерпретации» приведения указателя, указывающего на строку в кодировке UTF-8, к указателю, указывающему на UTF-16 -кодированная строка не будет ничего делать с содержимым этой строки: они останутся закодированными в UTF-8.

Решение

Итак, сначала необходимо выполнить правильное преобразование: подсчитать количество кодовых точек Unicode («рун»), содержащихся в исходной строке, выделить в два раза больше байтов для новой строки, а затем выполнить итерации по рунам в исходной строке по одному -one, правильное кодирование каждого в строку назначения (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.org/pkg/syscall отображает документацию для GOOS=linux, поэтому бесполезно читать, когда вы хотите использовать Windows -specifi c stdlib code.


Если вы ' Интересно, что в вашем случае, когда вы передали адрес значения указателя в CreateFileW, эта функция начала интерпретировать необработанную память, начиная с 1-го байта значения указателя 64-бит, как четыре последовательных UTF-16- затем закодированные символы перешли к полю длины строкового значения, которое содержало значение 0x0000000000000009 - длину строки «test_file» в байтах, - поэтому CreateFileW прочитал первый 0x0009, интерпретировал его как символ TAB и затем остановился на 0x0000, поскольку это NUL в кодировке UTF-16 (w он завершает строки в «широком» Win32 API).
Возможно, ему также удалось остановить раньше - в зависимости от фактического значения указателя: если в верхнем слове было 0x0000, это значение служило бы NUL -terminator.

2 голосов
/ 30 апреля 2020

Ссылаясь на это ...

В Windows некоторые процедуры, принимающие строковые аргументы, имеют два варианта: один для кодировки ANSI и один для UTF- 16 закодированных строк. Независимо от того, что вы выберете, ни один из этих типов строк напрямую не совместим со строками Go. Чтобы использовать их, вам нужно создать совместимые строки.

Вы можете использовать что-то подобное для преобразования Go строк в строки UTF-16 с нулевым символом в конце.

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

Слово предостережения (из "Go Proverbs" Роба Пайка)

С небезопасным пакетом нет никаких гарантий.

...