Home >> Justin Turney >> Advanced Programming Topics
 

Using a C++ Class from a Shared Library

Download Sample Code

The desire to add features and capability to existing programs is a common urge in the life of a computer programmer. Many programs make use of static libraries to share functionality. But in order to add new capabilities or upgrade existing ones you would have to recompile the static library and also recompile any program that linked to the library in order to benefit from the changes.

A simple way to overcome this problem is the use of shared libraries. In a shared library all the functionality that is used by the many programs is kept in a single file. The other programs, or Clients, simply load the shared library at runtime and they instantly have the capability without the need of recompilation.

This capability is relatively simple to do for a C program. The shared library simply needs to export the functions it wishes to make available. How does one do this with C++ classes? There are certainly existing system that already provide this functionality, but often a steep learning curve is required inorder to make use of the full potential of the system. This tutorial will illustrate a simple example of how to do this independently.

Step 1 - Define an Interface

The first step is to define some type of interface that is going to be used to share the C++ class to the Client. Since the Client is not necessarily a C++ program but maybe a C program a scheme must be used that is interchangable between the two languages. We can not use a class object because C has no idea how to deal with it. A common item between the two languages is struct so we will use that.

/*
 * FILE: sharedlib.h
 * This file will be shared between the library and
 * any Client wishing to use this library
 */

#ifndef __SHAREDLIB_H
#define __SHAREDLIB_H

// Declare our interface between the languages to be a struct
extern "C" {
  typedef struct IUnknown
  {
    virtual int QueryInterface(char*) = 0;
  };
};

#endif
The extern "C" statement above is just to make sure the compiler creates the struct in a C style, it should do it by default. The "= 0" after the function declaration states that any object derived from this structure must declare this function. In reality using the "= 0" is a C++ specific style and not understood by a C compiler. I am currently trying to figure out how to remove this dependency.

The file provided above will be shared with all programs that wish to use the shared library. It basically tells the Client how to interface with an object it is sharing.

Step 2 - Define the Class to be Shared

So, the Client program now knows how to interface with the object. The next step is to actually provide some code for the object itself. But first we have to declare what the object looks like:

/*
 * myclass.h
 * A class to be shared.
 */

#ifndef __MYCLASS_H
#define __MYCLASS_H

#include "sharedlib.h"

class MyClass : public IUnknown
{
public:
  MyClass();
  virtual ~MyClass();

  virtual int QueryInterface(char*);
};

#endif
It is important to derive your class from the shared structure. All functions that were declared in IUnknown must be declared in the class, otherwise the compiler will give errors about abstract functions not declared.

Now the code for the object:

/*
 * myclass.cc
 */
#include <stdlib.h>
#include <stdio.h>

#include "myclass.h"

MyClass::MyClass()
{
}

MyClass::~MyClass()
{
}

int MyClass::QueryInterface(char *teststring)
{
  printf("MyClass::QueryInterface - %s\n", teststring);
  return 1;
}

Step 3 - Provide a way to create the object

A method must be provided by the shared library for creating and deleting the objects. This is easily done by exporting a couple of functions.

/*
 * cfuncs.cc
 */
#include <stdio.h>
#include <stdlib.h>
#include "myclass.h"

// These type of externs mean to export the function
extern "C" IUnknown* create() {
  return new MyClass;
}

extern "C" void destroy(IUnknown *p) {
  delete (MyClass*)p;
}

Step 4 - A Simple Client

This client requires the libdl.a library in order to load a shared library and interface with it.

#include <stdlib.h>
#include <stdio.h>
#include <dlfcn.h>

#include "sharedlib.h"

typedef IUnknown* create_t();
typedef void destroy_t(IUnknown*);

int main(void)
{
  // Load the library
  void* testlib = dlopen("libSharedClass.so", RTLD_LAZY);

  if (!testlib) {
    printf("Cannot load library - %s\n", dlerror());
    return EXIT_FAILURE;
  }

  create_t* create_object = (create_t*)dlsym(testlib, "create");
  destroy_t* destroy_object = (destroy_t*)dlsym(testlib, "destroy");

  if (!create_object || !destroy_object) {
    printf("Cannot load symbols - %s\n", dlerror());
    return EXIT_FAILURE;
  }

  // Create the object
  IUnknown *iunk = create_object();

  // Print something to the screen
  iunk->QueryInterface("Simple Test of QueryInterface");

  // Destroy the object
  destroy_object(iunk);

  // Close the library
  dlclose(testlib);
  return EXIT_SUCCESS;
}
If it was successful the result on the screen will be:
local(testprog)% ./testprog
MyClass::QueryInterface - Simple Test of QueryInterface
local(testprog)%

Supplemental Material

Now for the oh so important Makefiles for compiling this tutorial.

For the shared library:

# Compiler
CC = g++
CFLAGS = -fPIC -g

# Library linker
LL = g++
LLFLAGS = -fPC -shared

# Library filename
LIBRARY = libShareClass.so

# Define locations of header files
INCLUDES = -I.

# Define source files to be compiled
CSRC = cfunc.cc myclass.cc

# Definitions and make rules
BINOBJ = $(CSRC:%.cc=%.o)

$(LIBRARY): $(BINOBJ)
        $(LL) $(LLFLAGS) -o $(LIBRARY) $^

%.o: %.cc
        $(CC) $(CFLAGS) $(INCLUDES) -c $<

The -fPIC is important when compiling a shared library. This tells the compiler and linker to generate Position Independent Code (PIC). This is important because the library cannot be certain where it will be loaded into memory, or even the position in a segment.

For the test program:

# Compiler
CC = g++
CFLAGS = -g
                                                                                
# Program filename
CODE = testprog 
                                                                                
# Define locations of header files and libraries
INCLUDES = -I.
LIBS = -ldl
                                                                                
# Define source files to be compiled
CSRC = testprog.cc
                                                                                
# Definitions and make rules
BINOBJ = $(CSRC:%.cc=%.o)
                                                                                
$(CODE): $(BINOBJ)
        $(CC) $(CFLAGS) $(LIBS) $^ -o $(CODE)
                                                                                
%.o: %.cc
        $(CC) $(CFLAGS) $(INCLUDES) -c $<

That is it. The program and library created in this tutorial can be extended to provide additional interfaces and much more.

Have fun!!