Hlavní navigace

Kylix (5) - knihovny, plug-iny a vlákna

Radek Červinka

Pátý díl našeho seriálu o Kylixu je tady. Dnes nás čekají sdílené knihovny, jejich využití v našem programu, a ukážeme si, jak napsat jednoduchý plug-in. Dále nakousnu, jak jednoduše použít vlákna. Nakonec se jako obvykle dozvíte o nějaké zajímavé komponentě.

Jak jste si jistě všimli, snažím se spíše publikovat řešení v co nejstručnější podobě (většinou konzolové aplikace). Je to z důvodu, že chci ukázat princip, do normální aplikace si to každý jistě vloží. V tomto trendu budeme dnes ještě částečně pokračovat.

Knihovny

V dnešní době je výhodné využívání sdílených knihoven .so (opětovné využití kódu, jednoduchá aktualizace a oprava chyb). Kylix vytváření sdílených knihoven samozřejmě podporuje.

Pro vytvoření sdílené knihovny stačí vybrat v menu File – New položku Shared library a posléze IDE vytvoří kostru sdílené knihovny.

Vytvoření sdílené knihovny

Sdílená knihovna se od programu liší klíčovým slovem Library místo Program. Dále se musí uvést seznam exportovaných funkcí uvozených klíčovým slovem Exports.

Vlastní knihovna (uložená v simple.dpr):

library simple;
function Math_Name:Char; stdcall; // první funkce
begin
  Result:='+';
end;

function Math_Oper(a,b:Integer):Integer; stdcall; // druhá funkce
begin
  Result:=a+b;
end;

exports
  Math_Name, Math_Oper;
end.

Tímto jsme vytvořili jednoduchou sdílenou knihovnu s názvem libsimple.so, která exportuje celé 2 (slovy dvě) funkce a to s volací konvencí stdcall. O tom, že exportujeme naše dvě funkce nás přesvědčí readelf -a libsimple.so.

Poznámka: pokud neuvedeme exports, tak linker usoudí, že funkce jsou nepoužívané a jednoduše je nepřilinkuje.

Volací konvence

Postupem času se ustálilo několik možností, jak předávat parametry do procedur (volací konvence). Jelikož je to velmi důležité (zvláště, když používáme cizí knihovnu), uvedu malou tabulku:

Tabulka č. 275
Direktiva Pořadí parametrů Uvolnění Používá registry
register zleva doprava rutina ANO
pascal zleva doprava rutina NE
cdecl zprava doleva volající NE
stdcall zprava doleva rutina NE
safecall zprava doleva runtina NE

Nejčastěji explicitně uvedenou konvencí v Linuxu bude cdecl – používají ji kompilátory jazyka C, tudíž, jestliže chceme použít knihovnu napsanou v Cčku, je nutno uvést tuto konvenci.

Pokud neuvedeme nic (skoro vždy), je implicitní volací konvence register (někdy označovaná jako fast-call), která umožňuje předat až tři parametry v registrech procesoru (EAX, EDX, ECX – zbytek je vložen na zásobník). Pokud chcete vědět, jakým způsobem je který typ předáván, koukněte do helpu na heslo Parameter passing.

Použití sdílené knihovny

Jelikož v Linuxu existuje spousta zajímavých knihoven, byla by škoda je nevyužít. Postup předvedu na výše uvedené knihovně, ale u jiných by to bylo podobné.

Nejdříve stvoříme „importní unit“ (uSimpleImp.pas):

unit uSimpleImp;
// Importní unit pro sdílenou knihovnu libsimple.so
interface
function Math_Name:Char; stdcall;
function Math_Operation(a,b:Integer):Integer;stdcall;

implementation
const clibsimple='libsimple.so'; // jméno knihovny
function Math_Name:Char; external clibsimple;
function Math_Operation(a,b:Integer):Integer;
  external clibsimple name 'Math_Oper';
end.

Všimněte si, že u druhé funkce jsem změnil jméno, takže i když je v knihovně uvedena jako Math_Oper, bude v programu vystupovat jako Math_Operation. Myslím, že je to naprosto jasné. Mimochodem: v implementační části nemusíme psát celou deklaraci.

Hlavní program je naprosto přímočarý (TestSimple.dpr):

program TestSimple;
uses
  SysUtils,
  uSimpleImp in 'uSimpleImp.pas';
begin
  Writeln(Format('%d %s %d = %d',[1, Math_Name, 2,
    Math_Operation(1,2)]));
end.

Plug-in

Nevíte, proč zásuvné moduly (jak zní prkenný překlad) používat? Pokud dobře navrhnete rozhraní, můžete očekávat, že i dlouho po vytvoření hlavního programu ho bude možno rozšiřovat (a to v různých programovacích jazycích). Příklady takových programů: ve Windows např. WinCommander, WinAmp, v Linuxu XMMS, Gnumeric a další.

Jelikož plug-in má určené rozhraní, tak se přímo nabízí implementovat ho jako objekt. S výhodou využijeme procedurální typ (viz minule a v následujícím příkladě). Pro spravování objektů s plug-iny použijeme nějakého následníka TList, nejlépe TObjectList, kde přidáme pár metod jako AddPlugIn, DeletePlugIn a nějaké property, které bude indexem vracet příslušný objekt. Že jsem se ještě o této možnosti u property nezmínil? Tak to hnedle napravíme:

type
  TPlugInWrapper=Class; // třída zapouzdřující plug-in nás nezajímá

  TPlugInsList=Class(TObjectList)
  protected
    function Get(Index:Integer):TPlugInWrapper;
  public
    function AddPlugIn(aPlugIn:TPlugInWrapper):Integer;
    procedure DeletePlugIn(Index:Integer);
      //read-only property
    property PlugIn[Index:Integer]:TPlugInWrapper read Get;
  end;

implementation

function TPlugInsList.Get(Index:Integer):TPlugInWrapper;
begin
  Result:=TPlugInWrapper(Items[Index]);
end;

function TPlugInsList.AddPlugIn(aPlugIn:TPlugInWrapper):Integer;
begin
  Result:=Add(aPlugIn);
end;

procedure TPlugInsList.DeletePlugIn(Index:Integer);
begin
  Delete(Index);
end;
end.

Pokud máme u TPlugInWrapper definovaný des­tructor, tak ho ObjectList před svým vlastním uvolněním postupně zavolá pro všechny své položky. Je evidentní, že je to ideální místo pro uvolnění (nahrané) knihovny.

Poznámka: jednou jsem potřeboval napsat program, který by fungoval na principu plug-inů, ale s tím, že jednotlivé plug-iny se mohou zavést do paměti vícekrát. Jenže knihovny sdílí všechna data pro všechny instance knihovny v paměti (což je někdy výhodné), takže žádné globální proměnné (jako handly na soubory apod.) jsem nemohl používat. Vyřešil jsem to předáváním ukazatele na záznam, který byl zaalokován hlavním programem jako blok paměti (globalní proměnné se u každého plug-inu lišily) a plug-in pouze řekl, kolik paměti záznam zabírá, a paměť si přetypoval. Dále jsem ještě pro každý plug-in vytvořil kritickou sekci, aby se vždy od každého plug-inu prováděl v jednom okamžiku pouze jeden.

Demo aplikace

V další části zkusím napsat jednoduchou GUI aplikaci, která bude využívat plug-iny. Cílem je najít všechny plug-iny na zadané cestě a voláním exportované funkce Math_Name zjistit, jakou že to operaci implementuje. Předesílám, že mi jde spíše o princip než o eňoňuňo aplikaci. Pro dnešek ještě uvedu postup vytváření aplikace.

Vytvořte nový projekt a zároveň ho uložte pod nějakým jménem. Hlavní formulář uložte pod jménem fMain.pas. U hlavního formuláře změňte jméno na frmMain a zvolte nějakou šikovnou popisku (Caption). Poslední věcí u formuláře je nastavení vlastnosti BorderStyle na fbsDialog (u okna nelze měnit rozměry).

Nyní na připravený formulář vložte komponentu GroupBox a změňte její vlastnost Caption na „Plug-ins“. Na její plochu vložte komponentu ListView (se jménem lvPlugIn) a nastavte u ní ViewStyle na vsReport (tím budeme mít zobrazeno více sloupců). Po stisku pravého tlačítka nad vloženou komponentou se rozbalí menu, a z něj vyberete položku Columns editor. V něm vložíte tři položky a změníte jejich Caption na „Library name“, „Implemented function“ a „Size“.

