An . so file is a compiled library file. It stands for "Shared Object" and is analogous to a Windows DLL. Archive libraries (.a) are statically linked (compile-time) and use the -c option in gcc. A change to a .a library means you need to compile and build your code again. The advantage of .so (shared object) over .a library is that they are linked during the runtime. If there's any change in .so file, you don't need to recompile your main program.
This makes for an ideal way to build a PlugIn architecture. Using a PlugIn architecture, several libraries may fill a certain role in the application (such as filtering an image), but each do it in a different way. The application does not need these capabilities to run, but they extend the capabilities of the application.
For this example we begin with the common PlugIn API.
// PlugInApi.h
#ifndef PlugIn_API_H_Include
#define PlugIn_API_H_Include
#include <stddef.h> // includes NULL, wchar_t, and size_t
#ifdef WIN32 // declared when compiling with windows (if not, declare it)
#ifdef API_EXPORT // must be declared in Preprocessor Definitions
#define API __declspec(dllexport)
#else
#define API __declspec(dllimport)
#endif
#else
#define API
#endif
#ifdef __cplusplus
extern "C" {
#endif
/**
* Gets the name of the PlugIn
* @return a character array
*/
API const char* get_PlugInName();
/**
* Initializes an object
* @return a pointer to the object
*/
API void* Object_ctor();
/**
* Disposes of the object
* @param a pointer to the object
* @return void
*/
API void Object_Dispose(void* ptr);
#ifdef __cplusplus
}
#endif
#endif
Given this API, we can start to define PlugIn functionality.
// SamplePlugIn.cpp
#include "PlugInApi.h"
const char* get_PlugInName() {
return "Sample1";
}
class MyObject {
public:
int Size = 0;
bool UsingGPU = false;
};
void* Object_ctor() {
return new MyObject();
}
void Object_Dispose(void* ptr) {
if (ptr == NULL) { return; }
delete (MyObject*)ptr;
ptr = NULL;
}
And another PlugIn is defined...
// SamplePlugIn2.cpp
#include "PlugInApi.h"
#include <string>
const char* get_PlugInName() {
return "Sample2";
}
struct MyObject {
int Size = 0;
};
void* Object_ctor() {
return malloc(sizeof(MyObject));
}
void Object_Dispose(void* ptr) {
if (ptr == NULL) { return; }
free((MyObject*)ptr);
ptr = NULL;
}
Now we can define a main method to show how the PlugIns are loaded at runtime.
// main.cpp
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <dirent.h>
std::string extension(char* f)
{
std::string file_name(f);
int position = file_name.find_last_of(".");
return file_name.substr(position+1);
}
int main(int argc, char** argv)
{
printf("\nStarting demo...\n\n\n");
void* handle;
const char* (*get_PlugInName)();
char* error;
DIR *dir;
struct dirent *ent;
if ((dir = opendir ("./")) != NULL) {
while ((ent = readdir (dir)) != NULL) {
// Check the extension
auto ext = extension(ent->d_name);
if (ext.compare("so") != 0){
continue;
}
// Make the file name
std::string fname(ent->d_name);
fname = "./" + fname;
// Open the file
handle = dlopen(fname.c_str(), RTLD_LAZY);
if (!handle || ((error = dlerror()) != NULL)) {
fprintf(stderr, "%s\n", error);
continue;
}
dlerror(); /* Clear any existing error */
*(void**)(&get_PlugInName) = dlsym(handle, "get_PlugInName");
if ((error = dlerror()) != NULL) {
// fprintf(stderr, "%s\n", error);
continue;
}
printf("PlugIn Name: %s\n", (*get_PlugInName)());
dlclose(handle);
}
closedir (dir);
}
printf("\n\nPress any key to exit...\n\n");
getchar();
exit(EXIT_SUCCESS);
}
Now it's time to compile (note the -shared for the *.so files).
g++ -shared PlugInApi.h SamplePlugIn.cpp -o SamplePlugIn.so g++ -shared PlugInApi.h SamplePlugIn2.cpp -o SamplePlugIn2.so g++ -rdynamic -lstdc++ main.cpp -ldl -o main
And run...
./main
So let's see the results:
PlugIn Name: Sample1 PlugIn Name: Sample2
Both PlugIns have been detected and successfully called.