Image
30.6.2016 0 Comments

C++ pod Windows / ActiveX & DLL / 25. časť

Späť na úvod >> Späť na programovanie >> Späť na seriál

Vítam vás pri predposlednej časti seriálu C++ pod Windows. Či už ju čítate na Slovensku v prehriatej kancelárii, alebo niekde v Karibiku s nohami namočenými v mori, obletovaní miestnymi krásavicami a s havanou v ústach – som rád, že ste si našli čas. Opäť budeme pokračovať v načatom rozprávaní a dokončíme komponenty ActiveX a povieme si niečo o písaní a používaní knižníc DLL.

Použitie komponentov ActiveX v programoch. V predošlej časti sme si zhruba povedali, čo sú komponenty ActiveX, a ak ste si pozreli odporúčané témy v helpe, mali by ste mať dostatok informácií aspoň na základnú orientáciu sa v pojmoch. Mali by ste teda vedieť, že komponenty ActiveX majú príponu DLL alebo OCX, že na ich využívanie je potrebné ich najprv zaregistrovať (program regsvr32.exe) a že okno, ktoré obsahuje komponent ActiveX, sa nazýva kontajner. Ak ste niekedy programovali vo Visual Basicu, potom sa vám bude zdať používanie ActiveX veľmi povedomé. Každý komponent ActiveX má svoje vlastnosti (properties), metódy (methods) a udalosti (events) (v helpe nájdete, čo na čo slúži). Ich zoznam a opis sa obyčajne dodáva spolu s komponentom ako súbor pomocníka (*.hlp). Prístup k nim máte cez objekt komponent, ktorý si v programe musíte vytvoriť.

Obr. 1  Pridanie komponentu do projektu

Príklad 1 – použitie komponentu Calendar. Komponent Calendar je v štandardnej dodávke Windows (súbor mscal.ocx v systémovom adresári Windows) a jeho novšia verzia sa dodáva aj v balíku Office. Použijeme ho na objasnenie použitia komponentov ActiveX v programoch. Naprogramujeme jednoduchú aplikáciu, ktorá umožní nastaviť rok v kalendári podľa nášho zadania v textovom poli. Vytvorte si čisté Workspace TFive a pridajte doň nový projekt typu MFC AppWizard exe s názvom XCalendar. Vyberte dialógovú aplikáciu. Uistite sa, že v kroku 2/4 je zaškrtnutá podpora pre komponenty ActiveX (ActiveX Controls). Ak už máte vytvorenú kostru, vložte do projektu ActiveX komponent mscal.ocx. Z menu Project > Add to Project > Components and Controls (tzv. galéria komponentov – pozri help). V zložke Registered ActiveX Controls nájdite Calendar Control X.X (X.X – verzia, závisí od verzie Windows, prípadne Office) – obr. 1.

V poli path to control vidíte fyzické umiestnenie komponentu na vašom disku. Ak kliknete na tlačidlo More Info, zobrazí sa vám help, kde nájdete spomínaný zoznam a opis všetkých vlastností, metód a udalostí, ktoré daný komponent podporuje. Kliknite na Insert. Po potvrdení, že naozaj chcete tento komponent do projektu vložiť, sa vám zobrazí dialóg, aký vidíte na obr. 2. Tento dialóg vás informuje, aké triedy budú pridané do vášho projektu na podporu vkladaného komponentu. Vyberte obidve triedy.

Kliknite na OK a zavrite dialóg Components and Controls Gallery. V Toolboxe Controls vám pribudla jedna ikona, reprezentujúca pridaný ActiveX ovládací prvok Calendar. Pridajte ho do zdroja dialógu ako bežný ovládací prvok a pridajte ešte jedno tlačidlo a jedno textové pole (tlačidlá OK a Cancel môžete odstrániť). Výsledný zdroj dialógu vidíte na obr. 3.

ID tlačidla zmeňte na IDC_BTN_NASTAV_ROK a nastavte mu vlastnosť Default Button (Caption vidíte na obr.). ID textového poľa nastavte na IDC_EDIT_ROK. Tlačidlu pridajte obsluhu správy BN_CLICKED – funkcia OnBtnNastavRok. Komponentu Calendar (IDC_CALENDAR1) pridajte členskú premennú m_calendar kategórie Control. Ovládaciemu prvku IDC_EDIT_ROK pridajte premennú m_sYear typu short (rozsah 1900-2100). Teraz už stačí dopísať kód do funkcie OnBtnNastavRok:

