Chapter:C dynamically loaded libraries Exercises

From Juneday education
Jump to: navigation, search

Add an interface to an existing software

This entire page will evolve around a small program that lists files (and directories) in a directory. You can find the source code here: https://github.com/progund/programming-with-c/tree/master/dyn-loaded-libs/exercise/simple-lister.

Your first task is to add a typedefed function pointer to a header file (interface/file-lister.h). The funtion pointer shall represent the following function, currently located in the file main.c:

static int default_present(char *name)
{
  if (name==NULL)
    {
      return FILE_LISTER_BAD_ARG;
    }
  fprintf(stdout, " * %s\n", name);
  return FILE_LISTER_OK;
}

The name of the new type should be present_ptr.

Note: we use the keyword static to make the function usable in this file only. The function pointer you shall defined should not be made static.

Expand using link to the right to see a possible solution/answer.

interface/file-lister.h:

typedef int (*present_ptr) (char *name);

Add a variable of the new type and use it

In the file main.c you should add a variable, typically in the main function, of the new type present_ptr. Assign this variable the function default_present and replace the call to default_present with a call to the new variable, which in turn will call the function default_present anyway ;).

Expand using link to the right to see a possible solution/answer.

main.c:

#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>

#include "interface/file-lister.h"

static int default_present(char *name)
{
  if (name==NULL)
    {
      return FILE_LISTER_BAD_ARG;
    }
  fprintf(stdout, " * %s\n", name);
  return FILE_LISTER_OK;
}

int main (int argc, char **argv)
{
  int ret;
  DIR *dir;
  struct dirent *dirent;
  char *dir_name = "./";

  present_ptr present_fun = default_present;

  if (argc>1)
    {
      dir_name = argv[1];
    }

  dir = opendir (dir_name);
  
  if (dir != NULL)
    {
      while ( (dirent = readdir (dir)) )
        {
          ret = present_fun (dirent->d_name);
          if ( ret != FILE_LISTER_OK )
            {
              fprintf(stderr, "Failed writing file info. Leaving.\n");
              break;
            }
        }
      closedir (dir);
    }
  else
    {
      perror ("Couldn't open the directory");
    }

  return 0;
}

Write a new lib implementing the interface

You should now create a shared object file implementing the interface interface/file-lister.h:

  • the c file shall be named c-files.c
  • the c file should be put in a folder called plugins
  • the function <present_ptr> should be called present
  • the function should print the string passed as an argument

Expand using link to the right to see a possible solution/answer.

plugins/c-files.c:

#include <stdio.h>
#include "interface/file-lister.h"

int present(char *name)
{
  if (name==NULL)
    {
      return FILE_LISTER_BAD_ARG;
    }
  fprintf(stdout, " * %s\n", name);
  return FILE_LISTER_OK;
}

Create a shared object file (shared lib) from the new file

Expand using link to the right to see a possible solution/answer.

plugins/c-files.c:

$ cd plugins
$ gcc -c -Wall -pedantic -Werror -Wextra -fno-strict-aliasing -g -fPIC -I../ c-files.c
$ gcc c-files.o -shared -o libc-files.so
$ file libc-files.so 
libc-files.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=fa4bb185c55a8bcca0d4a7de2216e9f53e0f01d7, with debug_info, not stripped

Create a new file to load the plugin

Create a new file plugin-loader.c. In the file you should add a function present_ptr load_plugin(char *name) that:

  • takes the name of a shared object file and loads it, using dlopen
  • looks up the symbol present using dlsym.
  • closes the shared object file using dlclose
  • return the symbol

Create a header file that corresponds to the c file.

Expand using link to the right to see a possible solution/answer.

plugins-loader.c:

#include <stdio.h>
#include <dlfcn.h>
#include "interface/file-lister.h"

