Answering the literal question first: You could, but you probably shouldn't have to.
COM support in the windows
crate exposes many types, and not all of them are meant for immediate use by client code. The *_Vtbl
structures specifically represent the raw function pointer tables used by COM internally to dispatch interface calls. They are declared and populated by the library and not intended to be used by clients directly (the #[doc(hidden)]
attribute is a hint, though I'm sure the library structure and documentation experience can be improved).
Attempting to populate the v-tables in client code puts you into a miserable situation. Luckily, none of that is required, as briefly explained in the FAQ:
How do I implement an existing COM interface?
If you need to implement a COM interface for a type, you'll need to add the implement
feature which (like any Cargo feature) can be enabled in your project's Cargo.toml file.
windows = { version = "..", features = ["implement"] }
Then you'll need to declare that your type implements a particular interface by adding the #[implement]
proc macro to your type and then writing an impl
block for the interface. For an interface called IMyInterface
you will need to implement the IMyInterface_Impl
trait (note the trailing _Impl
in the name).
#[windows::core::implement(IMyInterface)]
struct MyStruct;
impl IMyInterface_Impl for MyStruct {
fn MyMethod(&self) -> windows::core::HRESULT {
todo!("Your implementation goes here");
}
}
Version 0.37.0 made significant changes to the implement
macro, making this far more approachable than it may appear. Let's start out by declaring a simple structure with a bit of state information:
#[implement(IUIAutomationFocusChangedEventHandler)]
struct EventHandler {
count: Cell<u64>,
}
impl EventHandler {
fn new() -> Self {
Self {
count: Cell::new(0),
}
}
/// Increments the count and returns the new value
fn increment(&self) -> u64 {
let new_val = self.count.get() + 1;
self.count.set(new_val);
new_val
}
}
This keeps a cumulative count of focus change events that happened. Note that the implementation isn't actually correct: Since the event handler can be called from multiple threads we'd actually need a type that's Sync
(which Cell
isn't). That's something you'd need to change1.
What's missing is the IUIAutomationFocusChangedEventHandler
interface implementation. It only has a single member, so that's easy (the IUnknown
implementation is conveniently provided for you by the library already):
impl IUIAutomationFocusChangedEventHandler_Impl for EventHandler {
fn HandleFocusChangedEvent(&self, _sender: &Option<IUIAutomationElement>) -> Result<()> {
let count = self.increment();
println!("Focus changed (cumulative count: {})", count);
Ok(())
}
}
For every focus change event it first increments the cumulative count and then prints a message to STDOUT.
That's all that's required to implement a custom IUIAutomationFocusChangedEventHandler
interface. Using that from a program isn't much harder, either, even though there are a lot of pitfalls (see comments):
fn main() -> Result<()> {
// Initialize COM for the current thread. Since we are running event handlers on this
// thread, it needs to live in the MTA.
// See [Understanding Threading Issues](https://learn.microsoft.com/en-us/windows/win32/winauto/uiauto-threading)
// for more information.
unsafe { CoInitializeEx(ptr::null(), COINIT_APARTMENTTHREADED) }?;
// Instantiate a `CUIAutomation` object
let uia: IUIAutomation =
unsafe { CoCreateInstance(&CUIAutomation, None, CLSCTX_INPROC_SERVER) }?;
// Subscribe to the focus changed event; this transfers ownership of `handler` into
// `uia`, making it the sole owner
let handler = EventHandler::new();
unsafe { uia.AddFocusChangedEventHandler(None, &handler.into()) }?;
// Display a message box so that we have an easy way to quit the program
let _ = unsafe {
MessageBoxW(
None,
w!("Click OK to end the program"),
w!("UIA Focus Monitor"),
MB_OK,
)
};
// Optionally unsubscribe from all events; this is not strictly required since we have
// to assume that the `CUIAutomation` object properly manages the lifetime of our
// `EventHandler` object
unsafe { uia.RemoveAllEventHandlers() }?;
// IMPORTANT: Do NOT call `CoUninitialize()` here. `uia`'s `Drop` implementation will
// get very angry at us when it runs after COM has been uninitialized
Ok(())
}
To compile the code you'll want to use the following imports:
use std::{cell::Cell, ptr};
use windows::{
core::{implement, Result},
w,
Win32::{
System::Com::{
CoCreateInstance, CoInitializeEx, CLSCTX_INPROC_SERVER, COINIT_APARTMENTTHREADED,
},
UI::{
Accessibility::{
CUIAutomation, IUIAutomation, IUIAutomationElement,
IUIAutomationFocusChangedEventHandler, IUIAutomationFocusChangedEventHandler_Impl,
},
WindowsAndMessaging::{MessageBoxW, MB_OK},
},
},
};
and this Cargo.toml file:
[package]
name = "uia_focus_change"
version = "0.0.0"
edition = "2021"
[dependencies.windows]
version = "0.39.0"
features = [
"implement",
"Win32_Foundation",
"Win32_System_Com",
"Win32_UI_Accessibility",
"Win32_UI_WindowsAndMessaging",
]
1 Possible alternatives include an AtomicU64
and a Mutex
. An atomic is perfectly sufficient here, is easy to use, and will properly work in situations of re-entrancy:
use std::sync::atomic::{AtomicU64, Ordering};
#[implement(IUIAutomationFocusChangedEventHandler)]
struct EventHandler {
count: AtomicU64,
}
impl EventHandler {
fn new() -> Self {
Self {
count: AtomicU64::new(0),
}
}
/// Increments the count and returns the new value
fn increment(&self) -> u64 {
self.count.fetch_add(1, Ordering::SeqCst) + 1
}
}
A mutex, on the other hand, is substantially harder to use, its behavior in part unspecified, and equipped with lots of opportunities to fail. On the upside it is more versatile in protecting arbitrarily large structures:
use std::sync::Mutex;
#[implement(IUIAutomationFocusChangedEventHandler)]
struct EventHandler {
count: Mutex<u64>,
}
impl EventHandler {
fn new() -> Self {
Self {
count: Mutex::new(0),
}
}
/// Increments the count and returns the new value
fn increment(&self) -> u64 {
let mut guard = self.count.lock().expect("Failed to lock mutex");
*guard += 1;
*guard
}
}
Either one works and is compatible with COM objects that live in the MTA.