void CXCalendarDlg::OnBtnNastavRok()
{
      UpdateData(TRUE);
 
      // include potrebnych hlavickovych
      // suborov na deklarovanie
      // objektu m_calendar vykona automaticky
      // Class Wizard
      m_calendar.SetYear(m_sYear);
}

Tým sa naša práca skončila. Projekt skompilujte, zlinkujte a vyskúšajte jeho funkčnosť. Ak chcete vedieť, čo všetko môžete robiť s komponentom Calendar, pozrite sa do zdrojového súboru triedy CCalendar na zoznam všetkých členských funkcií. Niektoré si vyskúšajte. Všetky tieto funkcie majú niečo spoločné. Bez ohľadu na to, akú zložitú operáciu vykonávajú, každá volá len jednu „všemocnú“ funkciu InvokeHelper, ktorá všetko zariadi. Táto funkcia „vyvoláva“ (invoke) vlastnosti a metódy komponentu ActiveX. Akú, to určuje jej prvý parameter (tzv. dispatch ID). Ďalšie parametre nás teraz nemusia zaujímať (pre záujemcov je opäť k dispozícii help).

Knižnice DLL. Určite ste sa už pri svojej práci so systémom Windows stretli s knižnicami DLL (čiže s dynamicky linkovanými knižnicami). Teraz si ukážeme, ako môžeme takéto knižnice využiť pri písaní nášho softvéru. Najskôr však trocha teórie. Knižnica DLL je osobitný súbor (programový modul) uložený mimo programu (procesu), ktorý ho využíva. Obsahuje skompilované a zlinkované funkcie a iné dáta. Je nahrávaná počas behu (runtime) programom, ktorý ju využíva (môže to byť buď klasický program – EXE –, alebo aj ďalšia knižnica – DLL). Tento program sa nazýva klientsky program (klient).

Obr. 2 Triedy pridávané do projektu pri vkladaní komponentu Calendar

Po nahraní do pamäte je namapovaná do pamäťového priestoru klienta. Ako sme uviedli, obsahuje funkcie a „iné dáta“, pod ktorými sa myslia napríklad rôzne premenné, zdroje, triedy C++ a pod. Funkcie môžu byť dvojakého typu. Sú to buď tzv. interné funkcie, ktoré môže používať knižnica DLL ako svoje pomocné funkcie na vykonávanie „interných“ operácií. Môžu byť volané len vnútri knižnice DLL a žiadny iný modul k nim zvonku nemá prístup. A potom sú tu tzv. exportované funkcie, ktoré, ako z ich názvu vyplýva, sú určené na „export“, teda ich môžeme volať z iného programového modulu. Z hľadiska ich využitia môžeme zjednodušene prirovnať interné funkcie k súkromným a externé funkcie k verejným metódam v klasických triedach C++.

Obr. 3 Zdroj dialógu aplikácie XCalendar

Aby to však nebolo také jednoduché, existujú dva spôsoby, ako exportovať funkcie z knižnice DLL. Prvý a jednoduchší z nich je exportovať ich pomocou mena. Znamená to deklarovať každú funkciu v knižnici pomocou kľúčového slova __declspec a rozšíreného atribútu dllexport. Napríklad deklaráciou:

__declspec(dllexport) char* CopyBText(char* strInput)

sme zaviedli funkciu CopyBText vracajúcu reťazec s jedným vstupným parametrom – takisto reťazcom. Aby sme ju mohli použiť v inom module, musíme ju tam zase naimportovať:

__declspec(dllimport) char* CopyBText(char* strInput)

Takýto zápis pri použití C++ kompilátora však môže predstavovať problém. Ten totiž vygeneruje pre funkciu tzv. dekorované meno (čo je meno pozostávajúce z mena triedy, funkcie a zoznamu parametrov). S týmto menom nevedia pracovať iné jazyky (dokonca ani klasické céčko). Preto sa odporúča zadefinovať pre exportované (a importované) funkcie externé C linkovanie:

extern "C" __declspec(dllexport) char* CopyBText(char* strInput)
extern "C" __declspec(dllimport) char* CopyBText(char* strInput)

To však ešte nie je všetko. Takto zadeklarovanú funkciu v knižnici by sme nemohli použiť napríklad v jazyku Visual Basic. Prečo? Kompilátor implicitne používa na odovzdávanie parametrov konvenciu __cdecl (parametre funkcie vyberá zo stacku volajúci program). Visual Basic (a iné) vyžaduje konvenciu __stdcall (parameter zo stacku vyberá volaná funkcia). Takže pre tento prípad musíme použiť takúto deklaráciu exportovanej funkcie:

