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.
- CoInitialize: This function loads the required libraries for COM interfaces in the current thread. Every thread that uses COM Objects should call this function.
- 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);
}
}
}
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);
}
}
}