intercom_common/attributes/
com_class.rs

1use super::common::*;
2use crate::prelude::*;
3
4use crate::idents;
5use crate::idents::SomeIdent;
6use crate::model;
7use crate::utils;
8
9use crate::tyhandlers::ModelTypeSystem;
10
11use syn::spanned::Spanned;
12
13/// Expands the `com_class` attribute.
14///
15/// The attribute expansion results in the following items:
16///
17/// - Virtual table offset values for the different interfaces.
18/// - `IUnknown` virtual table instance.
19/// - `CoClass` trait implementation.
20pub fn expand_com_class(
21    attr_tokens: TokenStreamNightly,
22    item_tokens: TokenStreamNightly,
23) -> Result<TokenStreamNightly, model::ParseError>
24{
25    // Parse the attribute.
26    let mut output = vec![];
27    let cls = model::ComClass::parse(&lib_name(), attr_tokens.into(), item_tokens.clone().into())?;
28    let cls_ident = &cls.name;
29    let cls_name = cls_ident.to_string();
30    let (impl_generics, ty_generics, where_clause) = cls.generics.split_for_impl();
31
32    // IUnknown vtable match. As the primary query_interface is implemented
33    // on the root IUnknown interface, the self_vtable here should already be
34    // the IUnknown we need.
35    let support_error_info_vtbl = quote!(
36        <dyn intercom::ISupportErrorInfo as intercom::attributes::ComInterfaceVariant<
37            intercom::type_system::AutomationTypeSystem,
38        >>::VTable
39    );
40    let mut query_interface_match_arms = vec![
41        quote!(
42            if riid == <dyn intercom::IUnknown as intercom::attributes::ComInterfaceVariant<intercom::type_system::AutomationTypeSystem>>::iid() {
43                let ptr = ( &vtables._ISupportErrorInfo )
44                    as *const &#support_error_info_vtbl
45                    as *mut &#support_error_info_vtbl
46                    as intercom::raw::RawComPtr;
47                intercom::logging::trace(|l| l(module_path!(), format_args!(
48                    "[{:p}] {}::query_interface({:-X}) -> IUnknown [{:p}]",
49                    vtables, #cls_name, riid, ptr)));
50                ptr
51            } else
52        ),
53        quote!(
54            if riid == <dyn intercom::ISupportErrorInfo as intercom::attributes::ComInterfaceVariant<intercom::type_system::AutomationTypeSystem>>::iid() {
55                let ptr = ( &vtables._ISupportErrorInfo )
56                    as *const &#support_error_info_vtbl
57                    as *mut &#support_error_info_vtbl
58                    as intercom::raw::RawComPtr;
59                intercom::logging::trace(|l| l(module_path!(), format_args!(
60                    "[{:p}] {}::query_interface({:-X}) -> ISupportErrorInfo [{:p}]",
61                    vtables, #cls_name, riid, ptr)));
62                ptr
63            } else
64        ),
65    ];
66    let mut support_error_info_match_arms = vec![];
67
68    output.push(quote!(
69        impl #impl_generics intercom::IUnknown for #cls_ident #ty_generics #where_clause {}
70    ));
71
72    // Gather the virtual table list struct field definitions and their values.
73    // The definitions are needed when we define the virtual table list struct,
74    // which is different for each com_class. The values are needed when we
75    // construct the virtual table list.
76    //
77    // The primary IUnknown virtual table _MUST_ be at the beginning of the list.
78    // This is done to ensure the IUnknown pointer matches the ComBoxData pointer.
79    // We ensure this by defining the primary IUnknown methods on the
80    // ISupportErrorInfo virtual table and having that at the beginning.
81    let mut vtable_list_field_defs = vec![];
82    let mut vtable_list_field_decls = vec![quote!(
83        _ISupportErrorInfo:
84            &'static <dyn intercom::ISupportErrorInfo as intercom::attributes::ComInterfaceVariant<
85                intercom::type_system::AutomationTypeSystem,
86            >>::VTable
87    )];
88    let mut vtable_list_field_values = vec![];
89    let mut vtable_list_field_ptrs = vec![quote!(
90                 _ISupportErrorInfo :
91                &<dyn intercom::ISupportErrorInfo as intercom::attributes::ComInterfaceVTableFor<
92                    dyn intercom::ISupportErrorInfo,
93                    #cls_ident #ty_generics,
94                    intercom::type_system::AutomationTypeSystem>>::VTABLE
95    )];
96
97    // Create the vtable data for the additional interfaces.
98    // The data should include the match-arms for the primary query_interface
99    // and the vtable offsets used for the delegating query_interface impls.
100    for itf in &cls.interfaces {
101        let maybe_dyn = match cls.is_self_path(itf) {
102            true => quote!(),
103            false => quote_spanned!(itf.span() => dyn),
104        };
105        output.push(quote_spanned!(itf.span() =>
106            impl #impl_generics intercom::attributes::HasInterface<#maybe_dyn #itf> for #cls_ident #ty_generics #where_clause {}
107        ));
108
109        for &ts in &[ModelTypeSystem::Automation, ModelTypeSystem::Raw] {
110            // Various idents.
111            let itf_ident = itf.get_some_ident().expect("#[com_interface] had no ident");
112            let itf_variant = Ident::new(&format!("{}_{:?}", itf_ident, ts), itf.span());
113            let ts_type = ts.as_typesystem_type(itf.span());
114
115            // Implement ComClassInterface.
116            output.push(quote!(
117                #[allow(non_snake_case)]
118                impl #impl_generics intercom::attributes::ComClassInterface<
119                    #maybe_dyn #itf, #ts_type> for #cls_ident #ty_generics #where_clause {
120
121                    #[inline(always)]
122                    fn offset() -> usize {
123                        unsafe {
124                            &intercom::ComBoxData::< #cls_ident #ty_generics >::null_vtable().#itf_variant
125                                    as *const _ as usize
126                        }
127                    }
128                }
129            ));
130
131            // Access the ComInterfaceVariant through the ComInterface trait to
132            // ensure the error messages will first report missing ComInterface
133            // trait.
134            let itf_attrib_data = quote!(
135                <<#maybe_dyn #itf as intercom::attributes::ComInterface>::TSelf
136                    as intercom::attributes::ComInterfaceVariant<#ts_type>>);
137            let itf_vtable_for = quote!(
138                <#maybe_dyn #itf as intercom::attributes::ComInterfaceVTableFor<#maybe_dyn #itf, #cls_ident #ty_generics, #ts_type>>);
139
140            // Add the interface in the vtable list.
141            vtable_list_field_defs.push(quote!( #itf_variant : #itf_attrib_data::VTable));
142            vtable_list_field_decls.push(quote!( #itf_variant : &'static #itf_attrib_data::VTable));
143            vtable_list_field_values.push(quote!( #itf_variant : #itf_vtable_for::VTABLE));
144            vtable_list_field_ptrs.push(quote!( #itf_variant : &#itf_vtable_for::VTABLE));
145
146            // Define the query_interface match arm for the current interface.
147            // This just gets the correct interface vtable reference from the list
148            // of vtables.
149            let itf_name = itf_ident.to_string();
150            let ts_name = format!("{:?}", ts);
151            query_interface_match_arms.push(quote!(
152                if riid == #itf_attrib_data::iid() {
153                    let ptr = &vtables.#itf_variant
154                        as *const &#itf_attrib_data::VTable
155                        as *mut &#itf_attrib_data::VTable
156                        as intercom::raw::RawComPtr;
157                    intercom::logging::trace(|l| l(module_path!(), format_args!(
158                        "[{:p}] {}::query_interface({:-X}) -> {} ({}) [{:p}]",
159                        vtables, #cls_name, riid, #itf_name, #ts_name, ptr)));
160                    ptr
161                } else
162            ));
163
164            // Define the support error info match arms.
165            support_error_info_match_arms.push(quote!(
166                if riid == #itf_attrib_data::iid() {
167                    true
168                } else
169            ));
170        }
171    }
172
173    /////////////////////
174    // ISupportErrorInfo virtual table instance.
175    //
176    // The primary IUnknown virtual table is embedded in this one.
177    output.push(quote!(
178        #[allow(non_snake_case)]
179        impl #impl_generics intercom::attributes::ComClassInterface<
180            dyn intercom::ISupportErrorInfo,
181            intercom::type_system::AutomationTypeSystem>
182        for #cls_ident #ty_generics #where_clause {
183
184            #[inline(always)]
185            fn offset() -> usize { 0 }
186        }
187    ));
188
189    // Mark the struct as having IUnknown.
190    output.push(quote!(
191        impl #impl_generics intercom::attributes::HasInterface< dyn intercom::IUnknown > for #cls_ident #ty_generics #where_clause {}
192    ));
193
194    // The ComClass implementation.
195    //
196    // Define the vtable list struct first. This lists the vtables of all the
197    // interfaces that the coclass implements.
198
199    // VTableList struct definition.
200    let vtable_list_ident = Ident::new(
201        &format!("__intercom_vtable_for_{}", cls_ident),
202        Span::call_site(),
203    );
204    let visibility = &cls.visibility;
205    output.push(quote!(
206        #[allow(non_snake_case)]
207        #[doc(hidden)]
208        #[derive(Clone, Copy)]
209        #visibility struct #vtable_list_ident {
210            #( #vtable_list_field_decls ),*
211        }
212    ));
213
214    // The actual ComClass implementation.
215    let vtable_static_ident = Ident::new(
216        &format!("Static{}", vtable_list_ident),
217        vtable_list_ident.span(),
218    );
219    output.push(quote!(
220        #[allow(non_snake_case)]
221        #visibility struct #vtable_static_ident {
222            #( #vtable_list_field_defs ),*
223        }
224
225        #[allow(clippy::all)]
226        impl #impl_generics intercom::attributes::ComClass for #cls_ident #ty_generics #where_clause {
227            type VTableList = #vtable_list_ident;
228            const VTABLE : Self::VTableList = #vtable_list_ident {
229                #( #vtable_list_field_ptrs ),*
230            };
231            fn query_interface(
232                vtables : &Self::VTableList,
233                riid : intercom::REFIID,
234            ) -> intercom::RawComResult< intercom::raw::RawComPtr > {
235                if riid.is_null() {
236                    intercom::logging::error(|l| l(module_path!(), format_args!(
237                        "[{:p}] {}::query_interface(NULL)", vtables, #cls_name)));
238                    return Err( intercom::raw::E_NOINTERFACE );
239                }
240                unsafe {
241                    let riid = &*riid;
242                    intercom::logging::trace(|l| l(module_path!(), format_args!(
243                        "[{:p}] {}::query_interface({:-X})", vtables, #cls_name, riid)));
244                    Ok(
245                        #( #query_interface_match_arms )*
246                        {
247                            intercom::logging::trace(|l| l(module_path!(), format_args!(
248                                "[{:p}] {}::query_interface({:-X}) -> E_NOINTERFACE", vtables, #cls_name, riid)));
249                            return Err( intercom::raw::E_NOINTERFACE )
250                        }
251                    )
252                }
253            }
254
255            fn interface_supports_error_info(
256                riid : intercom::REFIID
257            ) -> bool
258            {
259                if riid.is_null() { return false; }
260                unsafe {
261                    let riid = &*riid;
262                    #( #support_error_info_match_arms )*
263                    { false }
264                }
265            }
266        }
267    ));
268
269    // CLSID constant for the class.
270    let clsid_ident = idents::clsid(cls_ident);
271    if let Some(ref guid) = cls.clsid {
272        let clsid_guid_tokens = utils::get_guid_tokens(guid, Span::call_site());
273        let clsid_doc = format!("`{}` class ID.", cls_ident);
274        let clsid_const = quote!(
275            #[allow(non_upper_case_globals)]
276            #[doc = #clsid_doc ]
277            pub const #clsid_ident : intercom::CLSID = #clsid_guid_tokens;
278        );
279        output.push(clsid_const);
280    }
281
282    output.push(create_get_typeinfo_function(&cls));
283
284    Ok(tokens_to_tokenstream(item_tokens, output))
285}
286
287fn create_get_typeinfo_function(cls: &model::ComClass) -> TokenStream
288{
289    let fn_name = Ident::new(
290        &format!("get_intercom_coclass_info_for_{}", cls.name),
291        Span::call_site(),
292    );
293    let cls_ident = &cls.name;
294    let cls_name = cls.name.to_string();
295    let clsid = match &cls.clsid {
296        Some(guid) => guid,
297        None => {
298            return quote!(
299                pub(crate) fn #fn_name() -> Vec<intercom::typelib::TypeInfo>
300                { vec![] }
301            )
302        }
303    };
304    let clsid_tokens = utils::get_guid_tokens(clsid, Span::call_site());
305    let (impl_generics, ty_generics, where_clause) = cls.generics.split_for_impl();
306    let (interfaces, interface_info): (Vec<_>, Vec<_>) = cls
307        .interfaces
308        .iter()
309        .map(|itf_path| {
310            let itf_name = itf_path.get_some_ident().expect("#[com_interface] had no ident").to_string();
311            let maybe_dyn = match cls.is_self_path(itf_path) {
312                true => quote!(),
313                false => quote_spanned!(itf_path.span() => dyn),
314            };
315
316            // Access the ComInterfaceVariant through the ComInterface trait to
317            // ensure the error messages will first report missing ComInterface
318            // trait.
319            let attr_cominterfacevariant = quote!(
320                <#maybe_dyn #itf_path as intercom::attributes::ComInterface>::TSelf
321                    as intercom::attributes::ComInterfaceVariant
322            );
323            (
324                quote!( intercom::typelib::InterfaceRef {
325                    name: #itf_name.into(),
326                    iid_automation: <#attr_cominterfacevariant<intercom::type_system::AutomationTypeSystem>>::iid().clone(),
327                    iid_raw: <#attr_cominterfacevariant<intercom::type_system::RawTypeSystem>>::iid().clone(),
328                } ),
329                quote!(
330                    r.extend(<#maybe_dyn #itf_path as intercom::attributes::ComInterfaceTypeInfo>::gather_type_info());
331                ),
332            )
333        })
334        .unzip();
335    quote!(
336        impl #impl_generics intercom::attributes::ComClassTypeInfo for #cls_ident #ty_generics #where_clause
337        {
338            fn gather_type_info() -> Vec<intercom::typelib::TypeInfo>
339            {
340                let mut r = vec![ intercom::typelib::TypeInfo::Class(
341                    intercom::ComBox::new( intercom::typelib::CoClass::__new(
342                        #cls_name.into(),
343                        #clsid_tokens,
344                        vec![ #( #interfaces ),* ]
345                    ) ) )
346                ];
347                #( #interface_info )*
348                r
349            }
350        }
351    )
352}