Subclassing KFile

In this tutorial you will learn how to subclass KFile to implement a wrapper over a library that provides FAT access. We are going to use the FAT library provided by ChaN ( http://elm-chan.org/fsw/ff/00index_e.html).

Structure definition

We need to define a struct that will store the status of our wrapper, for example:

typedef struct KFileFat
{
  KFile fd;
  FIL fat_file;
  FRESULT error_code;
} KFileFat;

Since we are "inheriting" from KFile, we need a reference to the base class, fd. It's very important to define the base class as the first member of the struct, otherwise the generic functions kfile_read() etc. won't work. The other members are a FAT file structure, used in FatFs, and the error code for calls to FatFs functions. This is needed since we have an API mismatch between FatFs functions and KFile methods, so we store the error code of the last function called inside the structure and we return it every time the user calls kfile_error().

Type extraction and cast function

Then we have to define a unique id for our new class. The macro MAKE_ID does the job: choose four unique characters and it will create a unique id for the class. Also we need to define a function that safely casts a generic KFile pointer into a KFileFat pointer. To tell the truth, this is not strictly necessary, but it's highly recommended to avoid segfaults in case something goes wrong! :) Such function just checks the type of the class and then casts the argument to the correct class. Here is the code:

#define KFT_KFILEFAT MAKE_ID('F', 'A', 'T', '0')

INLINE KFileFat * KFILEFAT_CAST(KFile *fd)
{
  ASSERT(fd->_type == KFT_KFILEFAT);
  return (KFileFat *)fd;
}

The assert will give a nice error message if you happen to use the class in the wrong place. As a side effect, this function will be optimized away when compiling without active asserts.

Initializer and interface extension methods

Finally we need to define an init() method (constructor) that will correctly initialize the function pointers inside KFile and all the other members of our structure. For a file, a reasonable constructor could be:

FRESULT kfile_fat_open(KFileFat *file, const char *file_path, BYTE mode)
{
        file->fd._type = KFT_KFILEFAT;
        file->fd.read = kfile_fat_read;
        /* ... fill in the remaining KFile function pointers ... */
        return f_open(&file->fat_file, file_path, mode);
}

In this case the return value and mode parameter are specific of the underlying library, but we could hide them inside the constructor and use our own interface. This function opens a file pointed by path with mode mode, fills KFile functions pointers and the type. Now it's time to define our specific methods.

Interface function implementation

KFile defines many interface functions, for example kfile_read(), kfile_flush(), kfile_close(). You don't need to implement them all, just set the corresponding function pointer to NULL and you'll be fine (as long as you don't try to use them...you'll get an assertion in case you try it). Have a look at KFile documentation to see the complete interface.

As you can see, all the interface prototypes take a KFile * as their first argument, which is in fact the object on which they will operate. However we need to cast it to the correct type before using it, so all the function implementations will start with a cast to the correct type, in our case to KFileFat *. Just to avoid being too abstract, here is a sample snippet:

static size_t kfile_fat_write(struct KFile *_fd, const void *buf, size_t size)
{
        KFileFat *fd = KFILEFAT_CAST(_fd);
        UINT count;
        fd->error_code = f_write(&fd->fat_file, buf, size, &count);
        return count;
}

In this case we just forward the call to the corresponding FatFs function, taking care of interface mismatch. That's all you have to do.

To see more examples of KFile subclassing, have a look at ser.c and kfile_fifo.c.