tags:

views:

98

answers:

3

I'm trying to design a portable API in ANSI C89/ISO C90 to access a wireless networking device on a serial interface. The library will have multiple network layers, and various versions need to run on embedded devices as small as an 8-bit micro with 32K of code and 2K of data, on up to embedded devices with a megabyte or more of code and data.

In most cases, the target processor will have a single network interface and I'll want to use a single global structure with all state information for that device. I don't want to pass a pointer to that structure through the network layers.

In a few cases (e.g., device with more resources that needs to live on two networks) I will interface to multiple devices, each with their own global state, and will need to pass a pointer to that state (or an index to a state array) through the layers.

I came up with two possible solutions, but neither one is particularly pretty. Keep in mind that the full driver will potentially be 20,000 lines or more, cover multiple files, and contain hundreds of functions.

The first solution requires a macro that discards the first parameter for every function that needs to access the global state:

// network.h
   typedef struct dev_t {
      int var;
      long othervar;
      char name[20];
   } dev_t;

   #ifdef IF_MULTI
      #define foo_function( x, a, b, c)      _foo_function( x, a, b, c)
      #define bar_function( x)               _bar_function( x)
   #else
      extern dev_t DEV;
      #define IFACE (&DEV)
      #define foo_function( x, a, b, c)      _foo_function( a, b, c)
      #define bar_function( x)               _bar_function( )
   #endif

   int bar_function( dev_t *IFACE);
   int foo_function( dev_t *IFACE, int a, long b, char *c);

// network.c
       #ifndef IF_MULTI
          dev_t DEV;
       #endif
   int bar_function( dev_t *IFACE)
   {
      memset( IFACE, 0, sizeof *IFACE);

      return 0;
   }

   int foo_function( dev_t *IFACE, int a, long b, char *c)
   {
      bar_function( IFACE);
      IFACE->var = a;
      IFACE->othervar = b;
      strcpy( IFACE->name, c);

      return 0;
   }

The second solution defines macros to use in the function declarations:

// network.h
   typedef struct dev_t {
      int var;
      long othervar;
      char name[20];
   } dev_t;

   #ifdef IF_MULTI
      #define DEV_PARAM_ONLY        dev_t *IFACE
      #define DEV_PARAM             DEV_PARAM_ONLY,
   #else
      extern dev_t DEV;
      #define IFACE (&DEV)
      #define DEV_PARAM_ONLY        void
      #define DEV_PARAM
   #endif

   int bar_function( DEV_PARAM_ONLY);
   // I don't like the missing comma between DEV_PARAM and arg2...
   int foo_function( DEV_PARAM int a, long b, char *c);

// network.c
       #ifndef IF_MULTI
          dev_t DEV;
       #endif
   int bar_function( DEV_PARAM_ONLY)
   {
      memset( IFACE, 0, sizeof *IFACE);

      return 0;
   }

   int foo_function( DEV_PARAM int a, long b, char *c)
   {
      bar_function( IFACE);
      IFACE->var = a;
      IFACE->othervar = b;
      strcpy( IFACE->name, c);

      return 0;
   }

The C code to access either method remains the same:

// multi.c - example of multiple interfaces
   #define IF_MULTI
   #include "network.h"
   dev_t if0, if1;

   int main()
   {
      foo_function( &if0, -1, 3.1415926, "public");
      foo_function( &if1, 42, 3.1415926, "private");

      return 0;
   }

// single.c - example of a single interface
   #include "network.h"
   int main()
   {
      foo_function( 11, 1.0, "network");

      return 0;
   }

Is there a cleaner method that I haven't figured out? I lean toward the second since it should be easier to maintain, and it's clearer that there's some macro magic in the parameters to the function. Also, the first method requires prefixing the function names with "_" when I want to use them as function pointers.

I really do want to remove the parameter in the "single interface" case to eliminate unnecessary code to push the parameter onto the stack, and to allow the function to access the first "real" parameter in a register instead of loading it from the stack. And, if at all possible, I don't want to have to maintain two separate codebases.

Thoughts? Ideas? Examples of something similar in existing code?

(Note that using C++ isn't an option, since some of the planned targets don't have a C++ compiler available.)

