VBA is a language that's lacking a lot of basic functionality. (Pun intended)
Most libraries, if they exist in the first place, are OS-specific, and even some of the inbuilt functions don't work on MacOS or have bugs in any environment.
Recently I've found that the StrConv(s, vbUnicode)
function transcoding a string to the VBA-internal UTF-16-LE encoding doesn't properly work in all environments and for all Unicode codepoints. Therefore, I simply implemented my own function for this task. (SO)
Since this is a fairly low-level task, I was trying to be efficient and to make the code as performant as possible by working on a single byte-array buffer and avoiding things like string concatenation which may be tempting here.
I ended up with this code which pretty much fits my needs. I'm sure though, there are still opportunities for improvement, and I have been asked to post it here for review so here you go.
Function DecodeUTF8(ByVal utf8str As String) As String
Dim utf8() As Byte, utf16() As Byte, i As Long, j As Long
Dim codePoint As Long, cpIndex As Long, numBytesOfCodePoint As Long
utf8 = utf8str
ReDim utf16(0 To (UBound(utf8) - LBound(utf8) + 1) * 2)
i = LBound(utf8): cpIndex = 0
Do While i <= UBound(utf8)
'Determine the number of bytes in the current UTF-8 codepoint
numBytesOfCodePoint = 1
If utf8(i) And &H80 Then
If utf8(i) And &H20 Then
If utf8(i) And &H10 Then
numBytesOfCodePoint = 4
Else: numBytesOfCodePoint = 3: End If
Else: numBytesOfCodePoint = 2: End If
End If
'Check if the UTF-8 codepoint is incomplete at the end of the string
If i + numBytesOfCodePoint - 1 > UBound(utf8) Then Err.Raise 5, _
"DecodeUtf8", _
"Incomplete UTF-8 codepoint at the end of the input string."
'Calculate the Unicode codepoint value from the UTF-8 bytes
If numBytesOfCodePoint = 1 Then
codePoint = utf8(i)
Else
codePoint = utf8(i) And (2 ^ (7 - numBytesOfCodePoint) - 1)
For j = 1 To numBytesOfCodePoint - 1
codePoint = (codePoint * 64) + (utf8(i + j) And &H3F)
Next j
End If
'Convert the Unicode codepoint to UTF-16LE bytes
If codePoint < &H10000 Then
utf16(cpIndex) = CByte(codePoint And &HFF&)
utf16(cpIndex + 1) = CByte(codePoint \ &H100&)
cpIndex = cpIndex + 2
Else 'Calculate surrogate pair
Dim m As Long, lowSurrogate As Long, highSurrogate As Long
m = codePoint - &H10000
'(m \ &H400&) = most significant 10 bits of m
highSurrogate = &HD800& + (m \ &H400&)
'(m And &H3FF) = least significant 10 bits of m
lowSurrogate = &HDC00& + (m And &H3FF)
'Concatenate highSurrogate and lowSurrogate as UTF-16LE bytes
utf16(cpIndex) = CByte(highSurrogate And &HFF&)
utf16(cpIndex + 1) = CByte(highSurrogate \ &H100&)
utf16(cpIndex + 2) = CByte(lowSurrogate And &HFF&)
utf16(cpIndex + 3) = CByte(lowSurrogate \ &H100&)
cpIndex = cpIndex + 4
End If
i = i + numBytesOfCodePoint 'Move to the next UTF-8 codepoint
Loop
ReDim Preserve utf16(cpIndex - 1)
DecodeUTF8 = utf16
End Function