windows_rpc_macros/
lib.rs

1//! Procedural macros for generating Windows RPC client and server code.
2//!
3//! This crate provides the [`macro@rpc_interface`] attribute macro that transforms
4//! Rust trait definitions into fully functional Windows RPC clients and servers.
5//!
6//! See the [`windows_rpc`](https://docs.rs/windows-rpc) crate for the main documentation and examples.
7
8mod client_codegen;
9#[allow(dead_code)]
10mod constants;
11mod ndr;
12mod ndr64;
13mod parse;
14mod server_codegen;
15mod types;
16
17use quote::ToTokens;
18use syn::{FnArg, ReturnType, TraitItem};
19
20use client_codegen::compile_client;
21use parse::InterfaceAttributes;
22use server_codegen::compile_server;
23use types::{Interface, Method, Parameter, Type};
24
25/// Generates Windows RPC client and server code from a trait definition.
26///
27/// This attribute macro transforms a Rust trait into a complete Windows RPC interface,
28/// generating both client and server implementations that handle all the NDR marshalling,
29/// format strings, and Windows RPC runtime integration automatically.
30///
31/// # Arguments
32///
33/// The macro requires two arguments:
34///
35/// - `guid(...)` - A unique interface identifier (UUID/GUID) in hexadecimal format
36/// - `version(major.minor)` - The interface version number
37///
38/// # Generated Types
39///
40/// For a trait named `MyInterface`, the macro generates:
41///
42/// - **`MyInterfaceClient`** - A struct for making RPC calls to a server
43/// - **`MyInterfaceServerImpl`** - A trait to implement for hosting a server
44/// - **`MyInterfaceServer`** - A struct that wraps your implementation and handles RPC dispatch
45///
46/// # Supported Types
47///
48/// The following Rust types can be used for parameters and return values:
49///
50/// | Rust Type | NDR Type | Notes |
51/// |-----------|----------|-------|
52/// | `i8` | FC_SMALL | Signed 8-bit integer |
53/// | `u8` | FC_USMALL | Unsigned 8-bit integer |
54/// | `i16` | FC_SHORT | Signed 16-bit integer |
55/// | `u16` | FC_USHORT | Unsigned 16-bit integer |
56/// | `i32` | FC_LONG | Signed 32-bit integer |
57/// | `u32` | FC_ULONG | Unsigned 32-bit integer |
58/// | `i64` | FC_HYPER | Signed 64-bit integer |
59/// | `u64` | FC_HYPER | Unsigned 64-bit integer |
60/// | `&str` | Conformant string | Input parameters only |
61/// | `String` | Conformant string | Return values only |
62///
63/// # Example
64///
65/// ```rust,ignore
66/// use windows_rpc::rpc_interface;
67/// use windows_rpc::client_binding::{ClientBinding, ProtocolSequence};
68///
69/// // Define the RPC interface
70/// #[rpc_interface(guid(0x12345678_1234_1234_1234_123456789abc), version(1.0))]
71/// trait Calculator {
72///     fn add(a: i32, b: i32) -> i32;
73///     fn multiply(x: i32, y: i32) -> i32;
74///     fn greet(name: &str) -> String;
75/// }
76///
77/// // Implement the server
78/// struct CalculatorImpl;
79/// impl CalculatorServerImpl for CalculatorImpl {
80///     fn add(&self, a: i32, b: i32) -> i32 {
81///         a + b
82///     }
83///     fn multiply(&self, x: i32, y: i32) -> i32 {
84///         x * y
85///     }
86///     fn greet(&self, name: &str) -> String {
87///         format!("Hello, {name}!")
88///     }
89/// }
90///
91/// // Start the server
92/// let mut server = CalculatorServer::new(CalculatorImpl);
93/// server.register("my_endpoint").expect("Failed to register");
94/// server.listen_async().expect("Failed to listen");
95///
96/// // Create a client and call methods
97/// let binding = ClientBinding::new(ProtocolSequence::Alpc, "my_endpoint")
98///     .expect("Failed to create binding");
99/// let client = CalculatorClient::new(binding);
100///
101/// assert_eq!(client.add(10, 20), 30);
102/// assert_eq!(client.multiply(5, 6), 30);
103///
104/// server.stop().expect("Failed to stop");
105/// ```
106///
107/// # Limitations
108///
109/// - Only ALPC (local RPC) protocol is currently supported
110/// - No support for input-output (`[in, out]`) parameters
111/// - No support for pointer types, structs, arrays, or other complex types
112/// - No interface security (authentication/authorization) support
113/// - No SEH exception handling
114///
115/// # Panics
116///
117/// The macro will fail to compile if:
118///
119/// - The trait contains non-function items
120/// - A method uses `self` receiver
121/// - An unsupported type is used in parameters or return values
122/// - The GUID format is invalid
123#[proc_macro_attribute]
124pub fn rpc_interface(
125    attr: proc_macro::TokenStream,
126    input: proc_macro::TokenStream,
127) -> proc_macro::TokenStream {
128    match rpc_interface_inner(attr.into(), input.into()) {
129        Ok(ts) => ts.into(),
130        Err(e) => e.into_compile_error().into(),
131    }
132}
133
134fn rpc_interface_inner(
135    attr: proc_macro2::TokenStream,
136    input: proc_macro2::TokenStream,
137) -> syn::Result<proc_macro2::TokenStream> {
138    // Parse interface attributes (guid and version)
139    let attrs: InterfaceAttributes = syn::parse2(attr)?;
140
141    let input_clone = input.clone();
142    let t: syn::ItemTrait = syn::parse2(input)?;
143
144    let mut methods = vec![];
145    for item in t.items {
146        let TraitItem::Fn(func) = item else {
147            return Err(syn::Error::new_spanned(
148                input_clone,
149                "Only functions are allowed on this trait",
150            ));
151        };
152
153        let return_type = match func.sig.output {
154            ReturnType::Default => None,
155            ReturnType::Type(_, t) => Some(Type::try_from(*t)?),
156        };
157
158        let mut params = vec![];
159        for param in func.sig.inputs {
160            let FnArg::Typed(typed) = param else {
161                return Err(syn::Error::new_spanned(
162                    input_clone,
163                    "Passing self is currently not supported",
164                ));
165            };
166
167            let syn::Pat::Ident(param_name) = *typed.pat else {
168                return Err(syn::Error::new_spanned(
169                    typed.pat.to_token_stream(),
170                    "Expected identifier",
171                ));
172            };
173
174            let param_type = Type::try_from(*typed.ty)?;
175
176            params.push(Parameter {
177                r#type: param_type,
178                name: param_name.ident.to_string(),
179                // FIXME: let mut affect this (can be in/out)
180                is_in: true,
181                is_out: false,
182            });
183        }
184
185        methods.push(Method {
186            return_type,
187            name: func.sig.ident.to_string(),
188            parameters: params,
189        });
190    }
191
192    let interface = Interface {
193        name: t.ident.to_string(),
194        uuid: attrs.guid,
195        version: attrs.version,
196        methods,
197    };
198
199    let client_code = compile_client(&interface);
200    let server_code = compile_server(&interface);
201
202    Ok(quote::quote! {
203        #client_code
204        #server_code
205    })
206}