Vous êtes sur la page 1sur 31

Modular Programming Techniques

eMN Technologies, www.emntech.com

In this document, let's understand some modular programming designs. We will take a case study to
understand the modular programming techniques.

Case Study

Let's assume that We want to implement a media player, Say mplayer. mplayer should be able to
play different types of media files like mp3, mp4, mpeg, wma and etc. mplayer should also provide
a user interface to select or take input the media file to be played.

Design 1

First let's implement mplayer to play only mp3 files. We should write code to provide user interface
and should write code to play the mp3 files. So, the following diagram shows how the two modules
(user interface and mp3 specific code ) look like.

main.c User Interace

mp3.c Format Specific Code

Let's write code to implement this small design.

//vim main.c

#include <stdio.h>
#include "mplayer.h"

#define MAX_FILE_NAME 40

int main(void)
{
char fname[MAX_FILE_NAME];
char *type;

eMN Technologies, www.emntech.com


/*
* Wait for ever reading the file and playing it
*/
while (1) {
printf("\nEnter File name to play(quit to quit):");
scanf("%s", fname);
if (!strncmp(fname, "quit", 4))
break;

mp3_play(fname);
}
}

//vim mp3

#include <stdio.h>
#include "mplayer.h"
#include <string.h>

int mp3_play(char *filename)


{
char *type;

type = strrchr(filename, '.');


if (type) {
/*
* Can we handle this?
*/
if (strncmp(type+1, "mp3", 3)) {
printf("\nmp3: We cannot play it!!!");
return 0;
}
/*
* we can play it
*/
printf("\nPlaying..., how is it?");
} else
printf("\nmp3: We cannot play it!!!");

return 0;
}

vim mplayer.h
#ifndef __MPLAYER_H
#define __MPLAYER_H

int mp3_play(char *fname);

#endif

eMN Technologies, www.emntech.com


Above you can see 3 files, main.c, mp3.c, mplayer.h are implemented as part of mplayer project.
main.c provides user interface and takes filenames as input and calls mp3_play() to play the files. In
the mp3.c we are checking if the file is of type mp3 just by checking extension of the file and
playing it(Of course just a printf, this is dummy mplayer!!!)). The design here is very simple, take
file name as input and call mp3_play() which will play if it is mp3 file.

Now, how do I add support to play .wma files?. In the main function I need to check if we can
handle the file and call the appropriate(mp3 or wma) function to play the file. So, you can write
code like this.

//vim main.c

#include <stdio.h>
#include "mplayer.h"

#define MAX_FILE_NAME 40

int main(void)
{
char fname[MAX_FILE_NAME];
char *type;

/*
* Wait forever reading the file and playing it
*/
while (1) {
printf("\nEnter File name to play(quit to quit):");
scanf("%s", fname);
if (!strncmp(fname, "quit", 4))
break;

type = strrchr(fname, '.');


if (type) {
if (!strncmp(type+1, “mp3”, 3)) { // is it mp3
mp3_play(fname);
} else if (!strncmp(type+1, “wma”, 3)) {
wma_play(fname);
} else
printf(“\nThis file format is not supported yet”);
} else {
printf(“\nThis file format is not supported yet”);
}

}
}

eMN Technologies, www.emntech.com


// vim mp3

#include <stdio.h>
#include "mplayer.h"
#include <string.h>

int mp3_play(char *filename)


{
/*
* we can play it, no need to check it, already checked in main.c
*/
printf("\nmp3: Playing..., how is it!!!");

return 0;
}

//vim wma.c

#include <stdio.h>
#include "mplayer.h"

int wma_play(char *filename)


{

printf("\nwma: Playing..., how is it!!!")


return 0;
}

vim mplayer.h
#ifndef __MPLAYER_H
#define __MPLAYER_H

int mp3_play(char *fname);


int wma_play(char *fname);
#endif

See in the main.c, we are checking if we can play it( if we can support the format) and calling
appropriate function to play it.

What if we want to support one more format, say, mp4. And what if we want to support one more
format, say .mpeg?. With the above design we always need to add the code in the main.c, that's in
the user interface to check the file type and call the appropriate function. Can we not change the
above design to add support for more formats without touching the user interface code and
changing the minimal code?

Design 2

We can do one thing, let the format specific modules provide their functions in a structure object
and store those structure objects in an array. In the main module, when a user enters the file name to

eMN Technologies, www.emntech.com


be played, we can loop through the array to find who can play the given file and call format specific
play() function. Look at this program.

//vim main.c:

#include <stdio.h>
#include "mplayer.h"