present_ptr load_plugin(char *name)
{
  present_ptr present_fun;
  char *error;
  
  if (name == NULL)
    {
      return NULL;
    }
  void *handle = dlopen(name, RTLD_LAZY);
  if (!handle) {
    fputs (dlerror(), stderr);
    return NULL;
  }  

  present_fun = (present_ptr) dlsym(handle, "present");
  if ((error = dlerror()) != NULL)  {
    fputs(error, stderr);
    return NULL;
  }

  return present_fun;
}

plugins-loader.h:

#ifndef PLUGIN_LOADER_H
#define PLUGIN_LOADER_H
#include "interface/file-lister.h"

present_ptr load_plugin(char *name);

#endif /* PLUGIN_LOADER_H */

Use the plugin in the main program

Use the present function in the newly created plugins directory.

  • include the header file plugin-loader.h
  • by calling load_plugin and store the returned pointer in a variable
  • use the variable to invole the function (from the plugin)

Expand using link to the right to see a possible solution/answer.

plugins-loader.c:

  //  present_ptr present_fun = default_present;                                                                                                
  present_ptr present_fun = load_plugin("plugins/libc-files.so");

Change the function in c-file.c

The function should only print the name of the file if the suffix ends with any of the following:

  • .c
  • .h
  • .C
  • .H
  • .cpp
  • .c++

If the name qualifies for printing, print the name as below: [C/C++] c-file.c. Note: that is, add "[C/C++] " before the name of the file.

If the name does NOT qualify for printing, print nothing and return FILE_LISTER_SUFFIX_UNKNOWN.

Expand using link to the right to see a possible solution/answer.

plugins/c-files.c:

#include <stdio.h>
#include <string.h>
#include "interface/file-lister.h"

#define SAME_STR(a,b) (strcmp(a,b)==0)

int present(char *name)
{
  //  int len;
  char *suffix;
  if (name==NULL)
    {
      return FILE_LISTER_BAD_ARG;
    }

  /* length of the name */
  //  len = strlen(name);
  
  /* last occurance of . in name */
  suffix = strrchr(name, '.');
  if ( ( suffix != NULL ) &&
       (
        SAME_STR(suffix, ".c")   ||
        SAME_STR(suffix, ".h")   ||
        SAME_STR(suffix, ".C")   ||
        SAME_STR(suffix, ".H")   ||
        SAME_STR(suffix, ".cpp") ||
        SAME_STR(suffix, ".c++") ) )
    {
      fprintf(stdout, "[C/C++] %s\n", name);
      return FILE_LISTER_OK;
    }
  return FILE_LISTER_SUFFIX_UNKNOWN;
}

Change the main file slightly

Your main function should first call the present function in the loaded shared object file. If this fails (if return value is not <code<FILE_LISTER_OK</code>) the code should fall back and call the function default_present.

When executing the program in your development folder you should see a file listing like this:

[C/C++] main.c
 --- main.o
 --- Makefile
[C/C++] plugin-loader.c
 --- plugin-loader.o
 --- plugins
 --- .
 --- fls
[C/C++] plugin-loader.h
 --- ..
 --- interface

Expand using link to the right to see a possible solution/answer.

main.c:

#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>

#include "interface/file-lister.h"
#include "plugin-loader.h"

static int default_present(char *name)
{
  if (name==NULL)
    {
      return FILE_LISTER_BAD_ARG;
    }
  fprintf(stdout, " --- %s\n", name);
  return FILE_LISTER_OK;
}

int main (int argc, char **argv)
{
  int ret;
  DIR *dir;
  struct dirent *dirent;
  char *dir_name = "./";

  present_ptr present_plugin_fun = load_plugin("plugins/libc-files.so");
  
  
  if (argc>1)
    {
      dir_name = argv[1];
    }

  dir = opendir (dir_name);
  
  if (dir != NULL)
    {
      while ( (dirent = readdir (dir)) )
        {
          ret = present_plugin_fun (dirent->d_name);
          if ( ret == FILE_LISTER_OK )
            {
              continue;
            }
          ret = default_present (dirent->d_name);
          if ( ret != FILE_LISTER_OK )
            {
              fprintf(stderr, "Failed writing file info. Leaving.\n");
              break;
            }
        }
      closedir (dir);
    }
  else
    {
      perror ("Couldn't open the directory");
    }

  return 0;
}

