How to use COM Objects in Rust

COM is a binary interface into the Windows API provided by Microsoft. The goal of this blog is to explain how to use them in Rust using the bindings provided by the windows-rs crate. I’ll assume that the reader is already familiar with COM.

There are 2 important functions for COM objects.

  1. CoInitialize: This function loads the required libraries for COM interfaces in the current thread. Every thread that uses COM Objects should call this function.
  2. CoCreateInstance: This function creates and default initializes a single object of the class with the specified CLSID (globally unique identifier that identifies a COM class object). List of CLSIDs can be found here https://strontic.github.io/xcyclopedia/index-com-objects.

Example Usage

Here, I am going to show you a sample program using COM Objects. To get started, add the windows crate as a dependency to your project with the following features.

[dependencies]
windows = { version = "0.43.0", features = ["Win32_Foundation", "Win32_System_Threading", "Win32_System_Com", "Win32_System_UpdateAgent"] }

We will be fetching the windows update history in this example. The interface required for to get this information is IUpdateSearcher. The CLSID for the IUpdateSearcher interface is B699E5E8-67FF-4177-88B0-3684A3388BFB (see this resource https://strontic.github.io/xcyclopedia/index-com-objects).

We first call CoInitialize to load the required libraries on the current thread. Note that we need to use unsafe for all the COM interface calls as they are external to Rust.

pub fn main() {
    unsafe { CoInitialize(None) };
    ...
}

Then We create an object of windows::core::GUID from the CLSID UUID string

fn main() {
    ...
    let guid = windows::core::GUID::from("B699E5E8-67FF-4177-88B0-3684A3388BFB");
    ...
}

We create the IUpdateSearcher instance using CoCreateInstance. We pass in the GUID of the class we want to create. The 2nd parameter is None and the third parameter is the CLS context.

pub fn main() {
        ...
    let searcher: IUpdateSearcher =
        unsafe { CoCreateInstance(&guid as _, None, CLSCTX_ALL) }.unwrap();
        ...
}

We are almost there💪🏽. Now, we can use the methods on the interface to fetch the history.

pub fn main() {
         ...
    let count = unsafe { searcher.GetTotalHistoryCount() }.unwrap_or(0);
    if count == 0 {
        println!("No Update history found");
        return;
    }

    if let Ok(history) = unsafe { searcher.QueryHistory(0, count) } {
        for idx in 0..count {
            let update = unsafe { history.get_Item(idx).unwrap() };
            let name = unsafe { update.Title().unwrap() };
            println!("{:?}", name);
        }
    }
}

Output:

The code for the above example is given below in full and it is also available in a gist.

use windows::Win32::System::Com::{CoCreateInstance, CoInitialize, CLSCTX_ALL};
use windows::Win32::System::UpdateAgent::IUpdateSearcher;

pub fn main() {
    unsafe {
        CoInitialize(None);
    }

    let guid = windows::core::GUID::from("B699E5E8-67FF-4177-88B0-3684A3388BFB");

    let searcher: IUpdateSearcher =
        unsafe { CoCreateInstance(&guid as _, None, CLSCTX_ALL) }.unwrap();

    let count = unsafe { searcher.GetTotalHistoryCount() }.unwrap_or(0);
    if count == 0 {
        println!("No Update history found");
        return;
    }

    if let Ok(history) = unsafe { searcher.QueryHistory(0, count) } {
        for idx in 0..count {
            let update = unsafe { history.get_Item(idx).unwrap() };
            let name = unsafe { update.Title().unwrap() };
            println!("{:?}", name);
        }
    }
}

Leave a Reply

Scroll to Top
%d bloggers like this: