How do I share data between C++ gauges in a shared cockpit scenario?

Based on the lack of references to "process_shared_event_out()" and "GAUGE_HEADER_FS1000" in a popular search engine and the fact that I've gotten a few questions on the subject, I'm guessing that the understanding of how to share data between two instances of the same C++ gauge in a shared cockpit scenario is quite limited.

First let me describe a possible scenario:

Let's say you're flying along in a multiplayer session in the C172 and your friend Mary (who is also in the session, even though she lives on the other side of the country) joins your aircraft.  If you click on the "SELECT" button on the clock to switch between time-of-day and stopwatch mode, Mary sees the clock change modes on her machine.  Likewise, if she clicks on the "SELECT" button, you see the clock change mode on your end.  How is this magic happening?

First let's think about how most gauges work, and then we'll look into how the clock is a special case.

Most gauges simply reflect gauge simulation state.  For example, an airspeed gauge doesn't have to do any work to stay in sync with the other user's machine; the airspeed is automatically synced by the multiplayer system.  Similarly, a gear lever automatically stays in sync because the GEAR_TOGGLE event (like most key events) are automatically sent to the other machine in the shared cockpit by the multiplayer system.

However, in some cases with default aircraft (and with many super-detailed 3rd-party aircraft), gauges maintain some of their own state data.  The clock in the C172 is an example.  The simulation engine doesn't know anything about the clock at all; hence it must not only maintain its own state data, but also send data to the other machine in the shared cockpit scenario in order to keep both machines in sync.

So how do we send this data?  Let's take a look basic premise behind the synchronization system in FSX and beyond. 

When two users' aircraft are first joined to a shared cockpit session, the state of the host's gauges is sent to the other user's machine.  This is called "serialization".  A few milliseconds later (or many, depending on network latency), the non-host receives this data, which must then be "deserialized".  Once the two machines are initially synchronized, gauge state only needs to be updated when something changes; for example, when a user on either side clicks on the "SELECT" button.  However, not all state needs to be synchronized; we can simply send an event to signal that the "SELECT" button was pressed.  Although in this situation we don't need to send any additional data, in some other situations it is both necessary and possible.

So, if we look at the new-fangled GAUGE_HEADER_FS1000 macro, we see that there are parameters that we can use to specify the serialization function, deserialization function, and event processing function.  In addition to this, we also see that we can specify the size of the buffers that will be allocated to pass data both for serialization and for events.  Lastly, the macro also requires a GUID so that each gauge can be uniquely identified on each machine.

#define GAUGE_HEADER_FS1000( \
gaugehdr_var_name, \
default_size_mm, \
gauge_name, \
element_list, \
pmouse_rect, \
pgauge_callback, \
user_data, \
usage, \
guid, \
serialize_callback, \
deserialize_callback, \
event_size_callback, \
process_event_callback) \

So, in the case of the clock, the setup and declaration of the gauge would look something like this:

#define GAUGEHDR_VAR_NAME       gaugehdr_clock

#define GAUGE_ITEM              clock

#define GAUGE_NAME              "Clock"

#define GAUGE_W                 54


char gauge_name_clock[] = GAUGE_NAME;


extern PELEMENT_HEADER list_clock;

extern MOUSERECT mouse_rect_clock[];


extern GAUGE_CALLBACK       gcb_clock;



SERIALIZE_CALLBACK          scb_clock;

DESERIALIZE_CALLBACK        dcb_clock;

EVENT_SIZE_CALLBACK         escb_clock;



BOOL  clock_control_button( PGAUGEHDR gauge_header);

BOOL  clock_select_button( PGAUGEHDR gauge_header);

BOOL  clock_toggle_temp_volts_button( PGAUGEHDR gauge_header);


// {7B293842-E2C2-4800-A190-33A7F20385AB}

GUID clock_guid = { 0x7b293842, 0xe2c2, 0x4800, { 0xa1, 0x90, 0x33, 0xa7, 0xf2, 0x3, 0x85, 0xab } };


GAUGE_HEADER_FS1000(GAUGEHDR_VAR_NAME, GAUGE_W, gauge_name_clock, &list_clock, mouse_rect_clock, gcb_clock, 0L, 0L, clock_guid, sscb_clock, scb_clock, dcb_clock, escb_clock, pecb_clock);



So, what do these callback functions do?  In the case of the clock, the process is fairly simple.  First, be aware that the clock has some of its own enums and a struct:


