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!


question

Robert_H avatar image
Robert_H asked Robert_H answered

Can't get READ SimVar CallBack to work properly using C++ x64 WinForm in VS2022

I'm trying to read engine RPM in a Cassna 152. After trying a multitude of ways to read a SimVar, I ended with Lockheed's sample and tweaked it (like changing the read interval to Frames).

{ I isolated my SimConnect code to separate .h file cause I use a USB code generator for my PIC and C++ code, and it rewrites over the C++ program. That way I have very little modifications to re-do each time I change my USB code. }

I trigger the CallBack from a Button control for the time being, it will be moved into a Timer later.

private: System::Void MainForm_Load(System::Object^ sender, System::EventArgs^ e)
{
    OpenSimConnect();
}

private: System::Void bUpdateIndicators_Click(System::Object^ sender, System::EventArgs^ e)
{
    SimConnect_CallDispatch(hSimConnect, MyDispatchProcPDR, NULL);

//this->tbEngine1_RPM->Text = intEngine1_RPM;
}

private: System::Void bClose_Click(System::Object^ sender, System::EventArgs^ e)
{
    CloseSimConnect();
    Application::Exit();
}


The SimConnect code:

//Copyright (c) Lockheed Martin Corporation.  All rights reserved. 
//------------------------------------------------------------------------------
//
//  SimConnect Tagged Data Request Sample
// 
//    Description:
//                After a flight has loaded, request the vertical speed and pitot
//                heat switch setting of the user aircraft, but only when the data
//                has changed
//------------------------------------------------------------------------------
#pragma once

#include <windows.h>
#include "SimConnect.h"
#include <tchar.h>
#include <stdio.h>
#include <strsafe.h>

int     quit = 0;
HANDLE  hSimConnect = NULL;

// A basic structure for a single item of returned data
struct StructOneDatum {
    int        id;
    float    value;
};

// maxReturnedItems is 3 in this case, as the sample only requests RPM,
// vertical speed and pitot heat switch data
#define maxReturnedItems    3

// A structure that can be used to receive Tagged data
struct StructDatum {
    StructOneDatum  datum[maxReturnedItems];
};

static enum EVENT_PDR {
    EVENT_SIM_START,
};

static enum DATA_DEFINE_ID {
    DEFINITION_PDR,
};

static enum DATA_REQUEST_ID {
    REQUEST_PDR,
};

static enum DATA_NAMES {
    DATA_ENGINE1_RPM,
    DATA_VERTICAL_SPEED,
    DATA_PITOT_HEAT,
};

void OpenSimConnect()
{
    HRESULT hr;

    if (SUCCEEDED(SimConnect_Open(&hSimConnect, "Open", NULL, 0, 0, 0)))
    {
        hr = SimConnect_AddToDataDefinition(hSimConnect, DEFINITION_PDR, "PROP RPM:1", "rpm",
            SIMCONNECT_DATATYPE_FLOAT32, 0, DATA_ENGINE1_RPM);

        hr = SimConnect_AddToDataDefinition(hSimConnect, DEFINITION_PDR, "Vertical Speed", "Feet per second",
            SIMCONNECT_DATATYPE_FLOAT32, 0, DATA_VERTICAL_SPEED);

        hr = SimConnect_AddToDataDefinition(hSimConnect, DEFINITION_PDR, "Pitot Heat", "Bool",
            SIMCONNECT_DATATYPE_FLOAT32, 0, DATA_PITOT_HEAT);

        hr = SimConnect_RequestDataOnSimObject(hSimConnect, REQUEST_PDR, DEFINITION_PDR,
            SIMCONNECT_OBJECT_ID_USER, SIMCONNECT_PERIOD_VISUAL_FRAME, 0, 0);
    }
}

void CloseSimConnect()
{
    HRESULT hr;
    hr = SimConnect_Close(hSimConnect);
}

void CALLBACK MyDispatchProcPDR(SIMCONNECT_RECV* pData, DWORD cbData, void* pContext)
{
    HRESULT hr;

    float fltEngine1_RPM;

    switch (pData->dwID)
    {
        case SIMCONNECT_RECV_ID_SIMOBJECT_DATA:
        {
            SIMCONNECT_RECV_SIMOBJECT_DATA* pObjData = (SIMCONNECT_RECV_SIMOBJECT_DATA*)pData;

            switch (pObjData->dwRequestID)
            {
                case REQUEST_PDR:
                {
                    int    count = 0;;
                    StructDatum* pS = (StructDatum*)&pObjData->dwData;

            // There can be a minimum of 1 and a maximum of maxReturnedItems
            // in the StructDatum structure. The actual number returned will
            // be held in the dwDefineCount parameter.

                    while (count < (int)pObjData->dwDefineCount)
                    {
                        switch (pS->datum[count].id)
                        {
                            case DATA_ENGINE1_RPM:
                                // printf("\nEngine 1 RPM = %f", pS->datum[count].value);
                                fltEngine1_RPM = pS->datum[count].value;
                                break;

                            case DATA_VERTICAL_SPEED:
                                // printf("\nVertical speed = %f", pS->datum[count].value);
                                break;

                            case DATA_PITOT_HEAT:
                                // printf("\nPitot heat = %f", pS->datum[count].value);
                                break;

                            default:
                                // printf("\nUnknown datum ID: %d", pS->datum[count].id);
                                break;
                        }
                        ++count;
                    }
                    break;
                }
                default:
                break;
            }
            break;
        }
    }
}


I added Engine1 RPM for my application. I get weird data when I add a Breakpoint at where "I think" Engine 1 RPM is read.


I'm a main-frame programmer by trade, so please disregard my ignorance when it comes to coding C++ and using structures and variables properly in C++ (I only started C++ this year on my own).

My goal is to get that Engine 1 RPM into a variable that I can handle, like displaying on-screen, but the main goal is to move to USB variables for download to a PIC mcu.

Thanks for any help!

Robert

simconnectc++
10 |10000

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

EPellissier avatar image
EPellissier answered

Hi @Robert_H,

It looks like you are trying to request data on a SimObject through tagged values. In that case, your call to SimConnect_RequestDataOnSimObject should read:

hr = SimConnect_RequestDataOnSimObject(hSimConnect, REQUEST_PDR, DEFINITION_PDR,
            SIMCONNECT_OBJECT_ID_USER, SIMCONNECT_PERIOD_VISUAL_FRAME, SIMCONNECT_DATA_REQUEST_FLAG_TAGGED, 0);

Please have a look at the online documentation for more information: https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/API_Reference/Events_And_Data/SimConnect_RequestDataOnSimObject.htm

Also note that the SDK comes with SimConnect samples - there's one called "TaggedData" that might be helpful in your case.

Best regards,

Eric / Asobo

10 |10000

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

Robert_H avatar image
Robert_H answered EPellissier commented

Yeah, I added VISUAL_Frame last night before going to bed. At least now it loops through automatically without me bashing on a button.

I saw that Tagged_Data sample after I found that code; it's essentially the same as what I found from Lockheed.

But my quest remains; how do I get to that engine RPM value hidden in the structure?

How can I move it to ordinary variables so I can format them for display and transfer by USB as desired?

I don't know enough C++ to copy it to a variable I can use in a WinForm, or to a variable for a USB interface (I use HIDmaker FS2). I just started earlier this year, I was a COBOL programmer that dabbled in VB6.


I can't just display the contents on a textbox, it generates error C2665 and E1767.

this->tbRPM->Text = (Struct1*)&pObjData->dwData;


This statement seems so simple at first glance to you, but I'm using a WinForm, I can't just Printf.

printf("\nEngine 1 RPM = %f", pS->datum[count].value)

But to me, I can't figure out how to get to "value". I tried all sorts of conversion routines.

sprintf(chrEngine1_RPM, "%f", pS->datum[count].value);


Robert
"Noob lost in the woods"

PS: I asked on C++ forums, but the only reply I got was essentially, "easy, just convert it with Sprintf".

1 comment
10 |10000

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

