Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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]
like image 804
rosencreuz Avatar asked Oct 10 '16 14:10

rosencreuz


1 Answers

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.

like image 86
JimB Avatar answered Oct 04 '22 01:10

JimB