Sunday, December 28, 2025
Create C programming library
Static library
A static library (.a) is an archive of object (.o) files; at link time the linker copies needed object code from the .a into the final executable, making the executable self-contained.
You can link static .a lib at program build/compile time what you can't do is load/unload it while programm is running.
Install compiler tools and editor (see how easy it is on Ubuntu Linux :D)
🚀 sudo apt update
🚀 sudo apt install build-essential geany geany-plugins
Create file "lib.c" that will be our lib
#include <stdio.h>
#define STR_1 "MY STRING ONE"
#define STR_2 "MY STRING TWO"
#define STR_3 "MY STRING THREE"
void write_one(){
puts(STR_1);
}
void write_two(){
puts(STR_2);
}
void write_three(){
puts(STR_3);
}
Generate object (.o) file
🚀 gcc -O2 -Wall -c lib.c -o lib.o
Archive utility creates static lib.a library
🚀 ar rcs lib.a lib.o
List members of lib.a
🚀 ar t lib.a
lib.o
List symbols
🚀 nm -g lib.a
lib.o:
U puts
0000000000000000 T write_one
0000000000000020 T write_three
0000000000000010 T write_two
Now, let's pretrend we have new project where we will import our .a lib
#include <stdio.h>
int main(void) {
printf("Hello, world!\n");
write_one(); //let's try to call function from our lib
return 0;
}
Compile main.c with lib.a
🚀 gcc -o main main.c -L. -l:lib.a
main.c: In function ‘main’:
main.c:6:5: warning: implicit declaration of function ‘write_one’ [-Wimplicit-function-declaration]
6 | write_one();
| ^~~~~~~~~
The warning means the compiler didn't see a declaration for write_one() before you called it. So we either add declarations in our main.c file or use header file. This is why we use header .h files with libs.
main.c with declarations (lib function names)
#include <stdio.h>
void write_one(void);
void write_two(void);
void write_three(void);
int main(void) {
printf("Hello, world!\n");
write_one();
return 0;
}
Or other option, lib header
#ifndef LIB_H
#define LIB_H
void write_one(void);
void write_two(void);
void write_three(void);
#endif
#include <stdio.h>
#include "lib.h" //imported header
int main(void) {
printf("Hello, world!\n");
write_one();
return 0;
}
Dynamic library
Compile position-independent object
🚀 gcc -O2 -fPIC -Wall -c lib.c -o lib.o
Create shared library
🚀 gcc -shared -o lib.so lib.o
Link with explicit filename
🚀 gcc -O2 -Wall -o main main.c ./lib.so -I.
Show shared dependencies
🚀 ldd ./main
linux-vdso.so.1 (0x0000777e16876000)
./lib.so (0x0000777e16866000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x0000777e16600000)
/lib64/ld-linux-x86-64.so.2 (0x0000777e16878000)
Show exported symbols
🚀 nm -D lib.so
w __cxa_finalize@GLIBC_2.2.5
w __gmon_start__
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
U puts@GLIBC_2.2.5
0000000000001120 T write_one
0000000000001140 T write_three
0000000000001130 T write_two
Install lib into system
🚀 sudo cp libmylib.so /usr/local/lib/
🚀 sudo cp lib.h /usr/local/include/
🚀 sudo ldconfig
ldconfig is a system utility on Linux that maintains the shared‑library cache used by the dynamic linker (ld.so).
Scans directories listed in /etc/ld.so.conf and any directories added with the -L option in the command line.
Finds shared libraries (*.so, *.so.*) in those directories.
Creates/updates the cache file /etc/ld.so.cache, which stores the full paths of the libraries.
Creates symbolic links (e.g., libfoo.so → libfoo.so.1.2.3) for versioned libraries when the -v or -n options are used.
After that you can use:
🚀 gcc -O2 -Wall -o main main.c -L. -lmylibname -I.
Plugin system
dlopen is a POSIX function that loads a shared library (dynamic‑link library) into a running process at runtime. It is part of the dynamic loading API defined in <dlfcn.h>.
Create new "main-load.c" file
#define _GNU_SOURCE
#include <stdio.h>
#include <dlfcn.h>
/* prototypes for function-pointer types matching the library symbols */
typedef void (*write_fn_t)(void);
int main(void){
void *handle;
write_fn_t write_one;
char *err;
handle = dlopen("./lib.so", RTLD_LAZY);
if (!handle) {
fprintf(stderr, "dlopen: %s\n", dlerror());
return 1;
}
dlerror();
*(void **)(&write_one) = dlsym(handle, "write_one");
if ((err = dlerror()) != NULL) {
fprintf(stderr, "dlsym: %s\n", err);
dlclose(handle);
return 1;
}
write_one();
dlclose(handle);
return 0;
}
Compile main-load.c
gcc -O2 -Wall -o main main-load.c -ldl
dlopen is a POSIX function that loads a shared library (dynamic‑link library) into a running process at runtime. It is part of the dynamic loading API defined in <dlfcn.h>.
| Function |
Purpose |
dlsym(void *handle, const char *symbol) |
Retrieve the address of a symbol (function or variable) from the loaded library. |
dlclose(void *handle) |
Unload the library and release resources. |
dlerror(void) |
Return a human‑readable string describing the most recent error from dlopen, dlsym, or dlclose. |
We can provide symbol that are not known like this:
Plugin code:
#include <stdio.h>
#define STR_1 "MY STRING ONE"
#define STR_2 "MY STRING TWO"
#define STR_3 "MY STRING THREE"
void write_one() { puts(STR_1); }
void write_two() { puts(STR_2); }
void write_three() { puts(STR_3); }
/* ------------------ Plugin interface ------------------ */
// Function pointer type for the writes
typedef void (*write_fn_t)(void);
// Structure to describe one function
typedef struct {
const char *name;
write_fn_t func;
} PluginFunction;
// This is the well-known entry point the host will call
// It returns the number of functions and fills the array pointer
void get_plugin_functions(PluginFunction **funcs, int *count)
{
static PluginFunction functions[] = {
{ "write_one", write_one },
{ "write_two", write_two },
{ "write_three", write_three }
};
*funcs = functions;
*count = sizeof(functions) / sizeof(functions[0]);
}
Plugin loader code:
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
// Type for the write functions
typedef void (*write_fn_t)(void);
// Type for the plugin functions description
typedef struct {
const char *name;
write_fn_t func;
} PluginFunction;
// Prototype for the registration function that every plugin must export
typedef void (*get_plugin_functions_t)(PluginFunction **funcs, int *count);
int main(void)
{
void *handle = dlopen("./my-plugin.so", RTLD_LAZY);
if (!handle) {
fprintf(stderr, "dlopen: %s\n", dlerror());
return 1;
}
// Clear any old errors
dlerror();
// Look up the registration function (this name is known in advance)
get_plugin_functions_t get_funcs = (get_plugin_functions_t)dlsym(handle, "get_plugin_functions");
char *err = dlerror();
if (err != NULL) {
fprintf(stderr, "dlsym (get_plugin_functions): %s\n", err);
dlclose(handle);
return 1;
}
// Ask the plugin to give us its functions
PluginFunction *functions = NULL;
int count = 0;
get_funcs(&functions, &count);
printf("Loaded %d functions from the plugin:\n", count);
// Call every discovered function
for (int i = 0; i < count; i++) {
printf(" Calling %s...\n", functions[i].name);
functions[i].func(); // Actually call it
}
dlclose(handle);
return 0;
}