Ruby and Unicode Win32 MessageBoxes

Have you ever needed to display Unicode characters in a Win32 MessageBox from a Ruby script? My pair and I needed to do just that and so I thought I would share what we found.

There are a number of ways to access Win32 calls from a Ruby script. The code we were working with did the following:

1
2
3
4
5
6
7
8
9
require 'dl'
def show_message_box(message, title)
  mb_ok = 0
  mb_iconexclamation = 48
  
  user32 = DL.dlopen("user32")
  message_box = user32['MessageBoxA', 'ILSSI']
  message_box.call(0, message, title, mb_ok | mb_iconexclamation)
end

That works well until you need to display Unicode characters in the MessageBox. It turns out the MessageBoxA version of the function is for ASCII characters. There is another version of the API call, MessageBoxW, that can handle Unicode, or wide characters. So the issue becomes converting your Ruby string into a wide string so it can be passed to MessageBoxW. The MultiByteToWideChar Win32 call can do this for you. And the windows-pr gem adds a nice ruby wrapper around the function.


gem install windows-pr
1
2
3
4
5
require 'windows/unicode' 
include Windows::Unicode 
$KCODE='UTF8'

str = multi_to_wide("This is a test")

This seemed to work quite well for the most part. However, from time to time we would see garbage text showing up at the end of our messages. It could easily be reproduced if the message was very short.

Having done enough C/C++ coding to recognize a string that was not being null terminated, we experimented with adding null characters to the end of the string. It turns out a wide null terminator (”\0\0”) is needed. The following code will properly display Unicode characters in a Win32 MessageBox:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
require 'dl'
require 'windows/unicode' 
include Windows::Unicode 
$KCODE='UTF8'

def win32_wide(text)
  multi_to_wide(text) + "\0\0"
end

def show_message_box(message, title)
  mb_ok = 0
  mb_iconexclamation = 48
  
  user32 = DL.dlopen("user32")
  message_box = user32['MessageBoxW', 'ILSSI']
  message_box.call(0, win32_wide(message), win32_wide(title), 
                             mb_ok | mb_iconexclamation)
end

show_message_box "MessageBox displayed from Ρουμπίνι", "¡Alert!"
Conversation
  • Lee Winder says:

    One problem I’ve encountered with this is that the dialog box can quite easily get lost, simply because the user is doing something else while the script is running the background and their window takes focus over the dialog.

    Do you know of a way to make thie Ruby dialog have properties such as always on top etc.?

    I know in the Win32 API there is a owner window, so could it possible be hooked into that?

    One other thought is that I am calling the script from a C# application. Is it possible to use the Window Handle of that in the Ruby script?

    Thanks
    Lee

  • Lee,

    The application I was working on was for a kiosk, so the issue of the dialog getting lost among other windows was not a problem I encountered.

    You might be able to find the handle of your C# application window by iterating over the system’s open windows with something like the EnumWindows win32 API call. The README for the win32-api project of win32utils (http://rubyforge.org/projects/win32utils) has an example of doing this in ruby.

    Passing that handle to message box might accomplish what you area looking for.

  • Lee Winder says:

    Thanks for the reply.

    I was hoping to be able to try this soon but my schedule is getting in the way. I’ll try what you suggested next time I have the time to work on the scripts and I’ll see what happens :)

    Thanks again

  • Comments are closed.