简体   繁体   中英

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?

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM