Callbacks in C
I just finished writing some code that worked with a library in C. I don't use straight C every day anymore, so it really felt quaint to use it. I'm fairly language-ambivalent, but I found myself missing elements of the C++ world, yet I still hold a fondness for C.
The library I was working with defined an error handling routine thusly:
typedef int (*fnErrorHandler)(int errorCode, const char *errorMessage);
extern void slSetErrorHandler(fnErrorHandler handler);
I have to tell you - this is entirely the wrong way to go about this. Yes, I can code to this easily. Yes, the intent is clear - but the code is limiting.
The problem is that when my error handler is invoked, the only context I have is global variables, which is 10 flavors of wrong. I really want to give the set routine a cookie that is handed back to me later. This cookie is how I will establish context.
Here is a way to do that same task but with the cookie.
/* this goes in your public API header */
typedef void *t_ErrorCookie;
typedef int (*fnErrorHandler)(int errorCode, const char *message, t_ErrorCookie cookie);
typedef struct t_ErrorPair {
fnErrorHandler m_handler;
t_ErrorCookie m_cookie;
} t_ErrorPair;
extern void slSetErrorHandler(fnErrorHandler handler, t_ErrorCookie cookie);
/* this goes in your implementation */
t_ErrorPair gHandler;
void slSetErrorHandler(fnErrorHandler handler, t_ErrorCookie cookie)
{
gHandler.m_handler = handler;
gHandler.m_cookie = cookie;
}
void ReportError(int errorCode, const char *errorMessage)
{
if (gHandler.m_handler) {
(gHandler.m_handler)(errorCode, errorMessage, gHandler.m_cookie);
}
}
This code has a few issues - gHandler is a global and we should avoid that when possible, but to draw that out requires creating more infrastructure that I want. Also there is no mechanism for localization other than the error code. You also might want a fallback error handler if the default one is null. Also, you'll note that the code is ignoring the return value of the handler - in the code I was looking at the ReportError routine interpreted non-zero to mean "reported the error, no biggie" and zero to mean "didn't report it, so I'll halt all execution" (which has its own problems).
That cookie, however, is vital. It means that a client of the library can attach a hook to their user interface to the error handler or better yet, create a bridge between C and C++:
// assume C++ compiler here
static int MyErrorHandler(int errorCode, const char *message t_ErrorCookie cookie)
{
if (!cookie)
return 0;
MyMainClass *myMain = (MyMainClass *)cookie;
return myMain->ReportError(errorCode, cookie);
}
MyMainClass::SetErrorHandler()
{
// set the C error handler to bridge into C++
slSetErrorHandler(myErrorHandler, (t_ErrorCookie)this);
}