#define MAX_FILE_NAME 40
#define MAX_FORMATS 20

extern struct format mp3_format, wma_format;

struct format *formats[MAX_FORMATS] = {&mp3_format, &wma_format, NULL};

int main(void)
{
char fname[MAX_FILE_NAME];
struct format *fp;
int i, played = 0;

/*
* Wait for ever reading the file and playing it
*/
while (1) {
printf("\nEnter File name to play(quit to quit):");
scanf("%s", fname);
if (!strncmp(fname, "quit", 4))
break;
/*
* loop through the available formats and
* find if we can play it
*/
for (i = 0; formats[i]; i++) {
fp = formats[i];
if (fp->can_play(fname)) {
fp->play(fname);
played = 1;
}
}
if (!played) // we have no one to play it
printf("\nWe cannot play it");
played = 0;
}
}

//vim mp3.c

#include <stdio.h>

eMN Technologies, www.emntech.com


#include "mplayer.h"
#include <string.h>

static int mp3_play(const char *filename)


{
printf("\nmp3: Playing..., how is it?");
return 0;
}

/*
* return 1 if we can play this file
*/
static int mp3_can_play(const char *fname)
{
char *type;

type = strrchr(fname, '.');


if (type) {
/*
* Can we handle this?
*/
if (!strncmp(type+1, "mp3", 3)) {
return 1;
}
}

return 0;
}

struct format mp3_format = {


.name = "mp3",
.can_play = mp3_can_play,
.play = mp3_play,
};

//vim wma.c:

#include <stdio.h>
#include "mplayer.h"
#include <string.h>

static int wma_play(const char *filename)


{
printf("\nwma: Playing..., how is it?");
return 0;
}

/*
* return 1 if we can play this file
*/

eMN Technologies, www.emntech.com


static int wma_can_play(const char *fname)
{
char *type;

type = strrchr(fname, '.');


if (type) {
/*
* Can we handle this?
*/
if (!strncmp(type+1, "wma", 3)) {
return 1;
}
}

return 0;
}

struct format wma_format = {


.name = "wma",
.can_play = wma_can_play,
.play = wma_play,
};

vim mplayer.h:

#ifndef _H_MPLAYER__
#define _H_MPLAYER__

struct format {
char name[40]; // name of the format
int (*can_play) (const char *fname); // can_play function pointer
int (*play) (const char *fname); // play function pointer
};

#endif

vim Makefile:

all:
gcc main.c mp3.c wma.c -o mplayer
clean:
rm -rf *.o mplayer

Carefully read the above code. Let's first start with mplayer.h. In mplayer.h we have defined a struct
format, which contains a name field which is used to store name of the format, can_play field
which is used to store format specific can_play function and finally the play field which is used to
store format specific play function. Now see the mp3.c file. At the end of mp3.c, we have defined
an object of type struct format and filling the object with format specific information. See the name
is “mp3” and the can_play function is mp3_can_play() and play function is mp3_play(). And the
object name is mp3_format. In the mp3_can_play() we are checking if it is mp3 file and returning 1

eMN Technologies, www.emntech.com


if it is, else 0. In the mp3_play() we are playing the file.

Take the wma.c file. There we have defined an object, wma_format, of type struct format and
filling the object with the wma format specific information. Name of the format is “wma”,
can_play() function is wma_can_play() and play() function is wma_can_play() function. Now
finally open the main.c. In main.c we have defined an array of pointers, formats, of type struct
format. And the array is filled with the addresses of the format specific objects, mp3_format and
wma_format. In the main() function we are providing interface to the user and taking the input from
the user. After taking the input file to be played we are looping through the formats array and
calling format specific can_play() function. If any of the format specific modules can handle this
file, can_play() returns 1. Then we are calling the format specific play() to play the file.

Now if we want to add support for another file format, add one more file <format>.c, create an
object of type struct format for this format and fill in the object with format specific information.
Then add the address of this object in the formats array. That's it we have added support for another
format. We did not touch any of the user interface code.

How modular and flexible this design is. Right?.

In this design we are filling the array statically to the formats. Can we not fill this array at run
time?. Let's change our design more modular. Look at this code.

//vim main.c:

#include <stdio.h>
#include "mplayer.h"
#include <errno.h>

#define MAX_FILE_NAME 40
#define MAX_FORMATS 20

struct format *formats[MAX_FORMATS] = {NULL};