typedef enum {




typedef enum {




typedef enum {




typedef enum {






typedef struct {

  CLOCK_MODE        clock_mode;

  TEMP_VOLTS_MODE   temp_volts_mode;

  CLOCK_FORMAT      format;

  FLOAT64           clock_timer_start;

  FLOAT64           timer_accumulated;

  BOOL              timer_running;

  BOOL              timer_stopped;



And now let's look at the callback function definitions.  Note that the amount of data sent for serialization and deserialization is significantly greater than the amount of data (one 32-bit number) that's sent with an event.


// serialization size (return length of buffer)

void sscb_clock(PGAUGEHDR gauge_header, UINT32* nSize)


    *nSize = sizeof(CLOCK_DATA) + sizeof(FLOAT64);



// serialize

void scb_clock(PGAUGEHDR gauge_header, BYTE* pBuf)


    CLOCK_DATA* data = (CLOCK_DATA*)gauge_header->user_data;


    memcpy(pBuf, data, sizeof(CLOCK_DATA));      



// deserialize

bool dcb_clock(PGAUGEHDR gauge_header, BYTE* pBuf )


    CLOCK_DATA* data = (CLOCK_DATA*)gauge_header->user_data;

    memcpy(data, pBuf, sizeof(CLOCK_DATA));       


    return true;



// event size

void escb_clock(PGAUGEHDR gauge_header, UINT32* nSize)


    if (!nSize)


    *nSize = sizeof(CLOCK_EVENT);



// process event - return true if successful

bool pecb_clock(PGAUGEHDR gauge_header, BYTE* pBuf)


    CLOCK_EVENT event;

    memcpy(&event, pBuf, sizeof(CLOCK_EVENT));




        // to pass to the mouse callback, we cast to a PPIXPOINT.


            return clock_control_button(gauge_header) ? true : false;


        case CLOCK_EVENT_SELECT:

            return clock_select_button(gauge_header) ? true : false;



            return clock_toggle_temp_volts_button(gauge_header) ? true : false;



            return false;

            break; // should NOT hit this.               



    return false; // should also NOT hit this.



And finally, the function that sends an event from one machine to the other.


BOOL  clock_select_button_mouse( PPIXPOINT relative_point, FLAGS32 /* mouse_flags */ )


  PGAUGEHDR gauge_header = GAUGEHDR_FOR_MOUSE_CALLBACK(relative_point);




  process_shared_event_out(gauge_header, (BYTE*)&event, sizeof(CLOCK_EVENT));


  return TRUE;



So, if you have a gauge that you want to be shared in a shared cockpit scenario but doesn't work currently because the gauge maintains some of its own state, give this a try!  Bear in mind that the above example doesn't contain all of the code for the C172 clock, but it should be enough to get across the concepts of serialization and event sharing.


Next time I'll talk about data sharing in XML gauges, so stay tuned!


Comments (17)

  1. Anonymous says:

    We have had many people lament that they cannot use the shared cockpit feature in FSX multi-player with

  2. Yes, Björn, you can send any data over this system that you want.  In the example above, a gauge-specific CLOCK_EVENT is sent in one case; a CLOCK_DATA struct is sent in another case.  So yes, you can create a big struct (presumably for serialization and deserialization) and events for all of the different state changes that can occur in your gauges.  Just make sure that you aren’t sending a big struct every time an event is sent over or performance could suffer.


  3. Björn says:


    Can we send any data over this system so not only built in FS enums and structs ? Same goes for the events, can we send custom events also ?

    I’m thinking of making one big struct that contains all the custom data that is used in my gauges (that’s over a 100 variables) and then creating events for all of these. Would that work ?


  4. Björn says:

    Thx for the reply Susan, one more question if I may… You are using the new FS10 gauge header to do the serialization, deserialzation etc… but this requires a gauge (or a number of gauges, but I prefer 1 gauge) to do the sending/receiving. this also requires that the gauge has to be loaded at one point. To avoid this I have been using a timer based system. In short, I’m moving all of the intelligence that’s now incorporated inside the gauges, out of the actual gauge and into a number of functions that are fired based on a windows timer. The big advantage being that all the calculations are in one place, and that I don’t need to worry about people using FS in 2D vs 3D mode (VC). So, is there any possibility to move the ser/deser/event code out of a gauge and into the timer based system ? (I start and stop the timer in the module_init and module_deinit events)

    Thx again for the help !


  5. Geoff_D says:


    Thanks so much for the  C++ Guage sharing info.

    Looking forward to :-

    "Next time I’ll talk about data sharing in XML gauges, so stay tuned!"


  6. Björn says:

    Any news on the XML shared cockpit stuff you were going to talk about ?


  7. Benchmark Avionics says:

    Great article.  Wish we could get on an email list that notified us when you guys post such critical info.  =)


  8. Benchmark Avionics says:

    BTW, how would you define a typical button function you have prototyped but not defined?  I have never seen it done this way before, and am curious.

  9. Cessna2520 says:

    My friend and I are having a problem with the PMDG 738, we can connect but we keep getting an error message saying that our gauges are different even though both of us have the same 737-800 can this help us to do shared cockpit?

Skip to main content