SimConnect + WASM combined project using VS2019

Chapter 3: Connection with SimConnect

Now we have the framework of our VS2019 Solution ready, we can start with the real work. First we start with some basics on how to connect and disconnect with SimConnect. We will discuss the WASM module in a later chapter.

I am not going to dig into the C++ approach, because I am using the C# version (managed code) which simplifies a lot. But just be aware the all the documentation is written with C++ in mind. You will see that C# uses a slightly different syntax.

The below shows the difference between a C++ and C# implementation for opening a SimConnect client session and using some function.

C++ implementation:

// SimConnect HANDLE
HANDLE hSimConnect = NULL;  

// SimConnect result
HRESULT hr;

// Open the SimConnect client session
hr = SimConnect_Open(&hSimConnect, "Your Application Name", hWnd, WM_USER_SIMCONNECT, 0, 0);
if (hr != S_OK)
    ... error processing

// Use some SimConnect function
hr = SimConnect_AddToDataDefinition(
    hSimConnect,
    DEFINITION_1,
    "Kohlsman setting hg",
    "inHg");
if (hr != S_OK)
    ... error processing where "hr" can be used to identify the error

C# implementation:

// SimConnect object
private SimConnect _oSimConnect = null;

try
{
    // Open the SimConnect client session
    _oSimConnect = new SimConnect("SimConnectHub", hWnd, WM_USER_SIMCONNECT, null, 0);

    // Use some SimConnect function
    _oSimConnect.AddToDataDefinition(
        DEFINITION_1,
        "Kohlsman setting hg",
        "inHg",
        SIMCONNECT_DATATYPE.FLOAT64,
        0.0f,
        SimConnect.SIMCONNECT_UNUSED);
}
catch (COMException ex)
{
    ... error processing where "ex.Message" can be used to identify the error
}

There are 2 main differences:

  1. C++ makes use of a HANDLE that identifies the SimConnect client session, and is obtained from the SimConnect_Open call. It is used as first parameter in all further calls to SimConnect functions.
    C# makes use of a SimConnect object, in which the constructor takes care of the HANDLE internally (probably some private property). The constructor acts as a wrapper of the SimConnect_Open function. The SimConnect functions are members of the SimConnect object, omitting the prefix “SimConnect_” from the function name.
    Example: “hr = SimConnect_AddToDataDefinition(hSimConnect, DEFINITION_1, …” becomese “_oSimConnect.AddToDataDefinition(DEFINITION_1, …”.
  2. The error handling in C++ is using the return value HRESULT. If this value is not equal to S_OK, there is an issue. In C# this is translated in the raising of a COMException, that can be used within a try/catch block.

It is definitely worth reading the section Programming SimConnect Clients using Managed Code. It contains a lot more specifics on the C# wrapper. I’m not going to repeat them all in this post.

The SimConnect constructor has the following signature:

public SimConnect(string szName, IntPtr hWnd, uint UserEventWin32, WaitHandle hEventHandle, uint ConfigIndex);

An easy way to find these signatures is right clicking on the method (in this case “SimConnect”, as that is the constructor name), and choose “Peek Definition”.

image

The problem with SimConnect is the lack of documentation :frowning_face:. So sometimes you will have to do some “guesswork”. For example, I have no clue what the “WaitHandle” is doing as the SimConnect documentation doesn’t talk about this in the context of C#. But we seem not to need it for now, so we will use null which seems to work just fine. (if anybody has a clue, don’t hesitate to comment).

Two important parameters are:

  1. IntPtr hWnd
  2. uint UserEventWin32

It means that when opening a SimConnect session, we will need to provide a window handle of a form that has a WndProc (if I’m not mistaken, all forms have one). SimConnect will then interact with this form by sending messages with the Msg defined in UserEventWin32. You simple need to hook into the WinProc, and dispatch all SimConnect messages to your application.

If you are planning to build a SimConnect client as a DLL, then you probably don’t want to have a visible form. In that case, you will have to use a “MessagePump” (which is actually some kind of “hidden” form). I suggest you look at this post written by @dragonlaird who is exactly doing this.

But as my implementation is based on a Windows Form, I can simply use that handle and pass it to my SimConnect constructor. A good moment to do that is when the Form is “shown”. In VS2019 this is even extremely simple by including the “Shown Event” - just double click on the empty space next to “Shown” below, and it will automatically add “FormHUB_Shown” for you.

image

Let’s have a look in the “FormHUB.cs” file included in my project (I only explain the parts that are relevant - the order of the methods might also differ):

// file FormHUB.cs

public partial class FormHUB : Form
{
    SimConnectHUB _SimConnectHUB = new SimConnectHUB();

    private void FormHUB_Shown(object sender, EventArgs e)
    {
        _SimConnectHUB.SetHandle(this.Handle);
    }
   
    protected override void WndProc(ref Message m)
    {
        _SimConnectHUB?.HandleWndProc(ref m);

        base.WndProc(ref m);
    }

    ....
}

The above is pretty selfexplanatory:

  1. A new “SimConnectHUB” object is created, which is the SimConnect client implementation (file SimConnectHUB.cs)
  2. When the Form is shown, I pass its windows handle to SimConnectHUB
  3. In the Form, WndProc is intercepted, and calls the SimConnectHUB.HandleWndProc before calling its base.WndProc (remark: I could have checked for the specific SimConnect message here, but I do that in HandleWndProc - doesn’t make a lot of difference really).

In the SimConnectHUB object, you will find the implemenation to store the Form handle and deal with the WndProc call:

// file SimConnectHUB.cs

// User-defined win32 event
public const int WM_USER_SIMCONNECT = 0x0402;
// Window handle
private IntPtr _handle = new IntPtr(0);

// Keep Window Handle for connecting with SimConnect
public void SetHandle(IntPtr handle)
{
    _handle = handle;
}

// WndProc hook
public void HandleWndProc(ref Message m)
{
    if (m.Msg == WM_USER_SIMCONNECT)
    {
        ReceiveSimConnectMessage();
    }
}

public void ReceiveSimConnectMessage()
{
    try
    {
        _oSimConnect?.ReceiveMessage();
    }
    catch (Exception ex)
    {
        LogResult?.Invoke(this, $"ReceiveSimConnectMessage Error: {ex.Message}");
        Disconnect();
    }
}

In HandleWndProc, we check if SimConnect has something to say. If it does, then we call “ReceiveSimConnectMessage()” that will call the SimConnect “ReceiveMessage()” method. This is the method that triggers all the SimConnect magic.

I could have put the call to “ReceiveMessage()” directly in “HandleWndProc”, but decided not to do so in case I want to call “ReceiveMessage()” myself somewhere in the code (although, so far this was not needed).

Now that the preparation is done, we are ready to use the “Connect” and “Disconnect” methods that are called by the Connect and Disconnect buttons on the form.

The Connect method looks like this:

// file SimConnectHUB.cs

public void Connect()
{
    if (_bConnected)
    {
        LogResult?.Invoke(this, "Already connected");
        return;
    }

    try
    {
        _oSimConnect = new SimConnect("SimConnectHub", _handle, WM_USER_SIMCONNECT, null, 0);

        // Listen for connect and quit msgs
        _oSimConnect.OnRecvOpen += SimConnect_OnRecvOpen;
        _oSimConnect.OnRecvQuit += SimConnect_OnRecvQuit;

        // Listen for Exceptions
        _oSimConnect.OnRecvException += SimConnect_OnRecvException;

        // Listen for SimVar Data
        _oSimConnect.OnRecvSimobjectData += SimConnect_OnRecvSimobjectData;

        // Listen for ClientData
        _oSimConnect.OnRecvClientData += SimConnect_OnRecvClientData;

        LogResult?.Invoke(this, "Connected");
    }
    catch (COMException ex)
    {
        LogResult?.Invoke(this, $"Connect Error: {ex.Message}");
    }
}

The C# wrapper of SimConnect provides an easy way to intercept interactions by adding EventHandlers that are called when some specific SimConnect events are happening. In my implementation, I only provided the ones that are relevant, but if you use “Peek Definition” you will find 27 of them. Some event names are self-explanatory, but for other ones you might have to dig in to the documentation or google, or simply experiment.

The ones that I added are:

  • OnRecvOpen: This will be called by SimConnect when the session successfully opened, and will be used to initialize some additional items.
  • OnRecvQuit: If we close MSFS2020, then SimConnect will notify our client through this event.
  • OnRecvException: If SimConnect discovers some error, it will notify our client through this event.
  • OnRecvSimobjectData: This is to receive data from Simobjects (SimConnect variables)
  • OnRecvClientData: This is to receive data from Client Area Data which is going to be used to communicate with our WASM module.

The Disconnect method looks like this:

// file SimConnectHUB.cs

public void Disconnect()
{
    if (!_bConnected)
    {
        LogResult?.Invoke(this, "Already disconnected");
        return;
    }

    // Raise event to notify client we've disconnected and get rid of client
    _oSimConnect?.Dispose(); // May have already been disposed or not even been created, e.g. Disconnect called before Connect
    _oSimConnect = null;
    _bConnected = false;
    LogResult?.Invoke(this, "Disconnected");
}

“LogResult” is my own EventHandler that is used to send information back to a multiline TextBox in the form. Because my SimConnectHUB object has no clue at compile time where to send its result to, we deal with that using an EventHandler.

// file SimConnectHUB.cs

public event EventHandler<string> LogResult = null;

When the constructor of the form is called, this EventHandler gets initialized like this:

// file FormHUB.cs

public FormHUB()
{
    InitializeComponent();

    _SimConnectHUB.LogResult += OnAddResult;
}

private void OnAddResult(object sender, string sResult)
{
    if (textResult.Text != "")
        textResult.AppendText(Environment.NewLine);
    textResult.AppendText(sResult);
}

Now SimConnectHUB can Invoke the EventHandler whenever it has something to tell. I’m using the “Null-conditional operator” (the ‘?’-character) in the call, which guarantees that LogResult will only be used when not null (see here).

// file SimConnectHUB.cs

LogResult?.Invoke(this, "some text");

Et voila, we can connect and disconnect our SimConnect Client, and get some results in a multiline TextBox. Ready to start using SimConnect in the next chapter.

Goto Chapter 4: Variables

6 Likes