oo_bindgen/backend/dotnet/
structure.rs

1use crate::backend::dotnet::conversion::*;
2use crate::backend::dotnet::doc::*;
3use crate::backend::dotnet::formatting::*;
4use crate::backend::dotnet::*;
5
6pub(crate) fn generate(
7    f: &mut dyn Printer,
8    lib: &Library,
9    st: &StructType<Validated>,
10) -> FormattingResult<()> {
11    match st {
12        StructType::FunctionArg(x) => {
13            generate_skeleton(f, x, lib, true, &|f| generate_to_native_conversions(f, x))
14        }
15        StructType::FunctionReturn(x) => {
16            generate_skeleton(f, x, lib, false, &|f| generate_to_dotnet_conversions(f, x))
17        }
18        StructType::CallbackArg(x) => {
19            generate_skeleton(f, x, lib, false, &|f| generate_to_dotnet_conversions(f, x))
20        }
21        StructType::Universal(x) => generate_skeleton(f, x, lib, true, &|f| {
22            generate_to_native_conversions(f, x)?;
23            generate_to_dotnet_conversions(f, x)
24        }),
25    }
26}
27
28trait DotNetVisibility {
29    fn to_str(&self) -> &str;
30}
31
32impl DotNetVisibility for Visibility {
33    fn to_str(&self) -> &str {
34        match self {
35            Visibility::Public => "public",
36            Visibility::Private => "internal",
37        }
38    }
39}
40
41fn get_field_value<T>(
42    field: &StructField<T, Validated>,
43    constructor: &Initializer<Validated>,
44) -> String
45where
46    T: StructFieldType,
47{
48    match constructor.values.iter().find(|x| x.name == field.name) {
49        Some(x) => match &x.value {
50            ValidatedDefaultValue::Bool(x) => x.to_string(),
51            ValidatedDefaultValue::Number(x) => match x {
52                NumberValue::U8(x) => x.to_string(),
53                NumberValue::S8(x) => x.to_string(),
54                NumberValue::U16(x) => x.to_string(),
55                NumberValue::S16(x) => x.to_string(),
56                NumberValue::U32(x) => x.to_string(),
57                NumberValue::S32(x) => x.to_string(),
58                NumberValue::U64(x) => x.to_string(),
59                NumberValue::S64(x) => x.to_string(),
60                NumberValue::Float(x) => format!("{x}F"),
61                NumberValue::Double(x) => x.to_string(),
62            },
63            ValidatedDefaultValue::Duration(t, x) => match t {
64                DurationType::Milliseconds => {
65                    format!("TimeSpan.FromMilliseconds({})", t.get_value_string(*x))
66                }
67                DurationType::Seconds => {
68                    format!("TimeSpan.FromSeconds({})", t.get_value_string(*x))
69                }
70            },
71            ValidatedDefaultValue::Enum(x, variant) => {
72                format!("{}.{}", x.name.camel_case(), variant.camel_case())
73            }
74            ValidatedDefaultValue::String(x) => format!("\"{x}\""),
75            ValidatedDefaultValue::DefaultStruct(handle, _, _) => {
76                format!("new {}()", handle.name().camel_case())
77            }
78        },
79        None => field.name.mixed_case(),
80    }
81}
82
83fn write_static_constructor<T>(
84    f: &mut dyn Printer,
85    handle: &Struct<T, Validated>,
86    constructor: &Handle<Initializer<Validated>>,
87) -> FormattingResult<()>
88where
89    T: StructFieldType + TypeInfo,
90{
91    write_constructor_documentation(f, handle, constructor, true)?;
92
93    let invocation_args = handle
94        .fields()
95        .map(|sf| get_field_value(sf, constructor))
96        .collect::<Vec<String>>()
97        .join(", ");
98
99    f.writeln(&format!(
100        "public static {} {}({})",
101        handle.name().camel_case(),
102        constructor.name.camel_case(),
103        constructor_parameters(handle, constructor)
104    ))?;
105
106    blocked(f, |f| {
107        f.writeln(&format!(
108            "return new {}({});",
109            handle.declaration.name().camel_case(),
110            invocation_args
111        ))
112    })
113}
114
115fn get_default_value_doc(x: &ValidatedDefaultValue) -> String {
116    match x {
117        ValidatedDefaultValue::Bool(x) => x.to_string(),
118        ValidatedDefaultValue::Number(x) => x.to_string(),
119        ValidatedDefaultValue::Duration(DurationType::Milliseconds, x) => {
120            format!("{}ms", x.as_millis())
121        }
122        ValidatedDefaultValue::Duration(DurationType::Seconds, x) => format!("{}s", x.as_secs()),
123        ValidatedDefaultValue::Enum(handle, variant) => format!(
124            "<see cref=\"{}.{}\" />",
125            handle.name.camel_case(),
126            variant.camel_case()
127        ),
128        ValidatedDefaultValue::String(x) => format!("\"{x}\""),
129        ValidatedDefaultValue::DefaultStruct(x, _, _) => {
130            format!("Default <see cref=\"{}\" />", x.name().camel_case())
131        }
132    }
133}
134
135fn write_constructor_documentation<T>(
136    f: &mut dyn Printer,
137    handle: &Struct<T, Validated>,
138    constructor: &Handle<Initializer<Validated>>,
139    write_return_info: bool,
140) -> FormattingResult<()>
141where
142    T: StructFieldType + TypeInfo,
143{
144    documentation(f, |f| {
145        xmldoc_print(f, &constructor.doc)?;
146
147        if !constructor.values.is_empty() {
148            f.newline()?;
149            f.writeln("<remarks>")?;
150            f.writeln("Default values:")?;
151            f.writeln("<list type=\"bullet\">")?;
152            for init_value in constructor.values.iter() {
153                f.writeln(&format!(
154                    "<item><description><see cref=\"{}.{}\" />: {}</description></item>",
155                    handle.name().camel_case(),
156                    init_value.name.camel_case(),
157                    get_default_value_doc(&init_value.value)
158                ))?;
159            }
160            f.writeln("</list>")?;
161            f.writeln("</remarks>")?;
162        }
163
164        f.newline()?;
165
166        for arg in handle.initializer_args(constructor.clone()) {
167            f.writeln(&format!("<param name=\"{}\">", arg.name.mixed_case()))?;
168            docstring_print(f, &arg.doc.brief)?;
169            f.write("</param>")?;
170        }
171
172        if write_return_info {
173            f.writeln(&format!(
174                "<returns>Initialized <see cref=\"{}\" /> instance </returns>",
175                handle.name().camel_case()
176            ))?;
177        }
178
179        Ok(())
180    })
181}
182
183fn constructor_parameters<T>(
184    handle: &Struct<T, Validated>,
185    constructor: &Handle<Initializer<Validated>>,
186) -> String
187where
188    T: StructFieldType + TypeInfo,
189{
190    handle
191        .initializer_args(constructor.clone())
192        .map(|sf| {
193            format!(
194                "{} {}",
195                sf.field_type.get_dotnet_type(),
196                sf.name.mixed_case()
197            )
198        })
199        .collect::<Vec<String>>()
200        .join(", ")
201}
202
203fn write_constructor<T>(
204    f: &mut dyn Printer,
205    visibility: Visibility,
206    handle: &Struct<T, Validated>,
207    constructor: &Handle<Initializer<Validated>>,
208) -> FormattingResult<()>
209where
210    T: StructFieldType + TypeInfo,
211{
212    if visibility == Visibility::Public && handle.visibility == Visibility::Public {
213        write_constructor_documentation(f, handle, constructor, false)?;
214    }
215
216    let visibility = match visibility {
217        Visibility::Public => handle.visibility,
218        Visibility::Private => Visibility::Private,
219    };
220
221    f.writeln(&format!(
222        "{} {}({})",
223        visibility.to_str(),
224        handle.name().camel_case(),
225        constructor_parameters(handle, constructor)
226    ))?;
227    blocked(f, |f| {
228        for field in &handle.fields {
229            indented(f, |f| {
230                f.writeln(&format!(
231                    "this.{} = {};",
232                    field.name.camel_case(),
233                    get_field_value(field, constructor)
234                ))
235            })?;
236        }
237        Ok(())
238    })?;
239    Ok(())
240}
241
242fn generate_to_native_conversions<T>(
243    f: &mut dyn Printer,
244    handle: &Struct<T, Validated>,
245) -> FormattingResult<()>
246where
247    T: StructFieldType + ConvertToNative,
248{
249    let struct_name = handle.name().camel_case();
250    let struct_native_name = format!("{struct_name}Native");
251
252    f.newline()?;
253
254    // Convert from .NET to native
255    f.writeln(&format!(
256        "internal static {struct_native_name} ToNative({struct_name} self)"
257    ))?;
258    blocked(f, |f| {
259        f.writeln(&format!("{struct_native_name} result;"))?;
260        for el in handle.fields() {
261            let el_name = el.name.camel_case();
262
263            let conversion = el
264                .field_type
265                .convert_to_native(&format!("self.{el_name}"))
266                .unwrap_or(format!("self.{el_name}"));
267            f.writeln(&format!("result.{el_name} = {conversion};"))?;
268        }
269        f.writeln("return result;")
270    })?;
271
272    f.newline()?;
273
274    // Convert from .NET to native reference
275    f.writeln(&format!(
276        "internal static IntPtr ToNativeRef({struct_name} self)"
277    ))?;
278    blocked(f, |f| {
279        f.writeln("var handle = IntPtr.Zero;")?;
280        f.writeln("if (self != null)")?;
281        blocked(f, |f| {
282            f.writeln("var nativeStruct = ToNative(self);")?;
283            f.writeln("handle = Marshal.AllocHGlobal(Marshal.SizeOf(nativeStruct));")?;
284            f.writeln("Marshal.StructureToPtr(nativeStruct, handle, false);")?;
285            f.writeln("nativeStruct.Dispose();")
286        })?;
287        f.writeln("return handle;")
288    })?;
289
290    f.newline()?;
291
292    // Finalizer
293    f.writeln("internal void Dispose()")?;
294    blocked(f, |f| {
295        for el in handle.fields() {
296            let el_name = el.name.camel_case();
297
298            if let Some(cleanup) = el.field_type.cleanup_native(&format!("this.{el_name}")) {
299                f.writeln(&cleanup)?;
300            }
301        }
302        Ok(())
303    })
304}
305
306fn generate_to_dotnet_conversions<T>(
307    f: &mut dyn Printer,
308    handle: &Struct<T, Validated>,
309) -> FormattingResult<()>
310where
311    T: StructFieldType + ConvertToDotNet,
312{
313    let struct_name = handle.name().camel_case();
314    let struct_native_name = format!("{struct_name}Native");
315
316    f.newline()?;
317
318    // Convert from native to .NET
319    f.writeln(&format!(
320        "internal static {struct_name} FromNative({struct_native_name} native)"
321    ))?;
322    blocked(f, |f| {
323        f.writeln(&format!("return new {struct_name}"))?;
324        f.writeln("{")?;
325        indented(f, |f| {
326            for el in handle.fields() {
327                let el_name = el.name.camel_case();
328
329                let conversion = el
330                    .field_type
331                    .convert_to_dotnet(&format!("native.{el_name}"))
332                    .unwrap_or(format!("native.{el_name}"));
333                f.writeln(&format!("{el_name} = {conversion},"))?;
334            }
335            Ok(())
336        })?;
337        f.writeln("};")
338    })?;
339
340    f.newline()?;
341
342    // Convert from native ref to .NET
343    f.writeln(&format!(
344        "internal static {struct_name} FromNativeRef(IntPtr native)"
345    ))?;
346    blocked(f, |f| {
347        f.writeln(&format!("{struct_name} handle = null;"))?;
348        f.writeln("if (native != IntPtr.Zero)")?;
349        blocked(f, |f| {
350            f.writeln(&format!(
351                "var nativeStruct = Marshal.PtrToStructure<{struct_native_name}>(native);"
352            ))?;
353            f.writeln("handle = FromNative(nativeStruct);")
354        })?;
355        f.writeln("return handle;")
356    })
357}
358
359fn generate_skeleton<T>(
360    f: &mut dyn Printer,
361    handle: &Struct<T, Validated>,
362    lib: &Library,
363    generate_builder_methods: bool,
364    conversions: &dyn Fn(&mut dyn Printer) -> FormattingResult<()>,
365) -> FormattingResult<()>
366where
367    T: StructFieldType + TypeInfo,
368{
369    let struct_name = handle.name().camel_case();
370    let struct_native_name = format!("{struct_name}Native");
371
372    print_license(f, &lib.info.license_description)?;
373    print_imports(f)?;
374    f.newline()?;
375
376    let doc = match handle.visibility {
377        Visibility::Public => handle.doc.clone(),
378        Visibility::Private => handle
379            .doc
380            .clone()
381            .warning("This class is an opaque handle and cannot be constructed by user code"),
382    };
383
384    namespaced(f, &lib.settings.name, |f| {
385        documentation(f, |f| {
386            // Print top-level documentation
387            xmldoc_print(f, &doc)
388        })?;
389
390        f.writeln(&format!("public class {struct_name}"))?;
391        blocked(f, |f| {
392            // Write .NET structure elements
393            for field in handle.fields() {
394                documentation(f, |f| {
395                    // Print top-level documentation
396                    xmldoc_print(f, &field.doc)?;
397                    Ok(())
398                })?;
399
400                f.writeln(&format!(
401                    "{} {} {};",
402                    handle.visibility.to_str(),
403                    field.field_type.get_dotnet_type(),
404                    field.name.camel_case()
405                ))?;
406            }
407
408            // Write builder methods
409            if handle.visibility == Visibility::Public && generate_builder_methods {
410                f.newline()?;
411
412                for field in handle.fields() {
413                    documentation(f, |f| {
414                        // Print top-level documentation
415                        xmldoc_print(f, &field.doc)?;
416                        Ok(())
417                    })?;
418
419                    f.writeln(&format!(
420                        "public {} With{}({} value)",
421                        struct_name,
422                        field.name.camel_case(),
423                        field.field_type.get_dotnet_type(),
424                    ))?;
425                    blocked(f, |f| {
426                        f.writeln(&format!("this.{} = value;", field.name.camel_case(),))?;
427                        f.writeln("return this;")
428                    })?;
429                }
430            }
431
432            for c in &handle.initializers {
433                match c.initializer_type {
434                    InitializerType::Normal => {
435                        f.newline()?;
436                        write_constructor(f, Visibility::Public, handle, c)?;
437                    }
438                    InitializerType::Static => {
439                        f.newline()?;
440                        write_static_constructor(f, handle, c)?;
441                    }
442                }
443            }
444
445            // If the struct doesn't already define a full constructor, write a private one
446            if !handle.has_full_initializer() {
447                let constructor = Handle::new(Initializer::full(InitializerType::Normal, doc));
448
449                f.newline()?;
450                write_constructor(f, Visibility::Private, handle, &constructor)?;
451            }
452
453            if !handle.has_default_initializer() {
454                // Internal parameter-less constructor
455                f.newline()?;
456                f.writeln(&format!("internal {}() {{ }}", handle.name().camel_case()))?;
457            }
458
459            Ok(())
460        })?;
461
462        f.newline()?;
463
464        // Write native struct
465        f.writeln("[StructLayout(LayoutKind.Sequential)]")?;
466        f.writeln(&format!("internal struct {struct_native_name}"))?;
467        blocked(f, |f| {
468            // Write native elements
469            for el in handle.fields() {
470                f.writeln(&format!(
471                    "{} {};",
472                    el.field_type.get_native_type(),
473                    el.name.camel_case()
474                ))?;
475            }
476
477            conversions(f)?;
478
479            f.newline()?;
480
481            // Ref cleanup
482            f.writeln("internal static void NativeRefCleanup(IntPtr native)")?;
483            blocked(f, |f| {
484                f.writeln("if (native != IntPtr.Zero)")?;
485                blocked(f, |f| f.writeln("Marshal.FreeHGlobal(native);"))
486            })
487        })
488    })
489}