-
Notifications
You must be signed in to change notification settings - Fork 503
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Simplify WinRT and COM class authoring #1094
Comments
@kennykerr Is it possible today to author WinRT components in Rust, either using windows-rs crate or not? 🤔 I suppose it would require the use of Midl compiler + writing COM wiring manually. Could you maybe provide a sample of what can be done today? Thanks! |
You can do so today but yes you'd need to write some IDL and call the MIDL compiler to produce a winmd. Next year I'm hoping to spend most of my time on improving authoring support and being able to produce a winmd directly from Rust so that IDL will not be required. |
Thank you for the quick response! 😊 I don't mind writing IDL and calling Midl compiler manually. Having an ability to author WinRT components would unblock the use of Rust for me 🙂 Do I understand correctly that I would need to call the And implement an |
Yep, that's it. It's a bit error-prone which is also why I'm planning on automating much of that. |
Yeah, I can imagine 😅 I'm gonna try this out and if I succeed, I could open a PR to the Samples repo, if you would be interested 🙂 |
Sounds good! |
Hi @kennykerr , you might be interested to take a look: https://github.com/Alovchin91/winrt-component-rs Please let me know if you have any suggestions, comments etc. 🙂 I'll also open a bunch of issues to share my experience with |
Thanks @Alovchin91, that's very helpful. I'm now starting to work on this and #1093 in earnest. We should get to a point where MIDL is no longer required. |
Synced a bit offline, just want to leave a comment here, please also consider authoring event and delegate, prototype a winrt "event" type (similar like c++/winrt winrt::event struct ) so when implement the event, we can know how to manage event handler and token. Thanks! |
0.36.0 has been released and includes the new |
Any update to this task? Looking forward to this so much! |
I'm hard at work maturing support for component authoring. You can already implement components, with some limitations. There is an example here that you can use as a starting point: https://github.com/microsoft/windows-rs/tree/master/crates/tests/component |
Trying to understand this, I'm coming from
Is the test component example somehow the same thing but without macros? I can't find similar types of macro shortcuts in there, maybe it's more manual now? Usage examples in com-rsDefining interface #[com_interface("CD403E52-DEED-4C13-B437-B98380F2B1E8")]
pub trait IVirtualDesktopNotification: IUnknown {
unsafe fn virtual_desktop_created(
&self,
monitors: ComRc<dyn IObjectArray>,
desktop: ComRc<dyn IVirtualDesktop>,
) -> HRESULT;
unsafe fn virtual_desktop_destroy_begin(
&self,
monitors: ComRc<dyn IObjectArray>,
desktopDestroyed: ComRc<dyn IVirtualDesktop>,
desktopFallback: ComRc<dyn IVirtualDesktop>,
) -> HRESULT;
// ...
} and implementing class for it #[co_class(implements(IVirtualDesktopNotification))]
struct VirtualDesktopChangeListener {
sender: Mutex<Option<VirtualDesktopEventSender>>,
}
impl IVirtualDesktopNotification for VirtualDesktopChangeListener {
/// On desktop creation
unsafe fn virtual_desktop_created(
&self,
_monitors: ComRc<dyn IObjectArray>,
idesktop: ComRc<dyn IVirtualDesktop>,
) -> HRESULT {
HRESULT::ok()
}
/// On desktop destroy begin
unsafe fn virtual_desktop_destroy_begin(
&self,
_monitors: ComRc<dyn IObjectArray>,
_destroyed_desktop: ComRc<dyn IVirtualDesktop>,
_fallback_desktop: ComRc<dyn IVirtualDesktop>,
) -> HRESULT {
HRESULT::ok()
}
// ...
} Above are snipptes from my code I have a lots of code written with com-rs crate. I'm trying to reimplement these in windows-rs, but I can't find similar examples as in com-rs crate had. |
I'm in the middle of developing first-class component authoring support, hence the lack of docs and samples but you can take this example as a guide. That happens to be a WinRT component but the same pattern applies for COM components. COM factories just implement By the way, your |
I got it working with similar macros: What I don't get is this instruction to use ManuallyDrop whenever there is #[windows_interface::interface("CD403E52-DEED-4C13-B437-B98380F2B1E8")]
pub unsafe trait IVirtualDesktopNotification: IUnknown {
unsafe fn virtual_desktop_created(
&self,
monitors: IObjectArray, // If I use ManuallyDrop<IObjectArray> it doesn't feel right here? It works without
desktop: IVirtualDesktop,
) -> HRESULT;
unsafe fn virtual_desktop_destroy_begin(
&self,
monitors: IObjectArray,
desktopDestroyed: IVirtualDesktop,
desktopFallback: IVirtualDesktop,
) -> HRESULT;
// ...
} And implementation: #[windows::core::implement(IVirtualDesktopNotification)]
struct VirtualDesktopNotification {}
impl IVirtualDesktopNotification_Impl for VirtualDesktopNotification {
unsafe fn virtual_desktop_created(
&self,
monitors: IObjectArray,
desktop: IVirtualDesktop,
) -> HRESULT {
HRESULT(0)
}
unsafe fn virtual_desktop_destroy_begin(
&self,
monitors: IObjectArray,
desktopDestroyed: IVirtualDesktop,
desktopFallback: IVirtualDesktop,
) -> HRESULT {
HRESULT(0)
}
// ....
} |
You’re passing |
@Alovchin91 Windows calls those functions, I give the instance to some register API. If I've understood COM correctly the caller increments before calling, and the one using it releases at the end. So it should work without ManuallyDrops? |
@Ciantic That seems to agree with how the That leaves me wondering, though...
|
I'm leaning that the table is not accurate. We should not use ManuallyDrop if COM API works as it should. However, a bigger example in the official FAQ might be in order. The use case people need to see is translating C++ to Rust. My current thought is this: C++ C++ C++ C++ Additionally example of
E.g. here is C++
MIDL_INTERFACE("B2F925B9-5A0F-4D2E-9F4D-2B1507593C10")
IVirtualDesktopManagerInternal : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE GetCount(
_In_opt_ HMONITOR monitor,
_Out_ UINT* pCount) = 0;
virtual HRESULT STDMETHODCALLTYPE MoveViewToDesktop(
_In_ IApplicationView* pView,
_In_ IVirtualDesktop* pDesktop) = 0;
virtual HRESULT STDMETHODCALLTYPE CanViewMoveDesktops(
_In_ IApplicationView* pView,
_Out_ BOOL* pfCanViewMoveDesktops) = 0;
virtual HRESULT STDMETHODCALLTYPE GetCurrentDesktop(
_In_opt_ HMONITOR monitor,
_Out_ IVirtualDesktop** desktop) = 0;
virtual HRESULT STDMETHODCALLTYPE GetAllCurrentDesktops(
_Out_ IObjectArray** ppDesktops) = 0;
virtual HRESULT STDMETHODCALLTYPE GetDesktops(
_In_opt_ HMONITOR monitor,
_Out_ IObjectArray** ppDesktops) = 0;
virtual HRESULT STDMETHODCALLTYPE GetAdjacentDesktop(
_In_ IVirtualDesktop* pDesktopReference,
_In_ AdjacentDesktop uDirection,
_Out_ IVirtualDesktop** ppAdjacentDesktop) = 0;
virtual HRESULT STDMETHODCALLTYPE SwitchDesktop(
_In_opt_ HMONITOR monitor,
_In_ IVirtualDesktop* pDesktop) = 0;
virtual HRESULT STDMETHODCALLTYPE CreateDesktopW(
_In_opt_ HMONITOR monitor,
_Out_ IVirtualDesktop** ppNewDesktop) = 0;
// ...
} I think it translates to this: #[windows_interface::interface("b2f925b9-5a0f-4d2e-9f4d-2b1507593c10")]
pub unsafe trait IVirtualDesktopManagerInternal: IUnknown {
unsafe fn get_count(&self, monitor: Option<HMONITOR>, outCount: *mut UINT) -> HRESULT;
unsafe fn move_view_to_desktop(
&self,
view: IApplicationView,
desktop: IVirtualDesktop,
) -> HRESULT;
unsafe fn can_move_view_between_desktops(
&self,
view: IApplicationView,
canMove: *mut i32,
) -> HRESULT;
unsafe fn get_current_desktop(
&self,
monitor: HMONITOR,
outDesktop: *mut Option<IVirtualDesktop>,
) -> HRESULT;
unsafe fn get_all_current_desktops(&self, outDesktops: *mut Option<IObjectArray>) -> HRESULT;
unsafe fn get_desktops(
&self,
monitor: HMONITOR,
outDesktops: *mut Option<IObjectArray>,
) -> HRESULT;
unsafe fn get_adjacent_desktop(
&self,
inDesktop: IVirtualDesktop,
direction: UINT,
out_pp_desktop: *mut Option<IVirtualDesktop>,
) -> HRESULT;
unsafe fn switch_desktop(&self, monitor: HMONITOR, desktop: IVirtualDesktop) -> HRESULT;
unsafe fn create_desktop(
&self,
monitor: HMONITOR,
outDesktop: *mut Option<IVirtualDesktop>,
) -> HRESULT;
// ...
} |
This is slightly confusing as a COM interface pointer is modeled as a value type in Rust. If you think of it in terms of C++ it makes a little more sense conceptually. An input parameter passes the raw pointer into the function. The caller ensures that the pointer is stable for the duration of the synchronous call and the callee depends on that assurance, but the caller does not transfer ownership to the callee. Rust models it more like a C++ smart pointer, but such abstractions are not valid on the ABI where the parameter must ultimately be the equivalent of a raw C++ pointer. This is why @wesleywiser correctly suggests using Anyway, I still plan to make this a lot simpler and safer in Rust. If you want to understand how this all works, I explain all of this and much more in great detail here: |
This does sound like I have used ComPtr in C++ successfully, and ComRc and ComPtr in I actually have the book Essential COM, Don Box it feels like I have enough COM info for my lifetime, maybe your content is more succinct. |
Keep in mind that COM relies on the |
Now I'm just getting clever. virtual HRESULT STDMETHODCALLTYPE SwitchDesktop(
_In_opt_ HMONITOR monitor,
_In_ IVirtualDesktop* pDesktop) = 0; For that // Behaves like ManuallyDrop but is kept alive for as long as the given
// reference
#[repr(transparent)]
pub struct ComIn<'a, T: Vtable> {
data: *mut c_void,
_phantom: std::marker::PhantomData<&'a T>,
}
impl<'a, T: Vtable> ComIn<'a, T> {
pub fn new(t: &'a T) -> Self {
Self {
// Copies the raw Inteface pointer
data: t.as_raw(),
_phantom: std::marker::PhantomData,
}
}
}
impl<'a, T: Vtable> Deref for ComIn<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { std::mem::transmute(&self.data) }
}
} And use it in interface definitions like this: unsafe fn switch_desktop(&self, monitor: HMONITOR, desktop: ComIn<IVirtualDesktop>) -> HRESULT; And call it like this: unsafe {
manager
.switch_desktop(0, ComIn::new(¤t_desk))
.unwrap()
}; NOTE I think the table is correct, but here is it again: C++
|
I'm not sure if this is the right place, but I want to prevent duplicated issues. #[interface("3a14c9c0-bc3e-453f-a314-4ce4a0ec81d8")]
unsafe trait IHostObjectSample: IDispatch {
fn greet(&self, name: BSTR) -> BSTR;
}
#[implement(IHostObjectSample)]
struct HostObjectSample {}
impl IHostObjectSample_Impl for HostObjectSample {
unsafe fn greet(&self, name: BSTR) -> BSTR {
let wide = name.as_wide();
BSTR::from_wide(&wide).unwrap()
}
} But it seems it's not possible for now, and it'll need to wait for authoring support. Or is there a way to implement |
You should be able to implement
Then you just need to include an implementation: impl IDispatch_Impl for HostObjectSample {
fn GetTypeInfoCount(&self) -> Result<u32> { todo!() }
fn GetTypeInfo(&self, _: u32, _: u32) -> Result<ITypeInfo> { todo!() }
fn GetIDsOfNames(&self, _: *const GUID, _: *const PCWSTR, _: u32, _: u32, _: *mut i32) -> Result<()> { todo!() }
fn Invoke(&self, _: i32, _: *const GUID, _: u32, _: DISPATCH_FLAGS, _: *const DISPPARAMS, _: *mut VARIANT, _: *mut EXCEPINFO, _: *mut u32) -> Result<()> { todo!() }
} |
@kennykerr would you please provide a simple |
For WebView2 host objects, you can skip implementing (Edit: FWIW, I'd check whether this gives better performance than other ways of communicating with your Rust code if performance is what you're aiming for. For the Tauri app that I'm working on, host objects are actually slightly slower than whatever Tauri is doing for its built-in commands, at least for basic in/out objects, but YMMV.) |
I wrote a macro called |
Something changed between 0.53 and 0.56, this code
Began to give an error that it requires a "windows_core" crate, I added |
I think this actually makes a lot of sense, as long as authoring WinRT components is still a supported scenario. IDL works well and aligns with what C++/WinRT developers do. If we could get metadata generation support via IDL with shim like DllGetActivationFactory, DllCanUnloadNow etc. generation instead, that would be awesome 👍 |
Alternatively, having a generated associated |
Yes, I plan to at least bring it on par with C++/WinRT in terms of the scaffolding provided for things like |
What about using the |
Oh! I didn't realise it was available. Great, thanks! 👍 |
Dedicating an issue to this topic. Originally part of #81 (huge thread).
The windows crate now supports implementing COM and WinRT interfaces, but more is required to support classes as as whole. This early sample illustrates what's possible today:
https://github.com/kennykerr/component-rs
I still have much work to do to streamline this experience.
The text was updated successfully, but these errors were encountered: