PROGRAMMING SIMCONNECT CLIENTS
USING MANAGED CODE

There is a managed wrapper for SimConnect that enables .NET language programmers to write SimConnect clients. This section describes how to set up a managed client project, and the key differences to look out for when programming to the wrapper.

 

The following sections use C# as the primary example language, although we also provide examples using VB.

 

 

Setup For .NET

Ensure that .NET Framework Version 4.7 is installed and then:

  • Create a new project using Visual Studio 2017 or higher, typically this would be a C# Windows Application, or a VB.NET Windows application.
  • Add a new reference to Microsoft.FlightSimulator.SimConnect in your project. This file can be found in the $(MSFS SDK)\SimConnect SDK\lib\managed folder.
  • Add the following two lines to the projects using statements (in C#, note the VB.NET equivalent to using is imports):

    using Microsoft.FlightSimulator.SimConnect; using System.Runtime.InteropServices;

  • Write the application using the functions in the SimConnect library, taking into account the following notes.

 

 

Notes On .NET Client Programming

The managed SimConnect assembly is installed in the GAC (global assembly cache) during the installation of the SimConnect SDK, so it should not be manually placed anywhere for your application to run. However, you do need to reference a copy of this dll in Visual Studio so that it can resolve symbols at compile time. As Visual Studio does not support referencing an assembly stored in the GAC, a copy of the SimConnect.dll outside the simconnect SDK msi is shipped solely for this purpose.

 

The native function calls SimConnect_Open and SimConnect_Close have been replaced by the SimConnect constructor, and Dispose method respectively. This means that there is no handle required for the function calls, so for the most part the managed calls use the same parameters as the native calls, except without the SimConnect handle. The code to open and close a SimConnect client using C# is:

SimConnect simconnect = null;
 
const int WM_USER_SIMCONNECT = 0x0402;
try
{
    simconnect = new SimConnect("Managed Data Request", this.Handle, WM_USER_SIMCONNECT, null, 0);
}
catch (COMException ex)
{
 
} 
 
if (simconnect != null)
{
    simconnect.Dispose();
    simconnect = null;
}

And using VB.NET it would be:

Rem Open
Try 
    fsx_simconnect = New SimConnect(" VB Managed Data Request", Me.Handle, WM_USER_SIMCONNECT, Nothing, 0) 
Catch ex As Exception 
    Rem Failed to connect 
End Try 
Rem Close
If fsx_simconnect IsNot Nothing Then
    fsx_simconnect.Dispose()
    fsx_simconnect = Nothing 
End If 

It is worth noting the following:

  • What would have been a failed HRESULT returned in the native API translates to a COMException.

  • The use of raw memory pointers is not permitted in the managed API. You must define your structs and attribute them properly so that the system marshaller can handle them.

  • When the client receives either a SIMCONNECT_RECV_SIMOBJECT_DATA_BYTYPE or a SIMCONNECT_RECV_SIMOBJECT_DATA structure, the dwData members will contain your struct as System.Object. This should be cast to the proper type.

  • Partial data return (SIMCONNECT_DATA_REQUEST_FLAG_TAGGED) is unsupported. For a description of this flag see the SimConnect_AddToDataDefinition function.

  • The top level ReceiveDispatch switch case statement is not necessary. The client should register a handler for the appropriate OnRecvXXX event, and call ReceiveMessage when it is notified that messages are waiting in the queue. For Windows applications use a win32 handle (a Control.Handle) to SimConnect to receive notifications when a message arrives. The code for this - using C# - is as follows:

protected override void DefWndProc(ref Message m)
{
    if (m.Msg == WM_USER_SIMCONNECT)
    {
        if (simconnect != null)
        {
            simconnect.ReceiveMessage();
        }
    }
    else
    {
        base.DefWndProc(ref m);
    }
}

And using VB.NET it would be:

Protected Overrides Sub DefWndProc(ByRef m As Message) 
If m.Msg = WM_USER_SIMCONNECT Then 
    If fsx_simconnect IsNot Nothing Then 
        fsx_simconnect.ReceiveMessage() 
    End If 
    Else 
        MyBase.DefWndProc(m) 
    End If
End Sub 

Note that:

  • Constants in the native simconnect.h are declared as static members of the SimConnect class. For example: SIMCONNECT_OBJECT_ID_USER becomes SimConnect.SIMCONNECT_OBJECT_ID_USER

  • Enums in managed code are scoped. The names of the enum members have been changed, for example, SIMCONNECT_RECV_ID_QUIT in native code is SIMCONNECT_RECV_ID.QUIT in C#, or Microsoft.FlightSimulator.SimConnect.SIMCONNECT_RECV_ID.QUIT in VB.NET. Note that in VB.NET the full reference is necessary to locate a structure or enumeration.

  • Structs are mostly unchanged, with the exception that a char array is represented as a System.String. However, they do need to be registered with the managed wrapper with a call to RegisterDataDefineStruct. If a string is too long during the data marshaling, it will get truncated safely. Some specific information must be provided with each structure, as shown in the C# sample below:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
struct Struct1
    {
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
    public String title;
    public double latitude;
    public double longitude;
    public double altitude;
    };
   
simconnect.AddToDataDefinition((uint)DEFINITIONS.Struct1, "Title", null, SIMCONNECT_DATATYPE.STRING256, 0, SimConnect.SIMCONNECT_UNUSED);
 
simconnect.AddToDataDefinition((uint)DEFINITIONS.Struct1, "Plane Latitude", "degrees", SIMCONNECT_DATATYPE.FLOAT64, 0, SimConnect.SIMCONNECT_UNUSED);
 
simconnect.AddToDataDefinition((uint)DEFINITIONS.Struct1, "Plane Longitude", "degrees", SIMCONNECT_DATATYPE.FLOAT64, 0, SimConnect.SIMCONNECT_UNUSED);
 
simconnect.AddToDataDefinition((uint)DEFINITIONS.Struct1, "Plane Altitude", "feet", SIMCONNECT_DATATYPE.FLOAT64, 0, SimConnect.SIMCONNECT_UNUSED);
 
simconnect.RegisterDataDefineStruct<Struct1>((uint)DEFINITIONS.Struct1);

The same sample using VB.NET would be:

<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi, Pack:=1)> _ 
Structure Struct1 
    Rem This is how you declare a fixed size string 
    <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=256)> _ 
    Public title As String 
    Public latitude As Double 
    Public longitude As Double 
    Public altitude As Double 