EPellissier avatar image EPellissier ♦♦ commented ·

@Robert_H,

You wrote above "I added VISUAL_Frame last night" but it could already be seen in your previous message - what I told you was missing was "SIMCONNECT_DATA_REQUEST_FLAG_TAGGED" - is it what you meant to write?

As for your question regarding how to use WinForms I don't think this belongs to the DevSupport platform - we provide support for the MSFS SDK/DevMode but the question seems related to a specific tech that is outside of this scope.

Maybe someone from the community who uses WinForms will be able to help you.

Best regards,

Eric / Asobo

0 Likes 0 ·
Robert_H avatar image
Robert_H answered

Yup, I've added the parameters as in the Tagged SDK example.

I've since learned that C++ .Net programs are managed code, while the CallBack routine is unmanaged code.

I can't be the only guy on this forum that is "looking for a way to get the variable value that he just read on SimConnect back to the managed code" side (in my case it's mainly an interface to USB, and out to my instrument panel.

So far the "C++ experts on google" are baffled by the SimConnect code, none I've seen seem to be able to figure out the structures. But it's certain that those that do know how to get to the values are here.

Robert

10 |10000

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

Steeler avatar image
Steeler answered

can't just display the contents on a textbox, it generates error C2665 and E1767.


Because you can‘t print an entire struct or assign it to a (presumed) text widget.


You need to cast the „raw bytestream data“ to the expected struct first, and then access its individual (and now properly typed) struct members.


Basic C++ struct knowledge ;)


And you probably still need to explicitly format the received integer to a („printable“) text.


Sorry, C++ ain‘t JavaScript ;) They say that „in C++ you don‘t pay for what you don‘t need“. But the opposite holds as well: „You pay dearly (in terms of coding efforts, that is) for what you need“


Well, mostly anyways. There are frameworks in C++, too, that do „the dirty work for you“ (I use Qt).

Oh, and if you thought that „casting the received bytestream“ was enough, think again: make double-sure to enforce „tight packing“ of your struct definition! Otherwise your compiler will likely do its duty and optimise the struct member access for performance, that is e.g. align the members to 64bit address boundaries.


Not a big deal if you have a bunch of FLOAT64 (only), but results will vary between „hmmm, that‘s interesting…“ and „bloody %<#+! compiler, WTF!“ if you add FLOAT/INT32 to the mix ;)

10 |10000

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

Steeler avatar image
Steeler answered

Here are some real-world examples, in C++:


https://github.com/till213/SkyDolly/blob/main/src/Plugins/Connect/MSFSSimConnectPlugin/src/MSFSSimConnectPlugin.cpp


And an example struct:

https://github.com/till213/SkyDolly/blob/main/src/Plugins/Connect/MSFSSimConnectPlugin/src/SimVar/Position/SimConnectPositionCommon.h


Again, pay extra attention to the #pragma compiler directive: it tells the compiler to „tightly pack the data“, because that is how we receive it from MSFS (and how MSFS expects the „byte stream“).


Btw that „packing“ is not specific to C++, you e.g. need to tell the same a C# compiler.

And believe me, I am talking from (bloody) experience ;)



10 |10000

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

Robert_H avatar image
Robert_H answered

Thank you for that detailed explanation, it confirms what I thought I understood. I read about "packing", "enforcing" and "wait till the garbage man comes by and craps all over your stuff" in my searches. :D

I have one last test to complete, then I'll report back with what I found before closing this thread (please).

Robert
:)

10 |10000

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

Robert_H avatar image
Robert_H answered

I just wanted to confirm that I can have multiple SimVars using this technique (I was only using Prop RPM for my tests). I now read 7 SimVars on each loop and I've bumped up my SimConnect delay to 10ms and I'm still a tad faster under full throttle acceleration than SimWatcher with full slider (I left only 3 variables).

rpm-with-10ms-delay-w-usb-others.png


No Callback, no tagged logic, just basic SimConnect all done in MainForm.

Robert
:)


10 |10000

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

Write an Answer

Hint: Notify or tag a user in this post by typing @username.

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