Managing Windows windows within mruby Part 2: Creating a window from mruby
Tags: ruby, windows, tutorial, cpp
Published: 2020-05-25 02:36:08

We'll build off of the previous program where we created a Windows window using the Win32 API. Now we're adding the mruby VM to our program and then push creation of the Win32 window into the VM instead of calling it from our `main()` function.

First we need to build mruby. This is a simple affair. There is a bit of a catch-22 as the automated build scripts are in Ruby. While Ruby used to be a pain on Windows it's simple today. Easiest is through [chocolatey](https://chocolatey.org) and install the Ruby package with `choco install ruby`. That's it!

Clone the mruby repo and check out latest tag, currently 2.1.0, or download the latest release and extract.

{% github https://github.com/mruby/mruby %}

Run `ruby minirake` from dir. There are some warnings about macro re-definitions, they're harmless. When finished, copy `build\host\*.lib` to the win32 project dir. Copy recursive `include` to the project dir as well.

Either modify the batch file, adding `/Imruby` to compile options, or modify the .h files to use relative paths `""` instead of `<>` include paths. Either works, I prefer the latter.

Within the batch file, add `libmruby.lib libmruby_core.lib` to the end of the line, telling the linker to use the mruby libraries for external symbols.

Now we'll test mruby. Add `#include "mruby.h"` to the top of the cpp file. At the top of `main` add `mrb_state *mrb = mrb_open();`. Bottom add `mrb_close(mrb)`. Compile and run, ensuring everything is fine.

## Important!

Before we go on there are two fundamental things required to understand. The entire VM state is in the `mrb_state` type we just created. We pass that state as the first parameter to all mruby functions. This is a common C pattern seen lately. Older C programs and libraries used a pattern of global hidden state. Examples include the main Ruby C interface and famously OpenGL. Now it's more common for C programs using a pointer to an internal state instead. This adds a great deal more flexibility at the cost of a bit more verbose syntax.

The other fundamental piece of information is everything with mruby uses an `mrb_value` type. It's similar to the C Ruby `VALUE` type.

```c++
union mrb_value_union {
  mrb_float f;
  void *p;
  mrb_int i;
  mrb_sym sym;
};
typedef struct mrb_value {
  union mrb_value_union value;
  enum mrb_vtype tt;
} mrb_value;
```

The `mrb_vtype tt` is important here. That specifies the table type of this value and informs how to access it. For float, fixnums, and symbol table types use the `f`, `i` and `sym` members of the union respectively. All others use the `void *p` type, with the `tt` property informing how to interpret the `void *`. For example if we have an `mrb_value` with a `tt` of `MRB_TT_CLASS` we cast the `p` member of the union to an `RClass *`. 

```c++
// or use reinterpret_cast<RClass *> if you like more explicit C++ casts
RClass *klass = (RClass *)foo.value.p;
```
Sometimes you see the API use or return types like `mrb_string` or `mrb_int`. These ultimately come from a source `mrb_value`. If you're debugging trying to figure out why you're getting information you don't expect, back up the stack until you find the source `mrb_value` these derived types come from.

Much of the mruby API looks like the main Ruby C API. The Ruby C API is well covered at this point. If you're looking for information on how to do something and the information for mruby isn't readily available you can find examples with the Ruby C API and translate it back. Beyond that reading the mruby header files, mruby C source implementation, and looking at the implementation of [mruby gems](https://github.com/mruby/mruby/tree/master/mrbgems) provides insight. All of the C code with mruby and gems surrounding it is easy to read. There are no C tricks or hard to parse syntax. Oftentimes reading the C implementation is close to reading Ruby code with some extra steps.

## Creating objects in the mruby vm

First thing we'll do is create a new class in the ruby VM. This is the same as `class W32Window; end;`. We can do this with `mrb_define_class`. After initializing the mruby state, add

```c++
// RClass *mrb_define_class(mrb_state *mrb, const char *name, struct RClass *super);
RClass *window_manager_class = mrb_define_class(mrb, "W32Window", mrb->object_class);
```

mruby automatically creates the base object when we initialize the `mrb_state` and places a reference in the `object_class` pointer in the state. Remember, everything in Ruby comes from `Object` eventually, even classes, so we set the third parameter to the root object to inherit from.

Right now we'll treat `W32Window` as a static object containing all of it's state. We could use `W32Window` as a `module` instead. If we were to do that we'd use `mrb_define_module(mrb, "W32Window");`, which corresponds to `module W32Window; end;`. Either works for now, though I'm using a class since I'll refactor it into a constructor later.

Now that we have an `RClass *` we can use C to define methods in it. Defining class which runs C code instead of Ruby code is a two step process. One, define a function in C which runs when we call the Ruby method. Two, define a method on a Ruby object pointing to the C function. Lets create a test method named `foo` on the `W32Window` class which prints something to standard out. We'll be doing the Ruby equivalent of

```ruby
class W32Window
  def self.foo
    puts "Hello from W32Window.foo"
  end
end
```

Bound mruby methods have a signature `mrb_value function_name(mrb_state *state, mrb_value self)`. I prefer naming bound C functions with a naming pattern of `class_name_method_name_method`. In this case we'll name the C method `w32_window_foo_method` means `W32Window.foo`. Filling it all out we have the function

```c++
mrb_value
w32_window_foo_method(mrb_state* state, mrb_value self) {
  printf("Hello from W32Window.foo\n");
  return mrb_nil_value();
}
```

Every Ruby method must return some value so we'll return `nil`, of which there's a helpful function for.

Now the last step is creating the method on an object. For a class method this is `mrb_define_class_method`. For a method on a module, it is `mrb_define_module_function`. And a normal instance method, `mrb_define_method`. All have a similar signature. We're defining a class method on `W32Window`.

```c++
// void mrb_define_class_method(mrb_state *mrb, struct RClass *cla, 
//                              const char *name, mrb_func_t fun, 
//                              mrb_aspec aspec);
mrb_define_class_method(mrb, window_manager_class, "foo",
                        w32_window_foo_method, MRB_ARGS_NONE());
```

Which is all straightforward. The last parameter differs from the mainline Ruby `rb_define_method`. mruby uses an `mrb_aspec` type. This defines bit patterns for specifying the number and kind of arguments, while Ruby uses an integer with extended parameters. There are helpful macros for creating the right bit pattern, such as `MRB_ARGS_NONE()`. You can read the `mruby.h` header for all the macros for specifying number and types of parameters for a C function called from Ruby.

That's really all there is to it. We can test it out by calling the C function `mrb_load_string`, which is like an `eval` from the C side. This function is fine for limited testing or one off execution. You should not use it repeatedly in code paths since it performs a parse, compilation and execution every time. It's function prototype lives in the `compile.h`, so `#include "mruby/compile.h"` first and then

```c++
mrb_load_string(mrb, "W32Window.foo");
```

If you're working off of the previous Win32 project, place this function call after the `CreateWindowEx` call. Windows does not set up stdout until after window creation. Build, run and you'll see `Hello from W32Window.foo` in the console just as you expect.

Lets take a step back and analyze what we just put together. We created a new mruby VM state with `mrb_open`. Then, using C functions, created a new class within the Ruby VM called `W32Window`. We added a class method named `foo` on the new `W32Window` class. That class method runs the C function `window_manager_foo_method` instead of running Ruby code. Within that C function we print a message to standard out. Finally, we execute some Ruby code within the VM with C.

## Creating a Win32 window within mruby

Now you see the back and forth between a C program and the mruby VM. What we want to do is push everything we can into the mruby VM. Lets do the simplest thing possible by pushing the Win32 window creating into a C function called by the VM instead. This is still fragile and has issues though it's a good first step.

Delete everything related to creating the `foo` method on `W32Window`. We'll add a `create` class method on the class which does all the win32 calls instead. We'll move all the Win32 window creation code into this function. It's a simple enough procedure, simply showing some of the code is sufficient for understanding. It's not much different than we already did with our test function with `printf`

```c++
mrb_value
w32_window_create_method(mrb_state* state, mrb_value self) {
  WNDCLASSEX wclass = {};
  wclass.cbSize = sizeof(WNDCLASSEX);
  wclass.lpszClassName = "mainWindowClass";
  wclass.lpfnWndProc = main_window_callback;
  wclass.hInstance = GetModuleHandle(nullptr);
  wclass.style = CS_OWNDC | CS_VREDRAW | CS_HREDRAW;
  RegisterClassEx(&wclass);

  DWORD main_window_style = (WS_OVERLAPPEDWINDOW | WS_VISIBLE);

  HWND main_window = CreateWindowEx(0, wclass.lpszClassName, "Playground",
                                    main_window_style, CW_USEDEFAULT, CW_USEDEFAULT,
                                    1024, 768,
                                    0, 0, GetModuleHandle(nullptr), 0);

  return mrb_nil_value();
}

/// before the main loop
mrb_define_class_method(mrb, w32_window_class, "create",
                        w32_window_create_method, MRB_ARGS_NONE());

mrb_load_string(mrb, "W32Window.create");
```

Run this program and... it's exactly the same as before. Which in this case is a good thing! There's a _profound_ difference in the execution path. Now when we create a Win32 window we are within the context of the mruby VM and all it provides us. Lets do a simple proof of that. Change the `mrb_load_string` to

```c++
mrb_load_string(mrb, "5.times { W32Window.create }");
```

Running this program we now see we have five identically behaving Win32 windows!

![](https://dev-to-uploads.s3.amazonaws.com/i/rzuichvdqihko8tb27q4.png)

They all behave identically in that closing any one of them quits the program. Initially we assumed we would have only one window. In our callback function when we receive a `WM_CLOSE` message, which happens when the window closes, we quit the program using `PostQuitMessage`. We want to keep track of all the open windows and then quit when the last window closes. We'll get to that next time when we start encapsulating C data into an mruby class. For now slap a //TODO in the code and lets move on.

## Extracting mruby parameters in C

Lets make our creation a bit more interesting. Lets make the `create` method take parameters for the window title and dimensions. Written in Ruby, the method would look like `def self.create window_title, width: 1024, height: 768`. Starting with `window_title` first, we have to change the `mrb_define_class_method` declaring that we have one required parameter. Change `MRB_ARGS_NONE` to `MRB_ARGS_REQ(1)`. The Ruby VM enforces one method argument now.

It's a simple process extracting Ruby method arguments in a method implemented in C. We use the `mrb_get_args` function for extracting copies of the arguments into `mrb_value` types. The first parameter is an `mruby_state`, second is a format specifier, and the rest are va_args matching the format specifier. Right from the mruby header file

```c++
/**
 * Retrieve arguments from mrb_state.
 *
 * @param mrb The current MRuby state.
 * @param format is a list of format specifiers
 * @param ... The passing variadic arguments must be a pointer of retrieving type.
 * @return the number of arguments retrieved.
 * @see mrb_args_format
 * @see mrb_kwargs
 */
MRB_API mrb_int mrb_get_args(mrb_state *mrb, mrb_args_format format, ...);
```

The format specifier specifies the type of argument passed in to what position. It's a standard c-string similar to `printf`. Since currently we're expecting a string for the window title we use the format specifier `"S"`. Lastly we pass a reference to an empty `mrb_value` that `mrb_get_args` populates. See the `mruby.h` header file detailing the format specifier arguments. At the top of `w32_window_create_method`

```c++
mrb_value window_name;
mrb_get_args(state, "S", &window_name);
```

Now all we need to do is use the passed parameter as the window title instead of the hardcoded string "Playground". Note that we have an `mrb_value` instead of a `char *`. There are helpful macros for extracting data out of `mrb_value` types to C types. First we need to `#include "mruby/string.h"` for their definitions. Then we can replace `"Playground"` with `RSTRING_PTR(window_name)`.

Change our call in `mrb_load_string` to something more interesting that demonstrates our new capabilities.

```c++
mrb_load_string(mrb, "5.times { |n| W32Window.create \"Window #{n}\" }");
```

Compile, run and we have five different uniquely named windows!

![](https://dev-to-uploads.s3.amazonaws.com/i/lmdwh9ww9ihhmvuho9eo.png)

Lets take a moment and marvel at what we did here. We pushed creating of new windows using Win32 into the Ruby VM and parameterized the construction of the windows. We didn't need any external window management libraries, a chain of dependencies from a gemfile, didn't need something like `FFI` creating tenuous bindings between the Ruby VM and an external library. It's all right in front of us in less than 80 lines of code. It's pretty amazing and easy! All we needed to get to where we're at is just the mruby library and the facilities provided with Windows programming.

If this is your first foray into this style of programming you may start to develop questions on why it takes thousands of interdependent NPM, Python, Java or Ruby libraries and hundreds of lines of boilerplate to generate a 'Hello World' in a browser. If you're like me and burnt out over years to decades of overly complicated development, where it takes a whole quarter to show a birthdate this probably feels refreshing and fun again.

{% youtube y8OnoxKotPQ %}

Lets keep going and finish this out. The last thing we want to add here are keyword arguments for the window dimensions `width` and `height`.

## Extracting keyword parameters

It's straightforward and simple extracting positional parameters passed C implemented Ruby methods. It's more complex for keyword parameters which is why I want to detail it here. Most of Ruby's history didn't support method arguments other than positional arguments, a block parameter, and a construction for grouping the 'rest' arguments, like the JavaScript `...args` in a function name.

We faked keyword parameters by passing a hash as a parameter into the method. The Ruby interpreter supported this by allowing the removal of the outer braces. This is similar to passing an object to a function in JavaScript While this worked enforcing some keyword parameters and providing defaults ended up with a lot of boilerplate

```ruby
def foo(args)
  a = args.fetch(:a, 'default value')
  b = args.fetch(:b, nil)
end
foo a: 1, b: 2 # the same as foo({a: 1, b: 2})
```

Skipping ahead in history, Ruby added support for, default parameters for any positional argument, keyword parameters, keyword parameters with a default, required keyword parameters, and block parameters also supports all of those. A method signature in Ruby can look tremendously complex. This is why the mruby C API looks complicated, as we will see right now for keyword arguments.

We want the full method signature for our `create` method to look like

```ruby
def W32Window.create window_title, width:, height:
```

When I'm working with Ruby I generally create methods with more than one parameter using keyword arguments. I usually put the most important information as the first positional argument and then the rest as keyword arguments.

`mrb_get_args` uses a special `mrb_kwargs` type for the definition on how to extract keyword arguments. We use this type instead of an `mrb_value` when extracting the arguments.

```c++
struct mrb_kwargs
{
  uint32_t num;
  mrb_value *values;
  const char *const *table;
  uint32_t required;
  mrb_value *rest;
};

```

`num` is the number of keyword arguments in total. `values` is a pre-created array of `mrb_values` that we extract the parameter value into, like we did with the string. `table` is a `char*[]`, an array of string, which are the names of the keyword arguments. `required` is the number of required keyword arguments.

Starting with the call to `mrb_get_args` we can work backwards and fill out the rest of the information.

```c++
const mrb_kwargs kwargs = { 2, kw_values, kw_names, 2, nullptr };
mrb_get_args(state, "S:", &window_name, &kwargs);
```

We know we want two keyword arguments and require 2, so we'll use 2 for both places. There won't be a `rest` param, so that's null. Now we just need the two arrays

```c++
const char* kw_names[2] = { "width", "height" };
mrb_value kw_values[2] = {};
```

Now we reference these parameters in `CreateWindowEx` instead of hardcoded values. We know we want the `mrb_value` types in `kw_values` as integers. We could cast them ourselves, however mruby usually has helpful macros for that already, so lets use them. In this case it's `mrb_fixnum`, which is directly compatible with standard integers without any casting necessary.

```c++
HWND main_window = CreateWindowEx(0, wclass.lpszClassName, RSTRING_PTR(window_name),
                                  main_window_style, CW_USEDEFAULT, CW_USEDEFAULT,
                                  mrb_fixnum(kw_values[0]),
                                  mrb_fixnum(kw_values[1]),
                                  0, 0, GetModuleHandle(nullptr), 0);
```

Lastly change `mrb_load_string` to provide the keyword arguments

```c++
mrb_load_string(mrb, "5.times { |n| W32Window.create \"Window #{n}\", width: 1024, height: 768 }");
```

Build, run, and of course everything is the same as before. Change the values of the `width` and `height` parameters and they change appropriately, for instance something like

```c++
mrb_load_string(mrb, "5.times { |n| W32Window.create \"Window #{n}\", width: n * 100, height: n * 100 }");
```

Which gives us a predictable 5 windows of increasing size

![](https://dev-to-uploads.s3.amazonaws.com/i/h6q9ucd546gy90mo1kok.png)

## Celebration!

That's it for our introduction to using the mruby C API. We tied together Win32, mruby and parameterized window creation from Ruby to Windows with just the mruby library in 80 lines of code. Total! Amazing.

While the volume of words here implies there's something complicated happening it really isn't. I'm being especially thorough on explaining all the details, repeating some key points so things stick. Once you understand the concepts you can delete everything, start from scratch and replicate it yourself rapidly in less than an hour the second time, and faster still the third time.

Next up we'll finish this off by turning `W32Window.create` into a `new` constructor instead, encapsulate the `HWND` and `HINSTANCE` in the instance of the class, add a reference to the mruby `W32Window` instance into the Win32 window so we can get it back in the `main_window_callback`, and then change the program behavior so that it exits when all windows close, not any of them.

The full code

```c++
#include <windows.h>
#include <stdio.h>
#include "mruby.h"
#include "mruby/compile.h"
#include "mruby/string.h"

bool g_should_quit = false;

LRESULT CALLBACK
main_window_callback(HWND window, UINT message, WPARAM wparam, LPARAM lparam) {
  LRESULT result = 0;
  switch(message) {
  case(WM_CLOSE): {
    PostQuitMessage(0);
    break;
  }
  default: {
    result = DefWindowProc(window, message, wparam, lparam);
  }
  }
  return result;
}

mrb_value
w32_window_create_method(mrb_state* state, mrb_value self) {
  mrb_value window_name;
  mrb_int width;
  mrb_int height;
  const char* kw_names[2] = { "width", "height" };
  mrb_value kw_values[2] = {};
  const mrb_kwargs kwargs = { 2, kw_values, kw_names, 2, nullptr };
  mrb_get_args(state, "S:", &window_name, &kwargs);

  WNDCLASSEX wclass = {};
  wclass.cbSize = sizeof(WNDCLASSEX);
  wclass.lpszClassName = "mainWindowClass";
  wclass.lpfnWndProc = main_window_callback;
  wclass.hInstance = GetModuleHandle(nullptr);
  wclass.style = CS_OWNDC | CS_VREDRAW | CS_HREDRAW;
  RegisterClassEx(&wclass);

  DWORD main_window_style = (WS_OVERLAPPEDWINDOW | WS_VISIBLE);

  HWND main_window = CreateWindowEx(0, wclass.lpszClassName, RSTRING_PTR(window_name),
                                    main_window_style, CW_USEDEFAULT, CW_USEDEFAULT,
                                    mrb_fixnum(kw_values[0]),
                                    mrb_fixnum(kw_values[1]),
                                    0, 0, GetModuleHandle(nullptr), 0);

  return mrb_nil_value();
}

int main() {
  mrb_state *mrb = mrb_open();
  RClass *w32_window_class = mrb_define_class(mrb, "W32Window", mrb->object_class);
  mrb_define_class_method(mrb, w32_window_class, "create",
                          w32_window_create_method, MRB_ARGS_REQ(1));

  mrb_load_string(mrb, "5.times { |n| W32Window.create \"Window #{n}\", width: n * 100, height: n * 100 }");

  while(!g_should_quit) {
    MSG message = {};
    while(PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) {
      switch (message.message) {
      case (WM_QUIT): {
        g_should_quit = true;
        break;
      }
      default: {
        TranslateMessage(&message);
        DispatchMessage(&message);
        break;
      }
      }
    }
  }

  mrb_close(mrb);

  return 0;
}
```