extern "C" __declspec(dllexport) char* __stdcall CopyBText(char* strInput)

Ak chcete, aby všetky funkcie dodržiavali konvenciu __cdecl (resp. __stdcall), môžete do zoznamu parametrov kompilátora v nastavení projektu knižnice DLL pridať prepínač /Gd (resp. /Gz).

To je všetko, čo sa týka exportovania funkcií pomocou mena. Druhým, zložitejším spôsobom je exportovať funkcie pomocou ich ordinálneho čísla. Používa sa na to súbor definícií (.DEF). V praxi je momentálne tento spôsob pre svoju relatívnu zložitosť málo používaný (boli časy – vo Win16 to bol preferovaný spôsob, vo Win32 už Microsoft odporúča exportovanie pomocou mena), a preto sa mu ani nebudeme venovať. Pre zvedavcov je tu help. Takisto sa nebudeme venovať práci s knižnicami pomocou funkcie LoadLibrary, ktorá zabezpečuje explicitné nahranie knižnice do pamäte z modulu, ktorý ju využíva. Túto prácu prenecháme linkeru (použijeme implicitné linkovanie) – viac na príklade (záujemcovia o explicitné linkovanie nájdu informácie v helpe).

Prečo DLL? Ešte vám nie je jasné, ako by ste mohli knižnice DLL využiť vo svojich projektoch? Môžem vám z vlastnej skúsenosti povedať, že DLL-ky sa v programátorskej praxi používajú veľmi často. Veď načo vytvárať niekoľkomegové súbory exe, ktoré vznikajú pri použití štandardných knižníc LIB, keď máme možnosť rozdeliť funkčnosť na niekoľko samostatných modulov a tie v runtime spojiť? Takisto sa výrazne zjednodušuje ladenie takto delenej aplikácie. Stačí si samostatne odladiť jednotlivé DLL-ky a v prípade problémov efektívne zasahovať len v module, kde sa vyskytla chyba (dokonca môžete priamo z vývojového prostredia „spustiť“ a odladiť knižnicu DLL, stačí len nastaviť cestu ku klientovi). Silu DLL knižníc dokumentuje aj samotný operačný systém Windows, ktorého základná funkčnosť je tiež podelená do rôznych knižníc DLL (napr. kernel32.dll, security.dll). MFC je tiež dobrým príkladom (knižnica mfc42.dll).  

Knižnice DLL a MFC. Ak chcete vytvoriť knižnicu DLL bez podpory MFC, môžete na to využiť voľbu Win32 Dynamic-Link Library pri vytváraní nového projektu. Ak chcete vo vašej knižnici využívať aj MFC, bude lepšie, ak využijete možnosti poskytované aplikačným systémom a vytvoríte ju pomocou App Wizardu pre knižnice DLL. Ak si zvolíte túto cestu, zistíte, že existujú dva typy MFC knižníc – regulárne (regular), dokonca dva druhy, a rozširujúce (extension DLLs). Aby ste si vedeli správne vybrať, ktorý typ využijete, musíte poznať základný rozdiel medzi nimi. A ten je v tomto prípade veľmi jednoduchý a jednoznačný. Regulárne knižnice MFC podporujú štandardné céčkovské rozhranie, čiže nedokážu exportovať prvky, ktoré podporuje jazyk C++ (t. j. nedokážu exportovať triedy, preťažené funkcie a pod.). Samozrejme, v týchto knižniciach môžete využívať prvky jazyka C++, ibaže tie časti kódu, ktoré ich využívajú, nemôžete exportovať. Naproti tomu rozširujúce knižnice MFC plne podporujú C++ rozhranie, a preto dokážu exportovať všetky prvky jazyka C++. Možno sa vám teraz zdá existencia regulárnych knižníc MFC zbytočná, no z hľadiska kompatibility s inými jazykmi má svoje opodstatnenie. Ak chcete, aby vaša knižnica MFC fungovala bez problémov s inými jazykmi (napr. Visual Basic), mali by ste ju vytvoriť ako regulárnu. Takisto ak neplánujete exportovať žiadne C++ prvky, mali by ste použiť regulárnu knižnicu (ak neplánujete využívať MFC, môžete, samozrejme, použiť aj klasickú Win32 knižnicu).

