Skip to content

Commit da293cc

Browse files
authored
feat: add support for struct simvar marshalling (#9)
1 parent ecef8b5 commit da293cc

File tree

5 files changed

+510
-39
lines changed

5 files changed

+510
-39
lines changed
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// <copyright file="SimConnectAttribute.cs" company="BARS">
2+
// Copyright (c) BARS. All rights reserved.
3+
// </copyright>
4+
5+
using System;
6+
using SimConnect.NET.SimVar;
7+
8+
namespace SimConnect.NET
9+
{
10+
/// <summary>Annotates a struct field with the SimVar you want marshalled into it.</summary>
11+
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
12+
public sealed class SimConnectAttribute : Attribute
13+
{
14+
/// <summary>
15+
/// Initializes a new instance of the <see cref="SimConnectAttribute"/> class with name and unit.
16+
/// The data type is inferred from the SimVar registry if available.
17+
/// </summary>
18+
/// <param name="name">The SimVar name to marshal.</param>
19+
/// <param name="unit">The unit of the SimVar.</param>
20+
public SimConnectAttribute(string name, string unit)
21+
{
22+
this.Name = name;
23+
this.Unit = unit;
24+
var simVar = SimVarRegistry.Get(name);
25+
if (simVar != null)
26+
{
27+
this.DataType = simVar.DataType;
28+
}
29+
else
30+
{
31+
throw new ArgumentException($"SimVar '{name}' not found in registry. Please specify unit and dataType explicitly.", nameof(name));
32+
}
33+
}
34+
35+
/// <summary>
36+
/// Initializes a new instance of the <see cref="SimConnectAttribute"/> class using the SimVar name.
37+
/// The unit and data type are inferred from the SimVar registry if available.
38+
/// </summary>
39+
/// <param name="name">The SimVar name to marshal.</param>
40+
public SimConnectAttribute(string name)
41+
{
42+
this.Name = name;
43+
var simVar = SimVarRegistry.Get(name);
44+
if (simVar != null)
45+
{
46+
this.Unit = simVar.Unit;
47+
this.DataType = simVar.DataType;
48+
}
49+
else
50+
{
51+
throw new ArgumentException($"SimVar '{name}' not found in registry. Please specify unit and dataType explicitly.", nameof(name));
52+
}
53+
}
54+
55+
/// <summary>
56+
/// Initializes a new instance of the <see cref="SimConnectAttribute"/> class.
57+
/// </summary>
58+
/// <param name="name">The SimVar name to marshal.</param>
59+
/// <param name="unit">The unit of the SimVar.</param>
60+
/// <param name="dataType">The SimConnect data type for marshaling.</param>
61+
public SimConnectAttribute(string name, string unit, SimConnectDataType dataType)
62+
{
63+
this.Name = name;
64+
this.Unit = unit;
65+
this.DataType = dataType;
66+
}
67+
68+
/// <summary>
69+
/// Initializes a new instance of the <see cref="SimConnectAttribute"/> class.
70+
/// </summary>
71+
/// <param name="name">The SimVar name to marshal.</param>
72+
/// <param name="unit">The unit of the SimVar.</param>
73+
/// <param name="dataType">The SimConnect data type for marshaling.</param>
74+
/// <param name="order">The order in which the SimVar should be marshaled.</param>
75+
public SimConnectAttribute(string name, string? unit, SimConnectDataType dataType, int order)
76+
{
77+
this.Name = name;
78+
this.Unit = unit;
79+
this.DataType = dataType;
80+
this.Order = order;
81+
}
82+
83+
/// <summary>
84+
/// Gets the SimVar name to marshal.
85+
/// </summary>
86+
public string Name { get; }
87+
88+
/// <summary>
89+
/// Gets the unit of the SimVar.
90+
/// </summary>
91+
public string? Unit { get; }
92+
93+
/// <summary>
94+
/// Gets the SimConnect data type for marshaling.
95+
/// </summary>
96+
public SimConnectDataType DataType { get; }
97+
98+
/// <summary>
99+
/// Gets the order in which the SimVar should be marshaled.
100+
/// </summary>
101+
public int Order { get; }
102+
}
103+
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
// <copyright file="SimVarStructBinder.cs" company="BARS">
2+
// Copyright (c) BARS. All rights reserved.
3+
// </copyright>
4+
5+
using System;
6+
using System.Linq;
7+
using System.Reflection;
8+
9+
namespace SimConnect.NET.SimVar.Internal
10+
{
11+
internal static class SimVarStructBinder
12+
{
13+
/// <summary>
14+
/// Returns the ordered [SimVar]-annotated fields for T, validating .NET types vs SimConnect types.
15+
/// </summary>
16+
internal static (System.Reflection.FieldInfo Field, SimConnectAttribute Attr)[] GetOrderedFields<T>()
17+
{
18+
var t = typeof(T);
19+
if (!t.IsLayoutSequential)
20+
{
21+
throw new InvalidOperationException($"{t.Name} must be annotated with [StructLayout(LayoutKind.Sequential)].");
22+
}
23+
24+
var fields = t.GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public)
25+
.Select(f => (Field: f, Attr: f.GetCustomAttribute<SimConnectAttribute>()))
26+
.Where(x => x.Attr != null)
27+
.OrderBy(x => x!.Attr!.Order)
28+
.ThenBy(x => x.Field.MetadataToken)
29+
.ToArray();
30+
31+
if (fields.Length == 0)
32+
{
33+
throw new InvalidOperationException($"{t.Name} has no fields annotated with [SimVar].");
34+
}
35+
36+
foreach (var (field, attr) in fields)
37+
{
38+
var ft = field.FieldType;
39+
switch (attr!.DataType)
40+
{
41+
case SimConnectDataType.FloatDouble:
42+
if (ft != typeof(double))
43+
{
44+
throw Fail(field, "double");
45+
}
46+
47+
break;
48+
case SimConnectDataType.FloatSingle:
49+
if (ft != typeof(float))
50+
{
51+
throw Fail(field, "float");
52+
}
53+
54+
break;
55+
case SimConnectDataType.Integer32:
56+
if (ft != typeof(int) && ft != typeof(uint))
57+
{
58+
throw Fail(field, "int/uint");
59+
}
60+
61+
break;
62+
case SimConnectDataType.Integer64:
63+
if (ft != typeof(long) && ft != typeof(ulong))
64+
{
65+
throw Fail(field, "long/ulong");
66+
}
67+
68+
break;
69+
case SimConnectDataType.String8:
70+
case SimConnectDataType.String32:
71+
case SimConnectDataType.String64:
72+
case SimConnectDataType.String128:
73+
case SimConnectDataType.String256:
74+
case SimConnectDataType.String260:
75+
if (ft != typeof(string))
76+
{
77+
throw Fail(field, "string");
78+
}
79+
80+
break;
81+
}
82+
}
83+
84+
return fields!;
85+
86+
static InvalidOperationException Fail(FieldInfo f, string expected)
87+
=> new($"Field {f.DeclaringType!.Name}.{f.Name} must be {expected} to match its [SimVar] attribute.");
88+
}
89+
90+
/// <summary>
91+
/// Builds a single SimConnect data definition for T (using [SimVar] attributes),
92+
/// registers T for marshalling, and returns the definition ID.
93+
/// </summary>
94+
/// <param name="handle">Native SimConnect handle.</param>
95+
internal static (uint DefId, (System.Reflection.FieldInfo Field, SimConnectAttribute Attr)[] Fields) BuildAndRegisterFromStruct<T>(IntPtr handle)
96+
{
97+
var t = typeof(T);
98+
if (!t.IsLayoutSequential)
99+
{
100+
throw new InvalidOperationException($"{t.Name} must be annotated with [StructLayout(LayoutKind.Sequential)].");
101+
}
102+
103+
var fields = GetOrderedFields<T>();
104+
105+
uint defId = unchecked((uint)Guid.NewGuid().GetHashCode());
106+
107+
var result = SimConnectNative.SimConnect_ClearDataDefinition(handle, defId);
108+
if (result != 0)
109+
{
110+
throw new InvalidOperationException($"Failed to clear data definition for {t.Name}: {result}");
111+
}
112+
113+
foreach (var (field, attr) in fields)
114+
{
115+
// Add each SimVar field to the SimConnect data definition using the native layer
116+
if (attr == null)
117+
{
118+
throw new InvalidOperationException($"Field {field.Name} is missing [SimVar] attribute.");
119+
}
120+
121+
result = SimConnectNative.SimConnect_AddToDataDefinition(
122+
handle,
123+
defId,
124+
attr.Name,
125+
attr.Unit ?? string.Empty,
126+
(uint)attr.DataType);
127+
128+
if (result != 0)
129+
{
130+
throw new InvalidOperationException($"Failed to add data definition for {field.Name}: {result}");
131+
}
132+
}
133+
134+
var size = SimVarDataTypeSizing.GetPayloadSizeBytes(fields.Select(f => f.Attr!.DataType));
135+
var offsets = SimVarDataTypeSizing.ComputeOffsets(fields.Select(f => f.Attr!.DataType));
136+
return (defId, fields);
137+
}
138+
}
139+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// <copyright file="SimVarDataTypeSizing.cs" company="BARS">
2+
// Copyright (c) BARS. All rights reserved.
3+
// </copyright>
4+
using SimConnect.NET;
5+
6+
namespace SimConnect.NET.SimVar
7+
{
8+
/// <summary>
9+
/// Provides utilities for determining the size and offsets of SimConnect data types in unmanaged payloads.
10+
/// </summary>
11+
public static class SimVarDataTypeSizing
12+
{
13+
/// <summary>
14+
/// Raw bytes for one datum of the given SimConnect type in the untagged payload.
15+
/// </summary>
16+
/// <param name="type">The SimConnect data type to evaluate.</param>
17+
/// <returns>The size in bytes of a single datum of the specified type.</returns>
18+
public static int GetDatumSizeBytes(SimConnectDataType type) => type switch
19+
{
20+
SimConnectDataType.Invalid => 0,
21+
22+
// Scalars (raw sizes)
23+
SimConnectDataType.Integer32 => 4, // uint (SimConnect uses unsigned 32-bit for Integer32)
24+
SimConnectDataType.Integer64 => 8, // long
25+
SimConnectDataType.FloatSingle => 4, // float
26+
SimConnectDataType.FloatDouble => 8, // double
27+
28+
// Fixed-length strings (ANSI, fixed buffer including NUL)
29+
SimConnectDataType.String8 => 8, // string
30+
SimConnectDataType.String32 => 32, // string
31+
SimConnectDataType.String64 => 64, // string
32+
SimConnectDataType.String128 => 128, // string
33+
SimConnectDataType.String256 => 256, // string
34+
SimConnectDataType.String260 => 260, // string
35+
36+
// Not supported in this marshaller
37+
SimConnectDataType.StringV => throw new NotSupportedException(
38+
"StringV is not supported. Use fixed-length String8..String260."),
39+
40+
// Composite structs (per SDK)
41+
SimConnectDataType.LatLonAlt => 24, // 3 x double
42+
SimConnectDataType.Xyz => 24, // 3 x double
43+
SimConnectDataType.InitPosition => 56, // 6 x double + 2 x DWORD (pack=1)
44+
45+
// These depend on your interop definition; use Marshal.SizeOf<T> in your code.
46+
SimConnectDataType.MarkerState => throw new NotSupportedException("Use Marshal.SizeOf<SIMCONNECT_DATA_MARKERSTATE>()."),
47+
SimConnectDataType.Waypoint => throw new NotSupportedException("Use Marshal.SizeOf<SIMCONNECT_DATA_WAYPOINT>()."),
48+
_ => throw new ArgumentOutOfRangeException(nameof(type)),
49+
};
50+
51+
/// <summary>
52+
/// Total payload size (bytes) for a sequence of datums in untagged SIMOBJECT_DATA.
53+
/// </summary>
54+
/// <param name="types">The sequence of SimConnect data types to calculate the total payload size for.</param>
55+
/// <returns>The total size in bytes of the payload for the provided sequence of data types.</returns>
56+
public static int GetPayloadSizeBytes(IEnumerable<SimConnectDataType> types)
57+
=> types.Sum(GetDatumSizeBytes);
58+
59+
/// <summary>
60+
/// Compute byte offsets for each datum in order (untagged).
61+
/// </summary>
62+
/// <param name="types">The list of SimConnect data types to compute offsets for.</param>
63+
/// <returns>An array of byte offsets for each datum in the provided list.</returns>
64+
public static int[] ComputeOffsets(IEnumerable<SimConnectDataType> types)
65+
{
66+
var offsets = new List<int>();
67+
int cursor = 0;
68+
foreach (var type in types)
69+
{
70+
offsets.Add(cursor);
71+
cursor += GetDatumSizeBytes(type);
72+
}
73+
74+
return offsets.ToArray();
75+
}
76+
}
77+
}

0 commit comments

Comments
 (0)