Open alexchandel opened 7 years ago
RIDL!
is meant to define interfaces so that you can invoke methods on instances of that interface easily. To define an interface using RIDL!
should be fairly obvious based on existing usage. To use an interface defined by RIDL!
, you acquire a pointer to an IFoo
of some sort, and then call methods on it foo.DoSomething(...)
.
RIDL!
does not parse IDL files. Turning IDL files into RIDL!
invocations is a manual process (although hopefully someone will write a program to automate it).
Implementing a COM interface yourself is an entirely different matter which I don't provide any convenience macros for yet, partially because it is so complicated.
So is RIDL!
only useful for the client, or for both?
And using them requires that you already obtained a pointer to the interface somehow? Do you have to initialize the Vtbl as well?
I'm not even sure where to begin implementing a COM server in Rust. You have to do something with CoCreateInstance right?
Many APIs exist to instantiate COM interfaces for you. DirectX for example has CreateDXGIFactory
which is a function that instantiates a IDXGIFactory
for you, and then you can call methods on it to instantiate other interfaces. Doing this from Rust is quite easy and is what RIDL!
is designed for. You don't have to deal with the vtable manually at all because you get a pointer to an object that is already initialized, and all the interface methods are provided as inherent methods.
If you want to implement an interface yourself for your own type, this is where you have to deal with initializing the vtable yourself, assigning a function for each entry in the vtable, and implementing each of the functions yourself as well. If you want to implement multiple interfaces for a type it gets even worse, because each interface ends up with a different offset relative to the base address of your type, so you have to use thunks to adjust This
to the correct pointer.
I don't know anything about COM servers. I only know how the COM ABI works.
@retep998 Unfortunately there are no standard APIs to instantiate the COM classes I need. They're normally created with CoCreateInstance
/etc.
This seems to cover how to implement a COM interface in a DLL. Implementing it now, and seeing how many times I'm forced to dereference an unsafe pointer from someone else due to the design of COM, I see why so many things make explorer crash.
I took a stab at implementing a single COM interface, IOPCServer
, in rust. Look at how much boilerplate garbage there is, and I haven't even added any methods to the interface yet, because the methods all rely on other interfaces.
We need some macros/traits to wrap some of this mess up.
I don't know if there is still any interest in this issue, but I needed to implement some COM interfaces with Rust.
I managed to create a procedural macro with syn
and quote
which generates the required boilerplate.
It only works on nightly for now.
The code can be found here. It is meant to be used together with interfaces defined through RIDL!
.
@GabrielMajeri This is pretty awesome! Being able to implement COM interfaces is required to create Windows Runtime apps (which rely on IFrameworkView and IFrameworkViewSource interfaces to set up the window). I'm tempted to try that out.
Edit: just noticed there are no bindings to the WinRT API surface. Got excited a bit too early.
Well done @GabrielMajeri. I forgot how horrifically complicated this was. Do your generated implementations perform the many COM checks correctly, like checking whether REFIID
is equal to ANY of its interfaces in QueryInterface
, including inherited ones? That's a source of many Windows bugs. Does it handle inheritance ?
These are some common COM fails with IUnknown: https://blogs.msdn.microsoft.com/oldnewthing/20040326-00/?p=40033
@alexchandel It's very bare bones, and you will have to implement QueryInterface
handle checking for REFIIDs manually. You also have to write your own reference counting implementation.
There's nothing keeping you from coming up with your own macros to implement these boilerplates automatically.
It is very unsafe, because it does not check if the method implementation matches the original signature (I didn't find a way to do this easily with proc macros).
It's more of a proof of work for now, I am not actively working on it anymore, but the code is open source.
Ah ok. Still saves lots of boilerplate. You might want to link https://blogs.msdn.microsoft.com/oldnewthing/20040326-00/?p=40033 in the README so implementers remember to jump through all the hoops. (I've seen code in the field that breaks the "no secret knock" rule!)
When you declare the interface
implementation struct, does it insert any fields other than __vtable?
#[interface(IDXGIFactory)]
struct MyInterface {}
Also, the result of Self::create_vtable()
an instance of Vtbl generated by RIDL!
? Since that has additional vtables for inherited interfaces, like IUnknown
, do you fill in all those as well? Does the box have to be dropped manually in the implementation's Release
(might be a good idea to mention that in the README as well)?
@alexchandel
When you declare the interface implementation struct, does it insert any fields other than __vtable?
Nope, just puts vtable as the first field in the struct and also ensures the struct is repr(C)
.
Also, the result of Self::create_vtable() an instance of Vtbl generated by RIDL!?
This function is very simple and #[implementation(IDXGIObject)]
simply tries to construct a new IDXGIObjectVtbl
struct which should be in scope. This struct should simply be made up of a parent vtbl
pointer, and
do you fill in all those as well
By implementing a ComInterface<I>
trait for every inherited interface (impl ComInterface<IDXGIFactory>
, impl ComInterface<IDXGIObject>
) and using Rust's type system,
the Self::create_vtable()
function will recursively construct the vtable for each and every implemented interface.
Does the box have to be dropped manually in the implementation's Release
All the memory handling stuff must be handled by the implementing crate. The idea is that you never have a raw ComInterface<T>
, you always have something like Box<ComInterface<T>>
, and the Release
function converts &self
into a Box
and frees it (something like doing delete this
in C++).
I will look into updating the README
with extra information.
Other than somewhat confusing usages in https://github.com/retep998/winapi-rs/blob/master/src/shobjidl.rs, there's no documentation on how to use
RIDL!
on an IDL file or snippet, or how to create a COM server or client from Rust, or how to implementIUnknown
for COM types.For example, how would one go about implementing a COM server like
IOPCServer
in OPC DA orIOPCServerList
in OPC Common? A lot of the implementation seems to happen behind the scenes in MSVC, and it's difficult to see how it carries over to Rust.