# Programming SimConnect Clients Using Managed Code There is a managed wrapper for [SimConnect](simconnect-sdk/) 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: ``` csharp 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: ``` vb 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(s) 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: ``` csharp 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: ``` vb 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: ``` csharp [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((uint)DEFINITIONS.Struct1); ``` The same sample using VB.NET would be: ``` vb _ Structure Struct1 Rem This is how you declare a fixed size string _ 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\#: ``` csharp 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: ``` vb 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) ```