I am trying to make readable/maintanable file that contains big array of struct instances, which have function reference in it. Example of application, where this can be used, is user line command handling, where struct contains name of command and function, which implements behaviour of the command, and array contains all the commands.
I came up with some ideas, how to organise file, but all of them have some downsides, which (IMHO) causes issues with maintanability or portability.
I will use this definitions in all examples:
typedef enum command_result_e {
COMMAND_OK,
COMMAND_ERROR
} command_result;
typedef command_result (command_func)(void *);
typedef enum command_id_e {
COMMAND_TEST1,
COMMAND_TEST2,
COMMAND_TEST3,
} command_id;
struct command_s {
char * name;
command_func * func;
// char * description; /* Can be used for "help" function
further in application, but not needed for examples below */
};
typedef struct command_s command_t;
Example 1:
Do it straightforward way, as is declared array of structs:
command_result test1_handle(void *args) {printf("Test1\r\n"); return COMMAND_OK;}
static const command_t test1 = {"Test1",
test1_handle};
command_result test2_handle(void *args) {printf("Test2\r\n"); return COMMAND_OK;}
static const command_t test2 = {"Test2",
test2_handle};
command_result test3_handle(void *args) {printf("Test3\r\n"); return COMMAND_OK;}
static const command_t test3 ={"Test3",
test3_handle};
const command_t command_list[] = {
[COMMAND_TEST1] = test1,
[COMMAND_TEST2] = test2,
[COMMAND_TEST3] = test3,
};
I think solution above are not ideal because here comes duplication of #if(def) statements if you consider keep some commands only in some builds. Also, as seen further, there is no easy way to sort commands other way than by hand.
Copy-pasting command_t structs into command_list feels some-what bad, but maybe here its seeking perfection where it does not needed.
Example 2:
Do some juggling with __ attribute __ (( __ section __("")) and linker script:
const command_t __attribute__((__section__(".grouped_const.begin"))) command_list_begin[0];
command_result test1_handle(void *args) {printf("Test1\r\n"); return COMMAND_OK;}
const command_t __attribute__((__section__(".grouped_const.content"))) test1 = {sizeof("Test1"), "Test1",
test1_handle, "Test 1 command."};
command_result test2_handle(void *args) {printf("Test2\r\n"); return COMMAND_OK;}
const command_t __attribute__((__section__(".grouped_const.content"))) test2 = {sizeof("Test2"), "Test2",
test2_handle, "Test 2 command."};
command_result test3_handle(void *args) {printf("Test3\r\n"); return COMMAND_OK;}
const command_t __attribute__((__section__(".grouped_const.content"))) test3 ={sizeof("Test3"), "Test3",
test3_handle, "Test 3 command."};
const command_t __attribute__((__section__(".grouped_const.end"))) command_list_end[0];
As I understand, with ld script modification
SECTIONS {
.grouped_const {
. = ALIGN(4) // for 32-bit systems
*(.grouped_const.begin .grouped_const.content .grouped_const.end)
. = ALIGN(4)
}
}
its possible to access commands as if there is an array, and further, if needed, its possible to automaticly order commands by alphabetical order via linker. But this solution requires ld script modifications and __ attribute __, what can be unportable.
Is there other way to organise such structure of application?
Edit 1: I am seeking solution for embedded application, so .dll and .so solutions won't work.
Since not many linkers can sort things for you, keeping this list sorted by the linker is simply impossible if you want it to be portable.
To do this in a portable way using only traditional tools, you must either sort them at run-time, or make your run-time code able to cope with the list not being sorted. This is simple and would be fine for the use-case you describe of a command line interface.
Trying to make a command line interface search its list of commands faster by having the list sorted at link-time sounds like unreasonable and unnecessary over optimization.
If your really must do this portably then you will need to use a portable scripting language (eg: python) to generate your code. You would use a simple human readable format (eg: json) to pair the command names with the functions.
The script can then sort the list itself and generate a static array in portable code, or generate non-portable code for each platform. You can choose to run the script manually only when you edit the commands, or have it run every time you build the code.