A pluginek tulajdonképpen kutyaközönséges dinamikus library-k, amiket a program futásidőben tud betölteni, vagy felszabadítani. Ennek megfelelően elsőként csináljunk egy dinamikus könyvtárat.
Legyen kezdetnek mondjuk három függvényünk. C-ben (mylib.c):
int mylib_a(int a)
{
return a - 5;
}
void mylib_b(int *b)
{
*b -= 5;
}
int mylib_c(int *c)
{
*c -= 5;
return *c - 5;
}
És Pascalban (mylib.pas
):
library mylib;
{$mode objfpc}{$H+}
function mylib_a(a: longint): longint; cdecl;
begin
result := a - 5;
end;
procedure mylib_b(b: plongint); cdecl;
begin
b^ := b^ - 5;
end;
function mylib_c(c: plongint): longint; cdecl;
begin
c^ := c^ - 5;
result := c^ - 5;
end;
exports mylib_a, mylib_b, mylib_c;
end.
A C library úgy áll elő, hogy először leforgatjuk relokálható (-fPIC
kapcsoló) objektumnak (-c
kapcsoló)
cc -fPIC -c mylib.c
majd utána linkeljük Shared Object-nek (-shared
kapcsoló, a -Wl,-soname,libmylib.so
pedig a linkernek mondja meg, hogy mi lesz az object neve)
cc -shared -Wl,-soname,libmylib.so -o libmylib.so mylib.o
Pascal alatt egyszerűbb a dolog; mivel a forrásban deklarálva van, hogy ez egy library, így elég csak simán a
fpc mylib.pas
paranccsal megküldeni.
Ezzel előállt a library mindkét nyelven, benne a mylib_a
, mylib_b
, mylib_c
symbolokkal; lehet ellenőrizni a
objdump -T libmylib.so
paranccsal. A kimenet a C-s esetén:
libmylib.so: file format elf64-x86-64
DYNAMIC SYMBOL TABLE:
0000000000000000 w D *UND* 0000000000000000 _ITM_deregisterTMCloneTable
0000000000000000 w D *UND* 0000000000000000 __gmon_start__
0000000000000000 w D *UND* 0000000000000000 _Jv_RegisterClasses
0000000000000000 w D *UND* 0000000000000000 _ITM_registerTMCloneTable
0000000000000000 w DF *UND* 0000000000000000 GLIBC_2.2.5 __cxa_finalize
0000000000201020 g D .data 0000000000000000 Base _edata
0000000000201028 g D .bss 0000000000000000 Base _end
0000000000000690 g DF .text 000000000000000f Base mylib_a
000000000000069f g DF .text 000000000000001a Base mylib_b
0000000000201020 g D .bss 0000000000000000 Base __bss_start
0000000000000550 g DF .init 0000000000000000 Base _init
00000000000006dc g DF .fini 0000000000000000 Base _fini
00000000000006b9 g DF .text 0000000000000022 Base mylib_c
És a Pascalos esetén:
libmylib.so: file format elf64-x86-64
DYNAMIC SYMBOL TABLE:
0000000000000a10 g DF .text 0000000000000005 mylib_a
0000000000000a20 g DF .text 0000000000000005 mylib_b
0000000000000a30 g DF .text 0000000000000005 mylib_c
Amint látszik a C-s dinamikusan linkelődik a libc-hez, a míg a Pascalosnak nincs külső függősége. Cserébe a C-s nincs 6 kB se (strippelve), míg a Pascalos 30 kB felett van.
Mielőtt a főprogramra térnénk, először meg kell csinálni a library-hoz tartozó API-t. C esetén ez egy header file (mylib.h
) képében fog manifesztálódni (lehetne .c
is .h
helyett; de így nem fog ütközni a neve a library-éval), Pascal esetén pedig egy unit file (mylib.pp
, szintén az ütközést preventálandó nem .pas
). Az API fog felelni azért, hogy betöltse/felszabadítsa a library-t, illetve, hogy a megfelelő szimbólumokat a főprogram számára használható függvény-prototípusokhoz kösse.
Kezdjük a betöltéssel és a bezárással.
Ezt C alatt a dlopen()
függvénnyel lehet elérni (manualok: POSIX, Linux, FreeBSD, OpenBSD, Solaris), Pascal alatt a LoadLibrary()
függvénnyel.
Utóbbinak csak a library útvonala kell (elég a fájlnév is, ha a library olyan könyvtárban van, ami szerepel a LD_LIBRARY_PATH
környezeti változóban), előbbinek azonban van egy második paramétere is, ami azt határozza meg, hogy milyen megközelítéssel töltse be, vagy épp szabadítsa fel a library-t, végezze el a relokálásokat (azon szimbólumok referenciáinak helyes címre való átirányítását, amiknek a címe csak a betöltődés után lesz ismert) és szabályozza a szimbólumok hozzáférhetőségét is. Az RTLD_NOW
flag használata (ill. Linux alatt a LD_BIND_NOW
környezeti változó bármilyen - nem üres - stringre való beállítása) azt eredményezi, hogy az összes relokációt azonnal elvégzi a program, míg a RTLD_LAZY
flag használata esetén csak akkor történik meg egy adott szimbólum relokálása, ha hozzá akar férni a program. Az RTLD_GLOBAL
flag azért felel, hogy a betöltött library szimbólumai hozzáférhetőek legyenek más a program által betöltött library-k számára is, a RTLD_LOCAL
pedig ennek ellentétét jelenti. Namármost, mivel globális hozzáférhetőség esetén az így betöltött library-k szimbólumai azonos nevek esetén ütközni fognak és a pluginek mind szükségszerűen tartalmazni fogják azokat a szimbólumokat, amikkel a program kapcsolódni tud hozzájuk, így ebből kiderül, hogy pluginek esetén a RTLD_LOCAL
használata javallott. A feloldási megközelítés alapvetően mindegy, de a program működésétől függően lehet a RTLD_LAZY
flag-gel teljesítménynövekedést elérni, ha pl. a program adott körülmények között adott szimbólumokhoz egyáltalán nem nyúl. A többi - egyébként non-POSIX - flag minket most nem érint.
Sikeres betöltés esetén egy pointert kapunk, ami a library handle-jére mutat, sikertelen esetben ez NULL
, ill. Pascalban DynLibs.NilHandle
(ami a gyakorlatban ugyanúgy nullát jelent, csak Int64
típussal). Azt viszont nem árt tudni, hogy bár újbóli kinyitási kísérlet esetén újból ugyanazt a handle-t fogjuk visszakapni, mert nem tölti be többször a library-t - viszont pont azért, mert a kódban akár több egymástól független rész is betölthet egy adott library-t - felszabadítani a library-t csak ugyanannyi bezárással lehet, mint ahányszor megnyitottuk. Bezárni pedig a dlclose()
függvénnyel lehet a library-t C alatt (manualok: POSIX, Solaris, a többieké ugyanott van, ahol a dlopen()
-é is) és a FreeLibrary()
függvénnyel Pascal alatt.
Ennek megfelelően a betöltés és bezárás C-ben:
#include <dlfcn.h>
...
void *handle = dlopen("libmylib.so", RTLD_NOW | RTLD_LOCAL);
dlclose(handle);
És Pascalban:
uses DynLibs;
var handle: TLibHandle;
...
handle := LoadLibrary('libmylib.so');
FreeLibrary(handle);
Folytassuk a relokálással.
A szimbólumok konkrét feloldásáért/relokálásáért a dlsym()
függvény felel C alatt (manualok: POSIX, Solaris, a többieké pedig ismét ugyanott található, ahol a dlopen()
) és a GetProcedureAddress()
függvény Pascal alatt.
Példa C-ben:
int (*mylib_a)(int a);
...
mylib_a = dlsym(handle, "mylib_a");
És Pascalban:
type Tmylib_a = function (a: longint): longint; cdecl;
var mylib_a: Tmylib_a;
...
mylib_a := Tmylib_a(GetProcedureAddress(handle, 'mylib_a'));
Most már tkp. be tudjuk tölteni a library-t és használni a függvényeit. Mivel azonban itt pluginekről beszélünk, azaz több library-nk lesz, azonos függvényekkel, így mindenképpen szükség lesz valamilyen szintű "konténeresítésre", hogy egyrészt ne akadjanak össze (ezúttal nem szimbólumszinten, mint feljebb, hanem API szinten), másrészt meg, hogy tudjuk, hogy melyik relokált függvény melyik handle-höz is tartozik. Ezt lehet akár handle és funkciótömbökkel is csinálni, de sokkal egyszerűbb, ha bepakoljuk egy struct
-ba
typedef struct
{
void *handle;
int (*mylib_a)(int a);
void (*mylib_b)(int *b);
int (*mylib_c)(int *c);
} mylib;
vagy record
-ba
type
Tmylib_a = function (a: longint): longint; cdecl;
Tmylib_b = procedure (b: plongint); cdecl;
Tmylib_c = function (c: plongint): longint; cdecl;
Tmylib = record
handle: TLibHandle;
mylib_a: Tmylib_a;
mylib_b: Tmylib_b;
mylib_c: Tmylib_c;
end;
Pmylib = ^Tmylib;
és akkor minden pluginhez tartozhat egy ilyen konténer.
Valamivel fentebb tisztáztuk, hogy egy ilyen plugint egyszer nyitunk ki, így ezt nem ártana pl. lekezelni a lezáráskor (C)
void close_mylib(mylib *lib)
{
if ((lib != NULL) && (lib->handle != NULL))
{
dlclose(lib->handle);
lib->handle = NULL;
}
}
(Pascal)
procedure close_mylib(lib: Pmylib);
begin
if ((lib <> nil) and (lib^.handle <> DynLibs.NilHandle)) then
begin
FreeLibrary(lib^.handle);
lib^.handle := DynLibs.NilHandle;
end;
end;
és a megnyitáskor is. C:
#define MYLIB_ERROR_INVALID_CONTAINER 1
#define MYLIB_ERROR_ALREADY_OPEN 2
#define MYLIB_ERROR_CANNOT_OPEN 3
#define MYLIB_ERROR_MISSING_SYMBOL 4
int open_mylib(mylib *lib, char *path)
{
if (lib == NULL)
{
return MYLIB_ERROR_INVALID_CONTAINER;
}
if (lib->handle != NULL)
{
return MYLIB_ERROR_ALREADY_OPEN;
}
lib->handle = dlopen(path, RTLD_NOW | RTLD_LOCAL);
if (lib->handle == NULL)
{
return MYLIB_ERROR_CANNOT_OPEN;
}
lib->mylib_a = dlsym(lib->handle, "mylib_a");
if (lib->mylib_a == NULL)
{
close_mylib(lib);
return MYLIB_ERROR_MISSING_SYMBOL;
}
lib->mylib_b = dlsym(lib->handle, "mylib_b");
if (lib->mylib_b == NULL)
{
close_mylib(lib);
return MYLIB_ERROR_MISSING_SYMBOL;
}
lib->mylib_c = dlsym(lib->handle, "mylib_c");
if (lib->mylib_c == NULL)
{
close_mylib(lib);
return MYLIB_ERROR_MISSING_SYMBOL;
}
return 0;
}
Pascal:
const
MYLIB_ERROR_INVALID_CONTAINER = 1;
MYLIB_ERROR_ALREADY_OPEN = 2;
MYLIB_ERROR_CANNOT_OPEN = 3;
MYLIB_ERROR_MISSING_SYMBOL = 4;
function init_mylib(lib: Pmylib; path: string): longint;
begin
if (lib = nil) then
begin
result := MYLIB_ERROR_INVALID_CONTAINER;
exit;
end;
if (lib^.handle <> DynLibs.NilHandle) then
begin
result := MYLIB_ERROR_ALREADY_OPEN;
exit;
end;
lib^.handle := LoadLibrary(path);
if (lib^.handle = DynLibs.NilHandle) then
begin
result := MYLIB_ERROR_CANNOT_OPEN;
exit;
end;
lib^.mylib_a := Tmylib_a(GetProcedureAddress(lib^.handle, 'mylib_a'));
if (lib^.mylib_a = Tmylib_a(DynLibs.NilHandle)) then
begin
close_mylib(lib);
result := MYLIB_ERROR_MISSING_SYMBOL;
exit;
end;
lib^.mylib_b := Tmylib_b(GetProcedureAddress(lib^.handle, 'mylib_b'));
if (lib^.mylib_b = Tmylib_b(DynLibs.NilHandle)) then
begin
close_mylib(lib);
result := MYLIB_ERROR_MISSING_SYMBOL;
exit;
end;
lib^.mylib_c := Tmylib_c(GetProcedureAddress(lib^.handle, 'mylib_c'));
if (lib^.mylib_c = Tmylib_c(DynLibs.NilHandle)) then
begin
close_mylib(lib);
result := MYLIB_ERROR_MISSING_SYMBOL;
exit;
end;
result := 0;
end;
(Pascalban természetesen ezt a két függvényt exportálni kell, C-ben pedig be kell húzni a stdlib.h
-t a NULL
-hoz.)
Ezzel le is tudtuk az API-t, most már jöhet a főprogram. Ebben semmi mást nem kell csinálni, mint behúzni az API-t és meghívogatni a megfelelő függvényeket a megfelelő paraméterrel, pl. a fájlnévvel és a deklarált konténerstruktúrával. Ezt most az egyszerűség kedvéért egy mezei globális változóban tároljuk; nyilván ezt allokálni kell mindegyik pluginhez, ha több pluginünk lesz, de szemléltetésnek most így is megteszi. C-ben:
char *libname = "./libmylib.so";
int err, a, b, c, c2;
mylib lib;
...
lib.handle = NULL;
rr = open_mylib(&lib, libname);
if (err != 0)
{
fprintf(stderr, "Cannot open \"%s\" - error %d.\n", libname, err);
return 1;
}
a = lib.mylib_a(9);
b = 10;
lib.mylib_b(&b);
c = 11;
c2 = lib.mylib_c(&c);
fprintf(stdout, "a = %d, b = %d, c = %d, c2 = %d\n", a, b, c, c2);
close_mylib(&lib);
És Pascalban:
const libname = './libmylib.so';
var
err, a, b, c, c2: longint;
lib: Tmylib;
...
lib.handle := DynLibs.NilHandle;
err := init_mylib(@lib, libname);
if (err <> 0) then
begin
WriteLn(StdErr, 'Cannot open "' + libname + ' - error ' + IntToStr(err) + '.');
Halt(1);
end;
a := lib.mylib_a(9);
b := 10;
lib.mylib_b(@b);
c := 11;
c2 := lib.mylib_c(@c);
WriteLn(StdOut, 'a = ' + IntToStr(a) + ', b = ' + IntToStr(b) + ', c = ' + IntToStr(c) + ', c2 = ' + IntToStr(c2));
close_mylib(@lib);
Pascalban ne felejtsük el a DynLibs
unitot is behúzni a DynLibs.NilHandle
használatához. (És persze a SysUtils
a WriteLn()
-hez és C-ben a stdio.h
a fprintf()
-hez.)
Ha megvan a főprogram, lehet fordítani. C-ben a dl
lib kelleni fog a dinamikus library-khoz.
cc mytest.c -ldl -o mytest
Pascalban itt is elég csak simán megküldeni a fordítóval.
fpc mytest.pas
(Pascalban egyébként itt is és a librarynál is érdemes a -CX -Xs -XX
kapcsolókat használni a smartlinkinghez és a strippinghez.)
És ezzel tulajdonképpen készen is vagyunk, a library működik. Illetve egy dolog még hátra van: az az eset, amikor nem a főprogram hívja meg a library valamelyik függvényét, hanem fordítva. Ezt kétféleképpen lehet megoldani: lokálisan és globálisan. Abban az esetben, amikor valamilyen aszinkron visszajelzést akarunk csinálni - azaz az az egy darab függvény fogja meghívni a főprogram valamely függvényét, akkor, amikor a főprogram őt meghívja - és a feladattól függően akár változhat, hogy mit is akarunk meghívatni, abban az esetben ezeket a callback függvényeket célszerű közvetlenül átadni a library egyes függvényeinek, tehát lokálisan megoldani. Amik viszont különféle segédfunkciókat valósítanak meg és több függvény is használhatja őket, bármikor, azokat az összes függvénynek el kell tudnia érni, azaz globálisan kell megoldani. Ez utóbbit két megközelítéssel lehet csinálni: automatikusan és manuálisan.
Automatikus megközelítés esetén a library-ben külsőnek megjelölt prototípusokat helyezünk el, a főprogramot pedig dinamikusan linkeljük; ilyenkor megnyitáskor a library fogja a saját függvényéhez kötni a főprogram szimbólumait.
Legyen egy új függvényünk a library kódjában. C:
int mylib_d(int *d)
{
*d += 5;
return mylib_caller(d);
}
Pascal:
function mylib_d(d: plongint): longint; cdecl;
begin
d^ := d^ + 5;
result := mylib_caller(d);
end;
Ne felejtsük el az interface fájlban az új függvényt betenni a "konténerbe", feloldani az inicializálófüggvényben, valamint Pascalban ne felejtsük el felvenni a library-ban az exports
listára sem.
Ez egy egyelőre még nem létező függvényt hív meg, ennek kell még a prototípusa. C:
extern int mylib_caller(int *ptr);
Pascal:
function mylib_caller(ptr: plongint): longint; cdecl; external name 'mylib_caller';
Ezek után már csak a főprogramba kell bebiggyeszteni ezt az új függvényt. C:
int mylib_caller(int *ptr)
{
*ptr += 5;
return *ptr + 5;
}
Pascal:
function mylib_caller(ptr: plongint): longint; cdecl;
begin
ptr^ := ptr^ + 5;
result := ptr^ + 5;
end;
És persze a meghívását is. C:
d = 12;
d2 = lib.mylib_d(&d);
fprintf(stdout, "a = %d, b = %d, c = %d, c2 = %d, d = %d, d2 = %d\n", a, b, c, c2, d, d2);
Pascal:
d := 12;
d2 := lib.mylib_d(@d);
WriteLn(StdOut, 'a = ' + IntToStr(a) + ', b = ' + IntToStr(b) + ', c = ' + IntToStr(c) + ', c2 = ' + IntToStr(c2) + ', d = ' + IntToStr(d) + ', d2 = ' + IntToStr(d2));
Ne felejtsük el a két új változót felvenni és Pascalban exportálni a főprogramban a függvényt. (exports mylib_caller;
)
A library fordítási opciói nem változnak, a főprogramé viszont igen: a linkernek át kell adni az --export-dynamic
kapcsolót. Ezt C-ben a már fentebb használt -Wl
segítségével lehet, míg Pascalban a -k
felel ugyanezért. Tehát C:
cc mytest.c -ldl -Wl,--export-dynamic -o mytest
Pascal:
fpc -k--export-dynamic mytest.pas
(A Pascalnál egy kellemetlen mellékhatása ennek a megközelítésnek, hogy a kapott bináris tízszer akkora lesz, mert egy tonna egyéb szimbólumot is belefordít...)
A manuális megközelítés esetén a külső függvényeknek pointereket kell betenni a library-be, a főprogramot pedig nem kell dinamikusan linkelni (azaz itt nem változik a főprogram fordítása az alap felálláshoz képest), hanem helyette a főprogramnak kell feloldania eme pointerek szimbólumait és betöltenie a helyükre az odavágó rutinjainak címét.
Tehát C-ben a függvény ezúttal így néz ki:
int (*mylib_caller)(int *ptr);
Pascalban pedig így:
var mylib_caller: function (ptr: plongint): longint; cdecl; CVar;
Ellentétben az automatikus megközelítéssel, itt értelemszerűen ezt a szimbólumot be kell tenni az interface-ben a konténerbe és fel is kell oldani, hogy a főprogram hozzáférjen. Viszont az interface-ben itt nem maga a függvény pointere kell, hanem egy arra mutató pointer, hogy a főprogram be tudja írni a megfelelő értéket. C-ben
typedef struct
{
...
int (**mylib_caller)(int *ptr);
}
Pascalban pedig
type
Tmylib_caller = function (ptr: plongint): longint; cdecl;
Pmylib_caller = ^Tmylib_caller;
Tmylib = record
...
mylib_caller: Pmylib_caller;
end;
Pmylib = ^Tmylib;
lesz a felállás és Pascal esetén a feloldás is ennek megfelelően alakul: Pmylib_caller
-re kell castolni, nem közvetlenül a Tmylib_caller
-re. A főprogram majdnem 100%-ban ugyanaz, mint az automatikus esetén, a különbség csak az, hogy itt a library megnyitása és a használata közé még be kell ékelni egy
*(lib.mylib_caller) = &mylib_caller;
sort C-ben és egy
(lib.mylib_caller)^ := @mylib_caller;
sort Pascalban, illetve ez utóbbiban az is eltér, hogy az exports mylib_caller;
itt nem a főprogramban kell, hanem a library-ben.
Hátra van még a callback-es felállás, az (értelemszerűen) ugyanaz mind automatikus, mind manuális linkelésnél: van egy függvényünk a library-ben, aminek át lehet adni egy másik függvény pointerét, amit belül meghívhat. C:
void mylib_e(int e, void (*callback_ptr)(int))
{
callback_ptr(e + 5);
}
Pascal:
type Tmylib_callback = procedure (val: longint); cdecl;
procedure mylib_e(e: longint; callback_ptr: Tmylib_callback); cdecl;
begin
callback_ptr(e + 5);
end;
Exportálást Pascalban nem elfelejteni, ahogy az interface-ben a feloldást és a konténerbe való felvételt sem (C)
typedef struct
{
...
void (*mylib_e)(int e, void (*callback_ptr)(int));
}
(Pascal)
type
Tmylib_callback = procedure (val: longint); cdecl;
Tmylib_e = procedure (e: longint; callback_ptr: Tmylib_callback); cdecl;
Tmylib = record
...
mylib_e: Tmylib_e;
end;
Pmylib = ^Tmylib;
Marad a főprogram, ahol először is kell maga a callback függvény (C)
void mylib_callback1(int val)
{
e = val + 5;
}
(Pascal)
procedure mylib_callback1(val: longint); cdecl;
begin
e := val + 5;
end;
és aztán a tényleges használat (C)
e = 13;
lib.mylib_e(e, &mylib_callback1);
fprintf(stdout, "a = %d, b = %d, c = %d, c2 = %d, d = %d, d2 = %d, e = %d\n", a, b, c, c2, d, d2, e);
(Pascal)
e := 13;
lib.mylib_e(e, @mylib_callback1);
WriteLn(StdOut, 'a = ' + IntToStr(a) + ', b = ' + IntToStr(b) + ', c = ' + IntToStr(c) + ', c2 = ' + IntToStr(c2) + ', d = ' + IntToStr(d) + ', d2 = ' + IntToStr(d2) + ', e = ' + IntToStr(e));
(Az új e
változó itt értelemszerűen globális, hiszen a fő függvényen kívül a callback függvény is közvetlenül fér hozzá.)
Nos, nagyjából ennyi lenne a pluginezés, legalábbis, ami a lényeget illeti, bár biztos van egy-két specifikus dolog, ami nem lett kivesézve. Pl. arra nem tértem ki, hogy mi van, ha a pluginek egymás függvényeit akarják hívogatni; nos ezt értelemszerűen úgy lehet megoldani, hogy vagy van a főprogramban egy wrapper/transmitter-callback erre a célra, vagy nemes egyszerűséggel a főprogram, miután feloldotta a szimbólumokat mind a két (vagy több) pluginben, egyszerűen az egyes pluginok megfelelő callback pointereire beírja a másik plugin függvényeinek címét. (Ez utóbbi felállás egyébként nem célszerű; két mezei library (nem plugin) közt simán működhet, még két eltérő interface-ű plugin közt is, bár ott is csak 1-1 közt, viszont azonos interface-ű pluginek garmadája esetén symbol-spagetti szintű, sok sikert kategóriás a dolog.)
Ha esetleg valami nem lenne tiszta, a komplett példákat, buildscripttel, tokkal-vonóval le lehet tölteni ezekről a linkekről:
C - Basic setup
Pascal - Basic setup
C - Automatically linked setup
Pascal - Automatically linked setup
C - Manually linked setup
Pascal - Manually linked setup
Egyébként írtam egy-egy alap unitot (C-hez és Pascalhoz is), ami ezeknek a feladatoknak a többségét elvégzi; allokálja a struct
-hoz/record
-hoz szükséges tárterületet, majd feloldja az átadott szimbólumokat és ha van callback pointer beállítva, akkor a szimbólum címére beírja azt is. (Direct cross-plugin calling not supported. :P) Kicsit közérthetőbben pl. a fenti manuálisan linkelt library használata így nézne ki C-ben vele:
#include <stdio.h>
#include "dynlib_handler.c"
typedef struct
{
void *handle;
int (*mylib_a)(int a);
void (*mylib_b)(int *b);
int (*mylib_c)(int *c);
int (*mylib_d)(int *d);
void (*mylib_e)(int e, void (*callback_ptr)(int));
void (**mylib_callback)(int *ptr);
} dynlib_handle;
int mylib_callback(int *ptr)
{
*ptr += 5;
return *ptr + 5;
}
dynlib_symbol_entry lib_entries[] =
{
{ "mylib_a", (void *)NULL },
{ "mylib_b", (void *)NULL },
{ "mylib_c", (void *)NULL },
{ "mylib_d", (void *)NULL },
{ "mylib_e", (void *)NULL },
{ "mylib_caller", (void *)&mylib_callback }
};
int lib_entry_count = sizeof(lib_entries) / sizeof(dynlib_symbol_entry);
int e;
void mylib_callback1(int val)
{
e = val + 5;
}
int main()
{
int err, serr, a, b, c, c2, d, d2;
char *libname = "./libmylib.so";
dynlib_handle *lib;
lib = create_dynlib_handler(libname, lib_entries, lib_entry_count, &err, &serr);
if (err != 0)
{
switch (err)
{
case DYNLIB_HANDLER_ERROR_CANNOT_ALLOC_MEMORY:
fprintf(stderr, "Cannot allocate memory.\n");
break;
case DYNLIB_HANDLER_ERROR_CANNOT_OPEN_LIBRARY:
fprintf(stderr, "Cannot open \"%s\".\n", libname);
break;
case DYNLIB_HANDLER_ERROR_MISSING_SYMBOL:
fprintf(stderr, "Missing symbol \"%s\".\n", lib_entries[serr].symbol);
break;
}
return e;
}
a = lib->mylib_a(9);
b = 10;
lib->mylib_b(&b);
c = 11;
c2 = lib->mylib_c(&c);
d = 12;
d2 = lib->mylib_d(&d);
e = 13;
lib->mylib_e(e, &mylib_callback1);
fprintf(stdout, "a = %d, b = %d, c = %d, c2 = %d, d = %d, d2 = %d, e = %d\n", a, b, c, c2, d, d2, e);
free_dynlib_handler(lib);
return 0;
}
És így Pascalban:
program mytest;
{$mode objfpc}{$H+}
uses dynlib_handler, SysUtils;
type
Tmylib_callback = procedure (val: longint); cdecl;
Tmylib_a = function (a: longint): longint; cdecl;
Tmylib_b = procedure (b: plongint); cdecl;
Tmylib_c = function (c: plongint): longint; cdecl;
Tmylib_d = function (d: plongint): longint; cdecl;
Tmylib_e = procedure (e: longint; callback_ptr: Tmylib_callback); cdecl;
Tmylib_caller = function (ptr: plongint): longint; cdecl;
Pmylib_caller = ^Tmylib_caller;
Tdynlib_handle = record
lib: TLibHandle;
mylib_a: Tmylib_a;
mylib_b: Tmylib_b;
mylib_c: Tmylib_c;
mylib_d: Tmylib_d;
mylib_e: Tmylib_e;
mylib_caller: Pmylib_caller;
end;
Pdynlib_handle = ^Tdynlib_handle;
var
err, serr, a, b, c, c2, d, d2, e: integer;
lib: Pdynlib_handle;
function caller(ptr: pinteger): integer; cdecl;
begin
ptr^ := ptr^ + 5;
result := ptr^ + 5;
end;
procedure mylib_callback1(val: longint); cdecl;
begin
e := val + 5;
end;
const
lib_entries: Tdynlib_symbols =
(
( symbol: 'mylib_a'; ext_func: nil ),
( symbol: 'mylib_b'; ext_func: nil ),
( symbol: 'mylib_c'; ext_func: nil ),
( symbol: 'mylib_d'; ext_func: nil ),
( symbol: 'mylib_e'; ext_func: nil ),
( symbol: 'mylib_caller'; ext_func: @caller )
);
libname = './libmylib.so';
begin
lib := Pdynlib_handle(create_dynlib_handler(libname, @lib_entries, @err, @serr));
if (err <> 0) then
begin
case err of
DYNLIB_HANDLER_ERROR_CANNOT_ALLOC_MEMORY:
begin
WriteLn(StdErr, 'Cannot allocate memory.');
end;
DYNLIB_HANDLER_ERROR_CANNOT_OPEN_LIBRARY:
begin
WriteLn(StdErr, 'Cannot open ' + libname + '.');
end;
DYNLIB_HANDLER_ERROR_MISSING_SYMBOL:
begin
WriteLn(StdErr, 'Missing symbol ' + lib_entries[serr].symbol + '.');
end;
end;
Halt(e);
end;
a := lib^.mylib_a(9);
b := 10;
lib^.mylib_b(@b);
c := 11;
c2 := lib^.mylib_c(@c);
d := 12;
d2 := lib^.mylib_d(@d);
e := 13;
lib^.mylib_e(e, @mylib_callback1);
WriteLn(StdOut, 'a = ' + IntToStr(a) + ', b = ' + IntToStr(b) + ', c = ' + IntToStr(c) + ', c2 = ' + IntToStr(c2) + ', d = ' + IntToStr(d) + ', d2 = ' + IntToStr(d2) + ', e = ' + IntToStr(e));
free_dynlib_handler(PPointer(lib));
end.
A unit is letölthető ezekről a linkekről:
C version
Pascal version
Asszem ennyi. Remélem sajtóhiba nem lesz a cikkben... :P
- TCH blogja
- A hozzászóláshoz be kell jelentkezni
- 465 megtekintés
Hozzászólások
Kapsz a fenti C so-dhoz egy Rust beillesztést is:
#[macro_use]
extern crate dlopen_derive;
use dlopen::wrapper::{Container, WrapperApi};
#[derive(WrapperApi)]
struct Api {
mylib_a: fn(arg: i32) -> u32,
mylib_b: fn(arg: &mut i32),
mylib_c: fn(arg: &mut i32) -> i32,
}
fn main() {
let cont: Container<Api> =
unsafe { Container::load("libmylib.so") }.expect("Could not open library or load symbols");
let a = 42;
println!("mylib_a retval: {}", cont.mylib_a(a));
println!("a: {}", a);
let mut b = 42;
cont.mylib_b(&mut b);
println!("b: {}", b);
let mut c = 42;
println!("mylib_c retval: {}", cont.mylib_c(&mut c));
println!("c: {}", c);
}
$ cargo new pelda
a main.rs-t a fentire cseréld ki
a Cargo.toml-be rakd a végére:
[dependencies]
dlopen = "0.1"
dlopen_derive = "0.1"
És mehet a
$ LD_LIBRARY_PATH=. cargo run --release
- A hozzászóláshoz be kell jelentkezni
A fenti C-vel kompatibilis lib-et is megírhatjuk Rust-ban (mylib.rs):
#![allow(dead_code)]
#[no_mangle]
fn mylib_a(a: i32) -> i32 {
a - 5
}
#[no_mangle]
fn mylib_b(b: &mut i32) {
*b -= 5;
}
#[no_mangle]
fn mylib_c(c: &mut i32) -> i32 {
*c -= 5;
*c - 5
}
$ rustc -O -crate-type=cdylib mylib.rs
- A hozzászóláshoz be kell jelentkezni
Köszi. Mondjuk én pont nem beszélek Rust-ül, de lehet valaki még hasznát veszi ennek a kiegészítésnek.
- A hozzászóláshoz be kell jelentkezni
Főként ezt a második kódot érdemes megnézned, és akkor .so állományt Rust-ban is alá tudsz tenni.
Ehhez: apt install cargo # felrakja a rustc-t és a cargo-t.
Amit nyersz:
- std-ben levő cuccokat kapásból (ehhez elég a rustc egy Makefile-ba, még nem kell cargo)
https://doc.rust-lang.org/std/
https://doc.rust-lang.org/std/collections/ # külön kiemelem az std-n belül ezt a részt, mert ez nagyon hatékony
- cargo célszerű, ha a https://crates.io/ jóságait is akarod használni.
A biztonságos programozáson túl kompakt kifejezései vannak. Lásd például: https://rust.godbolt.org/z/Tabs3aPhn
Én 15 évig C-ben programoztam. Alaposan ismerem. Megnéztem a C++, D, Go nyelveket C helyett. Egyike sem fogott meg.
Ellenben a Rust megfogott rengeteg olyan dologra, amit eddig C-ben írtam. De ezt pár hónapja megvitattuk. :)
- A hozzászóláshoz be kell jelentkezni
Köszi, én egyelőre nem tervezem, hogy nekiállnék Rust-özni, de másnak még hasznos lehet.
- A hozzászóláshoz be kell jelentkezni