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.
}
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.