BigBobby
BigBobby

Reputation: 443

Best Way to Declare and Define Initialized Const Structs that Reference each Other

There are a number of posts about how to define structures that reference each other, but I don't see one about the variables created with these defined structures.

I have a number of menus that can be navigated in my product that are represented by two structures. This code builds the structures that I want:

typedef void (*MENU_FUNCTION_TYPE)();

typedef enum
{
  MENU_INPUT_NAV,     // Menu just navigates to other menus.
  MENU_INPUT_ACTION,  // Menu displays the results of an action.
  MENU_INPUT_NUM,     // Menu take numeric input and processes it.
  NUM_MENU_INPUTS
} MENU_INPUT_TYPE;

// A menu is comprised of a list of menu items which you select with the number keys.
// This structure defines a menu and the menu items it contains.
struct MENU_ITEM_TYPE;
struct MENU_TYPE
{
  PGM_P text;                         // Pointer to text to display as menu name.
  const MENU_ITEM_TYPE* menu_items;   // Pointer to structures describing each menu item in this menu.
  uint8_t num_items;                  // The number of menu item in this menu.
  MENU_INPUT_TYPE type;               // Specifies how this menu processes keyboard input.
};

// This structure defines a menu item and what it does when you select it.
struct MENU_ITEM_TYPE
{
  PGM_P text;                         // Pointer to text to display as menu item name.
  const MENU_TYPE* link;              // Pointer to which menu selecting this menu item takes you.
  MENU_FUNCTION_TYPE action;          // Pointer to function that is executed when this menu item is selected.
};

extern const MENU_TYPE PROGMEM main_menu;
extern const MENU_TYPE PROGMEM todo_menu;
extern const MENU_TYPE PROGMEM todo2_menu;
extern const MENU_TYPE PROGMEM todo3_menu;
extern const MENU_TYPE PROGMEM todo4_menu;   

// **************************
// "Main Menu"
const char PROGMEM main_menu_text[] = "Main Menu";
const char PROGMEM main_menu_item_text1[] = "First Menu";
const char PROGMEM main_menu_item_text2[] = "Second Menu";
const char PROGMEM main_menu_item_text3[] = "Third Menu";
const char PROGMEM main_menu_item_text4[] = "Fourth Menu";
const MENU_ITEM_TYPE PROGMEM main_menu_items[] =
{
    {main_menu_item_text1, &todo_menu, NULL},
    {main_menu_item_text2, &todo2_menu, NULL},
    {main_menu_item_text3, &todo3_menu, NULL},
    {main_menu_item_text4, &todo4_menu, NULL}
};
const MENU_TYPE PROGMEM main_menu =
{
    main_menu_text,
    main_menu_items,
    LENGTH(main_menu_items, MENU_ITEM_TYPE PROGMEM),
    MENU_INPUT_NAV
};

// **************************
// "TODO Menu"
const char PROGMEM todo_menu_text[] = "TODO Menu";
const char PROGMEM todo_menu_item_text1[] = "todo";
const MENU_ITEM_TYPE PROGMEM todo_menu_items[] =
{
    {todo_menu_item_text1, &todo2_menu, NULL}
};
const MENU_TYPE PROGMEM todo_menu =
{
    todo_menu_text,
    todo_menu_items,
    LENGTH(todo_menu_items, MENU_ITEM_TYPE PROGMEM),
    MENU_INPUT_NAV
};

// **************************
// "TODO2 Menu"
const char PROGMEM todo2_menu_text[] = "TODO2 Menu";
const char PROGMEM todo2_menu_item_text1[] = "todo2";
const MENU_ITEM_TYPE PROGMEM todo2_menu_items[] =
{
    {todo2_menu_item_text1, &todo3_menu, NULL}
};
const MENU_TYPE PROGMEM todo2_menu =
{
    todo2_menu_text,
    todo2_menu_items,
    LENGTH(todo2_menu_items, MENU_ITEM_TYPE PROGMEM),
    MENU_INPUT_NAV
};

// **************************
// "TODO3 Menu"
const char PROGMEM todo3_menu_text[] = "TODO3 Menu";
const char PROGMEM todo3_menu_item_text1[] = "todo3";
const MENU_ITEM_TYPE PROGMEM todo3_menu_items[] =
{
    {todo3_menu_item_text1, &todo4_menu, NULL}
};
const MENU_TYPE PROGMEM todo3_menu =
{
    todo3_menu_text,
    todo3_menu_items,
    LENGTH(todo3_menu_items, MENU_ITEM_TYPE PROGMEM),
    MENU_INPUT_NAV
};

