简体   繁体   中英

golang: winapi call with struct parameter

I'm trying to call WinHttpGetIEProxyConfigForCurrentUser function to get the automatically detected IE proxy settings. It accepts an inout struct parameter according to documentation . I'm using following code:

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(&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
}

It looks like the call is successful, settings is not nil, but as soon as I access it I get panic. Here's the output:

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

You need to pass a pointer to your allocated struct, which you already created with the new function. Remove the extra & from the syscall; uintptr(unsafe.Pointer(settings))

You also need to have a struct that has the same layout as the C struct expected by the syscall. The struct definition looks like:

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

Which should translate to

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

Each of those LPWSTR fields is going to be a null-terminated, 16bit/char string. For windows you can generally use the syscall UTF16 functions, but here we first need to convert the *uint16 to a []uint16 slice, then decode that slice to a utf8 string. There function that the syscall pakcage uses to do this isn't exported, but we can easily copy that:

// 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 && 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))
}

Or use the exported version that was recently added to golang.org/x/sys/windows , windows.UTF16PtrToString .

You take the address of an address here:

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

// ...

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

Remove the & from Syscall call an it should work.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM