1

Shell_NotifyIcon Windows API sends a message to the taskbar's status area.

I'm looking a way to make Windows notification API works with UTF-8 characters.

The code below works well, but only with ANSI characters.

The UTF-8 Characters are displayed as "??".

Option Explicit
Private Declare PtrSafe Function Shell_NotifyIconA Lib "shell32.dll" (ByVal dwMessage As Long, ByRef nfIconData As NOTIFYICONDATA) As LongPtr
Public nfIconData As NOTIFYICONDATA
Private Type NOTIFYICONDATA
    cbSize As Long
    hwnd As LongPtr
    uID As Long
    uFlags As Long
    uCallbackMessage As Long
    hIcon As LongPtr
    szTip As String * 128
    dwState As Long
    dwStateMask As Long
    szInfo As String * 256
    uTimeout As Long
    szInfoTitle As String * 64
    dwInfoFlags As Long
End Type
Public Function toast(Optional ByVal title As String, Optional ByVal info As String, Optional ByVal flag As Long)
With nfIconData
    .dwInfoFlags = flag
    .uFlags = &H10
    .szInfoTitle = title
    .szInfo = info
    .cbSize = &H1F8
End With
Shell_NotifyIconW &H0, nfIconData
Shell_NotifyIconW &H1, nfIconData
End Function
'Flags for the balloon message..
'None = 0
'Information = 1
'Exclamation = 2
'Critical = 3
Sub TestANSI()
toast "Hi"    
End Sub

Sub testUTF8()
toast ChrW(55357) & ChrW(56397)
End Sub

Simply changing the A to W in windows API declaration doesn't fix it.

Anyone have experience using Shell_NotifyIconW for UTF-8 charachters?

I've tried with the code below, but it doesn't work.

Option Explicit
Private Declare PtrSafe Function Shell_NotifyIconW Lib "shell32.dll" (ByVal dwMessage As Long, ByRef nfIconData As NOTIFYICONDATA) As LongPtr
Public nfIconData As NOTIFYICONDATA
Private Type NOTIFYICONDATA
    cbSize As Long
    hwnd As LongPtr
    uID As Long
    uFlags As Long
    uCallbackMessage As Long
    hIcon As LongPtr
    szTip As String * 128
    dwState As Long
    dwStateMask As Long
    szInfo As String * 256
    uTimeout As Long
    szInfoTitle As String * 64
    dwInfoFlags As Long
End Type
Public Function toast(Optional ByVal title As String, Optional ByVal info As String, Optional ByVal flag As Long)
With nfIconData
    .dwInfoFlags = flag
    .uFlags = &H10
    .szInfoTitle = title
    .szInfo = info
    .cbSize = &H1F8
End With
Shell_NotifyIconW &H0, nfIconData
Shell_NotifyIconW &H1, nfIconData
End Function
'Flags for the balloon message..
'None = 0
'Information = 1
'Exclamation = 2
'Critical = 3