// **************************
// "TODO4 Menu"
const char PROGMEM todo4_menu_text[] = "TODO4 Menu";
const MENU_TYPE PROGMEM todo4_menu =
{
    todo4_menu_text,
    NULL,
    0,
    MENU_INPUT_NUM
};

The thing that bugs me about this code is the "extern" keyword. I need it to compile, but since the structures are defined in the same file it feels there should be a better way to write the code. I definitely don't want to put all of the declarations in the header file, since none of the code that uses the terminal should ever access these structures directly. Normally I would get over being bugged and keep coding, but this source is actually going to be seen by customers.

Is there a way I can write this code without the "extern" keywords?

Upvotes: 0

Views: 976

Answers (2)

Nominal Animal
Nominal Animal

Reputation: 39326

If this is user-visible code, and you wish it to compile without errors or warnings using avr-gcc -std=c99 -Wall -Wextra and avr-g++ -std=c++11 -Wall -Wextra (plus whatever -O and -mmcu= etc. options you need), I suggest you hide the fragile parts in macros, and make the macros robust. For example, using structures that closely match the ones shown in the original question:

#include <avr/pgmspace.h>
#define JOIN2TOKENS(token1, token2)  JOIN2TOKENS_(token1, token2)
#define JOIN2TOKENS_(token1, token2) token1 ## token2

struct menu_st {
    const char                      *const title;
    unsigned char                    const items;
    const struct menuitem_st *const *const item;
};

struct menuitem_st {
    const char                      *const label;
    void                           (*const action)();
    const struct menu_st            *const submenu;
};

#define DECLARE_MENU(name, titlestr) \
    extern const struct menu_st name PROGMEM; \
    static const PROGMEM char JOIN2TOKENS(title_of__, name) [] PROGMEM = titlestr

#define DECLARE_MENUITEM(name, labelstr, funcptr, menuptr) \
    static const char JOIN2TOKENS(label_of__, name) [] PROGMEM = labelstr; \
    static const struct menuitem_st name [1] PROGMEM = { { JOIN2TOKENS(label_of__, name), funcptr, menuptr } }

#define DECLARE_SUBMENU(name, labelstr, tomenu) \
    DECLARE_MENUITEM(name, labelstr, (void (*)())0, &(tomenu))

#define DECLARE_ACTION(name, labelstr, funcname) \
    DECLARE_MENUITEM(name, labelstr, funcname, (const struct menu_st *const )0)

#define DECLARE_ACTSUB(name, labelstr, funcname, tomenu) \
    DECLARE_MENUITEM(name, labelstr, funcname, &(tomenu))

#define DEFINE_MENU(name, ...) \
    static const struct menuitem_st *const JOIN2TOKENS(menu_items_of__, name) [] PROGMEM = { __VA_ARGS__ }; \
    const struct menu_st name PROGMEM = { \
        JOIN2TOKENS(title_of__, name), \
        sizeof (JOIN2TOKENS(menu_items_of__, name)) / sizeof (JOIN2TOKENS(menu_items_of__, name)[0]), \
        JOIN2TOKENS(menu_items_of__, name), \
    }


/* 0. Declare or define menu action functions.
 *    These are declared as static, so that they are only visible
 *    in the current compilation unit.
 *
 *    As an example, action_1() and action_2() are defined here,
 *    but defaults() is only declared.
*/
static void action_1(void) { return; }
static void action_2(void) { return; }
static void defaults(void);

/* 1. Declare each menu.
 *
 *    DECLARE_MENU(name, string)
 *        'name' is the menu variable name, and
 *        'string' is the menu title, a string literal.
*/
DECLARE_MENU(main_menu, "Main menu");
DECLARE_MENU(submenu_1, "First submenu");
DECLARE_MENU(submenu_2, "Second submenu");

/* 2. Declare each menu item. Below,
 *        'name' is the local variable name (used in DEFINE_MENU()),
 *        'string' is the text for this menu item,
 *        'function' is the function called when the menu item is activated, and
 *        'menu' is the name of the menu the menu item takes to.
 *
 *    DECLARE_SUBMENU(name, string, menu)
 *        Declare a menu item, that takes to another menu.
 *
 *    DECLARE_ACTION(name, string, function)
 *        Declare a menu item, that causes a function to be called.
 *
 *    DECLARE_ABTSUB(name, string, function, menu)
 *        Declare a menu item, that causes a function to be called,
 *        and then changes to another menu.
 *
*/
DECLARE_SUBMENU(to_main_menu, "Back to main menu", main_menu);
DECLARE_SUBMENU(to_submenu_1, "To first submenu",  submenu_1);
DECLARE_SUBMENU(to_submenu_2, "To second submenu", submenu_2);
DECLARE_ACTION(do_action_1,   "Action 1", action_1);
DECLARE_ACTION(do_action_2,   "Action 2", action_2);
DECLARE_ACTSUB(do_defaults,   "Reset to defaults", defaults, main_menu);