You can find the source code of our suggested solution here: https://github.com/progund/programming-with-c/tree/master/dyn-loaded-libs/exercise/dyn-lister.

Add a function to the interface

In the file interface/file-lister.h you should add a function: typedef int (*info_ptr) (); This function should be used to present some information about the plugin.

Expand using link to the right to see a possible solution/answer.

interface/file-lister.h:

#ifndef FILE_LISTER_H
#define FILE_LISTER_H

enum
  {
    FILE_LISTER_OK,
    FILE_LISTER_BAD_ARG,
    FILE_LISTER_SUFFIX_UNKNOWN

  } return_codes;

typedef int (*present_ptr) (char *name);
typedef int (*info_ptr) ();

#endif /* FILE_LISTER_H */

Create a datatype representing a plugin

The datatype should have the following fields:

  • a variable of type present_ptr
  • a variable of type info_ptr
  • a variable of type void * - this is the handle to the shared object file

Expand using link to the right to see a possible solution/answer.

plougin-loader.h:

typedef struct fl_plugin_
{
  present_ptr present_fun;
  info_ptr info_fun;
  void *handle;
} fl_plugin;

Create a datatype representing a list of plugins

The datatype should have the following fields:

  • a variable of type fl_plugin - a "list" of plugins
  • an int - the size of the plugin list

Expand using link to the right to see a possible solution/answer.

interface/file-lister.h:

typedef struct fl_plugins_
{
  fl_plugin* plugins;
  int size;
} fl_plugins;

Implement functions for the plugins

You should now add the following functions to plugin-loader.h and plugin-loader.c.

  • int plugin_add(fl_plugins *flps, char *name); - add the plugin in the shared object file name
  • fl_plugins* plugin_new(); - create a new fl_plugins struct

You can skip the following function (for now):

  • void plugin_free(fl_plugins*); - frees all memory in the plugin list

Expand using link to the right to see a possible solution/answer.

plugin-loader.h:

#ifndef PLUGIN_LOADER_H
#define PLUGIN_LOADER_H
#include "interface/file-lister.h"

typedef struct fl_plugin_
{
  present_ptr present_fun;
  info_ptr info_fun;
  void *handle;
} fl_plugin;

typedef struct fl_plugins_
{
  fl_plugin* plugins;
  int size;
} fl_plugins;

int plugin_add(fl_plugins *flps, char *name);

fl_plugins* plugin_new();
void plugin_free(fl_plugins*);


#endif /* PLUGIN_LOADER_H */

plugin-loader.c:

#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include "interface/file-lister.h"
#include "plugin-loader.h"

static int load_plugin(char *name, fl_plugin *flp)
{
  char *error;
  
  if ( (name == NULL) || (flp == NULL) )
    {
      return FILE_LISTER_BAD_ARG;
    }
  flp->handle = dlopen(name, RTLD_LAZY);
  if (!flp->handle) {
    fputs (dlerror(), stderr);
    return FILE_LISTER_BAD_ARG;
  }  

  flp->present_fun = (present_ptr) dlsym(flp->handle, "present");
  if ((error = dlerror()) != NULL)  {
    fputs(error, stderr);
    return FILE_LISTER_BAD_ARG;
  }

  flp->info_fun = (present_ptr) dlsym(flp->handle, "info");
  if ((error = dlerror()) != NULL)  {
    fputs(error, stderr);
    return FILE_LISTER_BAD_ARG;
  }

  return FILE_LISTER_OK;
}