Pod ListView vložíte komponetu Edit (edtPath) a dvě tlačítka (btnPath a btnFind) a upravíte je do stavu jako na obrázku. Můžete také upravit vlastnost Hint, která obsahuje text plovoucí nápovědy. V tom případě byste měli ještě nastavit ShowHint na True. Pojmenovávám pouze ty komponenty, na které se budu v kódu odkazovat. Pro jistotu připomínám, že pod jménem komponenty mám na mysli její vlastnost Name.


Program za běhu.

Tímto je uživatelské rozhraní prakticky hotovo a můžeme přistoupit k druhé části, kterou je oživení. Pokud v tomto okamžiku program spustíte, vypadá sice hezky (no dobře – beru zpět), ale nic nedělá.

Oživení bude spočívat v obsluze dvou událostí:

  • OnClick u btnPath – způsobí výběr cesty do sousedního editboxu
  • OnClick u btnFind – způsobí naplnění ListView nalezenými knihovnami

Výběr cesty
Uvedu přímo kód:

procedure TfrmMain.btnPathClick(Sender: TObject);
var
  sDir:String;
begin
  if SelectDirectory('Select directory',edtPath.Text, sDir,True) then
    edtPath.Text:=sDir+'/';
end;

Nalezení našich plug-inů
Toto je klíčový kód celého programu, takže jdeme na to:

procedure TfrmMain.btnFindClick(Sender: TObject);
type
  TIDProc=function:Char; stdcall; // prototyp identifikacni funkce
var
  sSearchFile:String;
  sr:TSearchRec;
  handle:Pointer;
  IDProc:TIDProc; // typ funkce
begin
  lvPlugIn.Items.Clear; // smaž možné předchozí položky
  sSearchFile:=IncludeTrailingPathDelimiter(edtPath.Text)+'*.so';
  if FindFirst(sSearchFile, faAnyFile,sr)=0 then
    repeat
    //nalezen nějaký soubor, zkusíme, zda je to naše knihovna
      handle:=dlopen(PChar(ExpandFileName(sr.Name)), RTLD_LAZY);
      if not assigned(handle) then Continue;
      try
        @IDProc:=dlsym(handle,'Math_Name'); // najdi naši funkci
        if assigned(@IDProc) then
        // je to naše knihovna, přidej ji do ListView
        with lvPlugIn.Items.Add do // vytvoř novou položku
        begin
          Caption:=sr.Name; // a vlož tam jméno knihovny
     { nelze nijak zjistit, zda parametry odpovídají -
     (tiše předpokládám, že mi nikdo nepodhodil .so se stejnou funkcí :)}
          SubItems.Add(IDProc); // to, co implementuje, zjistíme voláním
          SubItems.Add(IntToStr(sr.Size)) // a její velikost
        end;
      finally
        dlclose(handle); // zavři knihovnu
      end;
    until FindNext(sr)<>0; // a znovu
end;

Jelikož budeme používat některá volání z libc, musíme nejdříve ještě doplnit do implementační části uses Libc;. Dáme to do implementační čísti, jelikož v deklarační části (interface) nic z tohoto unitu nepotřebujeme.

Pokud chceme mít úplně přenositelný kód, musíme pro funkce z Libc použít podmíněný překlad, nebo použít zapouzdření od www.delphi-jedi.org.

A ještě deklarujeme prototyp volání funkce v knihovně (v sekci Type).

A teď vlastní program. Smažeme případné položky v ListView a získáme zadanou cestu, ke které připojíme (pokud tam není) koncové lomítko a zadáme, že chceme všechny soubory, které končí na „*.so“. Pokud nějaký nalezneme, zkusíme zavést jako knihovnu. Pokud se nám povedlo zavedení, zkusíme v něm najít naši funkci a její adresu přiřadíme do procedurální proměnné IDProc. Nenulová adresa značí existenci symbolu.

Zde bych rád uvedl, že podle mě neexistuje jednoduchý způsob, jak zkontrolovat, že jde o naši funkci s naším počtem a typem parametrů. Prostě musíme zvolit dostatečně unikátní jméno této poznávací funkce (což v našem případě není).

Pokud se tedy jedná o naši knihovnu, přidáme do ListView jeden záznam (Add vrací nově vytvořený záznam – viz Help). Poznámka: SubItems jsou typu TStrings – viz. minulý díl.

Volání dlclose je v sekci finally, jelikož sekce finally se provádí vždy (při chybě i bez ní), ale dobře si všimněte, kde je try. Tento způsob je často používán, chybu sice nijak neočekáváme, ale když k ní nahodou dojde, jsou zabrané prostředky uvolněny.

Zdrojové kódy (2 Kb)

Balíčky – packages

Kylix dále umožňuje vytvořit zvláštní druh knihoven, které se nazývají balíčky. Balíček obsahuje přeložený kód, který může být svázán s jiným balíčkem. Důležité je, že existuje několik balíčků, ve kterých je umístěna celá knihovna CLX a ve kterých se kód neopakuje.

Příklad: pokud vytvoříme normální program a k němu jednu knihovnu, každý z nich bude obsahovat určitý kód, který se bude v obou opakovat.

Pokud ale obojí vytvoříme tak, aby používaly balíčky, bude společný kód umístěn v balíčku.

Navíc pokud si vytvoříme vlastní balíček, můžeme u něj definovat závislosti na jiných balíčcích.

Zde musím uvést, že standardní balíčky jsou docela velké. Nastavení kompilace pro balíčky je v menu Project – Options – Packages – Run Time Packages. Výše uvedená GUI aplikace má přeložená s balíčky asi 33 Kb.

Vlákna

Kylix používá pro implementaci vláken knihovnu pthread. Můžete využívat všechny její možnosti, ale lepší je použít její zapouzdření, jak je nabízí Kylix ve třídě (TThread). Poznámka: samozřejmě lze použít také fork. Jednoduchý příklad uvedu někdy příště.

Zmíněná třída je abstraktní (nelze vytvořit její instanci). Pro využití je tedy nutno od zmíněné třídy zdědit. Pokud se vám nechce psát, lze v menu File – New zvolit vygenerování základní kostry objektu.

unit
  uTestThread;
interface
type
  TTestThread = class (TThread)
  protected
    procedure Execute; override; //předefinujeme klíčovou metodu
  end;
implementation
procedure TTestThread.Execute;
var
  xIndex:Integer;
begin //při ukončení této metody skončí vlákno
  xIndex:=0;
  repeat
    Inc(xIndex);
    sleep(100); //ať to chvilku potrvá
  until (xIndex>1000) or terminated;
end; //konec vlákna
end.

Je evidentní, že vlákno nic rozumného nedělá. Důležitá je proměnná terminated, která obsahuje požadavek na předčasné ukončení běhu vlákna. Samozřejmě, že se dá vlákno zabít i systémovým voláním, ale toto je čistější řešení.

Pokud chcete zajímavější příklad na vlákna, podívejte se do adresáře demos/threads.

Třída TThread publikuje několik zajímavých metod:

  • konstuktor Create – má jeden parametr, který určuje, zda bude vytvořené vlákno pozastaveno
  • Suspend – pozastaví spuštěné vlákno
  • Resume – obnoví běh zastaveného vlákna
  • Terminate – nastaví Terminated na True
  • WaitFor – čeká na dokončení běhu
  • Synchronize – zabezpečí serializaci provádění – používá se k přístupu k objektům CLX, které nejsou bezpečné vůči vláknům

Synchronizace provádění

S vlákny těsně souvisí jejich synchronizace. Kromě klasických přístupů, jak je nabízí Libc, zapouzdřuje Kylix některé synchronizační objekty, aby bylo dosaženo portability: TEvent, TMultiReadExclu­siveWriteSynchro­nizer nebo TCriticalSection.

Komponenty – Reporty

Jak jsem již uvedl dříve, komponenty QuickReport nebyly portovány pro CLX, tudíž je nutné pro výstupní sestavy použít jiné komponenty.

