plane icon Welcome to Microsoft Flight Simulator’s SDK Q&A Platform!

You have questions regarding the SDK? DevMode Tools? SimConnect? You would like to submit an idea for future improvements, seek help or exchange knowledge? You’re in the right place.

Please take a moment to read the platform’s guidelines before you get started!

article

bert.laverman avatar image
bert.laverman posted

CsSimConnect - A Modern and Reactive Alternative to the Managed ClassLibrary

Developing software using SimConnect can be a confusing and complex experience. If, like me, you're a developer with many years in C and C++, you'll instantly recognize the SimConnect SDK's function prototypes as using the Hungarian Notation, which feels like a welcome return to a comforting world when life was relatively simple, while at the same time causing a gnawing feeling that this is something we shouldn't need anymore. For those that started more recently, the naming standard that prefixes variables and function parameters with an encoded indication of its type was essential when compilers and development environments couldn't help you tell the type of a given name. Worse, pre-ANSI C compilers did not check types the way they do now, but that problem was mostly solved in the nineties. Nowadays, you hover your mouse over any identifier to get the full documentation on that item in a fly-over. If the compiler has even the slightest doubt that what you wrote makes sense, it'll complain loudly.

So you get used to the "DWORD"s (wait, a double-word is still only 32-bits?) and the "pszTitle"s (why wouldn't a string be zero-terminated?) and try to cope with the multiple levels of error reporting. (the "SimConnect_Xxx()" call may return non-zero, or an exception message may arrive referring to the SendID, to start with the obvious two) But then you get bitten by even weirder "gotcha"s, like how the C SimConnect library is not thread-safe and assumes you naturally have ordered your app's life around a window's message-loop.

Retrieving data, sending and receiving events, sending data, or defining and sending blocks of data you ordered yourself all require a considerable amount of "plumbing" code. Even worse, if you aren't running on the same machine as the simulator itself, you may lose the connection and are now scrambling to find out how much of your preparatory calls are still valid and which need to be redone to restore order.

In the world of Cloud-native and Enterprise Software Development, current programming practices have progressed a lot, and many of the lessons learned over there are big timesavers. For example, reactive programming is based on the idea that when communication is essentially asynchronous and often concerns a stream of replies, having to poll for data is generally not a good idea because you could have used the waiting time to do something useful. So instead, you want to write your code in a form that emphasizes what to do when data arrives, in that sense becoming reactive. Other important timesavers are Dependency Injection and the use of Reflection. The first uses annotated variables and parameters, where the annotation will refer to the value requested by name, and the actual value is injected at need. This allows you to put the burden of finding the correct value on a producer for that value, which again provides an additional benefit of allowing you to defer the choice. Finally, reflection is the word used to look at code and data at runtime and dynamically discover things you need.

So let’s put these to use in the context of a SimConnect application. Say I am listening to "ObjectAdded" events. Reactively doing this could be (should be) as simple as this:

EventManager.Instance.SubscribeToObjectAddedEvent().Subscribe(OnObjectAdded);

The "OnObjectAdded()" method will now be called every time an AI object is added. However, this is only going to give me the ObjectId of that object and a type. For example, if the object is an aircraft, I want to know some basic registration info, while for other object types, just the "title" is enough:

public void OnObjectAdded(SimulatedObject obj)
{
    if (IsSelected(obj.ObjectType))
    {
        uiUpdater(() => {
            if (!AIList.ContainsKey(obj.ObjectId))
                AIList.Add(obj.ObjectId, obj);
            else
                obj = AIList[obj.ObjectId];
            if (IsEmpty(obj.Title))
                Task.Run(() => getAIData(obj.ObjectId));
        });
    }
}

I have a filter on the displayed list, so only when the type is selected do I actually put it in the list, and I use an "uiUpdater()" method to ensure this update runs on the correct thread. I also check if I already have additional information on this object, and if not, I schedule a task to retrieve that.

private void getAIData(uint objectId)
{
    SimulatedObject foundObj = GetFromList(objectId);
    if ((foundObj != null) && IsSelected(foundObj.ObjectType))
    {
        if (foundObj is SimulatedAircraft aircraft)
        {
            var aircraftData = DataManager.Instance.RequestData<AircraftData>(objectId: objectId).Get();
            aircraft.Title = aircraftData.Title;
            aircraft.TailNumber = aircraftData.Id;
            if (aircraftData.Airline == "")
                aircraft.Details = aircraftData.Id;
            else
                aircraft.Details = $"{aircraftData.Id} ({aircraftData.Airline} flight {aircraftData.FlightNumber})";
            log.Debug?.Log("Aircraft data received: {0} {1} is a '{2}', details set to '{3}'", foundObj.ObjectType.ToString(), objectId, aircraftData.Title, aircraft.Details);
        }
        else
        {
            var title = DataManager.Instance.RequestData<VehicleTitle>(objectId: objectId).Get().Title;
            foundObj.Title = title;
            log.Debug?.Log("Vehicle title received: {0} {1} is a '{2}'", foundObj.ObjectType.ToString(), objectId, title);
        }
        uiUpdater?.Invoke(() => Update(objectId, foundObj));
    }
}

Now here you see me asking the "DataManager" to request either an object of class "AircraftData" if it actually is an aircraft, or else a "VehicleTitle." We don't subscribe to the result in both cases because it's not a stream of data. Also, you don't see me subscribing to a ClientDataReceived-like message because I don't care about any such message unless it is the one I just requested! The "Get()" method assumes we have a single result, and we want to wait for it. This application is multi-threaded, and we explicitly started a separate task to perform this work, so it is only this task that will block.

So, where are the "AddToDataDefinition()" calls, you may ask? Well, nowhere. The classes we used to request the data are annotated with the required information:

public class VehicleTitle
{
    [DataDefinition("TITLE", Type = DataType.String256)]
    public string Title { get; set; }
}

public class VehicleData
{
    [DataObjectId]
    public uint ObjectId { get; set; }

    [DataDefinition("TITLE", Type = DataType.String256)]
    public string Title { get; set; }

    [DataDefinition("ATC ID", Type = DataType.String32)]
    public string Id { get; set; }

    [DataDefinition("ATC AIRLINE", Type = DataType.String32)]
    public string Airline { get; set; }

    [DataDefinition("ATC FLIGHT NUMBER", Type = DataType.String8)]
    public string FlightNumber { get; set; }
}

The "DataDefinition" attribute (that is what these annotations are called in C#) describes everything the CsSimConnect library needs to know to request the data. When the "DataManager" first receives a request for data of such a class, it uses reflection to collect all fields and build the data block. Then when the data arrives, it creates a new object of this type and fills all the fields and properties as needed, looks up the subscription for this particular request, and sends over the data. The metadata in the message is also available so that you can get request-id, object-id, define-id, flags, or even the sequencing information injected.

I hope you'll agree that this approach makes life a lot easier for a developer at a minimal cost. The library does everything we always took for granted as ugly but necessary grunt-work. Some of this is arguably using a few more statements than when we'd write it out by hand, but it can be remarkably efficient about this.

Finishing up: where is this library, and when can we use it? All the code is freely available on GitHub in a repository named CsSimConnect, at https://github.com/bert-laverman/CsSimConnect. The source files contain a copyright statement and a reference to the Apache 2.0 license, but that is mostly to prevent others from running away with it and ask money for it. You are free to use it in public or private code or even in a commercial project. The current status is that you can do what I described in this article, but it certainly still has many rough edges and is not yet production-ready. There are two WPF-based applications as examples of what the library can do, and I use these to drive development. For example, the "AutoPilotController" app was the driver for implementing event sending, while the list of vehicles drove the first AI querying and creating bits.

Sometimes such a venture can cause work to grind to a halt, as I need to research stuff before continuing. For example, when I implemented "CreateParkedATCAircraft," I looked for a way to present the UI user with a dynamic list of ICAO codes. Unfortunately, querying the simulator is useless because it only returns data on airports within the "reality bubble." Pete Dowson (of FSUIPC fame) wrote a useful tool to scan the simulator and generate a list, but it is not particularly user-friendly while very robust. Consequently, new libraries scan for installed addons, and I am learning to scan BGL files. These again are very far from working but show promising progress.

Oh yes, before I forget: CsSimConnect works the same for Prepar3D and MSFS 2020. I still need to decide how to report to the calling app that a requested function does not work. Still, the SimConnect unmanaged libraries of Prepar3D v4 and v5 and the one from MSFS are linked to produce three different "CsSimConnectInterOp.dll" libraries. The C# classlibrary can dynamically choose (at startup) which one it needs to use, and the "CsSimConnectUI" demo app even pops up a selection list for this.

I'll write more about this on a blog site I set up at https://cssimconnect.wordpress.com (you can even start at https://blog.cssimconnect.org), and I claimed the obligatory names on Facebook and Twitter. Feel free to contact me for more information or if you'd like to help out!

simconnect
10 |10000 characters needed characters left characters exceeded

Up to 5 attachments (including images) can be used with a maximum of 4.8 MiB each and 23.8 MiB total.

Article

Contributors

bert.laverman contributed to this article

Related Articles