Dnes je modularita programov bežná vec. Každý užívateľ určite ocení vysokú konfigurovateľnosť a pluginovatelnosť programu. V tomto príspevku vám ukážem ako napísať modularitu programu.
Ingrediencie: program som písal pod operačným systémom Linux, takže určite gcc, a knižnicu dlfcn ktorá býva bezne obsiahnutá v glibc.
Takže najskôr si povieme ako bude taký modul vyzerať a čo bude obsahovať. Obsahovať bude určite základné informácie o mene autora, názov modulu, stručný popis, verziu modulu a nakoniec licenciu pod ktorou budeme modul publikovať. Ďalej bude obsahovať 2 základné funkcie module_init a module_exit. module_init sa bude volať zakaždým s loadovaním modulu a module_exit zasa pri odstranovaní modulu. Dalšia vec ktorú budeme potrebovať je nejaký loader ktorý bude modul načítavať a funkciu ktorá ho bude mazať. Poďme sa teda pozrieť ako bude vyzerať štruktúra MODULE a čo bude obsahovať:
typedef struct { const char *path; void *handle; void (*init)(); void (*exit)(); struct { const char *author; const char *title; const char *description; const char *license; const char *version; } info; } MODULE;
Takže premenná path označuje cestu k loadovanému modulu, handle označuje stream z ktorého sa budú načítať premenné a funkcie, dalej tu máme ukazovateľ na funkciu typu void ktorý sa volá init a exit, tie budú ukazovať práve na spomínané funkcie module_init a module_exit. Dalej obsahuje štruktúra MODULE dalšiu štruktúru menom info kde budeme ukladať spomenuté informácie o module.
Ako teda budeme zapisovať informácie o module? Premenné vyzerajúce takto:
const char *__module_info_author = "vl4kn0";
mi neprídu veľmi praktické a navyše sú škaredé. Preto si spravíme pekné makrá aby sme programátorom zjednodušili prácu.
Prvé základné makro bude mať takýto tvar:
#define MODULE_INFO(var, value) \
const char __module_info_##var[] = value;To nám hovorí, že MODULE_INFO(premenna, hodnota) prepíše za const char *__module_info_premenna = hodnota;
To ale stále nieje ono. Preto napíšeme týchto pár makier kôli väčšej komfortnosti:
#define MODULE_AUTHOR(_author) MODULE_INFO(author, _author) #define MODULE_LICENSE(_license) MODULE_INFO(license, _license) #define MODULE_DESCRIPTION(_desc) MODULE_INFO(description, _desc) #define MODULE_TITLE(_title) MODULE_INFO(title, _title) #define MODULE_VERSION(_version) MODULE_INFO(version, _version)
Tieto makrá hovoria o tom, že napríklad MODULE_AUTHOR(”vl4kn0″); prepíše za const char *__module_info_author = “vl4kn0″;
Ďalej tu máme správu chýb. Slúži na vrátenie hodnoty chyby ak takáto chyba naskytne. Môže sa naskytnúť pri alokácii pamäti, otváraní modulu alebo pri načítavaní funkcií z modulu. Kôli tomu som si napísal pár konštánt podla ktorých budem chyby rozpoznávať
#define EMEM 1 /* Error MEMory allocation */ #define EDLO 2 /* Error Dynamic Library Open */ #define ESYM 3 /* Error getting SYMbol from dynamic library */ #define ENOI 4 /* Error NO Init */ #define ENOE 5 /* Error NO Exit */
Veľmi jednoduché. Dalej tu budem mať ľ funkcie na prácu s týmyto errorovými číslami a jednu premennú v ktorej bude uložená hodnota posledného erroru.
static unsigned int __module_errno = 0; unsigned int inline module_errno(); void inline module_raise(unsigned int);
Funkcia module_raise bude ukladať hodnotu do premennej __module_errno a module_errno zase vracia hodnotu uloženú v tejto premennej. Obe obsahujú len jedno priradenie preto sú inline.
A nakoniec tu máme prototypy funkcii module_load a module_unload ktoré modul loadú a unloadujú. Takto teda bude vyzerať prvý súbor module.h
#ifndef __MODULE_H__ #define __MODULE_H__ #define EMEM 1 #define EDLO 2 #define ESYM 3 #define ENOI 4 #define ENOE 5 #define MODULE_INFO(var, value) \ const char __module_info_##var[] = value; #define MODULE_AUTHOR(_author) MODULE_INFO(author, _author) #define MODULE_LICENSE(_license) MODULE_INFO(license, _license) #define MODULE_DESCRIPTION(_desc) MODULE_INFO(description, _desc) #define MODULE_TITLE(_title) MODULE_INFO(title, _title) #define MODULE_VERSION(_version) MODULE_INFO(version, _version) typedef struct { const char *path; void *handle; void (*init)(); void (*exit)(); struct { const char *author; const char *title; const char *description; const char *license; const char *version; } info; } MODULE; static unsigned int __module_errno = 0; unsigned int inline module_errno(); void inline module_raise(unsigned int); MODULE * module_load(const char *); void module_unload(MODULE *); #endif /* __MODULE_H__ */
Teraz si ukážeme ako bude vyzerať súbor module.c. Bude includovat hlavičkové súbory dlfcn.h a module.h a stdlib.h. Dalej tu máme tu nejaké funkcie na prácu s errormi:
void inline module_raise(unsigned int errno) { __module_errno = errno; } unsigned int inline module_errno() { return __module_errno; }
Tie sú myslím jednoznačné.
Čo nás ale bude viac zaujímať bude funkcia module_load. Za prvé bude vraciať typ MODULE, ktorý som opísal vyššie. Ako parameter bude dostávať cestu k modulu ktorý budeme loadovať. Celkový proces bude vyzerať takto:
vytvorý ukazateľ na premennú typu MODULE, otvorý modul, vybernie z neho informácie, vyberie funkciu init, exit. spustí funkciu init. a tento ukazateľ vráti. Funkcia vyzerá takto:
MODULE * module_load(const char *path) { MODULE *mod; mod = (MODULE *)malloc(sizeof(MODULE)); if (!mod) { module_raise(-EMEM); goto error; } mod->path = path; mod->handle = dlopen(path, RTLD_LAZY); if (!mod->handle) { module_raise(-EDLO); goto error; } mod->init = dlsym(mod->handle, "module_init"); if (!mod->init) { module_raise(-ENOI); goto error; } mod->exit = dlsym(mod->handle, "module_exit"); if (!mod->exit) { module_raise(-ENOE); goto error; } mod->info.author = dlsym(mod->handle, "__module_info_author"); mod->info.title = dlsym(mod->handle, "__module_info_title"); mod->info.description = dlsym(mod->handle, "__module_info_description"); mod->info.license = dlsym(mod->handle, "__module_info_license"); mod->info.version = dlsym(mod->handle, "__module_info_version"); mod->init(); return mod; error: return NULL; }
Všimite si, že vždy keď môže nastať chybová situácia máme oštrenie pomocou funkcie module_raise.
Dalšia a posledná funkcia v tomto súbore bude funkcia module_unload. Ako parameter preberá ukazateľ na modul ktorý chceme unloadovať. Postup: zistí či je predávaný modul alokovaný, ak nieje funkcia skončí. Zavolá module_exit, uzavrie modul, a informácie odstráni z pamäti, kód je tu:
void module_unload(MODULE *mod) { if (!mod) return; mod->exit(); if (mod->handle) dlclose(mod->handle); free(mod); }
na zrekapitulovanie celý kod:
#include <stdlib.h> #include <dlfcn.h> #include "module.h" void inline module_raise(unsigned int errno) { __module_errno = errno; } unsigned int inline module_errno() { return __module_errno; } MODULE * module_load(const char *path) { MODULE *mod; mod = (MODULE *)malloc(sizeof(MODULE)); if (!mod) { module_raise(-EMEM); goto error; } mod->path = path; mod->handle = dlopen(path, RTLD_LAZY); if (!mod->handle) { module_raise(-EDLO); goto error; } mod->init = dlsym(mod->handle, "module_init"); if (!mod->init) { module_raise(-ENOI); goto error; } mod->exit = dlsym(mod->handle, "module_exit"); if (!mod->exit) { module_raise(-ENOE); goto error; } mod->info.author = dlsym(mod->handle, "__module_info_author"); mod->info.title = dlsym(mod->handle, "__module_info_title"); mod->info.description = dlsym(mod->handle, "__module_info_description"); mod->info.license = dlsym(mod->handle, "__module_info_license"); mod->info.version = dlsym(mod->handle, "__module_info_version"); mod->init(); return mod; error: return NULL; } void module_unload(MODULE *mod) { if (!mod) return; mod->exit(); if (mod->handle) dlclose(mod->handle); free(mod); }
Takže základ máme zmáknutý stačí napísať example modul, ktorý bude vyzerať veľmi jednoducho:
#include <stdio.h> #include "module.h" MODULE_AUTHOR("vl4kn0"); MODULE_TITLE("example"); MODULE_DESCRIPTION("example for byteleak.com"); void module_init() { puts("load"); } void module_exit() { puts("unload"); }
Veľmi jednoduché. Tento príklad teraz skompilujeme, ako shared library. To docielime prepínačon -shared
gcc -shared -o example.mo example.c
koncovka mo znamená module object. Môžete si samozrejme zvolit akú budete chcieť.
Teraz máme náš prvý modul. Podme sa teda pozrieť ako sa naloaduje.
Napíšeme teda jednoduchý program, ktorý naloaduje modul a vypíše autorove meno.
#include <stdio.h> #include "module.h" int main(int argc, char **argv) { MODULE *mod; mod = module_load("./example.mo"); printf("%s\n", mod->info.author); module_unload(mod); return 0; }
Tento program pomenujeme main.c. Skompilujeme ho teda spolu s module.c a knižnicou dlfcn
gcc main.c module.c -ldl -o main
Po spustení dostaneme takýto výstup:
load
vl4kn0
unload
Program vypísal load tak ako to bolo definované vo funkcii module_init, potom vypísal autorove meno
tak ako je to napísané vo funkcii main a nakoniec sa unloadoval a spustil module_exit co malo za následok vypísanie unload.
Dúfam, že sa vám článok páčil. Ak áno alebo aj ak nie píšte komenty.






Ďalšie články, ktoré by ťa mohli zaujať
Žiadne odpovede v “Ako na moduly v C”