/* 3. Define which menus have which menu items.
 *
 *    DEFINE_MENU(name, menu_item_1 [, ..., menu_item_N ])
 *        'name' is the menu variable name, and should have been
 *               declared using DECLARE_MENU(name, string) before.
 *        'menu_item_1' (and all additional parameters) are
 *               menu items declared before using
 *               DECLARE_SUBMENU(), DECLARE_ACTION(), or DECLARE_ACTSUB().
*/    
DEFINE_MENU(main_menu, to_submenu_1, to_submenu_2);
DEFINE_MENU(submenu_1, to_submenu_2, do_action_1, do_action_2, to_main_menu);
DEFINE_MENU(submenu_2, to_submenu_1, do_defaults, to_main_menu);

/* 4. If the menu action functions were only declared earlier,
 *    define them here.
*/
static void defaults(void) { return; }

The DECLARE_MENU() macro does use extern const, to forward-declare the menu structures. Only the menu names -- main_menu, submenu_1, and submenu_2, above -- are externally visible. This happens to work correctly in both C99 and C++11. If you wish the symbols to be local, you'll need different code for C99 and for C++11 (doable, via #ifdef __cplusplus - #else - #endif, but ugly, if you ask me).

Both avr-gcc-4.8.2 -std=c99 -Wall -Wextra and avr-g++-4.8.2 -std=c++11 -Wall -Wextra store all the data structures in .progmem.data section.

Note that because GCC does not support multiple address spaces (last I checked was avr-gcc-4.8.2), you'll have to use pgm_read_byte() and pgm_read_word() macros defined in <avr/pgmspace.h> to access the data structures. For example, instead of main_menu.items you'll need to use pgm_read_byte(&(main_menu.items)). Something like the following example -- but note that this part is untested, and might contain typos/thinkos, especially regarding dereferencing the pointers:

extern void print_clear_all(void);
extern void print_menu_title(const char *const pgm_string);
extern void print_menu_item(const char *const pgm_string, const unsigned char is_focus);
extern void print_clear_rest();

static const menu_t *current_menu = &main_menu;
static unsigned char current_item = 0;

void menu_refresh(void)
{
    const unsigned char n = pgm_read_byte(&(current_menu->items));
    const void *const *const itemarray = (const void *const *)pgm_read_word(&(current_menu->item));
    unsigned char i;
    print_clear_all();
    print_menu_title((const char *)pgm_read_word(&(current_menu->title)));
    for (i = 0U; i < n; i++)
        print_menu_item((const char *)pgm_read_word(itemarray + i), i == current_item);
    print_clear_rest();
}

void menu_prev(void)
{
    if (current_item > 0U)
        --current_item;
    menu_refresh();
}

void menu_next(void)
{
    if (current_item + 1U < pgm_read_byte(&(current_menu->items)))
        ++current_item;
    menu_refresh();
}

void menu_enter(void)
{
    const void *const *const itemarray = (const void *const *const)pgm_read_word(&(current_menu->item));
    const menuitem_t *const item = (const menuitem_t *const)pgm_read_word(itemarray + current_item);
    void (*const action)() = (void (*)())pgm_read_word(&(item->action));
    const menu_t *const submenu = (const menu_t *const)pgm_read_word(&(item->submenu));
    if (action)
        action();
    if (submenu)
        current_menu = submenu;
    menu_refresh();
}

It seems to me that using pointers makes the code pretty dense. A completely different approach, one that would use an array index to identify the menu, and another to identify the option, seems much more preferable to me.

Upvotes: 0

Frankie_C
Frankie_C

Reputation: 4877

In standard C a struct is not equivalent to a typedef.
This amended code compiles on a standard C compiler:

typedef void (*MENU_FUNCTION_TYPE)();

typedef enum
{
  MENU_INPUT_NAV,     // Menu just navigates to other menus.
  MENU_INPUT_ACTION,  // Menu displays the results of an action.
  MENU_INPUT_NUM,     // Menu take numeric input and processes it.
  NUM_MENU_INPUTS
} MENU_INPUT_TYPE;