int plugin_add(fl_plugins *flps, char *name)
{
  fl_plugin* tmp;
  int ret;
  
  /* If any of the args are NULL, bail out */
  if (flps==NULL || name==NULL)
    {
      return 1;
    }
  
  /* Realloc to tmp */
  tmp =
    realloc(flps->plugins, (flps->size+1)*sizeof(fl_plugin));

  if (tmp==NULL)
    {
      return 1;
    }
  /* Great tmp is not NULL, so we got mem */
  flps->plugins = tmp;

  ret = load_plugin(name, &flps->plugins[flps->size]);
  if (ret!=FILE_LISTER_OK)
    {
      return FILE_LISTER_BAD_ARG;
    }
  flps->size++;

  fprintf(stderr, "loaded : %s\n", name);
  return FILE_LISTER_OK;
}

fl_plugins* plugin_new()
{
  fl_plugins* flps = (fl_plugins*) calloc(1, sizeof(fl_plugins));
  if (flps==NULL)
    {
      return NULL;
    }
  flps->size=0;
  flps->plugins=NULL;
  return flps;
}

Create a plugin for object files

The plugin shall add "[obj]" before the file name if the suffix is ".o". Otherwise it should return as the c file plugin.

Load this function in the main file. Don't forget to create an shared object file from this new file.


When executing the program in your development folder you should see a file listing like this:

$ ./fls
[C/C++] main.c
[obj] main.o
 --- Makefile
 --- vgcore.3197
[C/C++] plugin-loader.c
[obj] plugin-loader.o
 --- plugins
 --- .
 --- fls
 --- vgcore.3344
[C/C++] plugin-loader.h
 --- ..
 --- interface


Expand using link to the right to see a possible solution/answer.

plugins/o-files.c:

#include <stdio.h>
#include <string.h>
#include "interface/file-lister.h"

#define SAME_STR(a,b) (strcmp(a,b)==0)

int present(char *name)
{
  //  int len;
  char *suffix;
  if (name==NULL)
    {
      return FILE_LISTER_BAD_ARG;
    }

  /* length of the name */
  //  len = strlen(name);
  
  /* last occurance of . in name */
  suffix = strrchr(name, '.');
  if ( ( suffix != NULL ) &&
       SAME_STR(suffix, ".o"))
    {
      fprintf(stdout, "[obj] %s\n", name);
      return FILE_LISTER_OK;
    }
  return FILE_LISTER_SUFFIX_UNKNOWN;
}

int info(void)
{
  printf("A plugin (for object files) to the file lister. Written by Juneday\n");
  return FILE_LISTER_OK;
}

main.c:

  fl_plugins* plugins = plugin_new();
  plugin_add(plugins, "plugins/libc-files.so");
  plugin_add(plugins, "plugins/libo-files.so");

Print the info text when loading the plugins

Use the info function to print out the information for each function before listing the files.

Expand using link to the right to see a possible solution/answer.

We chose to do this in the main function in the file main.c:

  for (i=0 ; i<plugins->size ; i++)
    {
      plugins->plugins[i].info_fun();
    }

Clean up the plugins, dl handles ...

If we run the program through valgrind or a similar tool we'll find that our program leaks memory and does not close our resources. This will not do. So let's:

  • close the opened shared object files (opened using dlopen
  • free the allocated memory

To give you some hints:

  • in the function plugin_add we call realloc once (per call)
  • in the function plugin_new we call calloc once (per call)

The call to realloc manages one chunk of memory so we need one call to free. The call to calloc is done once (when creating a new fl_plugins.

Expand using link to the right to see a possible solution/answer.

We chose to do this in the main function in the file main.c:

void plugin_free(fl_plugins* flps)
{
  int i;
  /* if null, we better bail out */
  if (flps==NULL)
    {
      return;
    }

  /* close all the handles (opened by dlopen) */
  for (i=0 ; i<flps->size ; i++)
    {
      dlclose(flps->plugins[i].handle);
    }


  /* free the array of plugins */
  if (flps->plugins!=null)
    {
      free(flps->plugins);
    }

  /* free the struct itself */
  free(flps);
}

Complete source code

You can find the final version of the source code of our suggested solution here: https://github.com/progund/programming-with-c/tree/master/dyn-loaded-libs/exercise/multi-lister-v2.