golang: вызов winapi с параметром struct

#windows #winapi #networking #go #dll

#Windows #winapi #сеть #Вперед #dll

Вопрос:

Я пытаюсь вызвать WinHttpGetIEProxyConfigForCurrentUser функцию, чтобы получить автоматически определяемые настройки прокси-сервера IE. Он принимает параметр inout struct в соответствии с документацией. Я использую следующий код:

 func GetProxySettings() {
    winhttp, _ := syscall.LoadLibrary("winhttp.dll")
    getIEProxy, _ := syscall.GetProcAddress(winhttp, "WinHttpGetIEProxyConfigForCurrentUser")

    settings := new(WINHTTP_CURRENT_USER_IE_PROXY_CONFIG)
    var nargs uintptr = 1

    ret, _, callErr := syscall.Syscall(uintptr(getIEProxy), nargs, uintptr(unsafe.Pointer(amp;settings)), 0, 0)
    fmt.Println(ret, callErr)
    if settings != nil {
        fmt.Println(settings.fAutoDetect)
        fmt.Println(settings.lpszAutoConfigUrl)
        fmt.Println(settings.lpszProxy)
        fmt.Println(settings.lpszProxyBypass)
    }
}

type WINHTTP_CURRENT_USER_IE_PROXY_CONFIG struct {
    fAutoDetect       bool
    lpszAutoConfigUrl string
    lpszProxy         string
    lpszProxyBypass   string
}
  

Похоже, что вызов выполнен успешно, settings не равен нулю, но как только я получаю к нему доступ, у меня возникает паника. Вот результат:

 1 The operation completed successfully.
panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x0 addr=0x1 pc=0x4d2bb4]
  

Комментарии:

1. Строка Go — это не то же самое, что строка C, и в этом случае документы выглядят так, как будто поля должны быть широкими символами (?), Поэтому они, вероятно, должны быть *uint16 вместо string . Вы также передаете указатель на указатель на системный вызов, для которого требуется только один указатель. избавьтесь от лишнего amp; в аргументах.

2. Спасибо за совет. Как мне преобразовать *uint16 в строку go? Я обнаружил UTF16PtrFromString , что делаю обратное преобразование, но ничего не нужно конвертировать обратно.

Ответ №1:

Вам нужно передать указатель на выделенную вами структуру, которую вы уже создали с new помощью функции. Удалите лишнее amp; из системного вызова; uintptr(unsafe.Pointer(settings))

Вам также необходимо иметь структуру, которая имеет тот же макет, что и структура C, ожидаемая системным вызовом. Определение структуры выглядит следующим образом:

 typedef struct {
  BOOL   fAutoDetect;
  LPWSTR lpszAutoConfigUrl;
  LPWSTR lpszProxy;
  LPWSTR lpszProxyBypass;
} WINHTTP_CURRENT_USER_IE_PROXY_CONFIG;
  

Который должен переводиться в

 type WINHTTP_CURRENT_USER_IE_PROXY_CONFIG struct {
    fAutoDetect       bool
    lpszAutoConfigUrl *uint16
    lpszProxy         *uint16
    lpszProxyBypass   *uint16
}
  

Каждое из этих LPWSTR полей будет строкой с нулевым завершением, 16 бит / символ. Для Windows обычно можно использовать функции системного UTF16 вызова, но здесь нам сначала нужно преобразовать *uint16 в []uint16 фрагмент, а затем декодировать этот фрагмент в строку utf8. Функция, которую для этого использует системный вызов pakcage, не экспортируется, но мы можем легко скопировать ее:

 // utf16PtrToString is like UTF16ToString, but takes *uint16
// as a parameter instead of []uint16.
// max is how many times p can be advanced looking for the null terminator.
// If max is hit, the string is truncated at that point.
func utf16PtrToString(p *uint16, max int) string {
    if p == nil {
        return ""
    }
    // Find NUL terminator.
    end := unsafe.Pointer(p)
    n := 0
    for *(*uint16)(end) != 0 amp;amp; n < max {
        end = unsafe.Pointer(uintptr(end)   unsafe.Sizeof(*p))
        n  
    }
    s := (*[(1 << 30) - 1]uint16)(unsafe.Pointer(p))[:n:n]
    return string(utf16.Decode(s))
}
  

Или используйте экспортированную версию, которая была недавно добавлена в golang.org/x/sys/windows , windows.UTF16PtrToString .

Комментарии:

1. Совет профессионала: преобразование строк намного проще syscall.UTF16ToString .

2. @Rodrigo: это не делает его намного проще, он выполняет здесь только половину работы по декодированию utf16, вам все еще нужно *uint16 выполнить []uint16 преобразование в, что и делает неэкспортируемая utf16PtrToString функция. Теперь, когда я думаю об этом, я должен просто скопировать эту функцию, поскольку она не оставляет предотвращение переполнения в качестве упражнения для читателя.

Ответ №2:

Здесь вы берете адрес адреса:

 // The type of settings is *WINHTTP_CURRENT_USER_IE_PROXY_CONFIG
settings := new(WINHTTP_CURRENT_USER_IE_PROXY_CONFIG)

// ...

// The type of amp;settings is **WINHTTP_CURRENT_USER_IE_PROXY_CONFIG
ret, _, callErr := syscall.Syscall(uintptr(getIEProxy), nargs, uintptr(unsafe.Pointer(amp;settings)), 0, 0)
  

Удалите amp; Syscall вызов from, и он должен работать.

Комментарии:

1. Вы правы. Однако все еще существует проблема с типом lpszProxy . string кажется, это неправильно. Я пытался *uint16 , но не могу найти способ преобразовать его в строку.