Daniel
Daniel

Reputation: 433

Callback in C without function pointers

In some communication stack legacy code I found a non-callback way of doing callbacks in C. My question is: Why was it done this way or what are the advantages of this method against the common way of passing function pointers of callback functions?

Here an pseudo code example of what I have found:

top_interfaces.h

void sendData(int);
void receiveDataHAL(int);

Top layer component

#include "top_interfaces.h"
void sendData(int){ send(int); //call hal function }
void receiveDataHAL(int) { //use data }

hal_interfaces.h

void send(int);
void receive(int);

Hardware abstraction layer

#include "hal_interfaces.h"
#include "top_interfaces.h"
void send(int){ //do sending over bus }
void receive(int){ receiveDataHAL(int); }

The lower layer component calls the upper layer function whose declaration was included. The upper layer component implements this callback-like function. This way the lower layer components knows about the upper layer component which is in general a poor design choice. Is there any good justification of introducing this amount of dependency?

Upvotes: 0

Views: 500

Answers (1)

Clifford
Clifford

Reputation: 93466

It is wrong to think of receiveDataHAL() as a "top layer" component or even a "callback". It is simply a required interface that is provided by the implementer - it is a hardware-specific function invoked by the hardware-abstraction layer, so is clearly lower than the abstraction layer. There is nothing unusual or somehow wrong about this. The position of receiveDataHAL() in the layer hierarchy is no different whether it is invoked through a pointer or statically linked.

If you have a lower-layer library that has a user provided "required interface", you can implement that as you suggest through a function pointer call-back where the higher layer must "initialise" the library by providing the call back. This is a run-time linkage and has the advantage that only the function signature need match, the function name is arbitrary, and the implementation can change dynamically too (though that might not be an advantage if it is not actually required). It has the disadvantage of requiring run-time initialisation, and failure is a run-time issue.

The alternative is to specify the required function by name and statically link it. The higher layer must then define the required interface by both name and signature or the link will fail. A link time failure is in most cases preferable to a run-time failure which may or may not be handled, and the handling is unlikely to be particularly elegant (and is yet more code).

Often the required interface might be defined by "weak symbol" - that is a default implementation that will linked if no override is defined. For example the hardware abstraction layer in this case might have:

void __attribute__((weak)) receiveDataHAL(int) { // do nothing }

and then you can use the abstraction layer without explicitly defining receiveDataHAL() if you don't need it.

A function-pointer is a variable. Where you don't need a variable, a static link is often preferable, with the advantages of link-time error checking and static un-modifiable linkage. I would not call it a "poor design choice" because in the case receiveDataHAL() is not truly a "upper layer" component, but rather a user provided lower layer component - that is true whether you call it through a function pointer to a static link.

The technique - using weak symbol implementations - is used for example in in the ARM Cortex-M CMSIS for interrupt and exception handlers so that to support an interrupt you only need override the appropriate "well-known" function name to directly assigna a function as an interrupt handler. For example defineing teh SYSTICK handler is as simple as:

void SysTick_Handler(void)  
{
    msTicks++ ;
}

Where startup_.s in there is code for example:

    .weak   SysTick_Handler
    .type   SysTick_Handler, %function
SysTick_Handler:
    B       .

that defines the default handler - in this case an infinite-loop.

Another example - not using weak links - is the Newlib C library syscalls stubs. These are a set of user provided calls to adapt the library to the platform. An application linking to Newlib must provide the syscall functions - that does not mean that they are application layer functions.

Upvotes: 2

Related Questions