Sub TestANSI()
toast "Hi"    
End Sub
Sub testUTF8()
toast ChrW(55357) & ChrW(56397)
End Sub
  • 1
    Convert your input to UTF-16 using [MultiByteToWideChar](https://learn.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-multibytetowidechar) and call `Shell_NotifyIconW`. – IInspectable Mar 21 '21 at 18:27
  • 1
    On Windows 10 build 1903 and later, you can use UTF-8 with `A` APIs, but only if you first opt-in to that behavior via global OS setting or app-specific manifest. Since VB strings are naturally Unicode, stick with the `W` APIs. – Remy Lebeau Mar 21 '21 at 18:32

1 Answers1

2

Unicode and UTF-8 are not interchangeable terms (please see https://www.joelonsoftware.com/articles/Unicode.html). One is a concept, and the other is a way of persisting that concept.

W functions in Windows use Unicode in the form of UTF-16, so you want to provide Unicode to them in that fashion.

Both your A and W versions are not declared correctly, you want to fix that first.
Then, for the W function, you have to avoid the implicit string conversion VB performs, for which you need to declare the fixed length strings as byte arrays. Because VB strings are already Unicode in UTF-16, which is what W functions expect, you simply need to copy the bytes from these strings into these byte arrays. No conversion is necessary, but you need to be careful to leave the last two bytes as zeroes in each string.

Note that unlike the A function, the W function requires you to provide both the title and the info (which is called out in the documentation). If you only provide one, it won't show.

Also note that unfortunately VBA does not provide correct padding for structure members in 64 bits which heavily undermines the usefulness of LongPtr, so you would need to provide that yourself.

Option Explicit

Private Declare PtrSafe Function Shell_NotifyIconW Lib "shell32.dll" (ByVal dwMessage As Long, ByRef nfIconData As NOTIFYICONDATAW) As Long
Private Declare PtrSafe Sub CopyMemory Lib "kernel32.dll" Alias "RtlMoveMemory" (ByRef Destination As Any, ByRef Source As Any, ByVal Length As Long)

Private Type NOTIFYICONDATAW
  cbSize As Long
#If Win64 Then
  padding1 As Long
#End If
  hwnd As LongPtr
  uID As Long
  uFlags As Long
  uCallbackMessage As Long
#If Win64 Then
  padding2 As Long
#End If
  hIcon As LongPtr
  szTip(1 To 128 * 2) As Byte
  dwState As Long
  dwStateMask As Long
  szInfo(1 To 256 * 2) As Byte
  uTimeout As Long
  szInfoTitle(1 To 64 * 2) As Byte
  dwInfoFlags As Long
End Type

Private Const NIM_ADD As Long = &H0&
Private Const NIM_MODIFY As Long = &H1&
Private Const NIF_INFO As Long = &H10&

Private Function Min(ByVal a As Long, ByVal b As Long) As Long
  If a < b Then Min = a Else Min = b
End Function

Public Sub Toast(Optional ByVal title As String, Optional ByVal info As String, Optional ByVal flag As Long)
  Dim nfIconData As NOTIFYICONDATAW
  
  With nfIconData
    .cbSize = Len(nfIconData)
    
    .uFlags = NIF_INFO
    .dwInfoFlags = flag
    
    If Len(title) > 0 Then
      CopyMemory ByVal VarPtr(.szInfoTitle(LBound(.szInfoTitle))), ByVal StrPtr(title), Min(Len(title) * 2, UBound(.szInfoTitle) - LBound(.szInfoTitle) + 1 - 2)
    End If
    
    If Len(info) > 0 Then
      CopyMemory ByVal VarPtr(.szInfo(LBound(.szInfo))), ByVal StrPtr(info), Min(Len(info) * 2, UBound(.szInfo) - LBound(.szInfo) + 1 - 2)
    End If
  End With
  
  Shell_NotifyIconW NIM_ADD, nfIconData
  Shell_NotifyIconW NIM_MODIFY, nfIconData
End Sub
Toast ChrW$(55357) & ChrW$(56397), ChrW$(55357) & ChrW$(56397)
GSerg
  • 76,472
  • 17
  • 159
  • 346
  • hi @GSerg Only worked when I hardcoded `cbsize` as 952 value. Do you know why?Thank you so much for you help and explanation. – Rafael Carvalho Mar 21 '21 at 23:02
  • 1
    @RafaelCarvalho 952 would be the size of the `W` structure in 32 bits without the last member (`hBalloonIcon`), but with the preceding `guidItem` (which is missing in your definition). You should not tell the OS that you have provided more members that you actually have, this leads to garbage memory reads. I don't see how this size (which is bigger than the current 936) would be allowed while 936 would not. 936 certainly works on Windows XP, 7 and 10 in 32-bit. What OS and what Office bitness are you using? – GSerg Mar 21 '21 at 23:18
  • I'm currently using Microsoft Windows 10 Pro 64 bits and Office 2013 64 bits – Rafael Carvalho Mar 21 '21 at 23:27
  • @RafaelCarvalho Indeed, 952 is the `NOTIFYICONDATAW_V2_SIZE`, which is the size of the structure for x64 without the last two members (as declared in your question). Yes, VBA in x64 [does not add padding](https://stackoverflow.com/a/17156922/11683) where it's due. You would need two versions of the structure, for 32 and 64 bits. – GSerg Mar 21 '21 at 23:52
  • Thank you very much @GSerg it works perfectly now. – Rafael Carvalho Mar 22 '21 at 01:15