Search code examples
arrayscparameter-passingheader-files

A library with variable in config.h file


I'm writing a library in which the user has to declare his own variables in config.h file.

This library has three files: the main file which is lib.c, the .h file, lib.h and the config file: config.h. As all libraries, user must only change the config.h file. He has to declare an array of numbers he needs. The problem is that we can't initialize the array in .h files.

A solution I was thinking of, is that the user passes his array and sizeof his array to all of my lib.c functions as arguments, but I want to know what is the standard? What do professional programmers do in other libraries? (edit: The lib.c includes the lib.h and lib.c includes the config.h file.)

Note: the array that the user needs, is constant. He has to define it just once and pass it to all the functions. This is why I wanted to initialize the array in the config.h file (So that it is declared just once). (No need to mention that the sizeof array is constant too.)

The config.h file:

#define BTN0    {BTN0_GPIO_Port, BTN0_Pin} //user defines these
#define BTN1    {BTN1_GPIO_Port, BTN1_Pin}

typedef struct {
    GPIO_TypeDef* port;
    uint16_t pin;
} btn;

const btn btns[] = {BTN0, BTN1};

#define BUTTON_ARR_SIZE     sizeof(btns)/sizeof(btns[0])
#define BUTTON_NOT_FOUND    BUTTON_ARR_SIZE //returns btn[n] which is not available. I use this line of definition in my library, and user needs it in his code.

in lib.c file:

bool checkPressAndReleaseUsingMask(btn button){ //private function. User doesn't call it.
    if (HAL_GPIO_ReadPin(button.port, button.pin) == GPIO_PIN_RESET) {
        while (HAL_GPIO_ReadPin(button.port, button.pin) == GPIO_PIN_RESET)
            osDelay(20);
        return 1;
    }
    return 0;
}

uint32_t find1Btn(){
    uint32_t whichBtn = BUTTON_NOT_FOUND;
    for (uint8_t i = 0; i < BUTTON_ARR_SIZE; i++){
        if(checkPressAndReleaseUsingMask(btns[i])){
            whichBtn = i;
            break;
        }
    }
    return whichBtn;
}
//bla bla bla

the lib.h:

#include "config.h"
#include <stdint.h>
uint32_t find1Btn(void);

User code:

        int x = find1Btn();
        if(x != BUTTON_NOT_FOUND){ //No button is pressed
            //do sth.
        }

Solution

  • but I want to know what is the standard?

    There is no standard.

    config.h is typically generated in autotools-ish projects.

    What do professional programmers do in other libraries?

    Every possible way. There are IDEs that mange configuration. There are build systems and most probably each and every solution has its own different interface to manage project configuration Linux kernel has built it's whole big kconfig. CMake has command line options, but also even cmake-gui. Mentioned GNU autotools has ./configure --enable-this --disable-that and it generates config.h file. Many simple libraries just have a user customizable header, just like you proposed.

    Note that using a central single header file will make it (almost) impossible to use the same library multiple times with different configuration within the same project. If going with a static header name, I would only recommend choosing a different name then such a common config.h. I like to use decentralized location, like library-user-config.h or something like that - that configuration header is specific to that library and other libraries do not need it. Or maybe use a centralized location - it's all project specific.

    Because of GPIO_TypeDef, you are most probably programming on some stm32. You may want to interest how other embedded software deals with project configuration. Browse github for many CMake and other libraries and build systems. Research MBed with its big mbed_config.h file. See Zephyr that I like and discover Kconfig with CMake and west.

    He has to define it just once

    In your header file you did const btn btns[] which means it will be defined in each and every file that #include <config.h>. To define it once, do extern const btn btns[2]; and do const btn btns[] = .... in like config.c. To keep the definition in a header, allow multiple definitions - add static like static const btn btns[] = ....

    The btn structure definitions seems pretty not-user-changable. I believe structure definition belongs to lib.h, as I doubt it the user will change it. To avoid name clashes, use a "namespace"/prefix symbols with common string - like btn_find1 or BTN_BUTTON_ARR_SIZE. And the macro BUTTON_NOT_FOUND seems pretty pointless to me and not part of user configuration - it is a constant, it could be just UINT32_MAX or -1. uint8_t i = 0; - I would say prefer size_t to express size and index in an array. And int x = find1Btn(); - note that find1Btn returns an uint32_t not int, the conversion may change value. Also, the array btns could be sorted (possible you could use some code generator tool, like mentioned build systems (like CMake), or Python (Jinja2) or m4 or other) and use bsearch to speed the code.