Statické a „zdieľané“ linkovanie s MFC. V prípade, že vytvárate regulárnu knižnicu MFC, máte na výber, či ju chcete s knižnicou MFC linkovať staticky alebo dynamicky (synonymum pre zdieľané linkovanie). Táto téma je veľmi podrobne opísaná v helpe (pozrite si topic: regular DLLs). Takže len stručne. Ak si zvolíte statické linkovanie, do vašej knižnice sa automaticky nakopíruje aj kód knižnice MFC (konkrétne je to knižnica mfc42.dll – platí pre Release mód). Zväčší to síce vašu výslednú DLL-ku a zníži sa rýchlosť prekladu, ale máte istotu, že na PC u zákazníka váš program nebude padať pre chýbajúcu alebo nekompatibilnú knižnicu MFC (veľmi časté problémy, hlavne ak vyvíjate na systéme NT a zákazník má 9.x). Ak zvolíte dynamické linkovanie (use MFC in a shared DLL), žiadny kód sa do vašej knižnice nekopíruje a všetko sa zostavuje až počas behu. Je teda úplne na vás, ktorú možnosť využijete – záleží na situácii. Statické linkovanie generuje naozaj odpudivo veľké výstupné moduly, ale je bezpečné.

Obr. 4 Vytváranie regulárnej knižnice MFC

Naproti tomu na obhajobu dynamického linkovania treba povedať, že väčšina problémov sa dá eliminovať kvalitným inštalačným programom (v súčasnosti napríklad vo Windows Installeri 1.1 je to otázka dvoch kliknutí). Statické a dynamické linkovanie môžete, samozrejme, použiť aj na klasické EXE programy, nielen na knižnice DLL (pozri Project Settings, karta General – poznámka: Statické linkovanie podporuje len Visual C++ verzie Professional a Enterprise). Varovanie: Veľký pozor si treba dať pri písaní rozširujúcich knižníc MFC. Rozširujúca knižnica totiž môže byť linkovaná len dynamicky. Túto podmienku však musí spĺňať aj klientsky program, ktorý túto knižnicu využíva. To znamená, že nemôžete používať rozširujúcu knižnicu MFC s klientom, ktorý používa statické linkovanie s knižnicou MFC. Táto chyba je o to zákernejšia, že všetko sa bez problémov skompiluje a zlinkuje, problémy nastanú až po spustení programu (podrobne pozri help: DLLs –> Extension).    

Všetky príklady umiestňujte do Workspace TFive.

Obr. 5 Dialóg klienta knižnice RegularDLL

Príklad 2 – RegularDLL. Prvý príklad bude veľmi jednoduchý. Naprogramujeme si knižnicu, ktorá vyexportuje nášmu klientskemu programu jedinú funkciu, ktorej úlohou bude obrátiť reťazec získaný z dialógu. Vytvorte nový projekt MFC AppWizard DLL s názvom RegularDLL. V kroku 1/1 vyberte Regular DLL using shared DLL (obr. 4).

Všetky ostatné nastavenia nechajte štandardné. V kóde do súboru RegularDLL.cpp pridajte túto („otáčaciu“) funkciu:

extern "C" __declspec(dllexport) char* CopyBText(char* strInput)
{
      // pouzivame dynamicke linkovanie
      // toto makro je  nutne
      AFX_MANAGE_STATE(AfxGetStaticModuleState());
 
      // obratime text
      return strrev(strInput);
}

Čo sa týka tajomného MFC makra AFX_MANAGE_STATE, to si vysvetlím jednoducho. Ak píšete regulárnu knižnicu MFC a využívate dynamické linkovanie (as shared DLL), toto makro musíte pridať do všetkých exportovaných funkcií – podrobnosti pozri v helpe). Ak projekt teraz skompilujete a zlinkujete, dostanete ako výstupný súbor knižnicu RegularDLL.dll. Pre nás bude zaujímavý ešte jeden súbor: RegularDLL.lib. Je to tzv. knižnica importov – (pozri help, topic DLLs –>Link Implicitly), ktorá obsahuje zoznam všetkých vyexportovaných symbolických mien (ale žiadny kód) a meno knižnice DLL. Tento súbor je „zástupcom“ knižnice DLL a pridáva sa do klientskeho programu, aby bolo možné v procese linkovania klienta vytvoriť väzby medzi importovanými (v kóde klienta) a exportovanými (tie sú v  knižnici LIB) symbolmi. Tento proces sa nazýva implicitné linkovanie, ktoré sme už spomínali. Tým máme vytvorenú knižnicu DLL. Ešte potrebujeme nejaký klientsky program, aby sme mohli vyskúšať jej funkčnosť. Vytvorte teda nový projekt s názvom RegularDLLClient, ktorý bude typu Dialog based application (všetky ostatné nastavenia nechajte default). Zdroj dialógu upravte podľa obr. 5. Vlastnosti textových polí nechajte default, zmeňte len ID tlačidla na IDC_BTN_UPRAV, jeho hlavičku vidíte na obrázku. Tomuto tlačidlu tiež nastavte vlastnosť Default Button.