Docela slušně vypadají komponenty FastReport, které jsou bohužel shareware. Na stejné stránce lze také stáhnout FreeReport, které jsou sice se zdrojáky, ale jen pod Windows. Na sourceforge se objevil projekt freereport, který je pod GPL (zatím jen pod Windows).

URL: www.fast-report.com

Existují také jiné komponenty, např. Synea.

Zatím nejlepší seznam komponent pro Kylix je k nalezení zde: www.torry.net/com­p_klx_interfa­ce.htm

V porovnání s počtem komponent pro Delphi je to zatím slabší, i když některé komponenty pro Delphi se dají jednoduše upravit. Nezapomínejme, že Kylix je stár pouze něco přes rok. Jinak jsem tam narazil na komponenty, které by měly nahradit komponentu na vykreslení různých typů grafů z Delphi.

Našli jste v článku chybu?

18. 5. 2002 17:31

Atlasz (neregistrovaný)

Kopa lidi rika, ze Object Pascal je jenom znetvorenim stejne mrtveho Pascalu. Koukl jsem se na c++... hmm... "Object C" No comment... Muj nazor je, ze na tom neni o moc lepe.
A k Jave: Jesti na jednem servru potrebuje spoustet nekolik uzivatelu 20 javovskych aplikaci pracujicich s databazi, se stava pouzivani tve oblibene Javy hodne zajimave. Napr. se spravou pameti. Skus v tom jazyce nekdy taky napsat neco vetsiho, nez aplet pro web. Skus nekdy trochu mene kecat. Ja chci programovat pro PC a n…

17. 5. 2002 11:33

chicken soup (neregistrovaný)

object pascal je stejne jen hnilobna mrsina, ktera povstala jiz z davno mrtveho pascalu.
Autor NENI nekrofil.
Autor ma rad Javu, Linux a pivo.



Vitalia.cz: 9 největších mýtů o mase

9 největších mýtů o mase

DigiZone.cz: „Black Friday 2016“: závěrečné zhodnocení

„Black Friday 2016“: závěrečné zhodnocení

Podnikatel.cz: Přehledná titulka, průvodci, responzivita

Přehledná titulka, průvodci, responzivita

Lupa.cz: Kdo pochopí vtip, může jít do ČT vyvíjet weby

Kdo pochopí vtip, může jít do ČT vyvíjet weby

Vitalia.cz: Jsou čajové sáčky toxické?

Jsou čajové sáčky toxické?

Vitalia.cz: Proč vás každý zubař posílá na dentální hygienu

Proč vás každý zubař posílá na dentální hygienu

120na80.cz: Na ucho teplý, nebo studený obklad?

Na ucho teplý, nebo studený obklad?

DigiZone.cz: Recenze Westworld: zavraždit a...

Recenze Westworld: zavraždit a...

Podnikatel.cz: Podnikatelům dorazí varování od BSA

Podnikatelům dorazí varování od BSA

Měšec.cz: Zdravotní a sociální pojištění 2017: Připlatíte

Zdravotní a sociální pojištění 2017: Připlatíte

120na80.cz: Co všechno ovlivňuje ženskou plodnost?

Co všechno ovlivňuje ženskou plodnost?

Lupa.cz: Proč firmy málo chrání data? Chovají se logicky

Proč firmy málo chrání data? Chovají se logicky

Vitalia.cz: I církev dnes vyrábí potraviny

I církev dnes vyrábí potraviny

Vitalia.cz: Znáte „černý detox“? Ani to nezkoušejte

Znáte „černý detox“? Ani to nezkoušejte

Podnikatel.cz: Udávání kvůli EET začalo

Udávání kvůli EET začalo

Vitalia.cz: Co pomáhá dítěti při zácpě?

Co pomáhá dítěti při zácpě?

Vitalia.cz: Když přijdete o oko, přijdete na rok o řidičák

Když přijdete o oko, přijdete na rok o řidičák

Vitalia.cz: Taky věříte na pravidlo 5 sekund?

Taky věříte na pravidlo 5 sekund?

120na80.cz: Pánové, pečujte o svoje přirození a prostatu

Pánové, pečujte o svoje přirození a prostatu

Lupa.cz: Co se dá měřit přes Internet věcí

Co se dá měřit přes Internet věcí