1use proc_macro::TokenStream;
5use quote::{format_ident, quote};
6use syn::{
7 Abi, Expr, ExprLit, FnArg, ForeignItemFn, Ident, Lit, LitStr, Meta, MetaNameValue, Pat,
8 PatType, Result, ReturnType, braced,
9 parse::{Parse, ParseStream},
10 parse_macro_input,
11 punctuated::{Pair, Punctuated},
12 token::Comma,
13};
14
15struct DelayLoadAttr {
16 pub name: LitStr,
17}
18
19impl Parse for DelayLoadAttr {
20 fn parse(input: ParseStream) -> Result<Self> {
21 let meta: Meta = input.parse()?;
22 match meta {
23 Meta::NameValue(MetaNameValue {
24 path,
25 value:
26 Expr::Lit(ExprLit {
27 lit: Lit::Str(name),
28 ..
29 }),
30 ..
31 }) if path.get_ident().map(Ident::to_string).as_deref() == Some("name") => {
32 Ok(DelayLoadAttr { name: name.clone() })
33 }
34 _ => Err(input.error(r#"expected #[delay_load(name = "...")]"#)),
35 }
36 }
37}
38
39struct ExternDecl {
40 pub abi: LitStr,
41 pub ident: Ident,
42 pub inputs: Punctuated<FnArg, Comma>,
43 pub output: ReturnType,
44}
45
46impl Parse for ExternDecl {
47 fn parse(input: ParseStream) -> Result<Self> {
48 let abi: Abi = input.parse()?;
49 let abi = abi
50 .name
51 .ok_or_else(|| input.error(r#"expected "system" or "cdecl""#))?;
52
53 let content;
54 braced!(content in input);
55 let foreign_item: ForeignItemFn = content.parse()?;
56
57 Ok(ExternDecl {
58 abi,
59 ident: foreign_item.sig.ident,
60 inputs: foreign_item.sig.inputs,
61 output: foreign_item.sig.output,
62 })
63 }
64}
65
66#[proc_macro_attribute]
68pub fn delay_load(attr: TokenStream, input: TokenStream) -> TokenStream {
69 let attr = parse_macro_input!(attr as DelayLoadAttr);
70 let ast = parse_macro_input!(input as ExternDecl);
71 impl_delay_load(&attr, &ast)
72}
73
74fn no_arg_size(undecorated: &str) -> bool {
75 use std::{collections::BTreeSet, sync::OnceLock};
76
77 static NO_ARG_SIZE_MAPI: OnceLock<BTreeSet<&'static str>> = OnceLock::new();
78 let no_arg_size_mapi = NO_ARG_SIZE_MAPI.get_or_init(|| {
79 BTreeSet::from([
80 "FixMAPI",
91 "GetOutlookVersion",
92 "HrGetOmiProvidersFlags",
94 "HrSetOmiProvidersFlagsInvalid",
95 ])
126 });
127
128 static NO_ARG_SIZE_OLMAPI: OnceLock<BTreeSet<&'static str>> = OnceLock::new();
129 let no_arg_size_olmapi = NO_ARG_SIZE_OLMAPI.get_or_init(|| {
130 BTreeSet::from([
131 "BMAPIAddress",
132 "BMAPIDetails",
133 "BMAPIFindNext",
134 "BMAPIGetAddress",
135 "BMAPIGetReadMail",
136 "BMAPIReadMail",
137 "BMAPIResolveName",
138 "BMAPISaveMail",
139 "BMAPISendMail",
140 "ClosePerformanceData",
141 "CollectPerformanceData",
142 "CreateMapiInitializationMonitor",
143 "CreateObject",
144 "DoDeliveryReport",
145 "EndBoot",
146 "EtwTraceMessage",
147 "FGetComponentPath",
148 "GetTnefStreamCodepage",
149 "HrEnsureProviderResourceDLL",
150 "HrGetDefaultStoragePathA",
151 "HrGetDefaultStoragePathW",
152 "HrGetEDPIdentifierFromStoreEIDOnMapi",
153 "HrGetOpenTnefStream",
154 "HrGetProviderResourceDLL",
155 "HrNotify",
156 "LAUNCHWIZARD",
157 "MAPIAddress",
158 "MAPIAdminProfiles",
159 "MAPIAllocateBuffer",
160 "MAPIAllocateBufferProv",
161 "MAPIAllocateMore",
162 "MAPIAllocateMoreProv",
163 "MAPICrashRecovery",
164 "MAPIDeleteMail",
165 "MAPIDetails",
166 "MAPIFindNext",
167 "MAPIFreeBuffer",
168 "MAPIInitialize",
169 "MAPILogoff",
170 "MAPILogon",
171 "MAPILogonEx",
172 "MAPIOpenFormMgr",
173 "MAPIOpenLocalFormContainer",
174 "MAPIReadMail",
175 "MAPIResolveName",
176 "MAPISaveMail",
177 "MAPISendDocuments",
178 "MAPISendMail",
179 "MAPISendMailW",
180 "MAPIUninitialize",
181 "MAPIValidateAllocatedBuffer",
182 "MSProviderInit",
183 "OpenPerformanceData",
184 "OpenStreamOnFile",
185 "OpenStreamOnFileW",
186 "OpenTnefStream",
187 "OpenTnefStreamEx",
188 "OverrideMAPIResourcePath",
189 "PRProviderInit",
190 "RPCTRACE",
191 "RTFSync",
192 "RTFSyncCpid",
193 "RopString",
194 "RpcTraceReadRegSettings",
195 "ScMAPIXFromCMC",
196 "ScMAPIXFromSMAPI",
197 "Unload",
198 "WrapCompressedRTFStream",
199 "WrapCompressedRTFStreamEx",
200 "fnevString",
201 "g_dwRpcThreshold",
202 ])
203 });
204
205 no_arg_size_mapi.contains(undecorated) || no_arg_size_olmapi.contains(undecorated)
206}
207
208fn impl_delay_load(attr: &DelayLoadAttr, ast: &ExternDecl) -> TokenStream {
209 let dll = &attr.name.value();
210 let abi = &ast.abi;
211 let name = &ast.ident;
212 let inputs = &ast.inputs;
213 let output = &ast.output;
214
215 let mut args_size = quote! { 0 };
216 let mut forward_args: Punctuated<Box<Pat>, Comma> = Punctuated::new();
217 for pair in inputs.pairs() {
218 match pair {
219 Pair::Punctuated(FnArg::Typed(PatType { pat, ty, .. }), comma) => {
220 forward_args.push_value(pat.clone());
221 forward_args.push_punct(*comma);
222 args_size = quote! { #args_size + mem::size_of::<#ty>() };
223 }
224 Pair::End(FnArg::Typed(PatType { pat, ty, .. })) => {
225 forward_args.push_value(pat.clone());
226 args_size = quote! { #args_size + mem::size_of::<#ty>() };
227 }
228 _ => panic!("should not have a receiver/self argument"),
229 }
230 }
231
232 let func_type = format_ident!("PFN{}", name);
233 let proc_name = LitStr::new(&format!("{name}"), name.span());
234
235 let undecorated = format!("{name}");
236 let build_proc_name = if no_arg_size(undecorated.as_str()) {
237 quote! {
238 let proc_name = s!(#proc_name);
239 }
240 } else {
241 quote! {
242 let mut proc_name: Vec<_> = #proc_name.bytes().collect();
243 #[cfg(target_pointer_width = "32")]
244 {
245 const ARG_SIZE: usize = #args_size;
246 proc_name.extend(format!("@{ARG_SIZE}").bytes());
247 }
248 proc_name.push(0);
249 let proc_name = PCSTR::from_raw(proc_name.as_ptr());
250 }
251 };
252
253 let call_export = if dll.as_str() == "olmapi32" {
254 quote! {
255 static EXPORT: OnceLock<Option<#func_type>> = OnceLock::new();
256
257 use ::windows::Win32::{Foundation::E_FAIL, System::LibraryLoader::*};
258
259 match (EXPORT.get_or_init(|| {
260 unsafe {
261 let module = crate::get_mapi_module();
262 GetProcAddress(module, proc_name).map(|export| unsafe { mem::transmute(export) })
263 }
264 })) {
265 Some(export) => {
266 unsafe {
267 export(#forward_args)
268 }
269 },
270 None => E_FAIL
271 }
272 }
273 } else {
274 let missing_export =
275 LitStr::new(&format!("{name} is not exported from {dll}"), name.span());
276
277 quote! {
278 static EXPORT: OnceLock<#func_type> = OnceLock::new();
279
280 (EXPORT.get_or_init(|| {
281 use ::windows::Win32::System::LibraryLoader::*;
282
283 unsafe {
284 let module = crate::get_mapi_module();
285 mem::transmute(GetProcAddress(module, proc_name).expect(#missing_export))
286 }
287 }))(#forward_args)
288 }
289 };
290
291 let output = quote! {
292 unsafe fn #name(#inputs) #output {
293 use std::{mem, sync::OnceLock};
294 use ::windows_core::*;
295
296 #build_proc_name
297
298 type #func_type = unsafe extern #abi fn(#inputs) #output;
299
300 #call_export
301 }
302 };
303
304 output.into()
305}