+2  A: 
Matt Curtis
Thanks for the feedback -- I'm going to try some other things today, and after posting the question I've started leaning toward my first solution as well.
tomlogic
+1  A: 

This will work on gcc:

#ifdef TOMSAPI_SMALL
#define TOMSAPI_ARGS( dev, ...) (__VA_ARGS__)
#else  // ! TOMSAPI_SMALL
#define TOMSAPI_ARGS( dev, ...) (dev, ## __VA_ARGS__)
#endif // TOMSAPI_SMALL

#ifdef TOMSAPI_SMALL
#define TOMSAPI_DECLARE_DEVP(local_dev_ptr) device_t * local_dev_ptr = &global_dev; NULL
// The trailing NULL is to make the compiler make you put a ; after calling the macro,
// but without allowing something that would mess up the declaration if you forget the ;
// You can't use the do{...}while(0) trick for a variable declaration.
#else  // ! TOMSAPI_SMALL
#define TOMSAPI_DECLARE_DEVP(local_dev_ptr) device_t * local_dev_ptr = arg_dev; NULL
#endif // TOMSAPI_SMALL

and then

int tomsapi_init TOMSAPI(device_t *arg_dev, void * arg_for_illustration_purposes ) {
    TOMSAPI_DECLARE_DEVP( my_dev );
    my_dev->stuff = arg_for_illustration_purposes;
    return 0;
}

Using this method you would have to ensure that all of your API functions used the same name for the device pointer, but all of your function definitions and declarations would look like they needed the full number of arguments. If this were not important to you you could do:

#ifdef TOMSAPI_SMALL
#define TOMSAPI_ARGS(...) (__VA_ARGS__)
#else  // ! TOMSAPI_SMALL
#define TOMSAPI_ARGS(...) (device_t *dev, ## __VA_ARGS__)
#endif // TOMSAPI_SMALL

#ifdef TOMSAPI_SMALL
#define TOMSAPI_DECLARE_DEVP() device_t * dev = &global_dev; NULL 
#else  // ! TOMSAPI_SMALL
#define TOMSAPI_DECLARE_DEVP(local_dev_ptr) NULL
#endif // TOMSAPI_SMALL

and then

int tomsapi_init TOMSAPI(void * arg_for_illustration_purposes ) {
    dev->stuff = arg_for_illustration_purposes;
    return 0;
}

But this ends up looking like dev is never declared to someone reading your code.

All of that being said, you may find that on the single device small platform that using a global device struct ends up costing more than passing the pointer around due to the number of times the address of this struct will have to be reloaded. This is more likely if you API is stacked (some of your functions call other of your functions and pass them the dev pointer), uses a lot of tail recursion, and/or your platform uses registers for passing most arguments rather than the stack.

EDIT: I just realized that there could be a problem with this method if you have api functions which take no additional arguments, even if you do use the ## operator if your compiler wants to force you to say int foo(void) for functions that take no arguments.

nategoose
I like where you're going with that solution, but I know at least one of the compilers I'm targeting doesn't support variable arguments in macros.I've played around with some more methods, including a mix of my two posted methods, but haven't come up with anything satisfactory.Thanks for the input -- I may incorporate pieces of all posted solutions into my final design.
tomlogic
+1  A: 

I like your second solution. I just prefer declaring every function twice rather than have that PARAM macro in the public header. I much prefer to put macro hijinks in the hidden C file.

// common header
#ifdef IF_MULTI
    int foo_func1(dev_t* if, int a);
    int foo_func2(dev_t* if, int a, int b);
    int foo_func3(dev_t* if);
#else
    int foo_func1(int a);
    int foo_func2(int a, int b);
    int foo_func3();
#endif

// your C file
#ifdef IF_MULTI
    #define IF_PARM dev_t* if,
    #define GET_IF() (if)
#else
    dev_t global_if;
    #define IF_PARM
    #define GET_IF() (&global_if)
#endif

int foo_func1(IF_PARM int a)
{
    GET_IF()->x = a;
    return GET_IF()->status;
}
int foo_func2(IF_PARM int a, int b)
int foo_func3(IF_PARM);
jmucchiello