I’d like to share my experience here another time, as I have sucessfully build a “win32 event based variable reading”. In my case I am building a C++ application together with a “QWidget-based (Qt) application”, but my code reflects what has been discussed above already and should be easy to generalise to other languages / frameworks.
So in case anyone stumbles across this and wants to see some “real world example code” (with all the usual disclaimers like “it works for me” and “whatever-you-do-it’s-your-fault” etc ;)):
https://github.com/till213/SkyDolly/blob/main/src/SkyConnect/src/SkyConnectImpl.cpp
An event-based notification basically boils down to:
- When opening the connection SimConnect_Open provide both a HWND (“window handle”) and a “user event identifier”
- When intercepting the window events look out for that “user event identifier”: when such an event has been sent…
- … then call SimConnect_CallDispatch (which takes a “dispatch procedure” (= callback) as argument which then processes the actually received data/events)
So far so good. But where do we get that “window handle” from? Where to “intercept” the window events?
In fact, the questions are related: the “window handle” (HWND) is to be from the same window (“widget” in Qt speak) which also intercepts the window messages. Typically that is the “main window”, but since in my case I want to keep the “SimConnect code” completely separate from the actual application implementation (in fact, the entire code is going to be “outsourced” into a “plugin” later on) I did not want my “main window” to handle any “SimConnect specific logic”, and I would have add to “pass down” its “window handle” (HWND) to the constructor, polluting also the “interface” (in C++: a header with all pure virtual methods - acts like a “Java interface” class) with Windows-specific headers etc.
Or in other words: within my “SimConnect” logic I create my own QWidget - called the EventWidget - which does nothing more than re-implement the “nativeEvent” method:
bool EventWidget::nativeEvent(const QByteArray &eventType, void *message, long *result)
{
Q_UNUSED(eventType)
Q_UNUSED(result)
bool handled;
MSG *msg = static_cast<MSG *>(message);
switch(msg->message) {
case SimConnnectUserMessage:
emit simConnectEvent();
handled = true;
break;
default:
handled = false;
break;
}
return handled;
}
So whenever a SimConnnectUserMessage comes in the widget emits a simConnectEvent signal and return true (= “we handled that event”). Otherwise we let the default implementation handle the event (by returning false).
That’s it! We have succesfully “intercepted” our “user message” which is sent by SimConnect whenever SimConnect data/events are ready to be processed.
(If you are not familiar with Qt signals and slots, don’t worry: think of it as a “callback mechanism”, or an “observer/model” kind of implementation: the model notifies all observers via signals, and all observers that have connected the signal to their slots (callbacks) get notified (= their slots (methods, callbacks) get called)
The SimConnnectUserMessage is the same value as also used in various SimConnect examples:
static constexpr int SimConnnectUserMessage = 0x0402;
(I guess the value does not really matter for as long as it lies in some “user range” - but since I simply take the same value 0x0402 here as in the various examples I did not do much more "research into the win32 API documentation here ;)).
Now back in the SkyConnectImpl class we get notified by that signal, because we connect that signal with our processEvents slot:
connect(d->eventWidget.get(), &EventWidget::simConnectEvent,
this, &SkyConnectImpl::processEvents);
Again, do not worry too much about this Qt way of “registering a callback”: the take-away here is that we simply call our “processEvents” method each time we receive our user message (SimConnnectUserMessage):
void SkyConnectImpl::processEvents()
{
// Update internal state such as the current “timestamp” etc.
…
// Process system events
::SimConnect_CallDispatch(d->simConnectHandle, SkyConnectImpl::dispatch, this);
}
Finally the SkyConnectImpl::dispatch implements the CALLBACK interface as specified in the SimConnect API documentation and is the same callback that is also used in the “polling” approach.
Finally how do we get that HWND (in a Qt based application)? Well, that is easy, with the winId method (which returns a platform-specific “window ID” or “handle”: in this case a HWND value):
HWND hWnd;
DWORD userEvent;
if (d->eventWidget != nullptr) {
hWnd = reinterpret_cast<HWND>(d->eventWidget->winId());
userEvent = EventWidget::SimConnnectUserMessage;
} else {
hWnd = nullptr;
userEvent = 0;
}
HRESULT result = ::SimConnect_Open(&(d->simConnectHandle), ::ConnectionName, hWnd, userEvent, nullptr, SIMCONNECT_OPEN_CONFIGINDEX_LOCAL);
In my application logic the eventWidget acts as a toggle: if it has been constructed we go for an “event-based notification” (and we disable the timer: no polling). Otherwise we use a timer (QTimer) for polling (enabled elsewhere in the code).
So what are the results?
With this “event based processing” I got “samples per second” rates of around 25 - 35. That is exactly the expected range, as FS 2020 renders around the same frames per second on my very mid-range system (an iMac 27" from 2017 ;)).
Note though that I am requesting simulation variable updates per calculated simulated frame (SIMCONNECT_PERIOD_SIM_FRAME) and not per visual frame (SIMCONNECT_PERIOD_VISUAL_FRAME), so those “samples per second” values (up to 35 on my system) absolutely make sense (I probably get quite a bit less visual FPS, but never really bothered to measure them).
And in my case I can still use a “fixed frequency polling” (using a QTimer), and the results match: even when I request variable updates at 60 Hz I still get the same 35 samples per second (“requesting more than what is available does not magically produce more” - even tough sometimes we all wish that this was true ;)).
I hope the above helps those which stumble over the same questions like we had here: the code above shows you both how to implement:
- a “polling” approach (timer based, with a fixed frequency)
- an “event-based approach”, by letting SimConnect inform us with a “user defined window message”