views:

216

answers:

9

I'm working on implementing a very, very basic component system in C, but now I am at a point where I want to 'dynamically' call some functions. The set-up is very easy: the main program is simply an endless while loop, in which some conditions are checked and in which a "process" function is called for each enabled component.

For example, now it works like this:

while (1) {
  input_process();
  network_process();
  graphics_process();
}

But I want to separate it into separate components, and somehow define in a central place which parts are used. This could be done with simple defines, like so:

#define HAS_NETWORK
...
while (1) {
  input_process();
#ifdef HAS_NETWORK
  network_process();
#endif
  graphics_process();
}

As you can see this is alright for 1 or maybe only a few components, but if I want to do this for all of these (input, network and graphics) and additional components in the future, I would have to put separate #ifdefs in there for each of them, and that's quite tedious.

In pseudo code, what I'm trying to accomplish is the following:

components = {'input', 'network', 'graphics'}
...
foreach component in components
  execute component_process()

This way components could easily be added in the future. I don't really mind if the checking is done compile time or run time (although I obviously prefer compile time, but I can imagine run time is easier to implement). I have no idea how to even start.

+3  A: 

What's wrong with the ol' if condition?

if (hasNetwork)
{
   network_process();
}
tster
Doesn't work with LOTS of different components. at least not without a huge if-then-else structure or switch... With just one component, the #if works just as well... (it's known at compile time so there's no real reason for the run-time check that may or may not be optimized away depending on how smart the optimizer is...)
Brian Postow
if(hasNetwork) network_process();if(hasSomething) something_process();Writing all those processes should take very long in contrast to inserting a single line.
tstenner
+10  A: 

You need pointers to functions, create an array of pointers to functions and index it dynamically.

Here link about function pointers.

Arkaitz Jimenez
This is the best solution IMO. It's exactly how I would handle this situation in a functional language, at least.
Matt
Yeah, in a function language, the answer is clearly a list of functions. In an OO language, the answer is clearly an array of objects with a process() method. In a structured language, it's a bit less clear I think... But then again, I have an irrational dislike of function pointers in C... (Love them in Scheme mind you, it's just the C syntax that I hate)
Brian Postow
+1  A: 

You can do this with an array of function pointers.Generally I try to avoid function pointers like the plague, but it may be your best bet.

Alternatively, you can create a component process function that takes an int argument, and then has a nasty switch statement... but for this to work, you need to keep adding to the component_process function.

Alternatively-alternatively, you could do this in C++, create a virtual Component class, that just has one method "process", with a bunch of subclasses, and you run through an array of components (actually objects of the subclasses) and call the process method.

Brian Postow
+1  A: 

your components should be an array of pointers to functions

enum components
{
    input,
    network,
    graphics,
    num_components
}

void process_audio()
{
}

void process_network()
{
}

void process_graphics()
{
}

void (*process_component[num_components])();

process_component[0] = &process_audio;
process_component[1] = &process_network
process_component[2] = &process_graphics;

for (int i = 0; i < num_components; i++)
    process_component[i]();
Nikola Smiljanić
components isn't an array.If it WERE an array, it wouldn't be an array of function pointers. This isn't portable at all because enums aren't guaranteed to be consecutive integers starting at 0.
Brian Postow
@Brian: In C and C++, enums *are* guaranteed to be consecutive integers starting with zero (unless you specify values, of course). Otherwise you're right though...
Jerry Coffin
@Jerry I thought it was compiler dependent, but it ALWAYS ends up that they are... Not guaranteed, but still never false... Then again, I haven't looked at the spec in a while so I may be mis-remembering.
Brian Postow
My mistake, I used the same name for enum and array of function pointers.
Nikola Smiljanić
+4  A: 

Compile-time solution: a pre-build step and include directive inside that loop, e.g.

while (1) {
#include "components.generated.c"
}

A basic script to generate that file might look like (Python):

components = ('input', 'networking', 'graphics')
# this could also be e.g. a glob on a directory or a config file

with open('components.generated.c', 'w') as fp:
    for component in components:
        print >>fp, '%s_process();' % component

Any decent build system will allow you to do that.

