Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions src/SimConnect.NET/SimConnectAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// <copyright file="SimConnectAttribute.cs" company="BARS">
// Copyright (c) BARS. All rights reserved.
// </copyright>

using System;
using SimConnect.NET.SimVar;

namespace SimConnect.NET
{
/// <summary>Annotates a struct field with the SimVar you want marshalled into it.</summary>
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
public sealed class SimConnectAttribute : Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref="SimConnectAttribute"/> class with name and unit.
/// The data type is inferred from the SimVar registry if available.
/// </summary>
/// <param name="name">The SimVar name to marshal.</param>
/// <param name="unit">The unit of the SimVar.</param>
public SimConnectAttribute(string name, string unit)
{
this.Name = name;
this.Unit = unit;
var simVar = SimVarRegistry.Get(name);
if (simVar != null)
{
this.DataType = simVar.DataType;
}
else
{
throw new ArgumentException($"SimVar '{name}' not found in registry. Please specify unit and dataType explicitly.", nameof(name));
}
}

/// <summary>
/// Initializes a new instance of the <see cref="SimConnectAttribute"/> class using the SimVar name.
/// The unit and data type are inferred from the SimVar registry if available.
/// </summary>
/// <param name="name">The SimVar name to marshal.</param>
public SimConnectAttribute(string name)
{
this.Name = name;
var simVar = SimVarRegistry.Get(name);
if (simVar != null)
{
this.Unit = simVar.Unit;
this.DataType = simVar.DataType;
}
else
{
throw new ArgumentException($"SimVar '{name}' not found in registry. Please specify unit and dataType explicitly.", nameof(name));
}
}

/// <summary>
/// Initializes a new instance of the <see cref="SimConnectAttribute"/> class.
/// </summary>
/// <param name="name">The SimVar name to marshal.</param>
/// <param name="unit">The unit of the SimVar.</param>
/// <param name="dataType">The SimConnect data type for marshaling.</param>
public SimConnectAttribute(string name, string unit, SimConnectDataType dataType)
{
this.Name = name;
this.Unit = unit;
this.DataType = dataType;
}

/// <summary>
/// Initializes a new instance of the <see cref="SimConnectAttribute"/> class.
/// </summary>
/// <param name="name">The SimVar name to marshal.</param>
/// <param name="unit">The unit of the SimVar.</param>
/// <param name="dataType">The SimConnect data type for marshaling.</param>
/// <param name="order">The order in which the SimVar should be marshaled.</param>
public SimConnectAttribute(string name, string? unit, SimConnectDataType dataType, int order)
{
this.Name = name;
this.Unit = unit;
this.DataType = dataType;
this.Order = order;
}

/// <summary>
/// Gets the SimVar name to marshal.
/// </summary>
public string Name { get; }

/// <summary>
/// Gets the unit of the SimVar.
/// </summary>
public string? Unit { get; }

/// <summary>
/// Gets the SimConnect data type for marshaling.
/// </summary>
public SimConnectDataType DataType { get; }

/// <summary>
/// Gets the order in which the SimVar should be marshaled.
/// </summary>
public int Order { get; }
}
}
139 changes: 139 additions & 0 deletions src/SimConnect.NET/SimVar/Internal/SimVarStructBinder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// <copyright file="SimVarStructBinder.cs" company="BARS">
// Copyright (c) BARS. All rights reserved.
// </copyright>

using System;
using System.Linq;
using System.Reflection;

