2

I'm trying to implement a framework, where I would need to declare (in .h file) the list of available "drivers" (struct variables) which would be defined in specific .c modules. As the list would probably grow in the future I would like to have it all in one place in the .h file to keep it easily extensible.

E.g. let's have "driver.h"

typedef struct driver {
    int id;
    char name[10];
    int(*init)();
    void (*deinit)();
    int (*doTheJob)(int);
} driver_t;

#define DRIVERLIST driver1, driver2, driver3
#define DRIVERS extern driver_t DRIVERLIST;

DRIVERS

Then the specific drivers (driver1, driver2, driver3) would be defined in dedicated modules.. e.g. driver1.c, driver2.c .. etc...

But then I would like to have a module e.g. manager.c where I would like to define the array of available drivers as declared in driver.h so that I'm able to iterate the array and get the drivers for usage in other parts of the framework..

So in manager.c I would need something like:

driver_t drivers[MAX_DRIVERS] = {DRIVERS}

But obviously it does not compile this way.. The main idea is to edit only driver.h when I need to add declaration for additional driver in the future and then just implement it in dedicated module, whithout the necessity to edit e.g. manager.c or other parts of the framework.. Do you have any idea, how to implement such mechanism in c?

pedroke
  • 31
  • 6
  • `driver_t drivers[MAX_DRIVERS] = {DRIVERSLIST};` – ReAl Oct 16 '18 at 12:50
  • @ReAl It does not compile that way neither :( – pedroke Oct 16 '18 at 12:53
  • 1
    Please add compiler error message. Where is `MAX_DRIVERS` dfeined? – ReAl Oct 16 '18 at 12:55
  • @ReAl .. error: initializer element is not constant, the MAX_DRIVERS is defined in driver.h as #define MAX_DRIVERS 10, but it works fine, the problem is with the actual array definition in manager.c.. Thanks. – pedroke Oct 16 '18 at 12:57
  • Oh, yes, `not constant` ! Sorry, it is C, not C++. Wait a minute. – ReAl Oct 16 '18 at 13:05
  • I think the main issue here is that you mix up the driver type ADT with the list of available drivers. These should be kept separate! Use OO - it doesn't make sense that a parent class knows which children it has in advance. Nor does it make sense that any class know how what allocated objects of that class there will be. Except some of these might have to be implemented as "singleton", but that's another story. – Lundin Oct 17 '18 at 06:29
  • @Lundin I see your point with OO principles.. I also like better OO languages, but in C, one is quite limited and I'm not sure that implementing solutions which ressemble OO approach in C is always a good idea. Because talking about classes, constructors and singletons in C feels to me quite inappropriate. – pedroke Oct 17 '18 at 07:26
  • @pedroke The only limit that the C language has here, is the ability to call the constructor/destructor automatically. Everything else can be achieved without any fuss, by someone with enough experience of program design in C. For example, look at [this example](https://stackoverflow.com/a/29121847/584518) about private encapsulation of one single driver. It's essentially the same thing as I posted as answer below. – Lundin Oct 17 '18 at 09:56
  • @Lundin Thank you.This is quite interesting topic.I have never thought about c this way.I'm working with c only very rarely,so this is quite new for me.Thanks! – pedroke Oct 17 '18 at 17:22
  • @pedroke Unfortunately most learning materials fail to mention program design in C. There's just the weird little book "OO Programming with ANSI-C", which we should stay away from. So the only chance to learn these things is to find some veteran C programmer to cling on to, pretty much. – Lundin Oct 18 '18 at 07:04

3 Answers3

1

In C you can't initialize an array with copies of some objects (in C++ can but it is not good practice because they are copies and will be changed independently with original objects).

drivers array should contain pointers to original objects. I suggest something like

/* driver.h */
typedef struct driver {
    int id;
    char name[10];
    int(*init)();
    void (*deinit)();
    int (*doTheJob)(int);
} driver_t;

#define MAX_DRIVERS 10

#define DRIVERLIST driver1, driver2, driver3
#define DRIVERS_INIT {&driver1, &driver2, &driver3}

#define DRIVERS extern driver_t DRIVERLIST;

DRIVERS
/* manager.c */
#include "driver.h"

/* ... */

driver_t * drivers[MAX_DRIVERS] = DRIVERS_INIT;

Manager code will use drivers[i]->id instead of drivers[i].id.

ReAl
  • 1,231
  • 1
  • 8
  • 19
  • Thanks, that compiles fine.. It is tempting to use this solution, but it would be really nice to be able to declare driver names in one place.. In your suggestion, when I need additional driver I need to add it both in DRIVERLIST and DRIVERS_INIT.. it is not a big deal, but.. Anyway many thanks! – pedroke Oct 16 '18 at 13:20
1

The proper way to do this in C is to immediately get rid of all extern-spaghetti with globals.

Instead you could put your struct definition inside driver.h and in driver.c initialize it through a "constructor":

// driver.c
#include "driver.h"
#include "specific_driver_x.h"

void driver_init (driver_t* driver)
{
  driver->init = specific_driver_init;
  driver->doTheJob = specific_driver_job;
}

For professional code, this can be further improved with the concept of "opaque type" as explained here, to achieve private encapsulation (and if needed polymorphism). In which case the struct definition can be (partially) hidden in driver.c and the constructor also handles memory allocation.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • Thanks.But.. how would the constructor look like when there are e.g 10 or more drivers?Each driver module would need to have unique names of functions? And adding the new driver would be quite cumbersome, wouldn't it? I see you point..I also don't like the strange macros and declarations, but in this case it would be quite nice to have possibility to simply add new driver by adding a string on single place and its implementation in uniform and dedicated module IMHO.. – pedroke Oct 16 '18 at 19:00
  • @pedroke You could either implement some sort of polymorphism, or you could simply leave out details to the specific drivers. This assuming that "driver_t" is some manner of interface/abstract base class - otherwise your design doesn't make any sense. Allocation of the objects is a separate issue. – Lundin Oct 17 '18 at 06:23
1

I think I found a solution. I took the inspiration from the rtl_433 project https://github.com/merbanan/rtl_433/blob/master/include/rtl_433_devices.h where they defined something similar for the devices declarations.

So it should be in header file:

/* driver.h */
#define DRIVERS \
    DECL(driver1) \
    DECL(driver2) 


#define DECL(name) extern driver_t name;
    DRIVERS
#undef DECL

And then in module:

/* driver.c */

driver_t* drivers[] = {  
#define DECL(name) &name,
    DRIVERS
#undef DECL
};
pedroke
  • 31
  • 6
  • This is known as "x macros" and is really the last resort when everything else fails. Combining them with spaghetti globals is not a good idea. – Lundin Oct 17 '18 at 06:24
  • This seems to me as a last resort when it comes to possibilities that C offers in this case.. I didn't find any other way how to declare the "pluggable" modules such as drivers in my case.. When there are multiple contributors to project and anyone can implement additional driver, it is much easier to maintain it on single place and there is no need to teach new contributors where and how to extend the code on different places. Anyone can implement the dedicated module with the driver behaviour and then just add a single line in the header file.. – pedroke Oct 17 '18 at 07:19