1 package stringbuffer 2 3 import ( 4 "sync" 5 "unicode/utf16" 6 ) 7 8 // TODO: worth exporting and using in mkwinsyscall? 9 10 // Uint16BufferSize is the buffer size in the pool, chosen somewhat arbitrarily to accommodate 11 // large path strings: 12 // MAX_PATH (260) + size of volume GUID prefix (49) + null terminator = 310. 13 const MinWStringCap = 310 14 15 // use *[]uint16 since []uint16 creates an extra allocation where the slice header 16 // is copied to heap and then referenced via pointer in the interface header that sync.Pool 17 // stores. 18 var pathPool = sync.Pool{ // if go1.18+ adds Pool[T], use that to store []uint16 directly 19 New: func() interface{} { 20 b := make([]uint16, MinWStringCap) 21 return &b 22 }, 23 } 24 25 func newBuffer() []uint16 { return *(pathPool.Get().(*[]uint16)) } 26 27 // freeBuffer copies the slice header data, and puts a pointer to that in the pool. 28 // This avoids taking a pointer to the slice header in WString, which can be set to nil. 29 func freeBuffer(b []uint16) { pathPool.Put(&b) } 30 31 // WString is a wide string buffer ([]uint16) meant for storing UTF-16 encoded strings 32 // for interacting with Win32 APIs. 33 // Sizes are specified as uint32 and not int. 34 // 35 // It is not thread safe. 36 type WString struct { 37 // type-def allows casting to []uint16 directly, use struct to prevent that and allow adding fields in the future. 38 39 // raw buffer 40 b []uint16 41 } 42 43 // NewWString returns a [WString] allocated from a shared pool with an 44 // initial capacity of at least [MinWStringCap]. 45 // Since the buffer may have been previously used, its contents are not guaranteed to be empty. 46 // 47 // The buffer should be freed via [WString.Free] 48 func NewWString() *WString { 49 return &WString{ 50 b: newBuffer(), 51 } 52 } 53 54 func (b *WString) Free() { 55 if b.empty() { 56 return 57 } 58 freeBuffer(b.b) 59 b.b = nil 60 } 61 62 // ResizeTo grows the buffer to at least c and returns the new capacity, freeing the 63 // previous buffer back into pool. 64 func (b *WString) ResizeTo(c uint32) uint32 { 65 // allready sufficient (or n is 0) 66 if c <= b.Cap() { 67 return b.Cap() 68 } 69 70 if c <= MinWStringCap { 71 c = MinWStringCap 72 } 73 // allocate at-least double buffer size, as is done in [bytes.Buffer] and other places 74 if c <= 2*b.Cap() { 75 c = 2 * b.Cap() 76 } 77 78 b2 := make([]uint16, c) 79 if !b.empty() { 80 copy(b2, b.b) 81 freeBuffer(b.b) 82 } 83 b.b = b2 84 return c 85 } 86 87 // Buffer returns the underlying []uint16 buffer. 88 func (b *WString) Buffer() []uint16 { 89 if b.empty() { 90 return nil 91 } 92 return b.b 93 } 94 95 // Pointer returns a pointer to the first uint16 in the buffer. 96 // If the [WString.Free] has already been called, the pointer will be nil. 97 func (b *WString) Pointer() *uint16 { 98 if b.empty() { 99 return nil 100 } 101 return &b.b[0] 102 } 103 104 // String returns the returns the UTF-8 encoding of the UTF-16 string in the buffer. 105 // 106 // It assumes that the data is null-terminated. 107 func (b *WString) String() string { 108 // Using [windows.UTF16ToString] would require importing "golang.org/x/sys/windows" 109 // and would make this code Windows-only, which makes no sense. 110 // So copy UTF16ToString code into here. 111 // If other windows-specific code is added, switch to [windows.UTF16ToString] 112 113 s := b.b 114 for i, v := range s { 115 if v == 0 { 116 s = s[:i] 117 break 118 } 119 } 120 return string(utf16.Decode(s)) 121 } 122 123 // Cap returns the underlying buffer capacity. 124 func (b *WString) Cap() uint32 { 125 if b.empty() { 126 return 0 127 } 128 return b.cap() 129 } 130 131 func (b *WString) cap() uint32 { return uint32(cap(b.b)) } 132 func (b *WString) empty() bool { return b == nil || b.cap() == 0 } 133