PiotrLegnica
On the one hand, that's pretty cool.On the other hand, script generated code #included into the middle? eww.I can't decide if this solution is awesome or awful. B-)
Brian Postow
Well, you could generate the entire loop as a separate function, and then just compile, link and call it from the real code.
PiotrLegnica
It is straight awesome! If you disagree, then you need to go read "The Pragmatic Programmer." Specifically the part about "writing code which writes code."
tster
The problem with writing code that writes code is, that whenever you need to make a tiny change you have to change the whole script.
tstenner
What's different from changing the script and changing any other code?
tster
Any change you do in generated code will be silently overwritten on the next make, or it will never be updated, if the script to generate the code doesn't get called/the interpreter (in this case) isn't available...
tstenner
As I said in the answer, decent build system is a must. If you're not sure whether pre-build step will execute, then your build system sucks. If you're worried about the interpreter of chosen language, then write it in C, compile, run, and then compile the rest.
PiotrLegnica
A: 

Here's an example of the syntax to do this at runtime with an array of function pointers:


void f( void ) { puts( "f" ); }
void g( void ) { puts( "g" ); }
void h( void ) { puts( "h" ); }
void (*array[])(void) = { f, h, 0 };
int main(void) {
    void (**t)(void);
    for( t = array; *t; t++ )
     (*t)();
}


William Pursell
A: 

At compile time with an X macro :

component.x is a file containing :

COMPONENT( graphic , "3D graphics rendering" )
COMPONENT( network , "Network" )
COMPONENT( other , "usefull stuff" )
#undef COMPONENT

Use it with :

#define COMPONENT( what , description ) what ## _process();
while (1)
{
#include "components.x"
}

And in another place for instance :

std::cout << "We use :\n" ;
#define COMPONENT( what , description )\
std::cout << #what << " : " << description << "\n" ;
#include "components.x"

and with this you can place the HAS_ defines in a single place in component.x :

#ifdef HAS_GRAPHIC
COMPONENT( graphic , "3D graphics rendering" )
#endif
#ifdef HAS_NETWORK
COMPONENT( network , "Network" )
#endif
#ifdef HAS_OTHER
COMPONENT( other , "usefull stuff" )
#endif
#undef COMPONENT
fa.
This looks very interesting... one would simply have to edit the component.x file and it would work out magically. Above are some comments who are against including a file in the middle of the code, but why would that be bad?
pbean
This is a bit forgotten today, but it's a very old and well known idiom,you can check this for more information : http://en.wikipedia.org/wiki/C_preprocessor#X-Macroshttp://www.ddj.com/cpp/184401387
fa.
I see! This idiom is really genious, if you ask me! I tested a bit, and it seems as though it's exactly I want. However just one more question: can I conditionally #include with this as well? For example if I have the components "input" and "network" which need special headers, can I in the same way include input.h and network.h? I reckon not, as they're both preprocessor steps.
pbean
You cannot use # in the macro definition, so you cannot #include with this.
fa.
see http://stackoverflow.com/questions/1135822/escaping-a-symbol-in-a-define-macro
fa.
A: 

Another possibility: Keep the loop as is, ie

while (1) {
  input_process();
  network_process();
  graphics_process();
}

and add the following preprocessor directives to the file header:

#ifndef HAS_NETWORK
#define network_process() ((void)0)
#endif

#ifndef HAS_GRAPHICS
#define graphics_process() ((void)0)
#endif
Christoph
For this you would need a full list of possible components beforehand, and include it into the while-loop? You can't introduce a new component at merely one place then, but have to introduce it in different places.
pbean
+3  A: 

Function pointers are great!

typedef void (*process_fun)(void);

process_fun processes[] = 
         { input_process, network_process, graphics_process };

#define NELEMS(A) (sizeof(A) / sizeof((A)[0]))

while (1) {
  for (int i = 0; i < NELEMS(processes); i++)
    processes[i]();
}

The NELEMS macro, which I learned from Dave Hanson, is also one of my favorites.


P.S. Avoid #ifdef at all costs :-)

Norman Ramsey
+1 for the macro and `#ifdef` comments
ezpz
I'm interested why one should avoid #ifed at all costs? :)
pbean
pbean
Norman Ramsey
@pbean: I couldn't resist posing the #ifdef question: http://stackoverflow.com/questions/1851181. I'm sure there will be many interesting answers. I'm too tired to write one myself :-)
Norman Ramsey