In this post, I’ll show you how to use the winapi crate to display a simple MessageBox on Windows. I’ll also describe the process I used to come up with the code. The end result should look something like this:
To get started, let’s create a new project using Cargo:
cargo new --bin hello_world
This will create a new folder called hello_world
in your current working directory. The --bin
argument tells Cargo that compiling this project will result in an executable (.exe). We now need to figure out what API to call to get a MessageBox. I googled “Windows MessageBox api” and found the MessageBox() function on MSDN. The “Requirements” table states that this function exists in User32.dll
, so we’ll need to use the corresponding crate user32-sys. Inside the hello_world
folder, you will find a file called Cargo.toml
. Open that file in your favorite text editor. We’re going to add dependencies on the winapi and user32-sys crates. Find the line that reads [dependencies]
and add the following below it:
winapi = "0.2.7"
user32-sys = "0.2.0"
This tells Cargo that our project depends on the winapi and user32-sys crates. When we build our project, Cargo will automatically download those crates and build them for us.
Now that we have finished setting up the project metadata, we can start writing the code. Open the file src/main.rs
in your text editor. Currently it should look like this:
fn main() {
println!("Hello, world!");
}
The first thing we need to do is tell the Rust compiler that we want to use the winapi and user32-sys crates in this file. To do that, add the following lines to the top of the file:
extern crate user32;
extern crate winapi;
We now need to figure out what the function signature should look like in Rust. Here’s the signature in C:
int WINAPI MessageBox(
_In_opt_ HWND hWnd,
_In_opt_ LPCTSTR lpText,
_In_opt_ LPCTSTR lpCaption,
_In_ UINT uType
);
If you aren’t familiar with native Windows programming, this probably looks very strange to you. For simplicity, you can think of WINAPI
, _In_opt_
, and _In_
as attributes describing the things that follow their usage. Ignoring those, we get this:
int MessageBox(
HWND hWnd,
LPCTSTR lpText,
LPCTSTR lpCaption,
UINT uType
);
Well that looks a lot more like C. HWND
is a handle (pointer) to a window, LPCTSTR
is a const
string, and UINT
is, of course, an unsigned integer. You may be asking, “What kind of string is LPCTSTR
? ASCII or Unicode?” and the answer is slightly complicated. In normal Windows C/C++ programming, there is a preprocessor symbol called UNICODE
which determines whether a LPCTSTR
is a Unicode string or an ASCII string. This preprocessor symbol is also responsible for determining which MessageBox
function gets called (You can read more about this here). Looking further down on the MSDN page, we see that the Unicode and ASCII names for this function are MessageBoxW
and MessageBoxA
respectively. This is important because we will need to call the correct version of the function ourselves. In this example, we’ll call the ASCII version (MessageBoxA
)1.
We know from reading the documentation that the first parameter is a pointer to the MessageBox’s parent window and that we can simply pass NULL
since the parameter is optional (_In_opt_
). We also know that the next two parameters are ASCII strings for the text and title of the MessageBox. The final parameter is a bit flag of display options which control which buttons show up and what icon to use. The documentation contains a list of constants we can choose but for this example, we’ll use MB_OK
to get an “Ok” button and MB_ICONINFORMATION
to set the icon to the informational exclamation point. Since this is a bit flag, we’ll combine those values with the bitwise OR operator (|
) to get a single value. In pseudo-code, this looks like:
MessageBoxA(
NULL,
"Hello, world!",
"MessageBox Example",
MB_OK | MB_ICONINFORMATION
);
At this point, we need to figure a few things out in order to write our code in Rust:
- Where are
MessageBoxA
,MB_OK
, andMB_ICONINFORMATION
declared? We need to know this so we can import them. - What is the equivalent of
NULL
? Idiomatically this would beNone
but we actually need a raw pointer. - How do we pass a Rust string into a C function?
To find the answer to #1, I opened the documentation for winapi and used the search box at the top to look for those names. I found them in user32
(for MessageBoxA
) and winapi::winuser
(for MB_OK
and MB_ICONINFORMATION
). Let’s add use
statements for those to our main.rs
file right after the extern crate
lines at the top:
use user32::MessageBoxA;
use winapi::winuser::{MB_OK, MB_ICONINFORMATION};
This brings those names into scope and allows us to use them without prefixes in the rest of this file.
On to #2. A quick Google search shows that we should use the std::ptr
module to get a NULL
pointer. Since the winapi crate defines MessageBoxA
as taking a HWND
value which is a mut
pointer, we’ll use the std::ptr::null_mut()
function.
Finally, #3. The standard library’s ffi
module already has a CString
type for precisely this purpose. We simply need to create a new value of this type and use the .as_ptr()
function to get a raw pointer to pass to the C API.
Putting this all together, we’ll add a use
statement for CString
and an unsafe
block to call MessageBoxA
because it is marked unsafe
resulting in:
extern crate user32;
extern crate winapi;
use std::ffi::CString;
use user32::MessageBoxA;
use winapi::winuser::{MB_OK, MB_ICONINFORMATION};
fn main() {
let lp_text = CString::new("Hello, world!").unwrap();
let lp_caption = CString::new("MessageBox Example").unwrap();
unsafe {
MessageBoxA(
std::ptr::null_mut(),
lp_text.as_ptr(),
lp_caption.as_ptr(),
MB_OK | MB_ICONINFORMATION
);
}
}
You can find all of the code from this post in this repository. Thanks for reading!
- Read the follow up post to see how to use the Unicode version of this API. [return]