You need to know a few things about how C++ works in order to use GraphApp, or indeed any C library which uses callback functions.
Basically, when a C++ program is compiled, class method functions are given a hidden first parameter, which is defined thus:
const ClassName * this
This hidden pointer refers to the object which called the method, so it will work as expected if you do something like this:
class ABC {
int x;
void mymethod(int y) { this->x = y; }
void andagain(int y) { x = y; }
}
void main() {
ABC a;
a.mymethod(5);
a.andagain(7);
}
The above program defines a class which contains an integer, x, as a class variable. Both the functions mymethod and andagain do the same thing: they lookup the hidden 'this' pointer and use it to access the object's x variable and change it.
All this is well and good, but the whole process breaks when using C++ with C libraries. Specifically, callback functions are a problem.
A callback is a function pointer which is used to call a function. The following is legal in C:
void (*thing)(int) = NULL; /* thing is a pointer to a function */
void mycallback(int z) { printf("z=%d\n", z); }
void main(void) { thing = mycallback; thing(5); }
The above code does the expected: it prints "z=5" on the terminal. Why does it work? Because 'thing' is defined to be a pointer to a function which takes a single int as a parameter and returns void, and this is exactly what mycallback is defined to be too. The word 'mycallback' is quite literally a pointer to the code contained in mycallback, and as such can be stored into 'thing', and 'thing' becomes a callable function as a result.
We can even do this:
Source file A.c:
void (*thing)(int) = NULL; /* local to file A.c */
void set_callback(void (*new_callback)(int))
{
thing = new_callback;
}
void call_callback(int z)
{
if (thing != NULL)
thing(z);
}
Source file B.c:
#include "A.h"
void mycallback(int z) { printf("z=%d\n", z); }
void main(void)
{
set_callback(mycallback);
call_callback(5);
}
Here we have two files, one which stores and calls a function pointer, the other which uses this file as a utility to call a function in this way.
GraphApp is a C library which works in exactly that way. You can store function pointers into GraphApp objects and the library will later call those functions when appropriate. For example, you can create a function which will be called whenever a window needs to be redrawn, and the system will call that function in response to exposure events from the windowing system.
The problem comes when file A.c is compiled as a C program, and B.c is compiled using C++. If the 'mycallback' function above is replaced by the following code, problems occur:
class ABC {
int x;
void mymethod(int z) { cout << "z=" << z << "\n"; }
}
void main(void)
{
ABC a;
set_callback(a.mymethod);
call_callback(5);
}
This code will not produce the required result, in fact it will produce all kinds of compilation warnings or errors. Even though mymethod looks to be defined the same way as the earlier mycallback, it isn't the same. The hidden 'this' parameter is there, and this stuffs up the calling of the function from source file A.c. So, mymethod is really defined like this:
void mymethod(ABC *this, int z) { ... }
In practice, this makes it difficult, but not impossible, to use GraphApp with C++ methods as callbacks. There are a few ways around the problems.
GraphApp defines all callbacks used by the library to have a first parameter which is a pointer. By casting a C++ class method to the appropriate callback type, it is possible to get a callback to work. Only problem is that the 'this' pointer will be wrong, but as we shall see, there are ways to fix this situation.
In C you would do the following to provide a GraphApp redraw callback:
void drawme(window w, rect r)
{
setcolor(Red);
fillrect(r);
}
void main(void)
{
window w = newwindow("Test", rect(50,50,100,100), StandardWindow);
setredraw(w, drawme);
show(w);
}
The drawme callback is defined correctly to accept a window and a rect as parameters. Whenever GraphApp calls this function it passes those two parameters to the callback. Note that window is defined to be a pointer to a data structure. This is an important fact, because it means the first parameter to a redraw callback is a pointer.
Here is the same code in C++:
class ABC {
int x;
void drawme(rect r) { setcolor(Red); fillrect(r); }
}
void main(void)
{
ABC a;
window w = newwindow("Test", rect(50,50,100,100), StandardWindow);
setredraw(w, (drawfn) a.drawme);
show(w);
}
Note that the window parameter is missing from the drawme function. It has been replaced by a hidden 'this' parameter as far as C++ is concerned. As far as GraphApp is concerned, the first parameter to a drawfn callback is the window pointer. So here's the problem: you cannot use the 'this' pointer, either explicitly or implicitly, inside a C++ callback!
The above trivial example works precisely because we don't make use of the 'this' parameter. If we try to use 'this' the program will probably crach horribly. For example:
class ABC {
int x;
void drawme(rect r) { x=7; /* wrong wrong wrong */ }
}
Why is it wrong? Because the above code translates to:
class ABC {
int x;
void drawme(ABC *this, rect r) { this->x = 7; }
}
And as can be seen from earlier discussion, the 'this' pointer will actually be a window pointer. What can be done?
Here's a trick: store the 'this' pointer with the window, and extract it later on:
class ABC {
int x;
void drawme(rect r) {
ABC *self = (ABC *) getdata((window)this);
self->x=7; }
}
void main(void)
{
ABC a;
window w = newwindow("Test", rect(50,50,100,100), StandardWindow);
setdata(w, &a); // store 'this' pointer
setredraw(w, (drawfn) a.drawme);
show(w);
}
The above code should work. Here's how: the 'this' pointer is stored using GraphApp's setdata/getdata mechanism, which allows you to store one void pointer with a window or control, and extract it later. The 'this' pointer is dutifully used later on in the drawme callback, to determine how to access the x variable.
Some restrictions on this approach:
There is another way around the problem, and it is to write a set of C callback functions which call their C++ counterparts. This approach avoids the problems of casting and 'this' pointers, but may require global variables to achieve. Here's an example:
class ABC {
int x;
void drawme(window w, rect r) {x=7; drawrect(r);}
}
ABC a; // global variable
void drawme(window w, rect r) // callback to call class callback
{
a.drawme(w, r);
}
void main(void)
{
window w = newwindow("Test", rect(50,50,100,100), StandardWindow);
setredraw(w, drawme); // use the C function
show(w);
}
It's a bit ugly, because of the used of globals, which kind of defeats the whole idea of making software modular using classes, but it has the advantage of being less ugly than the previous solution.
There is a third solution: don't use C++. The C language has many common features with C++, and you can use structs to simulate classes. You can be increadibly productive with C. It's a classic language.
The problem descibed in this document will occur when you pass any C++ class method function as a callback to a C library. You can still use C++ code with other C++ code. You can use C++ code and call the C code from the library, no problems. Your main problem is when trying to get a C library to call a C++ function, not knowing that there's meant to be a hidden 'this' pointer passed first.
There is another potential problem with using C++ with GraphApp, and that is to do with scope. If you give a class method the same name as a GraphApp function, you will find it difficult to use that GraphApp function without prefixing it with :: An example:
class ABC {
window win1, win2;
void draw(window w) { draw(w); } // oops, recursive
}
I saw a student do this once. He didn't realise that he'd called the method the same name as a builtin GraphApp function. He was trying to call the GraphApp function draw() which takes a window as a parameter. He ended up calling the method recursively. To fix this problem you need to do this yucky thing:
class ABC {
window win1, win2;
void draw(window w) { ::draw(w); } // this calls GraphApp's draw()
}
The :: makes the scope of the draw function be the 'outside' scope, which is the C scope.
A similar, and more subtle bug is this:
class ABC {
window win1, win2;
void draw(window w) { printf("thing\n"); } // oops, clobbers GraphApp
}
The above code can stop GraphApp working in places. Depending on how the C++ compiler works, the above might generate code for a function called 'draw' which the linker will then use to clobber the existing GraphApp draw function. Suddenly, GraphApp code which calls the GraphApp draw function will now be doing printf's instead. I've seen a student clobber GraphApp using just this approach, but that was in C. In C++ the problem is less likely to occur because usually C++ compilers change the names of methods to allow overloading, by including class names and parameter types in the function name.
To avoid such naming problems, always give your functions names which either include uppercase letters, or underscores. GraphApp functions are all lowercase with no underscores, so by using a good naming scheme these problem vanish.
Have a look at the files in the GraphApp examples directory. Compare the .c files with the .cpp files there. These may enlighten you further.