Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

Win32 GUI Programming In Rust Language

4.94/5 (13 votes)
12 Dec 2016CPOL3 min read 81K   1K  
In this tip, we will see how we can use the Rust language to do GUI (Graphical User Interface) programming. As an example program, we will create a simple Window. Using Win32 API functions.

Introduction

Rust is a general-purpose, multi paradigm, compiled programming language. It is designed to be a fast, secured and type-safe language. The language is Open-Source and developed by Mozilla Research.

According to https://en.wikipedia.org/wiki/Rust_(programming_language) -

Although its development is sponsored by Mozilla, it is an open community project.

Rust supports both Functional and Object-Oriented programming. It supports C/C++ like pointer manipulation. So what we do in C/C++ language, we can do in Rust language, but in a very different way.

Rust Language Featuring (Taken From https://www.rust-lang.org/):

  • zero-cost abstractions
  • move semantics
  • guaranteed memory safety
  • threads without data races
  • trait-based generics
  • pattern matching
  • type inference
  • minimal runtime
  • efficient C bindings

In this tip, we will see how we can use this language to do GUI (Graphical User Interface) programming. As an example program, we will create a simple Window. Using Win32 API functions.

Using the Code

As far as I know, libraries are called Crates in Rust language. We are going to use various crates libraries in this project. libc, winapi, etc. Those crates are available in Rust crate host website (https://crates.io/).

Crates are automatically downloaded when you build your project using Cargo tool.

The Cargo is a tool for Rust language that helps in managing Rust projects. It comes with Rust official installer. The Cargo tool downloads all the dependencies needed for the project, builds dependencies and builds the project. Cargo tool helps programmer to build Rust project nicely. So we better use Cargo to make things easier.

To use the Cargo tool in our project, first we have to follow some rule. We have to create a root folder. The folder name should be your project name. Inside the root folder, we need to create an another folder called ‘src’ for keeping project source files. Because the Cargo tool expects the project source files inside a src directory and then, inside the root folder, we need to create a configuration file called ‘Cargo.toml’. The file is in TOML format which is similar to INI file but has some extra advantage.

Create the ‘Cargo.toml’ file and write the following lines of code to it:

[package]

name = "simple_window"
version = "0.0.1"
authors = [ "Your name <you@example.com>" ]

Then, add the following dependencies in the ‘Cargo.toml’ file:

[dependencies]

libc = "0.1.10"
winapi = "0.2.4"
user32-sys = "0.1.2"
kernel32-sys = "0.1.4"

First, we extern the following necessary crates in our main source file called ‘main.rs’. I assume that you are familiar with pure Win32 API functions:

C++
extern crate kernel32;
extern crate user32;
extern crate winapi;
extern crate libc;

Then we ‘use’ necessary types and functions from those libraries:

C++
use winapi::windef::HWND;
use winapi::windef::HMENU;
use winapi::windef::HBRUSH;
use winapi::minwindef::HINSTANCE;

use winapi::minwindef::UINT;
use winapi::minwindef::DWORD;
use winapi::minwindef::WPARAM;
use winapi::minwindef::LPARAM;
use winapi::minwindef::LRESULT;
use winapi::winnt::LPCWSTR;

use winapi::winuser::WS_OVERLAPPEDWINDOW;
use winapi::winuser::WS_VISIBLE;
use winapi::winuser::WNDCLASSW;

use std::os::windows::ffi::OsStrExt;
use std::ffi::OsStr;

The following function is used to convert normal string to wide string:

C++
fn to_wstring(str : &str) -> Vec<u16> {
    let v : Vec<u16> =
            OsStr::new(str).encode_wide().chain(Some(0).into_iter()).collect();
    v
}

This is our window message handler function. Currently, it only processes the WM_DESTROY message to exit our window properly on close event.

C++
pub unsafe extern "system" fn window_proc(h_wnd :HWND, 
	msg :UINT, w_param :WPARAM, l_param :LPARAM) -> LRESULT
{
    if msg == winapi::winuser::WM_DESTROY {
        user32::PostQuitMessage(0);
    }
    return user32::DefWindowProcW(h_wnd, msg, w_param, l_param);
}

You may have noticed that we have used an ‘unsafe’ keyword in the above codes. According to https://doc.rust-lang.org/book/unsafe.html:

Rust’s main draw is its powerful static guarantees about behavior. But safety checks are conservative by nature: there are some programs that are actually safe, but the compiler is not able to verify this is true. To write these kinds of programs, we need to tell the compiler to relax its restrictions a bit. For this, Rust has a keyword, unsafe. Code using unsafe has less restrictions than normal code does.

So that’s why we have to use the unsafe keyword.

The following function hides the Console Window since we are building a GUI application. (Actually, I don’t know how to use ‘subsytem’ with Cargo tool! If someone knows, then please comment to improve this tip. :-> )

C++
fn hide_console_window() 
{
    let window = unsafe {
        kernel32::GetConsoleWindow()
    };

    if window != std::ptr::null_mut() {
        unsafe {
            user32::ShowWindow (window, winapi::SW_HIDE)
        };
    }
}

The following is the entry point function of our program. We create our window inside this function by using the typical Win32 GUI programming style:

C++
fn main()
{
  // Here our unsafe code goes - 
  unsafe 
  {
    // First we hide the console window - 
    hide_console_window();

    // Then we initialize WNDCLASS structure - 

    let class_name = to_wstring("my_window");

    let wnd = WNDCLASSW {
        style: 0,
        lpfnWndProc: Some(window_proc), 
        cbClsExtra: 0,
        cbWndExtra: 0,
        hInstance: 0 as HINSTANCE,
        hIcon: user32::LoadIconW(0 as HINSTANCE, winapi::winuser::IDI_APPLICATION),
        hCursor: user32::LoadCursorW(0 as HINSTANCE, winapi::winuser::IDI_APPLICATION),
        hbrBackground: 16 as HBRUSH,
        lpszMenuName: 0 as LPCWSTR,
        lpszClassName: class_name.as_ptr(),
    };

    // We register our class - 
    user32::RegisterClassW(&wnd);

    let h_wnd_window = user32::CreateWindowExW(0, class_name.as_ptr(), 
                       to_wstring("Simple Window").as_ptr(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, 
                       0, 0, 400, 400, 0 as HWND, 0 as HMENU, 0 as HINSTANCE, std::ptr::null_mut());

    let mut msg = winapi::winuser::MSG {
        hwnd : 0 as HWND,
        message : 0 as UINT,
        wParam : 0 as WPARAM,
        lParam : 0 as LPARAM,
        time : 0 as DWORD,
        pt : winapi::windef::POINT { x: 0, y: 0, },
    };

    user32::ShowWindow(h_wnd_window, winapi::SW_SHOW);

    // Finally we run the standard application loop - 
    loop
    {   
        let pm = user32::GetMessageW(&mut msg, 0 as HWND, 0, 0);
        if pm == 0 {
            break;
        }

        if msg.message == winapi::winuser::WM_QUIT {
            break;
        }

        user32::TranslateMessage(&mut msg);
        user32::DispatchMessageW(&mut msg);
    }      
  }
}

Conclusion

Rust is a nice programming language. Programmers can learn this language to enhance their skill on programming.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)