WhyRefcons
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.
Contents
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.