Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C configuration API

Tags:

c

When designing a C API for configuring a library/utility, I have a co-worker who prefers to lump all configuration parameters into 1 function call. For example:

int set_diagnostic_email_config( char *to_address,
                                 bool include_timestamp,
                                 bool send_for_crashes,
                                 bool send_daily_status,
                                 bool send_on_event1,
                                 bool send_on_event2 )

A similar "get" function exists with many parameters.. The main advantage of this method is that if someone adds a new option, e.g., "bool send_on_event3", then because the prototype changed you are forced to update every place this function call is used (assuming there are multiple places where people call this).

I prefer something along the lines of:

int get_diagnostic_email_config( struct email_config *p_config );
int set_diagnostic_email_config( struct email_config *p_config );

where you just change the structure elements as needed. But... if someone updates the email_config" structure it doesn't force people to update all the places where it is used (even though we often want to..). Plus my co-worker complains that if someone just tries to initialize "email_config" by hand, then if things get added later then those new fields will be uninitialized with no warnings.

Is there any strong consensus on which method is preferred? or perhaps there is another alternative I'm missing?

like image 467
Will Avatar asked Jul 03 '09 05:07

Will


3 Answers

A struct is better than a long list. Long list is hard to maintain, as nobody remembers the exact order.

You can create a constructor that fills this struct with safe (default and/or invalid) entries. Always (probably in the accessor function) checking for invalid values will make it easy to spot errors in initialization.

You can hide a magic number in this struct. The first field is CONFIG_MAGIC that needs to be equal to a constant that you defined. You set this field in the constructor and expect to be set at all times. This you avoid somebody just malloc()ing the struct and initalizing it by hand. Such programmer would need to learn about this CONFIG_MAGIC constant and is more than likely to find and use the proper constructor.

like image 151
Tadeusz A. Kadłubowski Avatar answered Oct 20 '22 17:10

Tadeusz A. Kadłubowski


I would use the following way which is an extension to your way.

struct INFO
{
    char *to_address;
    bool include_timestamp;
    bool send_for_crashes;
    bool send_daily_status;
    bool send_on_event1;
    bool send_on_event2;
};

struct INFO *createINFO()
{
    // initialize to defaults.

    // return a pointer to the new created struct.
}

void include_timestamp(struct INFO *info, bool vInclude_timestamp)
{
    // set the field.
}

// add the required setters...

void destroyINFO(struct INFO *info)
{
    // destroy the struct.
}

This way, you can add 'setters' on demand, when ever you add a new field. Without allowing the user to mess with the struct itself.

like image 27
Khaled Alshaya Avatar answered Oct 20 '22 17:10

Khaled Alshaya


Long parameter lists are not readable, in particular if it is a list of bools. Everytime you come to a function call that looks like this:


    set_diagnostic_email_config("[email protected]", 0, 1, 0, 1, 0, 1, 0, 0, 0);

you have to look into the documentation to see what this digit pattern is good for. And if you are using the function, you want to use in most cases with some sane defaults, ie. you end up in copying this line from some where else.

If you have only boolean values, I'd use flags, which you can combine with ORing. Here is a possible example:


   typedef enum {
       FLAG_DEFAULT = 0,
       FLAG_INCLUDE_TIMESTAMP = 0x1,
       FLAG_SEND_FOR_CRASHES = 0x2,
       FLAG_SEND_DAILY_STATUS = 0x4,
       FLAG_SEND_ON_EVENT1 = 0x8
    } Email_Flags;

    int set_diagnostic_email_config(const char *address, unsigned int flags);

Now you can call the function like this:

    set_diagnostic_email_config("[email protected]", FLAG_SEND_DAILY_STATUS | FLAG_SEND_ON_EVENT1);

This code is easy to read, you don't need to know every option to understand it. And this code is easy to write, because the order of the "paramters" (actually the order of the flags) is not important. And this function is easy to extend, you can simply add more flags, let's say FLAG_SEND_ON_EVENT2 and you don't need to change any function call, as long as you want to change its behavior.

like image 3
quinmars Avatar answered Oct 20 '22 18:10

quinmars