namespace SimConnect.NET.SimVar.Internal
{
internal static class SimVarStructBinder
{
/// <summary>
/// Returns the ordered [SimVar]-annotated fields for T, validating .NET types vs SimConnect types.
/// </summary>
internal static (System.Reflection.FieldInfo Field, SimConnectAttribute Attr)[] GetOrderedFields<T>()
{
var t = typeof(T);
if (!t.IsLayoutSequential)
{
throw new InvalidOperationException($"{t.Name} must be annotated with [StructLayout(LayoutKind.Sequential)].");
}

var fields = t.GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public)
.Select(f => (Field: f, Attr: f.GetCustomAttribute<SimConnectAttribute>()))
.Where(x => x.Attr != null)
.OrderBy(x => x!.Attr!.Order)
.ThenBy(x => x.Field.MetadataToken)
.ToArray();

if (fields.Length == 0)
{
throw new InvalidOperationException($"{t.Name} has no fields annotated with [SimVar].");
}

foreach (var (field, attr) in fields)
{
var ft = field.FieldType;
switch (attr!.DataType)
{
case SimConnectDataType.FloatDouble:
if (ft != typeof(double))
{
throw Fail(field, "double");
}

break;
case SimConnectDataType.FloatSingle:
if (ft != typeof(float))
{
throw Fail(field, "float");
}

break;
case SimConnectDataType.Integer32:
if (ft != typeof(int) && ft != typeof(uint))
{
throw Fail(field, "int/uint");
}

break;
case SimConnectDataType.Integer64:
if (ft != typeof(long) && ft != typeof(ulong))
{
throw Fail(field, "long/ulong");
}

break;
case SimConnectDataType.String8:
case SimConnectDataType.String32:
case SimConnectDataType.String64:
case SimConnectDataType.String128:
case SimConnectDataType.String256:
case SimConnectDataType.String260:
if (ft != typeof(string))
{
throw Fail(field, "string");
}

break;
}
}

return fields!;

static InvalidOperationException Fail(FieldInfo f, string expected)
=> new($"Field {f.DeclaringType!.Name}.{f.Name} must be {expected} to match its [SimVar] attribute.");
}

/// <summary>
/// Builds a single SimConnect data definition for T (using [SimVar] attributes),
/// registers T for marshalling, and returns the definition ID.
/// </summary>
/// <param name="handle">Native SimConnect handle.</param>
internal static (uint DefId, (System.Reflection.FieldInfo Field, SimConnectAttribute Attr)[] Fields) BuildAndRegisterFromStruct<T>(IntPtr handle)
{
var t = typeof(T);
if (!t.IsLayoutSequential)
{
throw new InvalidOperationException($"{t.Name} must be annotated with [StructLayout(LayoutKind.Sequential)].");
}

var fields = GetOrderedFields<T>();

uint defId = unchecked((uint)Guid.NewGuid().GetHashCode());

var result = SimConnectNative.SimConnect_ClearDataDefinition(handle, defId);
if (result != 0)
{
throw new InvalidOperationException($"Failed to clear data definition for {t.Name}: {result}");
}

foreach (var (field, attr) in fields)
{
// Add each SimVar field to the SimConnect data definition using the native layer
if (attr == null)
{
throw new InvalidOperationException($"Field {field.Name} is missing [SimVar] attribute.");
}

result = SimConnectNative.SimConnect_AddToDataDefinition(
handle,
defId,
attr.Name,
attr.Unit ?? string.Empty,
(uint)attr.DataType);

if (result != 0)
{
throw new InvalidOperationException($"Failed to add data definition for {field.Name}: {result}");
}
}

var size = SimVarDataTypeSizing.GetPayloadSizeBytes(fields.Select(f => f.Attr!.DataType));
var offsets = SimVarDataTypeSizing.ComputeOffsets(fields.Select(f => f.Attr!.DataType));
return (defId, fields);
}
}
}
77 changes: 77 additions & 0 deletions src/SimConnect.NET/SimVar/SimVarDataTypeSizing.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// <copyright file="SimVarDataTypeSizing.cs" company="BARS">
// Copyright (c) BARS. All rights reserved.
// </copyright>
using SimConnect.NET;

namespace SimConnect.NET.SimVar
{
/// <summary>
/// Provides utilities for determining the size and offsets of SimConnect data types in unmanaged payloads.
/// </summary>
public static class SimVarDataTypeSizing
{
/// <summary>
/// Raw bytes for one datum of the given SimConnect type in the untagged payload.
/// </summary>
/// <param name="type">The SimConnect data type to evaluate.</param>
/// <returns>The size in bytes of a single datum of the specified type.</returns>
public static int GetDatumSizeBytes(SimConnectDataType type) => type switch
{
SimConnectDataType.Invalid => 0,

// Scalars (raw sizes)
SimConnectDataType.Integer32 => 4, // uint (SimConnect uses unsigned 32-bit for Integer32)
SimConnectDataType.Integer64 => 8, // long
SimConnectDataType.FloatSingle => 4, // float
SimConnectDataType.FloatDouble => 8, // double

// Fixed-length strings (ANSI, fixed buffer including NUL)
SimConnectDataType.String8 => 8, // string
SimConnectDataType.String32 => 32, // string
SimConnectDataType.String64 => 64, // string
SimConnectDataType.String128 => 128, // string
SimConnectDataType.String256 => 256, // string
SimConnectDataType.String260 => 260, // string

// Not supported in this marshaller
SimConnectDataType.StringV => throw new NotSupportedException(
"StringV is not supported. Use fixed-length String8..String260."),

// Composite structs (per SDK)
SimConnectDataType.LatLonAlt => 24, // 3 x double
SimConnectDataType.Xyz => 24, // 3 x double
SimConnectDataType.InitPosition => 56, // 6 x double + 2 x DWORD (pack=1)

// These depend on your interop definition; use Marshal.SizeOf<T> in your code.
SimConnectDataType.MarkerState => throw new NotSupportedException("Use Marshal.SizeOf<SIMCONNECT_DATA_MARKERSTATE>()."),
SimConnectDataType.Waypoint => throw new NotSupportedException("Use Marshal.SizeOf<SIMCONNECT_DATA_WAYPOINT>()."),
_ => throw new ArgumentOutOfRangeException(nameof(type)),
};

/// <summary>
/// Total payload size (bytes) for a sequence of datums in untagged SIMOBJECT_DATA.
/// </summary>
/// <param name="types">The sequence of SimConnect data types to calculate the total payload size for.</param>
/// <returns>The total size in bytes of the payload for the provided sequence of data types.</returns>
public static int GetPayloadSizeBytes(IEnumerable<SimConnectDataType> types)
=> types.Sum(GetDatumSizeBytes);

/// <summary>
/// Compute byte offsets for each datum in order (untagged).
/// </summary>
/// <param name="types">The list of SimConnect data types to compute offsets for.</param>
/// <returns>An array of byte offsets for each datum in the provided list.</returns>
public static int[] ComputeOffsets(IEnumerable<SimConnectDataType> types)
{
var offsets = new List<int>();
int cursor = 0;
foreach (var type in types)
{
offsets.Add(cursor);
cursor += GetDatumSizeBytes(type);
}

return offsets.ToArray();
}
}
}
Loading