1

I'm writing CGO bindings for a library which uses LPCWSTR for all of its string types. How do I convert from C.LPCWSTR to string and vice-versa?

Ilia Choly
  • 18,070
  • 14
  • 92
  • 160

3 Answers3

2

You should be able to "cast" the LPCWSTR as a []uint16, and use the utf16 package to decode the characters

// take a C.wstr pointer, and convert it to a go slice
// `sz` is the length of the LPCWSTR
wstr := (*[1 << 30-1]uint16)(unsafe.Pointer(C.wstr))[:sz:sz]
runes := utf16.Decode(wstr)
goString := string(runes)

You generally don't want to be passing Go pointers into your C code, so when converting from a string to an LPCWSTR, you will want to alloc the memory in C. A solution for converting from a Go string s might look like:

func Encode(s string) C.LPCWSTR {
    wstr := utf16.Encode([]rune(s))

    p := C.calloc(C.size_t(len(wstr)+1), C.sizeof_uint16_t)
    pp := (*[1 << 30]uint16)(p)
    copy(pp[:], wstr)

    return (C.LPCWSTR)(p)
}

There may also be some MFC macros that could help convert to and from cstrings which you could take advantage of with simple wrapper functions in C. This way you could easily copy the data in and out just using the builtin C.CString and C.GoString functions.

JimB
  • 104,193
  • 13
  • 262
  • 255
  • 1
    Does this look right? https://gist.github.com/icholy/f8a07551c5aa3b97521767b1a3223275 – Ilia Choly Aug 15 '16 at 14:04
  • 1
    @iliacholy: added some more info. You probably want to allocate the memory in C, since it may not always be safe to pass that pointer from go into your C code. – JimB Aug 16 '16 at 00:49
  • 1
    The C.calloc() needs to be matched by a C.free() to avoid memory leaks (as with any memory management, one would need to be aware of the object's life cycle and careful not to de-allocate the memory too early) – David Airapetyan Sep 16 '20 at 21:54
1

If you are sure input does not contain null bytes, you can do the encoding yourself:

import (
   // #include <windows.h>
   "C"
   "unicode/utf16"
   "unsafe"
)

func PtrFromString(s string) C.LPCWSTR {
   r := []rune(s + "\x00")
   e := utf16.Encode(r)
   p := unsafe.Pointer(&e[0])
   return (C.LPCWSTR)(p)
}

https://golang.org/pkg/unicode/utf16

Zombo
  • 1
  • 62
  • 391
  • 407
0

As an alternative to allocating memory in C, one could just call syscall.UTF16PtrFromString:

func Encode(s string) C.LPCWSTR {
    ptr, _ := syscall.UTF16PtrFromString(s)
    return C.LPCWSTR(unsafe.Pointer(ptr))
}

This assumes that the API you're trying to call (supposedly a win32 API since you're using an LPCWSTR) does not making any copies of the string pointers. If this is indeed the case, not allocating C memory should be safe if the GoLang's code scope is appropriate.

For example, something along the lines of the code below should be okay:

func DoSomeWindowsStuff(arg string) {
  CallWin32API(Encode(arg))
}

Here the memory allocated for the string should survive until CallWin32API() returns.

David Airapetyan
  • 5,301
  • 4
  • 40
  • 62