11
22# Opening a Win32 Window
33
4- Alright, if we wanna draw a triangle, we have to have a window to draw the triangle in .
4+ On Windows, the C oriented API is called "Win32" .
55
6- Uh, how do we do that? For the sake of the lesson, let's imagine I don't know how to do that.
6+ There's also some C++ oriented APIs for interacting with Windows as well (called COM and WinRT).
7+
8+ It's * much* easier for Rust to bind to and interact with a C oriented API than a C++ oriented API,
9+ so we'll start with just using Win32 to interact with Windows.
710
811## Search The Web
912
10- Okay so we don't know what to do, let's ask the internet nicely.
13+ Okay, for the sake of the lesson let's pretend that * even I* don't know what to do.
14+
15+ Let's start by asking the internet nicely.
1116Something like [ "open a window win32"] ( https://duckduckgo.com/?q=open+a+window+win32 ) sounds right.
1217Hey look, that [ first result] ( https://docs.microsoft.com/en-us/windows/win32/learnwin32/creating-a-window ) is straight from Microsoft.
1318It's a whole little tutorial on how to open a window.
1419Perfect, just what we wanted.
1520
1621## Starting The Win32 Windowing Tutorial
1722
18- Alright, let 's read the first paragraph of the [ windowing tutorial] ( https://docs.microsoft.com/en-us/windows/win32/learnwin32/creating-a-window )
23+ Let 's read the first paragraph of the [ windowing tutorial] ( https://docs.microsoft.com/en-us/windows/win32/learnwin32/creating-a-window )
1924that we just found...
2025
2126To summarize the opening portion:
@@ -470,7 +475,9 @@ depending on how you're building the C++ program,
470475and also keeping in mind that we're always going to be using the `W` versions of things,
471476then the equivalent Rust would be something like this:
472477```rust
473- let mut wc: WNDCLASSW = unsafe { core::mem::zeroed() };
478+ fn main () {
479+ let mut wc: WNDCLASSW = unsafe { core::mem::zeroed() };
480+ }
474481```
475482We haven't even called the OS and we've already got ` unsafe ` stuff going on.
476483
@@ -519,7 +526,9 @@ It's a simple convention, but it keeps it obvious that the macro can go wrong if
519526
520527Now our rust can look like this:
521528``` rust
522- let mut wc = WNDCLASSW :: default ();
529+ fn main () {
530+ let mut wc = WNDCLASSW :: default ();
531+ }
523532```
524533And that's so much nicer, at least to my eyes.
525534
@@ -602,6 +611,7 @@ We do this with an [external block](https://doc.rust-lang.org/reference/items/ex
602611
603612An external block just declares the signature of a function, like this:
604613``` rust
614+ // EXAMPLE USAGE
605615extern ABI {
606616 fn NAME1 (args ) -> output ;
607617
@@ -622,6 +632,7 @@ But who tells the linker what to link with to find the external functions?
622632Well, you can use a build script, or you can put it right on the extern block.
623633
624634``` rust
635+ // EXAMPLE USAGE
625636#[link(name = " LibraryName" )]
626637extern ABI {
627638 fn NAME1 (args ) -> output ;
@@ -874,23 +885,27 @@ Hey, look, the MSDN docs are using some of that extended typography we mentioned
874885
875886Apparently we want our window creation to look something like this:
876887``` rust
877- let sample_window_name_wn = wide_null (" Sample Window Name" );
878- let hwnd = unsafe {
879- CreateWindowExW (
880- 0 ,
881- sample_window_class_wn . as_ptr (),
882- sample_window_name_wn . as_ptr (),
883- WS_OVERLAPPEDWINDOW ,
884- CW_USEDEFAULT ,
885- CW_USEDEFAULT ,
886- CW_USEDEFAULT ,
887- CW_USEDEFAULT ,
888- core :: ptr :: null_mut (),
889- core :: ptr :: null_mut (),
890- hInstance,
891- core :: ptr :: null_mut (),
892- )
893- };
888+ fn main () {
889+ // first register the class, as before
890+
891+ let sample_window_name_wn = wide_null (" Sample Window Name" );
892+ let hwnd = unsafe {
893+ CreateWindowExW (
894+ 0 ,
895+ sample_window_class_wn . as_ptr (),
896+ sample_window_name_wn . as_ptr (),
897+ WS_OVERLAPPEDWINDOW ,
898+ CW_USEDEFAULT ,
899+ CW_USEDEFAULT ,
900+ CW_USEDEFAULT ,
901+ CW_USEDEFAULT ,
902+ core :: ptr :: null_mut (),
903+ core :: ptr :: null_mut (),
904+ hInstance,
905+ core :: ptr :: null_mut (),
906+ )
907+ };
908+ }
894909```
895910
896911Now we just have to define ` WS_OVERLAPPEDWINDOW ` and ` CW_USEDEFAULT ` .
@@ -955,19 +970,14 @@ We just did it to fill in a little bit so the compiler would be cool.
955970Now that we're tying to turn on the program on for real (even for a second),
956971we need a real window procedure.
957972But we don't know how to write one yet.
958- Never fear, there's a [ DefWindowProcW] ( https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-defwindowprocw )
959- function that you can use to handle any messages you don't know how to handle.
973+ Never fear, there's a function called [ DefWindowProcW] ( https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-defwindowprocw ) .
974+ It's the "Default Window Procedure", that you can use to handle any messages you don't want to handle.
960975Right now, for us, that's all of them.
961976
962977``` rust
963978fn main () {
964- let hInstance = unsafe { GetModuleHandleW (null ()) };
965- let sample_window_class_wn = wide_null (" Sample Window Class" );
966-
967- let mut wc = WNDCLASSW :: default ();
979+ // ...
968980 wc . lpfnWndProc = Some (DefWindowProcW );
969- wc . hInstance = hInstance;
970- wc . lpszClassName = sample_window_class_wn . as_ptr ();
971981 // ...
972982}
973983
@@ -1041,14 +1051,18 @@ extern "system" {
10411051
10421052We have to get them in a loop of course, because we'll be getting a whole lot of them.
10431053``` rust
1044- let mut msg = MSG :: default ();
1045- loop {
1046- let message_return = unsafe { GetMessageW (& mut msg , null_mut (), 0 , 0 ) };
1047- if message_return == 0 {
1048- break ;
1049- } else if message_return == - 1 {
1050- let last_error = unsafe { GetLastError () };
1051- panic! (" Error with `GetMessageW`, error code: {}" , last_error );
1054+ fn main () {
1055+ // first open the window
1056+
1057+ let mut msg = MSG :: default ();
1058+ loop {
1059+ let message_return = unsafe { GetMessageW (& mut msg , null_mut (), 0 , 0 ) };
1060+ if message_return == 0 {
1061+ break ;
1062+ } else if message_return == - 1 {
1063+ let last_error = unsafe { GetLastError () };
1064+ panic! (" Error with `GetMessageW`, error code: {}" , last_error );
1065+ }
10521066 }
10531067}
10541068```
@@ -1129,6 +1143,7 @@ pub unsafe extern "system" fn window_procedure(
11291143```
11301144One little problem here is that ` DestroyWindow ` and ` PostQuitMessage ` have different return types.
11311145Even though we're ignoring the output of ` DestroyWindow ` , it's a type error to have it like this.
1146+ We can suppress the output of ` DestroyWindow ` by putting it in a block and having a ` ; ` after it.
11321147
11331148``` rust
11341149pub unsafe extern " system" fn window_procedure (
@@ -1144,7 +1159,7 @@ pub unsafe extern "system" fn window_procedure(
11441159 0
11451160}
11461161```
1147- Ehhhhhh, I'm not sure if I'm a fan of rustfmt making it look like that.
1162+ Ehhhhhh, I'm not sure if I'm a fan of ` rustfmt ` making it look like that.
11481163
11491164``` rust
11501165pub unsafe extern " system" fn window_procedure (
@@ -1160,7 +1175,7 @@ pub unsafe extern "system" fn window_procedure(
11601175```
11611176Oh, yeah, that's the good stuff.
11621177We can use ` drop ` to throw away the ` i32 ` value,
1163- so we don't need the ` ; ` and braces,
1178+ so then we don't need the ` ; ` and braces,
11641179so rustfmt keeps it on a single line.
11651180I am * all about* that compact code stuff.
11661181
@@ -1180,7 +1195,11 @@ and then the default window procedure will set the cursor to be the right image
11801195
11811196We're supposed to call it with something like:
11821197``` rust
1183- wc . hCursor = unsafe { LoadCursorW (hInstance, IDC_ARROW ) };
1198+ fn main () {
1199+ // ...
1200+ wc . hCursor = unsafe { LoadCursorW (hInstance, IDC_ARROW ) };
1201+ // ...
1202+ }
11841203```
11851204
11861205And the extern function is easy to do:
@@ -1369,6 +1388,7 @@ const WM_CREATE: u32 = 0x0001;
13691388
13701389And we check for them in our window procedure:
13711390``` rust
1391+ // in the window_procedure
13721392 match Msg {
13731393 WM_NCCREATE => {
13741394 println! (" NC Create" );
@@ -1400,6 +1420,7 @@ Okay, so far all of our messages have asked us to just *always* return 0 when th
14001420and this is the first message we' ve been handling that we had to decide to return 0 or not.
14011421Well, right now our window creation should always proceed, so here we go:
14021422```rust
1423+ // in the window_procedure
14031424WM_NCCREATE => {
14041425 println!("NC Create");
14051426 return 1;
@@ -1437,6 +1458,7 @@ Right, so, we have to have a void pointer to pass to the message.
14371458Uh, just to pick something, let' s pass our message a pointer to the number 5 .
14381459
14391460```rust
1461+ // in main
14401462let lparam: *mut i32 = Box::leak(Box::new (5_i32));
14411463let hwnd = unsafe {
14421464 CreateWindowExW (
0 commit comments