/*
* Function to register a format
*/
int register_format(struct format *fp)
{
int i;

/*
* Get the empty slot in the formats
*/
for (i = 0; i < MAX_FORMATS; i++) {
if (formats[i])
continue;
break;
}
if (i >= MAX_FORMATS) // no free slot

eMN Technologies, www.emntech.com


return -ENOMEM;

/*
* add the fp to the formats array
*/
formats[i] = fp;
return 0;
}
/*
* Initialize the mplayer
*/
int mplayer_init(void)
{
int ret;

/*
* Initialize mp3 format module
*/
ret = mp3_init();
if (ret)
return ret;

/*
* Initialize wma format module
*/
ret = wma_init();
return ret;
}

int main(void)
{
char fname[MAX_FILE_NAME];
struct format *fp;
int i, played = 0, ret;

/*
* Initialize the mplayer
*/
ret = mplayer_init();
if (ret) {
printf("\nCannot initialize mplayer, exiting...");
return 0;
/*
* Wait for ever reading the file and playing it
*/
while (1) {
printf("\nEnter File name to play(quit to quit):");
scanf("%s", fname);
if (!strncmp(fname, "quit", 4))

eMN Technologies, www.emntech.com


break;
/*
* loop through the available formats and
* find if we can play it
*/
for (i = 0; formats[i]; i++) {
fp = formats[i];
if (fp->can_play(fname)) {
fp->play(fname);
played = 1;
}
}
if (!played) // we have no one to play it
printf("\nWe cannot play it");
played = 0;
}
}

//vim mp3.c:

#include <stdio.h>
#include "mplayer.h"
#include <string.h>

static int mp3_play(const char *filename)


{
printf("\nmp3: Playing..., how is it?");
return 0;
}

/*
* return 1 if we can play this file
*/
static int mp3_can_play(const char *fname)
{
char *type;

type = strrchr(fname, '.');


if (type) {
/*
* Can we handle this?
*/
if (!strncmp(type+1, "mp3", 3)) {
return 1;
}
}

return 0;
}

eMN Technologies, www.emntech.com


struct format mp3_format = {
.name = "mp3",
.can_play = mp3_can_play,
.play = mp3_play,
};

int mp3_init(void)
{
int ret;

ret = register_format(&mp3_format);
return ret;
}

//vim wma.c:

#include <stdio.h>
#include "mplayer.h"
#include <string.h>

static int wma_play(const char *filename)


{
printf("\nwma: Playing..., how is it?");
return 0;
}

/*
* return 1 if we can play this file
*/
static int wma_can_play(const char *fname)
{
char *type;

type = strrchr(fname, '.');


if (type) {
/*
* Can we handle this?
*/
if (!strncmp(type+1, "wma", 3)) {
return 1;
}
}

return 0;
}

struct format wma_format = {


.name = "wma",
.can_play = wma_can_play,

eMN Technologies, www.emntech.com


.play = wma_play,
};

int wma_init(void)
{
int ret;

/*
* register our format
*/
ret = register_format(&wma_format);

return ret;
}

//vim mplayer.h:

#ifndef _H_MPLAYER__
#define _H_MPLAYER__

struct format {
char name[40]; // name of the format
int (*can_play) (const char *fname); // can_play function pointer
int (*play) (const char *fname); // play function pointer
};

int mp3_init(void);
int wma_init(void);
int register_format(struct format *fp);
#endif

Again read the above code carefully. Let's start with mp3.c. In mp3.c we have added one more
function mp3_init(). In the mp3_init() we are calling register_format() function to register the
format with the mplayer. We are doing the same thing in wma.c, we are defining one more function
wma_init() which calls register_format() to register its format with the mplayer.

In main.c we are defining two more functions register_format() and mplayer_init(). In the main()
function we are calling mplayer_init() function to call mp3_init() and wma_init() functions.
register_format() is called by mp3_init() and wma_init() to register it's objects with the mplayer. In
the register_format() we are checking for free slot in the formats array and storing the format object
in the formats array.

To add support for another format create another file for the new format and implement the
operations and define a function <format>_init(). You call this <format>_init() in the mplayer_init()
function. That's it, at the runtime we have filled the formats array with the objects.

In this design we have two layers, one is for user interface and another is to implement format
specific functionality. Diagrammatically the design looks like this.

eMN Technologies, www.emntech.com


main.c User Interace

mp3.c wma.c wav.c

In the above figure there is a user interface on top of mp3 and wma modules. Format specific
modules are isolated from user interface. If there are any changes in the format specific module, you
can change it without touching the user interface.

Design 3

In the Design 2, user interface, main.c, still contains some code that is specific to format, that is, we
are getting correct format module using the formats array, actually speaking, which is specific to
formats. If there is any change in the struct format, say for example, changing the can_play() to
canplay() in future or assume you changed the structure name itself. Then you need to again touch
the user interface. Can we not have another design which makes the user interface fully isolated and
format independent?. You can add one more layer in our design which takes care of common format
specific code. Let's design this and explain it. See the following diagram.

main.c User Interface

Common Interface Layer (CIL)


cil.c

mp3.c wma.c mp4.c

In the above diagram, we have added one more layer, Common Interface Layer(CIL). The lower

eMN Technologies, www.emntech.com


modules, mp3.c, wma.c, mp4.c registers with the Common Interface Layer(CIL) (i.e give their
operations to the CIL), and the CIL provides some common functions used by the User Interface
module. User Interface module calls common functions to play a file, which intern calls format
specific functions. Let's implement the code and explain the design in more detail.

User Interface

//vim main.c

#include <stdio.h>
#include "mplayer.h"
#include <errno.h>

/*
* Initialize the mplayer
*/
int mplayer_init(void)
{
int ret;

ret = init_formats();
return ret;
}

int main(void)
{
char fname[MAX_FILE_NAME];

int played = 0, fd, ret;

/*
* Initialize the mplayer
*/
ret = mplayer_init();
if (ret) {
printf("\nCannot initialize mplayer, exiting...");
return 0;
}

/*
* Wait for ever reading the file and playing it
*/
while (1) {
printf("\nEnter File name to play(quit to quit):");
scanf("%s", fname);
if (!strncmp(fname, "quit", 4))

break;

eMN Technologies, www.emntech.com


/* can this be played? */
fd = can_play(fname);
if (fd >= 0) {
// we can, play it
play(fd, fname);
played = 1;
}
if (!played) // we have no one to play it
printf("\nWe cannot play it");
played = 0;
}
}

Common Interface Layer Module

//vim cil.c

#include <stdio.h>
#include "mplayer.h"
#include <errno.h>

#define MAX_FORMATS 20

struct format *formats[MAX_FORMATS] = {NULL};

/*
* Function to register a format
*/
int register_format(struct format *fp)
{
int i;

/*
* Get the empty slot in the formats
*/
for (i = 0; i < MAX_FORMATS; i++) {
if (formats[i])
continue;
break;
}
if (i >= MAX_FORMATS) // no free slot
return -ENOMEM;

/*
* add the fp to the formats array
*/
formats[i] = fp;
return 0;
}

eMN Technologies, www.emntech.com


/* can_play - Check if the fname can be played.
*
* Input - File name
*
* Output - format descriptor, shoud be passed to play()
*/
int can_play(const char *fname)
{
struct format *fp;
int i;

/*
* loop through the available formats and
* find if we can play it
*/
for (i = 0; formats[i]; i++) {
fp = formats[i];
if (fp->can_play(fname)) {
return i;
}
}
return -ENOENT;
}

/* play - play a file, should be called after


* can_play()
*
* Input - fd - format descriptor returned by can_play()
* fname - name of the file
*
* Output - 0 on success, else a negative errno
*/
int play(int fd, const char *fname)
{
struct format *fp;

/*
* Play it
*/
if ((fd < 0) || (fd > MAX_FORMATS))
return -EBADFD;

fp = formats[fd];
fp->play(fname);
return 0;
}

int init_formats(void)
{

eMN Technologies, www.emntech.com


int ret;

/*
* Initialize mp3 format module
*/
ret = mp3_init();
if (ret)
return ret;

/*
* Initialize wma format module
*/
ret = wma_init();
return ret;
}

Format Specific Modules

Same as Design 2.

Header File

vim mplayer.h:

#ifndef _H_MPLAYER__
#define _H_MPLAYER__
#define MAX_FILE_NAME 40

struct format {
char name[40]; // name of the format
int (*can_play) (const char *fname); // can_play function pointer
int (*play) (const char *fname); // play function pointer
};

int mp3_init(void);
int wma_init(void);
int register_format(struct format *fp);

int can_play(const char *fname);


int play(int fd, const char *fname);
#endif

Makefile

vim Makefile:

all:
gcc main.c cif.c mp3.c wma.c -o mplayer
clean:

eMN Technologies, www.emntech.com


rm -rf *.o mplayer

Again read the above code carefully. To start with main.c, in main() function we are calling
mplayer_init() function. mplayer_init() function calls init_formats() function which is defined in
cil.c. init_formats() function calls all the initialization functions of formats specific modules.
Initialization routines of format specific code, mp3_init() and wma_init() functions create objects of
type struct format and fill in the objects with the format specific info and call register_format()
function which is defined in cil.c. register_format() fills in the formats array with the given struct
format objects. Now all the formats information is with the CIL module.

main() function waits for the user inputs and calls can_play() function of CIL module(cil.c).
can_play() function loops through the formats array and calls format specific can_play() function. If
the file can be played by any of the format modules, can_play() function returns the index of the
array which is corresponding to the format that can play the file to the main() function (we call this
index as format descriptor). main() calls play() function giving the format descriptor and file name
as its arguments (format descriptor is returned by the can_play() function). play() function is
defined in CIL module (cil.c) which will call format specific play() function.

So, how is this design? We have fully isolated the user interface with the format specific modules.
Any changes in the format specific code are invisible to the User interface module. User interface
can play any file format without knowing its format, every thing is taken care by Common Interface
Layer(CIL) module. Common Interface Layer module (or say CIL) hides the under laying modules
and provides a common interface to the user. CIL finds out the correct format module that can play
a given file and calls its functions to play the file.

Okay, In this design(design 3), to add a new format specific module we just add another file for it
and implement the can_play() and play() functions and create an object of type struct format and
call register_formats() functions, for this you need to call the format specific init function from the
init_formats(). That's it, you have added support for another file format support. But, here we are
recompiling the whole project (mplayer) and rerunning the mplayer. Can I not add a new file format
support while mplayer is running?. That means I should add the new format specific code to the
running mplayer process, just like adding a plug in module without restarting the mplayer.

We can do this. Let's implement this in Design 4.

Design 4

We are discussing design 4, which will allow format specific module be loaded at run time. In this
part we will see how actually we can load a module dynamically while the programming is running.
That means, we should be able to load a new file format module dynamically while mplayer is
running.

How do we add some code to the program while it is running?. We need a way to take the object
file of a module and add the object file to the running process. It should add all the text and other
sections of the module to the address space of a running program and should resolve all the
symbols. For this we need to implement code in our mplayer, which will take the name of the

eMN Technologies, www.emntech.com


object file to be loaded and add it to the running program. To do this we need to know how an
object file is organized, an object file is of ELF format. Then, we need to take all sections of the
object file and add them to the running program. But, instead of writing code to do all this work we
use a library called libdl. libdl provides functions to load a shared object while a programming is
running. The following are some of the functions provided by the libdl.

dlopen:

This function is used to load a shared object while a program is running. Prototype of this function
is:
void *dlopen(const char *filename, int flag);

dlopen() takes as its parameters ,shared object file to be loaded and some flags. It loads the module
and returns a opaque handle which should be used in the further libdl functions used. If there is an
error while loading the module, dlopen returns NULL. To know exactly what error happened,
dlerror() function should be used. dlerror() function returns a string which says why the dlopen
failed.

If filename is NULL, it returns address to the running program only. If filename contains a slash
("/"), then it is interpreted as a (relative or absolute) pathname. Otherwise, the dynamic linker
searches for the library as follows (see ld.so(8) for further details):

o (ELF only) If the executable file for the calling program contains a DT_RPATH tag, and
does not contain a DT_RUNPATH tag, then the directories listed in
the DT_RPATH tag are searched.

o If, at the time that the program was started, the environment variable
LD_LIBRARY_PATH was defined to contain a colon-separated list of directories,
then these are searched. (As a security measure this variable is ignored for set-user-ID and
set-group-ID programs.)

o (ELF only) If the executable file for the calling program contains a DT_RUNPATH tag, then
the directories listed in that tag are searched.

o The cache file /etc/ld.so.cache (maintained by ldconfig(8)) is checked to see whether it


contains an entry for filename.

o The directories /lib and /usr/lib are searched (in that order).

See man dlopen(3) for more about dlopen and other libdl functions.

dlsym():

dlsym() is used to get a pointer to a symbol in the memory. For example, if you want to get address
of the function xyz() in the library, you can use dlsym() function. dlsym() prototype looks like this:

void *dlsym(void *handle, const char *symbol);

dlsym() takes as its parameters opaque pointer returned by the dlopen() and symbol name that you
want to refer. dlsym() returns address of the symbol.

eMN Technologies, www.emntech.com


We will use libdl and load format specific module as and when required. Build mplayer executable
with main.c, cil.c. And all other format specific modules are to be built as shared libraries. Let's do
this. First we will see the all modules implemention and then we will explain each module in detail.

//vim main.c:

#include <stdio.h>
#include "mplayer.h"
#include <errno.h>

int main(void)
{
char fname[MAX_FILE_NAME];
char module[100] = {0};
int ch;
int i, played = 0, fd, ret;

/*
* Wait for ever reading the file and playing it
*/
while (1) {
printf("\n1 Play");
printf("\n2 Load Module");
printf("\n3 Quit");
printf("\nEnter a choice:");
scanf("%d", &ch);
if (ch == 3)
return 0;

if (ch == 1) {
printf("\nEnter File name to play(quit to quit):");
scanf("%s", fname);
if (!strncmp(fname, "quit", 4))
break;

/* can this be played? */


fd = can_play(fname);
if (fd >= 0) {
// we can, play it
play(fd, fname);
played = 1;
}
if (!played) // we have no one to play it
printf("\nWe cannot play it");
played = 0;
} else {
printf("\nEnter module to Load:");
scanf("%s", module);
load_module(module);

eMN Technologies, www.emntech.com


}
}
}

//vim cil.c:

#include <stdio.h>
#include "mplayer.h"
#include <errno.h>
#include <dlfcn.h>
#include <string.h>

#define MAX_FORMATS 20

struct format *formats[MAX_FORMATS] = {NULL};

/*
* Function to register a format
*/
int register_format(struct format *fp)
{
int i;

/*
* Get the empty slot in the formats
*/
for (i = 0; i < MAX_FORMATS; i++) {
if (formats[i])
continue;
break;
}
if (i >= MAX_FORMATS) // no free slot
return -ENOMEM;

/*
* add the fp to the formats array
*/
formats[i] = fp;
return 0;
}

/* can_play - Check if the fname can be played.


*
* Input - File name
*
* Output - format descriptor, shoud be passed to play()
*/
int can_play(const char *fname)

eMN Technologies, www.emntech.com


{
struct format *fp;
int i;

/*
* loop through the available formats and
* find if we can play it
*/
for (i = 0; formats[i]; i++) {
fp = formats[i];
if (fp->can_play(fname)) {
return i;
}
}
return -ENOENT;
}

/* play - play a file, should be called after


* can_play()
*
* Input - fd - format descriptor returned by can_play()
* fname - name of the file
*
* Output - 0 on success, else a negative errno
*/
int play(int fd, const char *fname)
{
struct format *fp;

/*
* Play it
*/
if ((fd < 0) || (fd > MAX_FORMATS))
return -EBADFD;

fp = formats[fd];
fp->play(fname);
return 0;
}

/*
* Load a module on the fly
*
* Input - module - Module to be loaded
*/
int load_module(char *module)
{
char *temp, tmodule[40];
int i;
void *handle;

eMN Technologies, www.emntech.com


int (*init_func) (void);
int ret;
char *module_init;

temp = module;
/* is this absolute path? */
if (strchr(temp, '/')) {
/*
* Get the module name
*/
temp = strrchr(temp, '/');
temp++; // skip '/'
}
i = 0;
/* copy only module name(exclude .so) into the tmodule */
while ((temp[i] != '.') && (i < 40)) {
tmodule[i] = temp[i];
i++;
}
tmodule[i] = '\0';

/*
* Load the module, using dlopen
*/
printf("\nLoading module '%s'...", module);
handle = dlopen(module, RTLD_NOW);
if (!handle) {
printf("\nCannot load the module:%s", dlerror());
return -1;
}
/*
* get the pointer to <module>_init(), and call it
*/
module_init = strcat(tmodule, "_init");
init_func = dlsym(handle, module_init);
if (!init_func) {
printf("\nNo initialization function found in the module???");
dlclose(handle);
return -1;
}
printf("\nCalling '%s'...", module_init);
ret = init_func();
if (ret) {
printf("\nModule initialization Failed: %d", ret);
return ret;
}
printf("\nModule Loaded Successfully\n");
return 0;
}

eMN Technologies, www.emntech.com


Above we have two modules, user interface(main.c) and Common Interface Layer(CIL) module.
First lets start with main.c. In the main() function, we have provided user with three options, to play
a file, to load a module on the fly and to exit from the mplayer. When user enters option 1, it will
ask to enter file to be played and if user enters option 2 it will ask for module name to be loaded.
Play option is same as in the design 3. The Load Module option takes shared library(.so) of the
format specific module and calls load_module() function defined in cil.c. load_module() function
takes the module name to load and calls dlopen() function to load the shared object. Each module
should implement an initialization function which will be called when the module is loaded.
Initialization function of module should register itself with CIL module. See the mp3 module here.

vim mp3.c:

#include <stdio.h>
#include "mplayer.h"
#include <string.h>

static int mp3_play(const char *filename)


{
printf("\nmp3: Playing..., how is it?");
return 0;
}

/*
* return 1 if we can play this file
*/
static int mp3_can_play(const char *fname)
{
char *type;

type = strrchr(fname, '.');


if (type) {
/*
* Can we handle this?
*/
if (!strncmp(type+1, "mp3", 3)) {
return 1;
}
}

return 0;
}

struct format mp3_format = {


.name = "mp3",
.can_play = mp3_can_play,
.play = mp3_play,
};

int mp3_init(void)

eMN Technologies, www.emntech.com


{
int ret;

ret = register_format(&mp3_format);
return ret;
}

You see the above module. We have defined a function mp3_init(). mp3_init() calls
register_format() function to give its operation to CIL module. Once the register_format() is called
CIL has all the functions to play the mp3 files. So, every time when a module is loaded it should
call register_format() function. In particular, we need to call mp3_init() function when the mp3
module is loaded. How can we do this?. We load the module using the dlopen(). After loading the
module, if we can get reference to initialization function of the module, we can call it. As the
function is a symbol we can use dlsym to get the reference to the initialization function. But, how?
Each module's initialization function is different. How can we get the reference to that symbol? We
need to define a protocol such that modules follow some naming convention for their initialization
function. Let's say that the naming convention is: <modulename>_init(). So, initialization function
of a module should be concatenation of module name and _init. For example in the mp3 module
case, the initialization function name is mp3_init() which is a concatenation for mp3 and _init.
Take another example, mpeg module. Initialization function should be mpeg_init(). Name of the
shared object of the module should be <modulename>.so.

load_module() function takes .so file and loads it. Then it will extract module name from the
<modulename>.so and concatenates _init to it and forms a new string <modulename>_init.
Then it calls dlsym to get reference to <modulename>_init symbol which is initialization function
of that module. If the module defines the initialization function dlsym() returns address of the
function. load_module() then calls <modulename>_init() function which will call register_format()
function. With this, CIL module gets reference to new module. That's it the module is loaded
successfully and the user can use play option to play files supported by the module.

Like this user can plug in or add as many modules as they want into the running mplayer. No need
to touch the user interface module or CIL module. Just implement a new module and build a shared
object for that and use Load Module option of mplayer to load the new module. Makefile for the
above implementation looks like this.

vim Makefile:

all:
gcc main.c cil.c -o mplayer -ldl -rdynamic
gcc -c wma.c mpeg.c
gcc -shared -o wma.so wma.o
gcc -shared -o mpeg.so mpeg.o
clean:
rm -rf *.o *.so mplayer

First, we have built mplayer using main.c and cil.c. After that we have created shared objects for
each module.

eMN Technologies, www.emntech.com


Design 5

Can we not change the design 4 in such a way that user need not to use the Load Module Option
and the mplayer should automatically load the module whenever a file format of that module is
given by the user?. That means user interface prompts only for the file name to be played and if the
file can not be played by any of the modules that the CIL has, CIL should automatically find if there
is a module (shared object) in the specified directory which can play the file and load the module
and play it. Look at this code.

vim main.c:

#include <stdio.h>
#include "mplayer.h"
#include <errno.h>

int main(void)
{
char fname[MAX_FILE_NAME];
char module[100] = { 0 };
int i, played = 0, fd, ret;

/*
* Wait for ever reading the file and playing it
*/
while (1) {
printf("\nEnter File name to play(quit to quit):");
scanf("%s", fname);
if (!strncmp(fname, "quit", 4))
break;

/* can this be played? */


fd = can_play(fname);
if (fd >= 0) {
// we can, play it
play(fd, fname);
played = 1;
}
if (!played) // we have no one to play it
printf("\nWe cannot play it");
played = 0;
}
}

Here, in the main() function, we are just prompting for the file to play and calling can_play()
function of CIL to see if we can play it. main.c is same as design 3's main.c. Now see the cil.c.

eMN Technologies, www.emntech.com


vim cil.c:

#include <stdio.h>
#include "mplayer.h"
#include <errno.h>
#include <dlfcn.h>
#include <string.h>
#include <unistd.h>

#define MAX_FORMATS 20

struct format *formats[MAX_FORMATS] = {NULL};

/*
* Function to register a format
*/
int register_format(struct format *fp)
{
int i;

/*
* Get the empty slot in the formats
*/
for (i = 0; i < MAX_FORMATS; i++) {
if (formats[i])
continue;
break;
}
if (i >= MAX_FORMATS) // no free slot
return -ENOMEM;

/*
* add the fp to the formats array
*/
formats[i] = fp;
return 0;
}

/* can_play - Check if the fname can be played.


*
* Input - File name
*
* Output - format descriptor, shoud be passed to play()
*/
int can_play(const char *fname)
{
struct format *fp;
int i;

eMN Technologies, www.emntech.com


char module[100] = {MODULE_PATH}, *temp;
int ret = -ENOENT;

/*
* loop through the available formats and
* find if we can play it
*/
for (i = 0; formats[i]; i++) {
fp = formats[i];
if (fp->can_play(fname)) {
return i;
}
}
/*
* We dont find any module that can play 'fname',
* let's see if there is a module that we can load
* for this format
*/
temp = strrchr(fname, '.');
if (!temp)
return -ENOENT;

strcat(module, temp+1);
strcat(module, ".so");
if (!access(module, F_OK | X_OK))
ret = load_module(module);
if (ret)
return -ENOENT;
/*
* Module loaded successfully. loop through the available
* formats and index for this module
*/
for (i = 0; formats[i]; i++) {
fp = formats[i];
if (fp->can_play(fname)) {
return i;
}
}

return -ENOENT;
}

/* play - play a file, should be called after


* can_play()
*
* Input - fd - format descriptor returned by can_play()
* fname - name of the file
*
* Output - 0 on success, else a negative errno
*/

eMN Technologies, www.emntech.com


int play(int fd, const char *fname)
{
struct format *fp;

/*
* Play it
*/
if ((fd < 0) || (fd > MAX_FORMATS))
return -EBADFD;

fp = formats[fd];
fp->play(fname);
return 0;
}
/*
* Load a module on the fly
*
* Input - module - Module to be loaded
*/
int load_module(char *module)
{
char *temp, tmodule[40];
int i;
void *handle;
int (*init_func) (void);
int ret;
char *module_init;

temp = module;
/* is this absolute path? */
if (strchr(temp, '/')) {
/*
* Get the module name
*/
temp = strrchr(temp, '/');
temp++; // skip '/'
}

i = 0;
/* copy only module name(exlude .so) into the tmodule */
while ((temp[i] != '.') && (i < 40)) {
tmodule[i] = temp[i];
i++;
}
tmodule[i] = '\0';

/*
* Load the module, using dlopen
*/
handle = dlopen(module, RTLD_NOW);

eMN Technologies, www.emntech.com


if (!handle) {
return -1; // we cannot load it
}
/*
* get the pointer to <module>_init(), and call it
*/
module_init = strcat(tmodule, "_init");
init_func = dlsym(handle, module_init);
if (!init_func) {
printf("\nNo initialization function found in the module '%s'???",
module);
dlclose(handle);
return -1;
}
ret = init_func();
if (ret) {
printf("\n'%s' Module initialization Failed: %d", module, ret);
return ret;
}
return 0;
}

Read the above code carefully. Change is there only in the can_play() function. When the user
interface calls can_play() to find out the suitable module to play the given file, can_play() function
checks available modules if they can play the file. If any of the modules that are with CIL cannot
play the file, can_play() functions checks if shared object with the name <fileextenstion>.so exist
and it has executable permissions. If the shared object exists, can_play() calls load_module() to load
the shared object. load_module() is same as above. If load_module() can load the module
successfully it returns 0 and then can_play() again checks in the available modules if any of the
modules can play the given file. As the load_module() loaded the module for this format and called
the initialization function of the module, the module must have registered with the CIL. So,
can_play() gets index of this new module and returns it to the user interface (to main()). If the
module for this format doesn't exist or it cannot be loaded can_play() returns error -ENOENT which
says no module is there to play the given file.

Header file for the above code looks like this:

vim mplayer.h:

#ifndef _H_MPLAYER__
#define _H_MPLAYER__
#define MAX_FILE_NAME 40
#define MODULE_PATH "./" // default modules path is pwd
struct format {
char name[40]; // name of the format
int (*can_play) (const char *fname); // can_play function pointer
int (*play) (const char *fname); // play function pointer
};

eMN Technologies, www.emntech.com


int mp3_init(void);
int wma_init(void);
int register_format(struct format *fp);

int can_play(const char *fname);


int play(int fd, const char *fname);
int load_module(char *module);
#endif

That's it, seamlessly any module can be loaded dynamically without intervention of the user and
without knowing the user that a module has been loaded.

With the design 5 we are at end of Modular Programming section. This section explained how to
design an application in a more generic way and more modular. Almost all the kernel subsystems
are implemented like this only. I mean, each subsystem is implemented as layered architecture
which reuses a lot of code and provides a generic solution for any design.

eMN Technologies, www.emntech.com

Vous aimerez peut-être aussi