microsoft / windows-rs

Rust for Windows
https://kennykerr.ca/rust-getting-started/
Apache License 2.0
10.23k stars 475 forks source link

Add `Ref` and `OutRef` to enhance COM authoring support #3025

Closed kennykerr closed 3 months ago

kennykerr commented 3 months ago

This update introduces a number of improvements to the implement and interface macros as well as the windows-core crate that work together to enhance support for COM interface declarations and implementations as well as general FFI/ABI support.

Consider the following C++ function signature:

HRESULT __stdcall DllGetActivationFactory(HSTRING, IActivationFactory**)

This function must be exported by DLLs for WinRT (COM) support. The ABI requirements are such that the HSTRING and IActivationFactory types are "borrowed" in the sense that neither the caller nor the callee must free or drop those values as part of the call itself. This leads to Rust developers having to write something like this:

unsafe extern "system" fn DllGetActivationFactory(
    name: std::mem::ManuallyDrop<HSTRING>,
    factory: *mut std::mem::ManuallyDrop<Option<IActivationFactory>>,
) -> HRESULT

The implementation is then forced to carefully consider and manage lifetimes and transfer ownership and is easy to get wrong.

The Ref and OutRef types represent borrowed types, providing the same memory layout but also offer lifetime management and conveniences to make dealing with in and out parameters safe and convenient:

unsafe extern "system" fn DllGetActivationFactory(
    name: Ref<HSTRING>,
    factory: OutRef<IActivationFactory>,
) -> HRESULT

Ref implements the Deref trait that returns a reference to the borrowed type while OutRef provides the write method that will overwrite the memory location with the given value. The write method can only be called once and checks for null. It also provides the is_null method if you need to conditionally support optional out parameters.

The interface macro will also generate bindings for Ref and OutRef that behave like the bindings generated by the windows-bindgen crate, making it easier to call such interface methods naturally from Rust. This leverages the new ParamMut trait that compliments the existing Param trait for handling out parameters. It takes care of dealing with uninitialized memory in out parameters as well as dropping &mut values in Rust to avoid leaks.

In addition, the interface macro now supports Result return types for convenience. The underlying virtual function will still return an HRESULT but the interface and implement macros will automatically translate that for you:

#[interface("09428a59-5b40-4e4c-9175-e7a78514316d")]
unsafe trait ITest: IUnknown {
    unsafe fn Void(&self);
    unsafe fn Code(&self) -> HRESULT;
    unsafe fn Result(&self) -> Result<()>;
}