| package stringbuffer |
| |
| import ( |
| "sync" |
| "unicode/utf16" |
| ) |
| |
| // TODO: worth exporting and using in mkwinsyscall? |
| |
| // Uint16BufferSize is the buffer size in the pool, chosen somewhat arbitrarily to accommodate |
| // large path strings: |
| // MAX_PATH (260) + size of volume GUID prefix (49) + null terminator = 310. |
| const MinWStringCap = 310 |
| |
| // use *[]uint16 since []uint16 creates an extra allocation where the slice header |
| // is copied to heap and then referenced via pointer in the interface header that sync.Pool |
| // stores. |
| var pathPool = sync.Pool{ // if go1.18+ adds Pool[T], use that to store []uint16 directly |
| New: func() interface{} { |
| b := make([]uint16, MinWStringCap) |
| return &b |
| }, |
| } |
| |
| func newBuffer() []uint16 { return *(pathPool.Get().(*[]uint16)) } |
| |
| // freeBuffer copies the slice header data, and puts a pointer to that in the pool. |
| // This avoids taking a pointer to the slice header in WString, which can be set to nil. |
| func freeBuffer(b []uint16) { pathPool.Put(&b) } |
| |
| // WString is a wide string buffer ([]uint16) meant for storing UTF-16 encoded strings |
| // for interacting with Win32 APIs. |
| // Sizes are specified as uint32 and not int. |
| // |
| // It is not thread safe. |
| type WString struct { |
| // type-def allows casting to []uint16 directly, use struct to prevent that and allow adding fields in the future. |
| |
| // raw buffer |
| b []uint16 |
| } |
| |
| // NewWString returns a [WString] allocated from a shared pool with an |
| // initial capacity of at least [MinWStringCap]. |
| // Since the buffer may have been previously used, its contents are not guaranteed to be empty. |
| // |
| // The buffer should be freed via [WString.Free] |
| func NewWString() *WString { |
| return &WString{ |
| b: newBuffer(), |
| } |
| } |
| |
| func (b *WString) Free() { |
| if b.empty() { |
| return |
| } |
| freeBuffer(b.b) |
| b.b = nil |
| } |
| |
| // ResizeTo grows the buffer to at least c and returns the new capacity, freeing the |
| // previous buffer back into pool. |
| func (b *WString) ResizeTo(c uint32) uint32 { |
| // allready sufficient (or n is 0) |
| if c <= b.Cap() { |
| return b.Cap() |
| } |
| |
| if c <= MinWStringCap { |
| c = MinWStringCap |
| } |
| // allocate at-least double buffer size, as is done in [bytes.Buffer] and other places |
| if c <= 2*b.Cap() { |
| c = 2 * b.Cap() |
| } |
| |
| b2 := make([]uint16, c) |
| if !b.empty() { |
| copy(b2, b.b) |
| freeBuffer(b.b) |
| } |
| b.b = b2 |
| return c |
| } |
| |
| // Buffer returns the underlying []uint16 buffer. |
| func (b *WString) Buffer() []uint16 { |
| if b.empty() { |
| return nil |
| } |
| return b.b |
| } |
| |
| // Pointer returns a pointer to the first uint16 in the buffer. |
| // If the [WString.Free] has already been called, the pointer will be nil. |
| func (b *WString) Pointer() *uint16 { |
| if b.empty() { |
| return nil |
| } |
| return &b.b[0] |
| } |
| |
| // String returns the returns the UTF-8 encoding of the UTF-16 string in the buffer. |
| // |
| // It assumes that the data is null-terminated. |
| func (b *WString) String() string { |
| // Using [windows.UTF16ToString] would require importing "golang.org/x/sys/windows" |
| // and would make this code Windows-only, which makes no sense. |
| // So copy UTF16ToString code into here. |
| // If other windows-specific code is added, switch to [windows.UTF16ToString] |
| |
| s := b.b |
| for i, v := range s { |
| if v == 0 { |
| s = s[:i] |
| break |
| } |
| } |
| return string(utf16.Decode(s)) |
| } |
| |
| // Cap returns the underlying buffer capacity. |
| func (b *WString) Cap() uint32 { |
| if b.empty() { |
| return 0 |
| } |
| return b.cap() |
| } |
| |
| func (b *WString) cap() uint32 { return uint32(cap(b.b)) } |
| func (b *WString) empty() bool { return b == nil || b.cap() == 0 } |