Search code examples
cstructembeddedtypedefencapsulation

How to pass typedef'd definitions between source files using encapsulation?


In the link provided- https://embeddedgurus.com/barr-code/2010/11/what-belongs-in-a-c-h-header-file/

Michael Barr states the following:

DON’T expose the internal format of any module-specific data structure passed to or returned from one or more of the module’s interface functions. That is to say there should be no “struct { … } foo;” code in any header file. If you do have a type you need to pass in and out of your module, so client modules can create instances of it, you can simply “typedef struct foo moduleb_type” in the header file. Client modules should never know, and this way cannot know, the internal format of the struct.

What I understand is that if there is a module say "led" wants to be used by a client module, say "main", main module should not know the inner workings of module "led". Here is what I did following the advice but it just seems impossible to implement it:

led.c:

#include "led.h"

typedef enum
{
    RED = 0,
    GREEN

} e_LedColor_t;

typedef enum
{
    FAST = 0,
    SLOW,
    DIRECT,
    OFF,
    HEARTBEAT,
    DOUBLE_BLINK,
    IDENTIFICATION

} e_LedMode_t;

struct Led
{
    e_LedColor_t color;
    e_LedMode_t mode;

};

led.h:

#ifndef LED_H
#define LED_H

typedef struct Led led_t;

#endif

main.c

#include "led.h"

int main() {

    led_t led;

    return 1;
}

I receive the error on line led_t led; in main saying:
error: field has incomplete type 'ledt_t'(aka 'struct led')

Just because the main module cannot recognize a definition for the Led struct, it throws an error. But if I do a definition, then the whole idea of encapsulation is lost. There must be something that I misunderstand but I do not know what it is. Can anyone help me?


Solution

  • It probably bares more explanation than Micheal has given, but I think his intent was to set out the "rules" for experienced practitioners, rather then teach about opaque types - he as assumed that level of understanding amongst his audience - perhaps you have to buy one of his books to get the full skinny ;-)

    Later in the comments, Micheal replies to a question about that "rule" and defines:

    typedef struct window* window_handle ;
                         ^
    

    note the type is a pointer typedef, not an instance type.

    The type foo in the article cannot be instantiated, you can only create a pointer, so the interface will take parameters of type foo*, rather then foo. This for example how stdio's FILE type is defined and used - you cannot create a FILE instance, only a FILE*

    You might either define:

    typedef struct Led* led_t;
    

    or keep your typedef and instantiate an led_t*:

    led_t* led;
    

    The type is opaque in both cases.

    The first hides the fact that the "handle" let_t is a pointer, which may be useful because in some cases it may not be a pointer - it could be an integer index into a resource array of finite length for example.

    The point is you can only instantiate pointers to incomplete types, not concrete instances.