Over the past year or so, I've really gotten into playing Microsoft Flight Simulator 2020. I'm an MS Flight Sim fan from way back; I still have version 4 - on a single floppy! - from 1991 out in my garage. I've been a plane nut even longer than that, ever since my dad got his private certificate back in the 70s. I hadn't used MSFS since the 98 version, but I got a small bonus at work and decided to give it a try again. I've since spent probably too much money buying a bunch of the Logitech flight sim controls to go with it.
More recently, I've been working with an Arduino kit to see about making my own controls - the Logitech multi-panel's trim control is not that great, so I'm trying to see if I can come up with something better. In order to do that, I need to use the SimConnect SDK to communicate with MSFS 2020. I haven't gotten very far with it, but I'm going to share some of the stuff that I have learned so far, since one of the obstacles has been having to piece together how to do things from the documentation, plus various forum posts I've found through Google. Maybe having all this stuff in one place will be helpful to someone else.
I'm going to assume that anyone looking for this type of information has at least a basic understanding of C# programming (that's me, too, I'm not trying to put myself out there as some kind of guru) and understands how to use the Visual Studio IDE (I'm using VS 2022 Community Edition) or whatever other tool set they are using.
What is SimConnect?
SimConnect is a library that allows us to write clients that communicate with the built-in SimConnect server that runs inside MSFS 2020. SimConnect has been around since at least Microsoft Flight Simulator X from 2006. It also is used with Lockheed Martin's Prepar3d, since that product is derived from FSX. Clients can interact with the simulator to create alternative interfaces (such as physical controls that mimic real-world aircraft controls) or other extended functionality.
The "real" SimConnect library is designed to work with unmanaged C/C++ code, but there is a wrapper library (Microsoft.FlightSimulator.SimConnect) that allows using SimConnect from C# or other managed .NET languages. The managed library has some limitations compared to the unmanaged library, but by and large the functionality is the same. However, most of the documentation is written for the unmanaged library, so if you're using the managed library, as I am, you have to cross-reference that documentation with the page that describes the differences, and adjust as you go.
Hello, SimConnect!
To start off, we'll build a console app that initially does nothing other than connect to MSFS 2020 via SimConnect. I'm planning to add further posts that go into some of the basic functionality of interacting with the sim, but for now we'll just get a "Hello, World!"-type program running.
You need to have downloaded the MSFS 2020 SDK by following the instructions on this page. This will, among other things, put a local copy of both the managed and unmanaged libraries on your machine.
Once that is done, and you've created a console app in the tool of your choice, you'll need to add a reference to the managed library DLL. Here's where it is in the current version of the SDK on my machine.
In the Program.cs file (or wherever you have your Main() method), get rid of any boilerplate "Hello, World!" code and update it to look like this.
Right off the bat, in VS 2022 you'll get some warnings and messages. First, the warning.
While I'm not sure if this will have any actual impact, it's easy enough to fix: just add "x64" as a target platform and use that for any future builds.
The messages are more about stylistic matters.
The first one is basically saying "hey, you've created an instance of this 'SimConnect' class, but then you don't do anything with it." Which is true. Since the library implements IDisposable, we can explicitly call the Dispose() method (even though the C# GC should automatically call it for us), and in so doing, keep the IDE happy.
As for the second message, while I appreciate that C# no longer requires constructors to be so verbose (which is what IDE0090 is about), I don't think it's that big of a deal to say new SimConnect() instead of just new().
And speaking of the constructor, just a quick rundown of its arguments:
szName: the name of the client application. Can be anything you like (clearly!)
hWnd: for Windows applications, a window handle. Since this is a console app, we don't need one. The documentation (again, for the unmanaged equivalent) says that it can be null, but the examples I have seen all use IntPtr.Zero, so I've gone with that.
UserEventWin32: a client-defined number that would be used in a window-object message loop to distinguish SimConnect messages from other messages. Again, not relevant to us in this example.
hEventHandle: yet another parameter only relevant to an app with a GUI. This one allows using Windows Events instead of a polling approach to listen for messages.
ConfigIndex: allows referencing a numbered section of the SimConnect.cfg file, if applicable.
Even though this is a managed wrapper library, the old-school Win32 lineage of the underlying library shows through in the Hungarian-notation argument names. One thing you'll notice in working with the library is that it's a fairly thin wrapper - not a lot of effort was expended to make it look more "modern."
Anyway, that should be about it, right? Let's build and run it!
Oops!
I guess it can't find the library DLL that we referenced. It's not in the GAC, so let's take a look at the bin folder.
Huh. It's there! What gives?
The secret* is that your app can find the managed library just fine, but the managed library can't find what it needs: the actual (unmanaged) SimConnect library that it wraps. You can find the DLL for that here:
Copy it to the bin directory, then re-run the program. This time for sure, right?
What now? Well, when you construct a new instance of the SimConnect class, right then and there it calls the unmanaged library, using the same arguments, to open a connection to the SimConnect server in MSFS 2020, So unless you already had MSFS running (and if so, feel free to look smug), the library call fails.
Well, we should have had exception handling anyway, shouldn't we? So let's add that, after closing out the running program.
If you're on an older version of .NET, the question marks may not be necessary, they're a new thing (to me at least) in 8.0 where you tell the compiler that you might be using a null value for a particular variable, and acknowledge that the value may be null when you call, e.g., Dispose(), instead of having if (simConnect == null) checks everywhere.
(If you're using an older version, the $"{}" strings may not work for you either, and you may have to concatenate the string literals and variable values.)
Now if we run without MSFS running, we'll just get our own error message back, and the program will exit gracefully instead of hanging on the exception or crashing outright. For non-trivial uses of SimConnect, we might want to implement some sort of retry mechanism instead of just exiting when the sim isn't running.
So let's fire up MSFS, shall we? After all the vanity plates and hey-here's-the-next-expansion-we're-bringing-you load screens, and maybe even a fun mandatory update, once you're in the home screen, run our program again. Success!
But...kind of underwhelming? I mean, no errors. Yay! But how do we know it's actually doing anything? Where's the equivalent of seeing "Hello, World!" on the console?
Well, how about we first insert a simple pause after connecting (or failing to connect) and calling Dispose()?
(Save your "but I don't have an any key on my keyboard, hurr durr" jokes.)
Now, while the program is running, enable developer mode in MSFS if you've turned it off, and go to Tools -> SimConnect Inspector.
Open up the drop-down, and there it is! "Hello, SimConnect!"
OK, so that isn't much better. But hey, you can see our little program in action.
You can go on to part two to see how SimConnect uses messages to communicate between a client and the simulator. I also have a GitHub repository with all the code from this series in it, tagged at each stage.
* And it is kind of a secret; the managed library documentation page doesn't mention it, although luckily there's more than one forum post explaining the issue.