Spustite Class Wizard a pridajte ovládacím prvkom členské premenné (tab. 1) a tlačidlu IDC_BTN_UPRAV namapujte správu BN_CLICKED. Tu je telo jej obslužnej funkcie:

void CRegularDLLClientDlg::OnBtnUprav()
{
      UpdateData(TRUE);
     
      char strTemp[20];
 
      strcpy(strTemp, m_strInput);
      // volame funkciu z kniznice DLL
      m_strOutput=CopyBText(strTemp);
 
      UpdateData(FALSE);
}

 

ID ovl. prvku

Členská premenná

Typ

Max. znakov

IDC_EDIT1

m_strInput

CString

20

IDC_EDIT2

m_strOutput

CString

20

Tab. 1 – Členské premenné dialógu RegularDLLClient

Do klienta naimportujte funkciu CopyBText (tento riadok pridajte na začiatok súboru RegularDLLClientDlg.cpp):

extern "C" __declspec(dllimport) char* CopyBText(char* strInput);

Teraz sa pokúste takýto kód skompilovať a zlinkovať. Kompilácia by mala prebehnúť bez problémov, ale linker vyhlási chybu 2001 - unresolved external. Táto chyba znamená, že nebol schopný nájsť telo importovanej funkcie CopyBText. Preto ešte nahrajte súbor RegularDLL.dll do adresára \Debug projektu klienta. Súbor RegularDLL.lib nahrajte k zdrojovým textom klienta a pridajte ho do projektu (v nastaveniach projektu – karta Link, pole Object/Library modules – pozri 23. časť a pridanie knižnice winmm.lib). Knižnicu DLL však nemusíte nahrať len do adresára, kde sa nachádza EXE súbor klienta (aj keď to považujem za najvhodnejšie na vývoj). Môžete ju nahrať napr. do systémového adresára Windows, do adresára Windows alebo do adresára, ktorý je uvedený v premennej PATH (túto možnosť však neodporúčam). Jednoduchý spôsob, ako zistiť, kam všade môžete umiestniť knižnicu, je spustiť klienta a ten, ak si nebude môcť knižnicu nájsť, vypíše chybové hlásenie so zoznamom všetkých adresárov, kde knižnicu neúspešne hľadal.

Príklad 3 – ExtensionDLL. Ako sa tak pozerám na rozsah článku, na tento príklad už v tejto časti nezostane priestor. Takže nabudúce...

Nabudúce. O mesiac sa pri seriáli C++ pod Windows stretneme naposledy. Dokončíme si rozširujúce knižnice DLL, povieme si niečo o budúcnosti programovania s Visual C++ a o programovaní pod Windows vôbec, pozrieme sa na Visual Studio .NET a uvedieme si aj rozlúčkové FAQ. Takže naposledy dovidenia v septembri.

Zobrazit Galériu

Nechajte si posielať prehľad najdôležitejších správ emailom

Mohlo by Vás zaujímať

Ako na to

Ako zbaviť fotky hmly

08.12.2016 11:59

Hmla alebo dym sú často veľmi kreatívne nástroje. No všetkého veľa škodí. Fotka potom stráca kontrast a v podstate na nej nič nevidieť. Hmlu môžete neraz následnými úpravami odstrániť alebo zredukovať ...

Ako na to

Užitočné SW nástroje

08.12.2016 11:53

AllDup v4.0.3 Určenie: program na vyhľadávanie a odstraňovanie duplicitných súborov Vlastnosti: duplicitné súbory sa vyhľadávajú len na zvolených diskových jednotkách alebo len v rámci vybraných ...

Ako na to

Fotografovanie s bleskom

08.12.2016 11:47

Ak máte moderný fotoaparát so vstavaným alebo externým bleskom, zdá sa vám téma článku triviálna. Jednoducho nastavíte vhodný režim, vyberiete najlepšiu kompozíciu záberu, exponujete a o zvyšok sa už ...

Žiadne komentáre

Vyhľadávanie

Kyocera - prve-zariadenia-formatu-a4-s-vykonom-a3

Najnovšie videá