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}