Considering that I wrote most of the basis of what ended up to be the GDI+/C++ sample for Microsoft included in the ESP SDK (that code is derived from the F/A-18), I might probably be able to reply to this...
I don't use a singleton pattern (as far as you would recognise it in C++)
That doesn't really have much to do with the use of the Singleton pattern or not. Using the Singleton pattern is really optional, and it's only a way to enforce the use of something that is instantiable only once.
Meaning: you don't run the risk of declaring global variables twice, don't run the risk of opening Simconnect twice, etc.
This is very appropriate for two things: the whole set of data that is retrieved from FSX ( the Simvars ) AND the Simconnect initialization and closure IS inherently something that exist in one copy only, and it's global by nature.
You don't have *two* sets of Simvars, and you don't need (and don't want) open Simconnect twice so, the Singleton is just a way to ensure you CAN'T do that, not even by mistake, because even if you TRY to create another instance of a Singleton, you'll get the another pointer to the object you already created, without creating the object again. That's the whole idea of the Singleton pattern.
However, that's just the beginning of the story. The MS Example has been streamlined a bit, and the Singleton is used ONLY to deal with Simconnect, which is fine for that sample, that is very short.
The Singleton, instead (at least in the F/A-18 code), was mainly used to handle all the "Simulation" in a SINGLE place, trying to obey to those basic design rules:
1) Put everything that deals with "poking FSX" and interfacing with it, in a SINGLE PLACE.
2) Don't read FSX variables in the Gauges. The Gauges should read data ONLY from the Singleton.
This way, the complexity is kept all in a single place. The actual Gauge code only does what is supposed to do: draw items depending on data variables that are read, retrieved or in any other way calculated, somehwere else. In the Singleton, obviously.
The Gauge code shouldn't remotely involved in, let's say, calculate the ETA of arrival at a waypoint and the estimated fuel on arrival. This is something that should be done in a place were some kind of "simulation" is running, and it's the Singleton as well. The Gauge that will *display* the result of that calculation, will just take that value, and display it, as dumbly as possible.
The Gauge shouldn't also be concerned if a variable is coming from FSX Simvars or it's an user created var, or it's something that comes from the GPS.DLL, or it's coming from the 3D model interaction ( an L: var used to create custom animations ).
From the Gauge point of view, they are ALL variables, which can be usually read and, sometimes, can be also written, depending on what WE decide, using access properties and member functions in the Singleton.
The Singleton pattern it's just the "C++ way" to create global variables. We all know that globals are "evil" but, as far as a gauge or any FSX program, Flight Sim variables ARE globals, from the point of view of our program.
Since it's very easy they'll eventually work together with our own created variables, and are inherently globals in nature, our own variables might end up being use like globals as well so, by using the Singleton pattern, we obtain the following:
1) We have a common place were variables that are read from "outside" our program ( the FSX Simvars, obviously ) AND variable that we create, can be together at disposal of the Gauges to be used.
2) Even if the Variables inside the Singleton object are Globals ( of course, assuming the *pointer* to the Singleton object has been declared as a global variable ), we can use what C++ provides in terms of data access protection.
Why a Global variable is usually considered "evil" ? Most of the time, because you don't know who can write to it. Most of the bugs happens when a global variable is used in two places for different uses. BUT, since our Globals are not *real* globals, but the only thing that is really global is the pointer to the Singleton object, using the C++ access function, we can easily specify what can be read/write and what can't.
By declaring a variable "private" in the Singleton class, and providing only a member function to retrieve its value, but no member function to write it, we forced the "global" variable to behave like a read-only property so, we get the convenicence of a value available from everywhere, but we can't screw up with it, because it's read-only...
But, as I've said, the Singleton is not really the "meat" of that example. It would be probably overkill JUST to open Simconnect but, I guess that sample was purposely made that way, because the program is very short.
You really start to appreciate the advantage of using C++ patterns, when the program becomes BIG, like a complex airplane with hundreds/thousands of different variables.
That sample is just a modifed version of a "skeleton" program that makes sense for a large project like a whole airplane.
I've always tried to stay clear of using C++ class structures in gauges as they just don't fit the very C-styled paradigm of the macro driven gauge system. (But that's not to say you couldn't and it might be more elegant if done well.)
Well...it's exactly the opposite: the "very C-styled" paradigm of the Gauge system, it's a way to simulate object orientation without having it, because it was written in C a long time ago, around FS6. It has evolved during the years, but the basic foundation is still there.
But, if you think about it, the static parts of the Gauge that are compiled (bitmaps, icons, etc), are instantiated during run time, just AS IF they were "templates" for the actual gauge. It's OOP without an OOP language so, it SCREAMS to be wrapped in C++.
What really makes that sample Object oriented, however, it's not the use of the Singleton, but more the fact that the Gauge callback are declared as a C++ Template, and the *actual* Gauge is a class derived from that, and both the actual Callback AND the Callback states, are implemented as Virtual functions so, you have a DoCallbak, an OnDraw, an OnGenerate, etc...
In fact, the original C++ wrapper does MUCH more that what is found in that sample so, it included all sort of helper functions to ease GDI+ usage, without having to rewrite the wheel each time so, all the gauges derived from that class would automatically get GDI+ support capabilities and ready to use canvases, automatic background bitmaps handling in case of GDI+ on *top* of a bitmapped background (which is commonly tought to be not possible) to create GDI+ analog instruments with GDI+ needles, etc...
because I create the simconnect interface in the pre-initialise callback, service it in the pre-update and close it in the quit callbacks and put an "if" around the functions that check the state of a global variable that's set up when the SimConnect interface is still opened.
mmmhh, the very usage of such an "if" command, makes for a potentially messy code, and of course we all know that globals are "evil" (without proper treatment, as I've said before).
A better way to open Simonnect, assuming you don't want to use the Singleton and you don't want to use C++, would be to put SimConnect_Open inside your module_init() function, and SimConnect_Close inside the module_deinit() function.
This way, you don't have to use any "if", you don't have to use any global variable in order to check you don't try to open Simconnect twice, and you don't need to worry about having Simconnect not being open at start, because the user has saved a flight in outside view (or any other view that doesn't have that Gauge in the panel ) so, the sub-Gauge you have SimConnect_Open in will never be called until the user goes back to a view that contains that sub-Gauge, possibly creating unexpected results.
Instead, by opening Simconnect in the module_init() and closing it in module_deinit(), you can be 100% sure that this function will be called ONLY ONCE, when loading the AIRPLANE, regardless if the actual sub-gauge is in view or not, because module_init is always called as long the selected airplane has a panel.cfg that contains *any* reference to the main Gauge, even if it's on a panel that is not visible, or the airplane is loaded from an outside view. The same is true for module_deinit(): it's called ONLY when you unload that airplane, either because you selected another one, or because you just quit FSX.
That's why those are the safest places to initialize *everything*, which includes of course your own variables. Which, of course, can be done in the Singleton. And, what better place would be, other than the Singleton's constructor ?
That's why the ::Instance(), which is the "fake" Singleton constructor (we already said, the *real* one can't be accessed, because this is a Singleton), it's called in the module_init() function. So, you can have a very easy to read module_init(), that basically just contains the Singleton instantiation, and you do all the initialization stuff (including Simconnect, but not only...) INSIDE the Singleton so, you get foolproof check agains double initializations, guaranteed that your initalizations WILL be called only once, guarantee that you de-initialization will be called only once, you get everything you need...
See, when you start reasoning in C++ terms, everything will fall in place...