Overview
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. There are also a number of Managed Code Samples included with the SDK.
Getting Started With Managed SimConnect
- Ensure the Microsoft .NET Framework 4.7.2 Developer Pack is installed as many of the Managed Samples target this version of the framework.
- Create a new project using Visual Studio 2017 using one of the following project types:
- C#: WinForms, WPF, Console
- VB.NET: WinForms, WPF, Console
- Add a new reference to LockheedMartin.Prepar3D.SimConnect.dll in your project.
This file can be found in this directory of the SDK:
SDK\lib\SimConnect\managed
- Import the following assemblies in the code files using SimConnect functionality:
C#
using LockheedMartin.Prepar3D.SimConnect;
using System.Runtime.InteropServices;VB.NET
Imports LockheedMartin.Prepar3D.SimConnect
Imports System.Runtime.InteropServices Set up the initialization and deinitialization of the SimConnect Client.
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 is:
C#
// OpenWhat would have been a failed HRESULT returned in the native API translates to a COMException.
// Declare a SimConnect object
SimConnect simconnect = null;
// User-defined win32 event
const int WM_USER_SIMCONNECT = 0x0402;
try
{
simconnect = new SimConnect("Managed Data Request", this.Handle, WM_USER_SIMCONNECT, null, 0);
}
catch (COMException ex)
{
// A connection to the SimConnect server could not be established
}
// Close
if (simconnect != null)
{
simconnect.Dispose();
simconnect = null;
}
VB.NET
Rem OpenWhat would have been a failed HRESULT returned in the native API translates to a COMException.
Try
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 simconnect IsNot Nothing Then
simconnect.Dispose()
simconnect = Nothing
End If
Recieve messages from the SimConnect server.
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 is as follows:
C#: WinForms
protected override void DefWndProc(ref Message m)
{
if (m.Msg == WM_USER_SIMCONNECT)
{
if (simconnect != null)
{
simconnect.ReceiveMessage();
}
}
else
{
base.DefWndProc(ref m);
}
}
C# WPF
IntPtr handle;
HwndSource handleSource;
// Register WPF window
YourWPFWindow()
{
handle = new WindowInteropHelper(this).Handle; // Get handle of main WPF Window
handleSource = HwndSource.FromHwnd(handle); // Get source of handle in order to add event handlers to it
handleSource.AddHook(HandleSimConnectEvents);
}
~YourWPFWindow()
{
if (handleSource != null)
{
handleSource.RemoveHook(HandleSimConnectEvents);
}
}
private IntPtr HandleSimConnectEvents(IntPtr hWnd, int message, IntPtr wParam, IntPtr lParam, ref bool isHandled)
{
isHandled = false;
switch (message)
{
case WM_USER_SIMCONNECT:
{
if (simConnect != null)
{
simConnect.ReceiveMessage();
isHandled = true;
}
}
break;
default:
break;
}
return IntPtr.Zero;
}
VB.NET
Protected Overrides Sub DefWndProc(ByRef m As Message)
If m.Msg = WM_USER_SIMCONNECT Then
If simconnect IsNot Nothing Then
simconnect.ReceiveMessage()
End If
Else
MyBase.DefWndProc(m)
End If
End Sub
Managed SimConnect Programming Differences
Besides the differences in initialization, deinitialization, and recieving dispatched messages, there are other nuances to be aware of. This section enumerates key differences to be aware of as a developer:
- Previously, the managed SimConnect assembly was installed in the GAC (global assembly cache) during the installation of the SDK, so it did not have to be manually placed anywhere for your application to run. However, you did need to reference a copy of this dll in Visual Studio so that it could resolve symbols at compile time. As Visual Studio does not support referencing an assembly stored in the GAC, a copy of the SimConnect.dll was shipped solely for this purpose. It is no longer handled this way in Prepar3D.
- 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. See the code in the Managed Data Request sample for C#, and VB Managed Data Request sample for VB.NET.
- When the client receives either a SIMCONNECT_RECV_SIMOBJECT_DATA_BYTYPE or a SIMCONNECT_RECV_SIMOBJECT_DATA structure, the dwData[0] member 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.
- 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 LockheedMartin.Prepar3D.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 bold in the example below (also
refer to the Managed Data Request or
VB Managed Data Request samples):
C#
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
struct Struct1
{
// this is how you declare a fixed size string
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public String title;
public double latitude;
public double longitude;
public double altitude;
};
// define the data structure
// Note that the DATATYPE.STRING256 matches the SizeConst 256 in the MarshalAs statement above
//
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);
//
// IMPORTANT: register it with the simconnect managed wrapper marshaller
// if you skip this step, you will only receive a uint in the .dwData field.
//
simconnect.RegisterDataDefineStruct<Struct1>((uint)DEFINITIONS.Struct1);
VB.NET
<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
simconnect.AddToDataDefinition(StructDefinitions.Struct1, "title", "", LockheedMartin.Prepar3D.SimConnect.SIMCONNECT_DATATYPE.STRING256, 0, 0)
simconnect.AddToDataDefinition(StructDefinitions.Struct1, "Plane Latitude", "degrees", LockheedMartin.Prepar3D.SimConnect.SIMCONNECT_DATATYPE.FLOAT64, 0, 1)
simconnect.AddToDataDefinition(StructDefinitions.Struct1, "Plane Longitude", "degrees", LockheedMartin.Prepar3D.SimConnect.SIMCONNECT_DATATYPE.FLOAT64, 0, 2)
simconnect.AddToDataDefinition(StructDefinitions.Struct1, "Plane Altitude", "feet", LockheedMartin.Prepar3D.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.
simconnect.RegisterDataDefineStruct(Of Struct1)(StructDefinitions.Struct1)
- 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:
C#
// Note that the client code should already have declared a
// data definition, DEFINITIONS.1, and
// calledSimConnect_RequestDataOnSimObjectType to return an AI aircraft ID.
// Step 1: Add a the waypoint list to the data definition
simconnect.AddToDataDefinition(DEFINITIONS.1,"AI WAYPOINT LIST", "number", SIMCONNECT_DATATYPE.WAYPOINT, 0.0f, SimConnect.SIMCONNECT_UNUSED);
// Step 2: Declare an array of the appropriate size
SIMCONNECT_DATA_WAYPOINT[] waypoints = new SIMCONNECT_DATA_WAYPOINT[2];
// Step 3: Populate the array with all the required data
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;
// Step 4: The managed wrapper marshaling code expects a polymorphic array
Object[] objv = new Object[ waypoints.Length ];
waypoints.CopyTo(objv, 0);
// Step 5: Now make the call to apply the waypoint structure to the AI aircraft
simconnect.SetDataOnSimObject(DEFINITIONS.1, AIAircraftID, SIMCONNECT_DATA_SET_FLAG.DEFAULT, objv);
VB.NET
REM Note that the client code should already have declared a
REM data definition, StructDefinitions.DEFINITIONS.1, and
REM called simconnect.RequestDataOnSimObjectType to return an AI aircraft ID.
REM Step 1: Add a the waypoint list to the data definition
simconnect.AddToDataDefinition(StructDefinitions.DEFINITION1, "AI WAYPOINT LIST", "number", LockheedMartin.Prepar3D.SimConnect.SIMCONNECT_DATATYPE.WAYPOINT, 0.0F, LockheedMartin.Prepar3D.SimConnect.SimConnect.SIMCONNECT_UNUSED)
REM Step 2: Declare an array of the appropriate size
Dim waypoints(2) As LockheedMartin.Prepar3D.SimConnect.SIMCONNECT_DATA_WAYPOINT
REM Step 3: Populate the array with all the required data
waypoints(0).Flags = LockheedMartin.Prepar3D.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 = LockheedMartin.Prepar3D.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
simconnect.SetDataOnSimObject(StructDefinitions.DEFINITION1, AIAircraftID, LockheedMartin.Prepar3D.SimConnect.SIMCONNECT_DATA_SET_FLAG.DEFAULT, objv)