End Structure 
 
Rem define a data structure, note the last parameter, datumID must be different for each item 
 
fsx_simconnect.AddToDataDefinition(StructDefinitions.Struct1, "Title", "", Microsoft.FlightSimulator.SimConnect.SIMCONNECT_DATATYPE.STRING256, 0, 0) 
 
fsx_simconnect.AddToDataDefinition(StructDefinitions.Struct1, "Plane Latitude", "degrees", Microsoft.FlightSimulator.SimConnect.SIMCONNECT_DATATYPE.FLOAT64, 0, 1) 
 
fsx_simconnect.AddToDataDefinition(StructDefinitions.Struct1, "Plane Longitude", "degrees", Microsoft.FlightSimulator.SimConnect.SIMCONNECT_DATATYPE.FLOAT64, 0, 2) 
 
fsx_simconnect.AddToDataDefinition(StructDefinitions.Struct1, "Plane Altitude", "feet", Microsoft.FlightSimulator.SimConnect.SIMCONNECT_DATATYPE.FLOAT64, 0, 3) 
 
Rem IMPORTANT: register it with the simconnect managed wrapper marshaller
Rem if you skip this step, you will only receive an int in the .dwData field.
fsx_simconnect.RegisterDataDefineStruct(Of Struct1)(StructDefinitions.Struct1)  

 

Things to note:

  • Variable length strings are not supported in the managed layer.

  • Optional parameters are not supported in the managed layer

 

To send data in an array, copy the array to a polymorphic object array. For example, to apply a waypoint list to an AI aircraft, go through the following steps using C#:

simconnect.AddToDataDefinition(DEFINITIONS.1,"AI WAYPOINT LIST", "number", SIMCONNECT_DATATYPE.WAYPOINT, 0.0f, SimConnect.SIMCONNECT_UNUSED);
 
SIMCONNECT_DATA_WAYPOINT[] waypoints = new SIMCONNECT_DATA_WAYPOINT[2];
waypoints[0].Flags = (uint)SIMCONNECT_WAYPOINT_FLAGS.SPEED_REQUESTED;
waypoints[0].ktsSpeed = 100;
waypoints[0].Latitude = 10;
waypoints[0].Longitude = 20;
waypoints[0].Altitude = 1000;
 
waypoints[1].Flags = (uint)SIMCONNECT_WAYPOINT_FLAGS.SPEED_REQUESTED;
waypoints[1].ktsSpeed = 150;
waypoints[1].Latitude = 11;
waypoints[1].Longitude = 21;
waypoints[1].Altitude = 2000;
 
Object[] objv = new Object[ waypoints.Length ];
      waypoints.CopyTo(objv, 0);
 
simconnect.SetDataOnSimObject(DEFINITIONS.1, AIAircraftID, SIMCONNECT_DATA_SET_FLAG.DEFAULT, objv);

The same waypoint code using VB.NET would be like this:

REM Note that the client code should already have declared a
REM data definition, StructDefinitions.DEFINITIONS.1, and
REM called fsx_SimConnect_RequestDataOnSimObjectType  to return an AI aircraft ID.
 
REM Step 1: Add a the waypoint list to the data definition
 
fsx_simconnect.AddToDataDefinition(StructDefinitions.DEFINITION1, "AI WAYPOINT LIST", "number", Microsoft.FlightSimulator.SimConnect.SIMCONNECT_DATATYPE.WAYPOINT, 0.0F, Microsoft.FlightSimulator.SimConnect.SimConnect.SIMCONNECT_UNUSED)
 
REM Step 2: Declare an array of the appropriate size
 
Dim waypoints(2) As Microsoft.FlightSimulator.SimConnect.SIMCONNECT_DATA_WAYPOINT
 
REM Step 3: Populate the array with all the required data
 
waypoints(0).Flags = Microsoft.FlightSimulator.SimConnect.SIMCONNECT_WAYPOINT_FLAGS.SPEED_REQUESTED
waypoints(0).ktsSpeed = 100
waypoints(0).Latitude = 10 
waypoints(0).Longitude = 20 
waypoints(0).Altitude = 1000
 
waypoints(1).Flags = Microsoft.FlightSimulator.SimConnect.SIMCONNECT_WAYPOINT_FLAGS.SPEED_REQUESTED
waypoints(1).ktsSpeed = 100
waypoints(0).Latitude = 11 
waypoints(0).Longitude = 21 
waypoints(0).Altitude = 1000
 
REM Step 4: The managed wrapper marshaling code expects a polymorphic array
 
Dim objv(waypoints.Length) As Object
waypoints.CopyTo(objv, 0)
 
REM Step 5: Now make the call to apply the waypoint structure to the AI aircraft
 
fsx_simconnect.SetDataOnSimObject(StructDefinitions.DEFINITION1, AIAircraftID, Microsoft.FlightSimulator.SimConnect.SIMCONNECT_DATA_SET_FLAG.DEFAULT, objv)