Calling your first WinRT API

Windows 8 introduced the Windows Runtime, which at its heart, is just COM with a few more conventions thrown in to make language bindings appear more seamless. The windows crate already makes calling COM APIs far more seamless than it is for C++ developers, but WinRT goes further by providing first-class support for modeling things like constructors, events, and class hierarchies. In calling your first COM API, we saw that you still had to bootstrap the API with a C-style DLL export before calling COM interface methods. WinRT works the same way but abstracts this away in a generalized manner.

Let's use a simple example to illustrate. The XmlDocument "class" models an XML document that can be loaded from various sources. The Rust docs for the windows crate indicate that this type resides in the Data::Xml::Dom module so we can configure our windows crate dependency as follows:

[dependencies.windows]
version = "0.52" 
features = [
    "Data_Xml_Dom",
]

And we can employ a use declaration to make this API a little more accessible. The windows crate's core module just provides a few helpers to make it easier to work with Windows APIs, so we'll include that as well:

#![allow(unused)]
fn main() {
use windows::{core::*, Data::Xml::Dom::XmlDocument}; 
}

For this example, I'll just use a simple main function with a Result type from the windows::core module to provide automatic error propagation and simplify the subsequent API calls:

fn main() -> Result<()> {

    Ok(())
}

Unlike the previous Win32 and COM examples, you'll notice that this main function does not need an unsafe block since WinRT calls are assumed to be safe thanks to its more constrained type-system.

To begin, we can simply call the new method to create a new XmlDocument object:

#![allow(unused)]
fn main() {
let doc = XmlDocument::new()?;
}

This looks a lot more like an idiomatic Rust type than your typical COM API, but under the hood a similar mechanism is used to instantiate the XmlDocument implementation via a DLL export. We can then call the LoadXml method to test it out. There are various other options for loading XML from different sources, which you can read about in the official documentation or from the Rust docs for the XmlDocument API. The windows crate also provides the handy h! macro for creating an HSTRING, the string type used by WinRT APIs:

#![allow(unused)]
fn main() {
doc.LoadXml(h!("<html>hello world</html>"))?;
}

And just like that, we have a fully-formed Xml document that we can inspect. For this example, let's just grab the document element and then do some basic queries as follows:

#![allow(unused)]
fn main() {
let root = doc.DocumentElement()?;
assert!(root.NodeName()? == "html");
println!("{}", root.InnerText()?);
}

First we assert that the element's name is in fact "html" and then print out the element's inner text. As with the previous COM example, those methods all invoke virtual functions through COM interfaces, but the windows crate makes it very simple to make such calls directly from Rust. And that's it. Running the sample should print something like this:

hello world

Here's the full sample for reference.