1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
#![recursion_limit = "1024"]

//! Implements a COM Object struct with automatic reference counting and implements
//! IUnknown for you. This covers the most common use cases of creating COM objects
//! from Rust. Supports generic parameters!
//! 
//! ```
//! #[macro_use]
//! extern crate derive_com_impl;
//! 
//! extern crate com_impl;
//! extern crate winapi;
//! extern crate wio;
//! 
//! use com_impl::{VTable, Refcount};
//! use winapi::ctypes::c_void;
//! use winapi::shared::winerror::{ERROR_INVALID_INDEX, HRESULT, HRESULT_FROM_WIN32, S_OK};
//! use winapi::um::dwrite::{IDWriteFontFileStream, IDWriteFontFileStreamVtbl};
//! use wio::com::ComPtr;
//! 
//! #[repr(C)]
//! #[derive(ComImpl)]
//! #[interfaces(IDWriteFontFileStream)]
//! pub struct FileStream {
//!     vtbl: VTable<IDWriteFontFileStreamVtbl>,
//!     refcount: Refcount,
//!     write_time: u64,
//!     file_data: Vec<u8>,
//! }
//! 
//! impl FileStream {
//!     pub fn new(write_time: u64, data: Vec<u8>) -> ComPtr<IDWriteFontFileStream> {
//!         let ptr = FileStream::create_raw(write_time, data);
//!         let ptr = ptr as *mut IDWriteFontFileStream;
//!         unsafe { ComPtr::from_raw(ptr) }
//!     }
//! }
//! 
//! #[com_impl]
//! unsafe impl IDWriteFontFileStream for FileStream {
//!     unsafe fn get_file_size(&self, size: *mut u64) -> HRESULT {
//!         *size = self.file_data.len() as u64;
//!         S_OK
//!     }
//! 
//!     unsafe fn get_last_write_time(&self, write_time: *mut u64) -> HRESULT {
//!         *write_time = self.write_time;
//!         S_OK
//!     }
//! 
//!     unsafe fn read_file_fragment(
//!         &self,
//!         start: *mut *const c_void,
//!         offset: u64,
//!         size: u64,
//!         ctx: *mut *mut c_void,
//!     ) -> HRESULT {
//!         if offset > std::isize::MAX as u64 || size > std::isize::MAX as u64 {
//!             return HRESULT_FROM_WIN32(ERROR_INVALID_INDEX);
//!         }
//! 
//!         let offset = offset as usize;
//!         let size = size as usize;
//! 
//!         if offset + size > self.file_data.len() {
//!             return HRESULT_FROM_WIN32(ERROR_INVALID_INDEX);
//!         }
//! 
//!         *start = self.file_data.as_ptr().offset(offset as isize) as *const c_void;
//!         *ctx = std::ptr::null_mut();
//! 
//!         S_OK
//!     }
//! 
//!     unsafe fn release_file_fragment(&self, _ctx: *mut c_void) {
//!         // Nothing to do
//!     }
//! }
//! 
//! fn main() {
//!     let ptr = FileStream::new(100, vec![0xDE, 0xAF, 0x00, 0xF0, 0x01]);
//! 
//!     // Do things with ptr
//! }
//! ```

#[macro_use]
extern crate quote;
#[macro_use]
extern crate syn;

extern crate proc_macro;
extern crate proc_macro2;

use proc_macro::TokenStream;
use syn::DeriveInput;
use syn::{AttributeArgs, Item};

mod derive;
mod com_impl;

#[proc_macro_derive(ComImpl, attributes(interfaces))]
/// `#[derive(ComImpl)]`
/// 
/// Automatically implements reference counting for your COM object, creating a pointer via
/// `Box::into_raw` and deallocating with `Box::from_raw`. A private inherent method named
/// `create_raw` is added to your type that takes all of your struct members except the vtable
/// and refcount as parameters in declaration order.
/// 
/// ### Additional attributes:
/// 
/// `#[interfaces(ISome, IThing)]`
/// 
/// - Specifies the COM interfaces that this type should respond to in QueryInterface. IUnknown
///   is included implicitly. If this attribute is not specified it will be assumed that the only
///   types responded to are IUnknown and the type specified in the VTable.
pub fn derive_com_impl(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    
    derive::expand_derive_com_impl(&input)
        .unwrap_or_else(compile_error)
        .into()
}

#[proc_macro_attribute]
/// `#[com_impl]`
/// 
/// Generates a VTable for the functions implemented in the `impl` block this attribute is
/// applied to.
/// 
/// For the general syntax see the example in the crate root.
/// 
/// ### Additional parameters
/// 
/// `#[com_impl(no_parent)]`
/// 
/// Specifies that the vtable being implemented here does not have a `parent` member. These
/// are very rare, but include IUnknown.
/// 
/// ### Attributes on methods
/// 
/// `#[com_name = "..."]`
/// 
/// Overrides the method name this function corresponds to in the VTable. Method names by
/// default are mapped from snake_case to PascalCase to determine their winapi names.
/// 
/// <hb/>
/// 
/// `#[panic(abort)]`
/// 
/// Specifies that in the stub function, code should be generated to catch any unwinding from
/// the user-provided bodies and abort on panic.
/// 
/// <hb/>
/// 
/// `#[panic(result = "EXPRESSION")]`
/// 
/// Specifies that in the stub functions code should be generated to catch any unwinding from
/// the user-provided bodies and return the specified expression. The expression should have
/// the same type as the standard function body return. This is most useful with functions that
/// return an HRESULT.
pub fn com_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
    let args = parse_macro_input!(attr as AttributeArgs);
    let item = parse_macro_input!(item as Item);

    com_impl::expand_com_impl(&args, &item)
        .unwrap_or_else(compile_error)
        .into()
}

fn compile_error(message: String) -> proc_macro2::TokenStream {
    quote! {
        compile_error!(#message);
    }
}