7

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.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
pbean
  • 727
  • 1
  • 10
  • 20

9 Answers9

11

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

Here link about function pointers.

Arkaitz Jimenez
  • 22,500
  • 11
  • 75
  • 105
  • 2
    This is the best solution IMO. It's exactly how I would handle this situation in a functional language, at least. – Matt Dec 03 '09 at 16:33
  • 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 Dec 03 '09 at 16:44
5

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.

Cat Plus Plus
  • 125,936
  • 27
  • 200
  • 224
  • 1
    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 Dec 03 '09 at 16:04
  • Well, you could generate the entire loop as a separate function, and then just compile, link and call it from the real code. – Cat Plus Plus Dec 03 '09 at 16:06
  • 1
    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 Dec 03 '09 at 16:06
  • 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 Dec 03 '09 at 17:50
  • What's different from changing the script and changing any other code? – tster Dec 03 '09 at 18:02
  • 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 Dec 03 '09 at 18:59
  • 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. – Cat Plus Plus Dec 03 '09 at 19:22
2

What's wrong with the ol' if condition?

if (hasNetwork)
{
   network_process();
}
tster
  • 17,883
  • 5
  • 53
  • 72
  • 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 Dec 03 '09 at 16:02
  • 1
    if(hasNetwork) network_process(); if(hasSomething) something_process(); Writing all those processes should take very long in contrast to inserting a single line. – tstenner Dec 03 '09 at 18:00
2

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
  • 198,648
  • 61
  • 360
  • 533
  • 1
    I'm interested why one should avoid #ifed at all costs? :) – pbean Dec 04 '09 at 14:41
  • Also, below, Nikola Smiljanić puts elements in the array using the ampersand sign (&) which as far as I know is correct, because you want to get the address of / a pointer to the function. Is this correct, or do both work? – pbean Dec 04 '09 at 15:42
  • Regarding the ampersand: It's a rule of C that when the name or an array or a function appears in an rvalue context (that is a context when a value is expected, such as on the right hand side of an = sign), the compiler interprets the name to stand for the *address* of the array or function, even if no explicit & is included. (You could say that "the compiler automaticaly inserts the &" which is the right idea but a bit sloppy.) I believe this feature was added to C when they made the 1989 ANSI standard, but I'm not sure. It's a nice convenience. – Norman Ramsey Dec 05 '09 at 05:04
  • 1
    @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 Dec 05 '09 at 05:07
  • Sorry to say that, but you totally missed the conditional part. Even if this answer is kind of useful (and NELEMS is of course great) I'll try to force you to another look. I'd be happy to revert my [-1] soon :) – Wolf Jan 13 '21 at 09:48
  • @Wolf the conditional choice is the one OP requested: which functions to include in the `processes` array. – Norman Ramsey Jan 16 '21 at 14:33
  • Thanks for taking the time to take another look at this old answer. If I look carefully on the question and your answer, I see **no difference in terms of maintainability** between `{ input_process, network_process, graphics_process };` and `{ input_process(); network_process(); graphics_process(); }` – Wolf Jan 16 '21 at 15:21
1

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
  • 11,709
  • 17
  • 81
  • 125
1

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ć
  • 26,745
  • 6
  • 48
  • 60
  • 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 Dec 03 '09 at 15:59
  • 1
    @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 Dec 03 '09 at 16:18
  • @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 Dec 03 '09 at 16:33
  • My mistake, I used the same name for enum and array of function pointers. – Nikola Smiljanić Dec 03 '09 at 16:35
1

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.
  • 2,456
  • 16
  • 17
  • 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 Dec 04 '09 at 09:22
  • 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-Macros http://www.ddj.com/cpp/184401387 – fa. Dec 04 '09 at 10:49
  • 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 Dec 04 '09 at 11:32
  • You cannot use # in the macro definition, so you cannot #include with this. – fa. Dec 04 '09 at 12:09
  • see http://stackoverflow.com/questions/1135822/escaping-a-symbol-in-a-define-macro – fa. Dec 04 '09 at 12:09
0

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
  • 204,365
  • 48
  • 270
  • 300
0

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
  • 164,997
  • 36
  • 182
  • 240
  • 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 Dec 04 '09 at 15:37