klutzy / rust-windows

work-in-progress Win32/Win64 wrapper for Rust (INACTIVE PROJECT: has no proper goal right now)
Other
57 stars 14 forks source link

COM binding generator #5

Open klutzy opened 10 years ago

klutzy commented 10 years ago

COM is implemented as flat vtable structs. For example,

interface A {
    HRESULT DoSomethingA([in] UINT value);
}
interface B: A {
    HRESULT DoSomethingB([in] UINT value);
}

becomes (in C)

struct AVtbl {
    // A methods
    HRESULT (STDMETHODCALLTYPE *DoSomethingA)(A* This, UINT value);
};
struct A { AVtbl* vtable; };

struct BVtbl {
    // A methods (inherited)
    HRESULT (STDMETHODCALLTYPE *DoSomethingA)(B* This, UINT value);

    // B methods
    HRESULT (STDMETHODCALLTYPE *DoSomethingB)(B* This, UINT value);
};
struct B { BVtbl * vtable; };

That is, "inheritance" on COM interfaces are flatten down in C/C++ world (struct B does not inherit struct A but instead struct B copies all methods from struct A), and since COM uses simple C++ vtable ABI, it's straightforward to use COM in C (AVtbl/BVtbl).

So it's possible to generate "good" COM bindings. e.g.

struct BVtbl {
    DoSomethingA: extern "stdcall" fn(This: *mut B, value: UINT) -> HRESULT;
    DoSomethingB: extern "stdcall" fn(This: *mut B, value: UINT) -> HRESULT;
};
struct B { vtable: *const BVtbl; };
impl B {
    fn DoSomethingA(&mut self, value: UINT) -> HRESULT {
        unsafe { (self.vtable.DoSomethingA)(self, value) }
    }
    fn DoSomethingB(&mut self, value: UINT) -> HRESULT {
        unsafe { (self.vtable.DoSomethingB)(self, value) }
    }
}

then some_b.DoSomethingA(value); is possible and it looks good. See proof-of-concept demo.

mingw-w64 maintains .idl files (e.g. shobjidl.idl) as well as corresponding C headers (e.g. shobjidl.h). This means we may use rust-bindgen to generate COM binding from C header, although the "raw binding" may not be convenient.

It's also possible to create COM bindings by hands. Some months ago I've created a some COM bindings by hands, but it's definitely not feasible.

It's also possible to write a MIDL-to-rust binding generator. I think it's the best way, but currently I don't know how hard it is.. :P

emberian commented 9 years ago

I spent quite some time looking at a MIDL-to-Rust generator. It's basically impossible, at least with the IDL files that Wine distributes, because they include significant portions of C headers in them. I think it's a better idea to just generate from C headers. Note though that due to https://github.com/crabtw/rust-bindgen/issues/94 rust-bindgen can produce bad code. I've worked around this by defining _C89_NAMELESSUNION1 etc to nameless1 etc.

alexchandel commented 8 years ago

A MIDL-to-Rust compiler would be very useful. Creating bindings for things like https://github.com/mascarenhas/luacomgen/blob/master/opc/opcda.idl is exceedingly tedious, even though they're quite regular. Such a binding generator could rely on retep998/winapi-rs to resolve typedefs.

And crabtw/rust-bindgen#94 may be resolved by crabtw/rust-bindgen#135.