Jump to content

[GUIDE] Putting the fun in Function pointers: Emulating Objects in C

Last year I was having some fun with emulating Objects inside ANSI-C, and I thought just now it'd be a fun guide. All code in this guide can be compiled to a console executable on windows.
 

Why?
For our first college projects we were limited to writing our code in C, no C++ allowed. But it's still useful to have some sort of Object-like functionality.

Obviously C++ is a lot better at this, but if for some reason you find yourself limited to C, or you just wanna try it out, this is pretty fun!

 

In our case we had a microcontroller that had two UARTs being used; we had initially written two drivers; one for each UART, with the only difference really being which port they controlled.
This means that every time we wanted to maintain/change the way UART worked, we had to change the same thing twice. Not very practical.

/* UART Pseudocode */void UART0_sendByte ( byte x ){    UART_SEND_0 = x;}void UART1_sendByte ( byte x ){    UART_SEND_1 = x;}

You'll notice the code is practically the same, only the number of the UART port to control has changed. Let's see how we changed this.
 

Function Pointers in C
Just as you can access data by a pointer, you can also execute a function by its pointer. This is the key to emulating objects in C, so let's take a look at how to use them.

Of course this also works with functions that can accept or return data; simply change the function pointer declaration to whatever function you'll be pointing it to.

 

 

Alright great, how do we use this to get object-like stuff?

In C++ the UART problem could easily be solved by creating one class for UART, and then constructing an object from that class and remember in that object which UART to use. That way, there would only be one function for each action for all UART ports. 

In C, we can achieve something similar for our goals: we can use a struct to hold the data for each "object", and we can then put function pointers inside that struct.

Instead of having UART0_sendByte, we get UART0 -> sendByte, and instead of UART1_sendByte, we get UART1 -> sendByte.

 

Back to some example code: let's say we want to handle a user system; we don't want to rewrite a function for every user.

Instead, we can write a struct with function pointers to handle all the users we can imagine. This example is fairly simple, but it'll show our intentions.

 

 

Conclusion

There's a couple things that stand out: first off, you're passing the pointer to the "object" to a function belonging to it. This is because C is not object oriented, so you have to do it explicitly. 

Another thing that stands out is that because C does not have access control, you could just as well do users_printInfo(firstUser); however I do think this looks clearer and neater, as it explicitly shows the function belongs to an "object"

 

There's a lot more you can do with this, but I leave that up to you. I hope this guide was informative, and if you have any questions be sure to ask!

 

 

Bonus

Here's the code we ended up using for our UART ports on the uC; Each UART "object" has it's own receive buffer and Last known connection state, but shares the functionality to send/receive bytes for example.

Another upside for this is since the ATMEGA256 (Our microcontroller) has 4 UARTs in total, this code can be used without any changes for the other two ports, as all we'd have to do in the main routine to just create two extra "objects".

 

/* functionPointer.c */#include <stdio.h>#include <stdlib.h>/* Function Declaration */void printHello( void );int main( void ){    /* Declare Function Pointer */    void ( * testPointer ) ( void );    /* Set pointer to point to printHello's address */    testPointer = printHello;    /* Using testPointer and printHello is now the same */    testPointer();    /* We're done */    return 0;}/* Print Hello World */void printHello ( void ){    printf( "'sup, world?" );}
/* moreFunctionPointer.c */#include <stdio.h>#include <stdlib.h>/* Function Declaration */int doubleValue ( int iNumber );int main( void ){    /* Declare Function Pointer */    int ( * testPointer ) ( int );    /* Declare int to store doubleValue result */    int iDoublerResult = 0;    /* Set pointer to point to doubleValue's address */    testPointer = doubleValue;    /* iDoublerResult becomes 8 */    iDoublerResult = testPointer( 4 );    /* Outputs "Result is: 8" */    printf( "Result is: %d", iDoublerResult );    /* We're done */    return 0;}/* Return double the passed value */int doubleValue ( int iNumber ){    return iNumber * 2;}
/* userObjectTest.c */#include <stdio.h>#include <stdlib.h>#include "Users.h"int main( void ){    /* Create as many users as you want from just 1 struct */    Users firstUser  = newUser( "Joey van Hummel", 22 );    Users secondUser = newUser( "Pietje Puk", 101 );    /* Null Pointer checks */    if ( firstUser == NULL || secondUser == NULL )    {        printf( "b0ss error pls" );        return 0;    }    /* Print each user's info. Note that we need to pass the "object" explicitly */    firstUser ->  information ( firstUser  );    secondUser -> information ( secondUser );    /* We're done */    return 0;} 
/* users.c */#include <stdio.h>#include <stdlib.h>#include "Users.h"/* Declaration for what mimics a Constructor in C++ */Users newUser( char * newUserName, int newUserAge ){    /* Make space for the new Object */    Users self = ( Users ) malloc( sizeof( struct usersStruct ) );    /* Check returned pointer */    if ( self != NULL )    {        /* Attach Function pointers */        self -> information = users_printInfo;        /* Save User's Data */        self -> userName = newUserName;        self -> userAge  =  newUserAge;    }    /* Return Pointer to object */    return self;}/* Universal Function to print user's info */void users_printInfo( const void* self ){    printf( "User %s is %d years old!\n", ( ( Users )self ) -> userName, ( ( Users )self ) -> userAge );} 
/* users.h */#ifndef USERS_H_INCLUDED#define USERS_H_INCLUDED/* Struct for users */typedef struct usersStruct{    /* Function Pointer */    void ( *information ) ( const void* self );    /* Store the user's data in these variables */    char * userName;    int     userAge;} *Users;/* Declaration for what mimics a Constructor in C++ */Users newUser( char * newUserName, int newUserAge );/* Universal Function to print user's info */void users_printInfo( const void* self );#endif 
/********************************************************************************** RoombAddon - UART.c v1.2						   ******** Author: Joey van Hummel						   **********************************************************************************//* AVR Includes */#include <stdlib.h>#include <stdbool.h>#include <avr/io.h>#include <util/delay.h>/* Project Includes */#include "../Headers/UART.h"UART newUart( volatile uint8_t* UCSRXA ){	/* Make space for the new Object */	UART self = ( UART ) malloc( sizeof( struct UART_Struct ) );	/* Check returned pointer and hang on error */	if ( self == NULL )	{			while ( 1 )		{			/* Empty Statement */		}	}	/* Attach Function pointers */	self -> init		= &initUart;	self -> send		= &sendByte;	self -> read		= &readByte;	self -> timedRead   = &readByteTime;	/* Save pointers to AVR registers */	self -> UCSRXA = UCSRXA;	self -> UCSRXB = UCSRXA + 1;	self -> UCSRXC = UCSRXA + 2;	self -> UBRRX  = ( volatile uint16_t * ) ( UCSRXA + 4 );	self -> UDRX   = UCSRXA + 6;	self -> receivedByte   = 0x00;	self -> lastKnownState = NOT_CONNECTED;	/* Return Pointer to Object */	return self;}void initUart( const void* self,  uint32_t baudRate ){	/* Set U2X bit to enable Double Speed/Precision */	*( ( UART )self ) -> UCSRXA |= ( 1 << 1 );		/* Enable RX Interrupt, TX and RX circuitry*/	*( ( UART )self ) -> UCSRXB = ( 1 << 7 ) | ( 1 << 4 ) | ( 1 << 3 );		/* 8 Databits, no parity, 1 stopbit */	*( ( UART )self ) -> UCSRXC = ( 1 << 3 ) | ( 1 << 2 ) | ( 1 << 1 );		/* Set Baud rate */	*( ( UART )self ) -> UBRRX = ( F_CPU / ( 8UL * baudRate ) ) - 1;		/* Wait for just a bit so the circuitry can settle */	_delay_ms( 5 );}void sendByte( const void* self, uint8_t byteToSend ){	/* Wait till sending is possible */	while ( !( *( ( UART )self ) -> UCSRXA & ( 1 << 5 ) ) )	{			/* Empty Statement */	}		/* Send byte */	*( ( UART )self ) -> UDRX = byteToSend;}bool readByte( const void* self ){	/* Check if a byte is available */	if ( ( *( ( UART )self ) -> UCSRXA & ( 1 << 7 ) ) )	{		/* Read byte */		( ( UART )self ) -> receivedByte = *( ( UART )self ) -> UDRX;				return BYTE_READ;	}	return BYTE_NOT_READ;}bool readByteTime ( const void* self, uint16_t msTimeOut ){	uint32_t usTimeOut = msTimeOut * 1000;		while ( usTimeOut > 0 )	{		/* Check if a byte is available */		if ( ( *( ( UART )self ) -> UCSRXA & ( 1 << 7 ) ) )		{			/* Read byte */			( ( UART )self ) -> receivedByte = *( ( UART )self ) -> UDRX;						return BYTE_READ;		}				_delay_us( 1 );		usTimeOut--;	}		return BYTE_NOT_READ;}/* UART.C */ 
/********************************************************************************** RoombAddon - UART.h v1.0						   ******** Author: Joey van Hummel						   **********************************************************************************/#ifndef UART_H_#define UART_H_/* Defines */#define BYTE_NOT_READ		false#define BYTE_READ		true#define NOT_CONNECTED		false#define IS_CONNECTED		true/* Struct for each Object */typedef struct UART_Struct{	/* Function Pointers */	void    ( *init	     )	( const void* self, uint32_t baudRate   );	void    ( *send	     )	( const void* self, uint8_t  byteToSend );	bool	( *read      )	( const void* self			);	bool	( *timedRead )  ( const void* self, uint16_t msTimeOut  );	/* AVR Data Register Pointers */	volatile uint16_t *UBRRX;	volatile  uint8_t *UCSRXA, *UCSRXB, *UCSRXC, *UDRX;	volatile  uint8_t receivedByte;		volatile  bool	  lastKnownState;} *UART;/* Function Protos */UART newUart( volatile uint8_t* UCSRXA );/**********************************************************************************								           ******** Initializes a new UART Object					   ********									   ******** Input:								   ********	Pointers to this UART's AVR data registers			   ******** Precondition:							   ********	None								   ******** Output:								   ********	Pointer to a new Object						   ******** Postcondition:							   ********	Object now resides in memory					   ********									   **********************************************************************************/void initUart( const void* self,  uint32_t baudRate );/**********************************************************************************									   ******** object -> init() - Initializes the UART AVR Registers; sets Baud Rate ********									   ******** Input:								   ********	*self: Reference to the Object, since C is not inherently OO	   ********	baudRate: Set this UART's baud rate as desired			   ******** Precondition:							   ********	Object exists							   ******** Output:								   ********	None								   ******** Postcondition:							   ********	UART can now work and/or has Baud rate set			   ********									   **********************************************************************************/void sendByte( const void* self,  uint8_t  byteToSend );/**********************************************************************************									   ******** object -> send() - Sends a byte					   ********									   ******** Input:								   ********	*self: Reference to the Object, since C is not inherently OO	   ********	byteToSend: The data to send					   ******** Precondition:							   ********	Object exists, Uart is initialized				   ******** Output:								   ********	None								   ******** Postcondition:							   ********	None								   ********									   **********************************************************************************/bool readByte( const void* self );/**********************************************************************************									   ******** object -> read() - Reads a byte					   ********									   ******** Input:								   ********	*self: Reference to the Object, since C is not inherently OO	   ******** Precondition:							   ********	Object exists, Uart is initialized				   ******** Output:								   ********	BYTE_READ, BYTE_NOT_READ.					   ******** Postcondition:							   ********	Byte exists in object's receivedByte				   ********								           **********************************************************************************/bool readByteTime( const void* self, uint16_t msTimeOut );/**********************************************************************************								           ******** object -> timedRead() - waits for and Reads a byte			   ********									   ******** Input:								   ********	*self: Reference to the Object, since C is not inherently OO	   ******** Precondition:							   ********	Object exists, Uart is initialized				   ******** Output:								   ********	BYTE_READ, BYTE_NOT_READ.					   ******** Postcondition:							   ********	Byte exists in object's receivedByte				   ********									   **********************************************************************************/#endif/* UART_H_ */ 
Link to comment
Share on other sites

Link to post
Share on other sites

Looks like a nice guide and I know I should learn C as well as C++, but haven't got around to it yet.

 

@alpenwasser I thought you'd like to see this :)

I've had a lot of fun with C and C++ so far. Once you have the time and you still feel like it, go for it  :D

Link to comment
Share on other sites

Link to post
Share on other sites

My inital thought was to create a new operator, but that's essentially creating the building blocks toward turning C into C++. Your method is a much better in-between without all the hassle needed by just creating a new "new" operator. Neat idea.

Link to comment
Share on other sites

Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×