msft_typelib/func.rs
1//! [`FuncRecord`] -- zero-copy view of a variable-length `MSFT_FuncRecord`.
2//!
3//! Each function record describes one method or property accessor within a
4//! TypeInfo. The record layout is:
5//!
6//! ```text
7//! [Info: u32] [DataType: i32] [Flags: u32] [VtableOff: i16] [FuncDescSize: i16]
8//! [FKCCIC: u32] [NrArgs: i16] [NrOArgs: i16]
9//! [optional attrs 0..N as i32]
10//! [per-arg custom data offsets, if FKCCIC bit 12 set]
11//! [ParameterInfo entries, nrargs * 12 bytes]
12//! ```
13//!
14//! The optional attributes (in order) are: help context, help string offset,
15//! oEntry/DISPID, name offset, help string context, and custom data offset.
16
17use crate::{
18 ParameterInfo,
19 util::{read_i16_le, read_i32_le, read_u32_le},
20};
21
22/// Zero-copy view of a variable-length `MSFT_FuncRecord`.
23///
24/// The base fixed portion is [`BASE_SIZE`](Self::BASE_SIZE) (0x18) bytes.
25/// Optional attribute DWORDs and per-parameter data follow.
26///
27/// Constructed by [`FuncIter`](crate::FuncIter); the backing slice length
28/// equals the record size encoded in the `Info` field.
29#[derive(Clone, Copy, Debug)]
30pub struct FuncRecord<'a> {
31 bytes: &'a [u8],
32}
33
34impl<'a> FuncRecord<'a> {
35 /// Wraps raw record bytes as a `FuncRecord`. The caller is responsible
36 /// for slicing `bytes` to exactly the record's encoded size.
37 pub(crate) fn new(bytes: &'a [u8]) -> Self {
38 Self { bytes }
39 }
40
41 /// Minimum record size (the fixed fields before optional attributes).
42 pub const BASE_SIZE: usize = 0x18;
43
44 /// Returns the raw backing bytes for this record.
45 #[inline]
46 pub fn as_bytes(&self) -> &'a [u8] {
47 self.bytes
48 }
49
50 /// Raw `Info` field at offset 0x00.
51 ///
52 /// Low 16 bits = record size in bytes; high 16 bits = member index.
53 #[inline]
54 pub fn info(&self) -> u32 {
55 read_u32_le(self.bytes, 0x00).unwrap_or(0)
56 }
57
58 /// Record size in bytes (low 16 bits of [`info`](Self::info)).
59 #[inline]
60 pub fn record_size(&self) -> usize {
61 (self.info() & 0xFFFF) as usize
62 }
63
64 /// Return type (encoded `DataType` at offset 0x04).
65 ///
66 /// Negative values encode simple `VT_*` types inline.
67 /// Non-negative values are offsets into the type descriptor table.
68 #[inline]
69 pub fn datatype(&self) -> i32 {
70 read_i32_le(self.bytes, 0x04).unwrap_or(-1)
71 }
72
73 /// `FUNCFLAG_*` flags at offset 0x08.
74 #[inline]
75 pub fn flags(&self) -> u32 {
76 read_u32_le(self.bytes, 0x08).unwrap_or(0)
77 }
78
79 /// VTable slot offset at offset 0x0C.
80 #[inline]
81 pub fn vtable_offset(&self) -> i16 {
82 read_i16_le(self.bytes, 0x0C).unwrap_or(0)
83 }
84
85 /// `FUNCDESC` size at offset 0x0E.
86 #[inline]
87 pub fn funcdesc_size(&self) -> i16 {
88 read_i16_le(self.bytes, 0x0E).unwrap_or(0)
89 }
90
91 /// `FKCCIC` field at offset 0x10.
92 ///
93 /// Encodes function kind (bits 0-2), invoke kind (bits 3-6),
94 /// calling convention (bits 8-11), and flags (bits 7, 12).
95 #[inline]
96 pub fn fkccic(&self) -> u32 {
97 read_u32_le(self.bytes, 0x10).unwrap_or(0)
98 }
99
100 /// `FUNC_*` kind (bits 0-2 of [`fkccic`](Self::fkccic)).
101 ///
102 /// 0 = Virtual, 1 = PureVirtual, 2 = NonVirtual, 3 = Static, 4 = Dispatch.
103 #[inline]
104 pub fn func_kind(&self) -> u8 {
105 (self.fkccic() & 0x07) as u8
106 }
107
108 /// `INVOKE_*` kind (bits 3-6 of [`fkccic`](Self::fkccic)).
109 ///
110 /// 1 = Func, 2 = PropertyGet, 4 = PropertyPut, 8 = PropertyPutRef.
111 #[inline]
112 pub fn invoke_kind(&self) -> u8 {
113 ((self.fkccic() >> 3) & 0x0F) as u8
114 }
115
116 /// Calling convention (bits 8-11 of [`fkccic`](Self::fkccic)).
117 ///
118 /// 0 = FastCall, 1 = CDecl, 2 = Pascal, 4 = StdCall.
119 #[inline]
120 pub fn callconv(&self) -> u8 {
121 ((self.fkccic() >> 8) & 0x0F) as u8
122 }
123
124 /// Number of parameters at offset 0x14.
125 #[inline]
126 pub fn nrargs(&self) -> i16 {
127 read_i16_le(self.bytes, 0x14).unwrap_or(0)
128 }
129
130 /// Number of optional parameters at offset 0x16.
131 #[inline]
132 pub fn nroargs(&self) -> i16 {
133 read_i16_le(self.bytes, 0x16).unwrap_or(0)
134 }
135
136 /// Number of standard optional-attribute DWORDs between the base
137 /// fixed fields and the parameter array.
138 ///
139 /// The record layout after `BASE_SIZE` is:
140 ///
141 /// ```text
142 /// [standard attrs: 0..6 DWORDs] [arg cust data: nrargs DWORDs if bit 12]
143 /// [ParameterInfo: nrargs * 12 bytes]
144 /// ```
145 ///
146 /// This method returns only the count of standard attribute DWORDs
147 /// (up to 6), excluding per-argument custom data.
148 fn nrattribs(&self) -> usize {
149 let size = self.record_size();
150 let nrargs = self.nrargs().max(0) as usize;
151 let params_size = nrargs * ParameterInfo::SIZE;
152 let arg_cust_size = if self.has_arg_cust_data() {
153 nrargs * 4
154 } else {
155 0
156 };
157 size.saturating_sub(Self::BASE_SIZE)
158 .saturating_sub(params_size)
159 .saturating_sub(arg_cust_size)
160 / 4
161 }
162
163 /// Whether this function has custom data (FKCCIC bit 7).
164 #[inline]
165 pub fn has_cust_data(&self) -> bool {
166 self.fkccic() & 0x80 != 0
167 }
168
169 /// Whether this function has per-argument custom data (FKCCIC bit 12).
170 #[inline]
171 pub fn has_arg_cust_data(&self) -> bool {
172 self.fkccic() & 0x1000 != 0
173 }
174
175 /// Help context (attribute 0, offset 0x18).
176 ///
177 /// Returns `None` if the record is too short to contain this field.
178 pub fn help_context(&self) -> Option<i32> {
179 if self.nrattribs() > 0 {
180 read_i32_le(self.bytes, 0x18)
181 } else {
182 None
183 }
184 }
185
186 /// Help string offset in the string table (attribute 1, offset 0x1C).
187 ///
188 /// Returns `None` if the record is too short to contain this field.
189 pub fn help_string_offset(&self) -> Option<i32> {
190 if self.nrattribs() > 1 {
191 read_i32_le(self.bytes, 0x1C)
192 } else {
193 None
194 }
195 }
196
197 /// `oEntry` / DISPID (attribute 2, offset 0x20).
198 ///
199 /// For `DISPATCH` interfaces this is the DISPID.
200 /// For `MODULE` types this is a name offset or ordinal.
201 ///
202 /// Returns `None` if the record is too short to contain this field.
203 pub fn oentry(&self) -> Option<i32> {
204 if self.nrattribs() > 2 {
205 read_i32_le(self.bytes, 0x20)
206 } else {
207 None
208 }
209 }
210
211 /// Function name offset in the name table (attribute 3, offset 0x24).
212 ///
213 /// Note: in practice, function names are read from the auxiliary
214 /// arrays via [`TypeLib::func_name`](crate::TypeLib::func_name)
215 /// rather than from this record attribute.
216 ///
217 /// Returns `None` if the record is too short to contain this field.
218 pub fn name_offset(&self) -> Option<i32> {
219 if self.nrattribs() > 3 {
220 read_i32_le(self.bytes, 0x24)
221 } else {
222 None
223 }
224 }
225
226 /// Help string context (attribute 4, offset 0x28).
227 ///
228 /// Returns `None` if the record is too short to contain this field.
229 pub fn helpstringcontext(&self) -> Option<i32> {
230 if self.nrattribs() > 4 {
231 read_i32_le(self.bytes, 0x28)
232 } else {
233 None
234 }
235 }
236
237 /// Offset into the CDGuids directory for this function's custom data
238 /// (attribute 5, offset 0x2C).
239 ///
240 /// Only meaningful when [`has_cust_data`](Self::has_cust_data) returns `true`.
241 ///
242 /// Returns `None` if the record is too short to contain this field.
243 pub fn cust_data_offset(&self) -> Option<i32> {
244 if self.nrattribs() > 5 {
245 read_i32_le(self.bytes, 0x2C)
246 } else {
247 None
248 }
249 }
250
251 /// Returns the custom data offset for argument `arg_index`.
252 ///
253 /// The per-argument custom data array follows all optional attributes
254 /// (when FKCCIC bit 12 is set). Returns `None` if the function has
255 /// no per-argument custom data or the index is out of bounds.
256 pub fn arg_cust_data_offset(&self, arg_index: usize) -> Option<i32> {
257 if !self.has_arg_cust_data() {
258 return None;
259 }
260 let nrargs = self.nrargs().max(0) as usize;
261 if arg_index >= nrargs {
262 return None;
263 }
264 let attrs = self.nrattribs();
265 let offset = Self::BASE_SIZE + attrs * 4 + arg_index * 4;
266 read_i32_le(self.bytes, offset)
267 }
268}