// A menu is comprised of a list of menu items which you select with the number keys.
// This structure defines a menu and the menu items it contains.
typedef struct tag_MENU_ITEM_TYPE MENU_ITEM_TYPE;
typedef struct tag_MENU_TYPE
{
  PGM_P text;                         // Pointer to text to display as menu name.
  const MENU_ITEM_TYPE* menu_items;   // Pointer to structures describing each menu item in this menu.
  uint8_t num_items;                  // The number of menu item in this menu.
  MENU_INPUT_TYPE type;               // Specifies how this menu processes keyboard input.
} MENU_TYPE;

// This structure defines a menu item and what it does when you select it.
typedef struct tag_MENU_ITEM_TYPE
{
  PGM_P text;                         // Pointer to text to display as menu item name.
  const MENU_TYPE* link;              // Pointer to which menu selecting this menu item takes you.
  MENU_FUNCTION_TYPE action;          // Pointer to function that is executed when this menu item is selected.
} MENU_ITEM_TYPE;

//These now acts as forward declarations
const MENU_TYPE PROGMEM main_menu;
const MENU_TYPE PROGMEM todo_menu;
const MENU_TYPE PROGMEM todo2_menu;
const MENU_TYPE PROGMEM todo3_menu;
const MENU_TYPE PROGMEM todo4_menu;   

// **************************
// "Main Menu"
const char PROGMEM main_menu_text[] = "Main Menu";
const char PROGMEM main_menu_item_text1[] = "First Menu";
const char PROGMEM main_menu_item_text2[] = "Second Menu";
const char PROGMEM main_menu_item_text3[] = "Third Menu";
const char PROGMEM main_menu_item_text4[] = "Fourth Menu";
const MENU_ITEM_TYPE PROGMEM main_menu_items[] =
{
    {main_menu_item_text1, &todo_menu, NULL},
    {main_menu_item_text2, &todo2_menu, NULL},
    {main_menu_item_text3, &todo3_menu, NULL},
    {main_menu_item_text4, &todo4_menu, NULL}
};
const MENU_TYPE PROGMEM main_menu =
{
    main_menu_text,
    main_menu_items,
    LENGTH(main_menu_items, MENU_ITEM_TYPE PROGMEM),
    MENU_INPUT_NAV
};

// **************************
// "TODO Menu"
const char PROGMEM todo_menu_text[] = "TODO Menu";
const char PROGMEM todo_menu_item_text1[] = "todo";
const MENU_ITEM_TYPE PROGMEM todo_menu_items[] =
{
    {todo_menu_item_text1, &todo2_menu, NULL}
};
const MENU_TYPE PROGMEM todo_menu =
{
    todo_menu_text,
    todo_menu_items,
    LENGTH(todo_menu_items, MENU_ITEM_TYPE PROGMEM),
    MENU_INPUT_NAV
};

// **************************
// "TODO2 Menu"
const char PROGMEM todo2_menu_text[] = "TODO2 Menu";
const char PROGMEM todo2_menu_item_text1[] = "todo2";
const MENU_ITEM_TYPE PROGMEM todo2_menu_items[] =
{
    {todo2_menu_item_text1, &todo3_menu, NULL}
};
const MENU_TYPE PROGMEM todo2_menu =
{
    todo2_menu_text,
    todo2_menu_items,
    LENGTH(todo2_menu_items, MENU_ITEM_TYPE PROGMEM),
    MENU_INPUT_NAV
};

// **************************
// "TODO3 Menu"
const char PROGMEM todo3_menu_text[] = "TODO3 Menu";
const char PROGMEM todo3_menu_item_text1[] = "todo3";
const MENU_ITEM_TYPE PROGMEM todo3_menu_items[] =
{
    {todo3_menu_item_text1, &todo4_menu, NULL}
};
const MENU_TYPE PROGMEM todo3_menu =
{
    todo3_menu_text,
    todo3_menu_items,
    LENGTH(todo3_menu_items, MENU_ITEM_TYPE PROGMEM),
    MENU_INPUT_NAV
};

// **************************
// "TODO4 Menu"
const char PROGMEM todo4_menu_text[] = "TODO4 Menu";
const MENU_TYPE PROGMEM todo4_menu =
{
    todo4_menu_text,
    NULL,
    0,
    MENU_INPUT_NUM
};

Upvotes: 1

Related Questions