WhyRefcons

From X-Plane SDK
Jump to: navigation, search

Why do "refcons" exist? Why does nearly every XPLM API that can take a function pointer also take a pointer to a void *, add just what does a void * point to anyway? The answers to these questions are obvious to professional programmers who have used C before, but often don't make sense to users who are used to scripting languages (JavaScript, Python, etc.) with first-class functions and closures. This technote attempts to explain the concept.

What is a "refcon"

A refcon, short for "reference constant", is a pointer that you pass to the XPLM when you register a callback; when the callback runs, the XPLM gives you the pointer back.

Refcons exist to allow you to use data in a callback that doesn't come directly from global variables.

Sandy and Ben did not invent refcons; they have been present in a wide range of plugin APIs for decades.

An example

This sample plugin snippet creates a series of read-only floating point datarefs based on variables within a plugin.

/* Global variables that store nav frequencies */
float nav1 = 0;
float nav2 = 0;
float com1 = 0;
float com2 = 0;
/* Dataref read functions */
float get_nav1(void * refcon) { return nav1; }
float get_nav2(void * refcon) { return nav2; }
float get_com1(void * refcon) { return com1; }
float get_com2(void * refcon) { return com2; }

...

int XPluginStart(char * name, char * sig, char * desc)
{
 ...
 /* register datarefs */
 XPLMRegisterData("ben/nav1",xplmType_Float,0,NULL,NULL,get_nav1,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
 XPLMRegisterData("ben/nav2",xplmType_Float,0,NULL,NULL,get_nav2,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
 XPLMRegisterData("ben/com1",xplmType_Float,0,NULL,NULL,get_com1,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
 XPLMRegisterData("ben/com2",xplmType_Float,0,NULL,NULL,get_com2,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
}

For those not used to seeing this, ... often means "more code here, omitted from the example" in C sample code. In this case, we haven't shown the entire plugin, just the parts relating to our datarefs.

One thing to immediately note: we have to write four dataref accessor functions, all of which are almost exactly the same, except for the particular radio.

Refcons let us clean this up. /* Global variables that store nav frequencies */

float nav1 = 0;
float nav2 = 0;
float com1 = 0;
float com2 = 0;
/* Dataref read functions */
float get_float(void * refcon)
{
  /* convert type of refcon back to float */
  float * my_var = (float *) refcon;
  /* return thing pointed to by var. */
  return *my_var;
}

...

int XPluginStart(char * name, char * sig, char * desc)
{
 ...
 /* register datarefs */
 XPLMRegisterData("ben/nav1",xplmType_Float,0,NULL,NULL,get_float,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,&nav1,NULL);
 XPLMRegisterData("ben/nav2",xplmType_Float,0,NULL,NULL,get_float,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,&nav2,NULL);
 XPLMRegisterData("ben/com1",xplmType_Float,0,NULL,NULL,get_float,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,&com1,NULL);
 XPLMRegisterData("ben/com2",xplmType_Float,0,NULL,NULL,get_float,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,&com2,NULL);
}

In this second example, one callback runs all datarefs; the refcon 'tells' the callback which dataref is really being used.

Put another way, a refcon lets one C function service many callback registrations.

Do I Really Care?

Some of you may be going "so what"? You saved 4 lines but you still have a global and registration for every single dataref.

Well, you may not care.

  • Refcons aren't always necessary. In particular, if the thing you are modeling is truly one of a kind (only one APU in the plane) you may not need refcons because there is no more than one registration for each function.
  • The usefulness of refcons is proportional to how much code you have. Until you write a really huge plugin, the code above may not seem like a maintenance problem.

In other words, refcons aren't necessary to write a plugin, but they are necessary to keep your sanity when writing a really complex plugin.

A Grammatical Example

Another way to view refcons is in terms of grammer. Consider XPLMRegisterFlightLoopCallback. This function essentially says:

XPLM, every time the sim processes X number of frames, I want you to do Y.

Where you pass in X (the callback interval) and Y (the callback function - the "what to do").

XPLMRegisterFlightLoopCallback takes a Z - a refcon, and we can think of this as the data to act on - the direct object. In other words it really says:

XPLM, every time the sim processes X number of frames, I want you to do Y to Z.

where X is the number of frames, Y is the function, and Z is a pointer to the data to act upon.

Why Are Refcons void *

Another aspect of refcons that throws new programmers off is their declaration of void *, and their ubiquitous presence, even if you don't want them.

Ideally, the plugin SDK would have a registration API for every data type you might want back, e.g.

typedef (float *)(XPLMFlightLoopFloat_f)(float,float,int,float *refcon);
XPLMRegisterFlightLoopCallback(XPLMFlightLoopFloat_f callback, float interval, float * refcon);
typedef (float *)(XPLMFlightLoopChar_f)(float,float,int,int *refcon);
XPLMRegisterFlightLoopCallback(XPLMFlightLoopInt_f callback, float interval, int * refcon);
typedef (float *)(XPLMFlightLoopInt_f)(float,float,int,char *refcon);
XPLMRegisterFlightLoopCallback(XPLMFlightLoopChar_f callback, float interval, char * refcon);
...

The problem with this is that we might need 100 different functions to cover all of the types of data you'd want to pass as a refcon. In fact, you may want to pass a pointer to a struct that you invented - in this case there is no way the XPLM could have an accessor for your data type.

The problem here is that C is a type-safe language - all types must be declared and matched, but your plugin may use types we don't know about.

This is why refcons are void *s - in C, a void * means "pointer to who knows what" - that is, it's a pointer, but we don't know what type of data it points to. In C, a pointer's type can be changed, but the underlying data location remains the same (warning: this is not always true in C++!). This works because a pointer is really just the location in memory where a variable lives; the 'type' of the pointer is just a note to the compiler about who lives there.

So we have one API:

typedef (float *)(XPLMFlightLoopFloat_f)(float,float,int,float *void refcon);
XPLMRegisterFlightLoopCallback(XPLMFlightLoopFloat_f callback, float interval, void * refcon);

When we pass our pointer to XPLMRegisterFlightLoopCallback we cast from our type to void *, and in the callback, we cast back. Note that in most examples (including the dataref examples above) the cast is not even written. In other words,

XPLMRegisterFlightLoopCallback(my_cb, -1.0, &nav_freq1);  // typically
XPLMRegisterFlightLoopCallback(my_cb, -1.0, (void *) &nav_freq1); // explicit

The cast can be omitted because C will cast from any type to a void * for you. In the callback, the cast back to the type of nav_freq1 must be done explicitly - the compiler can't convert from void * to some other type automatically.

That's Insane

If you are used to dynamically typed languages, this situation may seem completely insane: the compiler insists that everything have a type and be carefully checked, but then the language requires us to intentionally get rid of our type safety to get anything done.

Welcome to C.

There are a few motivations behind such a typing system:

  • When 'strong typing' is used, it can catch mistakes in type, helping programmers to catch bugs.
  • Automatically checking and changing type at run time is slow - in order for Python or JavaScript or php to not care what data type you have, it has to do work every time you write a variable. In C, the data types are fully known ahead of time, so the actual produced code does the minimum. This makes C fast (and dangerous).
  • C doesn't have a great way to handle "polymorphism", the problem that is forcing us to use void *s and casts. C++ addressed this with objects, but compiler technology wasn't advanced in the 1970s when C was invented.

What About C++?

If you are used to C++, this style of programming (passing blobs of data in a void ptr that gets casted back to its original type) may seem odd, byzantine, or just old fashion. This is true - C++ polymorphic classes make refcons unnecessary.

However, the XPLM API is not a C++ API; it is a C API. One of the design requirements for the X-Plane SDK was to have it be accessible from multiple compilers/tool chains on multiple platforms. Unfortunately, C++'s "ABI" (that is, the standard by which C++ turns a class into data and code in memory) is not standardized between compilers. If the XPLM API were a C++ API of classes, you'd have to use the same compiler for your plugin as we used to build the XPLM. To make things worse, for years we used CodeWarrior, a commercial compiler, which would have meant a significant investment to amateur plugin developers.

Therefore my suggestion in trying to understand refcons is: think C, not C++. The API is a C API and refcons are a C idiom and a C solution to a C problem.