20. Používání DLL
  1. Dynamicky sestavitelné knihovny (DLL) umožňují modularizovat aplikace a tak usnadňují aktualizování a opětovné používání jejich funkčnosti. Také pomáhají redukovat paměťové nároky, pokud několik aplikací používá ve stejnou dobu stejné funkce, neboť každá aplikace získá svou vlastní kopii dat, ale kód je sdílen. Ve Windows, DLL jsou moduly, které obsahují funkce a data. DLL jsou zavedeny za běhu volajícím modulem. Když DLL je zavedena, je mapována do adresového prostoru volajícího procesu.

  2. DLL mohou definovat dva typy funkcí: exportované a interní. Exportované funkce mohou být volány ostatními moduly. Interní funkce mohou být volány pouze uvnitř DLL, ve které jsou definovány. Vytváření a používání DLL je podrobně popsáno v SDK Win32.
    DLL Windows mohou být použity v aplikacích C++ Builderu stejně jako v libovolných jiných aplikacích C++. Ke statickému zavedení DLL při zavádění naší aplikace C++ Builderu, sestavíme importní knihovní soubor pro tuto DLL do naší aplikace C++ Builderu během sestavování. Pro přidání importní knihovny k aplikaci C++ Builderu, otevřeme vytvářecí soubor (BPR) pro aplikaci a přidáme jméno importní knihovny do seznamu knihovních souborů přiřazených k proměnné ALLLIB. Je-li to nutné, přidáme cestu k importní knihovně k cestám uvedeným ve volbě -L proměnné LFLAGS.
    Exportované funkce z DLL potom budou dostupné pro použití naší aplikací. Prototypy funkcí DLL naše aplikace používá s modifikátorem __declspec(dllimport):
    extern "C" __declspec(dllimport) návratový_typ jméno_importované_funkce(parametry);
    K dynamickému zavádění DLL za běhu aplikace C++ Builderu, použijeme funkci Windows API LoadLibrary k zavedení DLL a potom používáme funkci API GetProcAddress k získání ukazatele na funkci, kterou chceme použít. Pozor: na začátek identifikátorů jazyk C přidává znak podtržení. Další informace můžeme nalézt v SDK Win32.
    Vytváření DLL v C++ Builderu je stejné jako ve standardním C++. Importované funkce v kódu musí být identifikovány modifikátorem __declspec(dllimport) stejně jako musí být označeny i v jiných překladačích. Např. následující kód je přípustný v C++ Builderu a v ostatních překladačích C++:
    double dblValue(double);
    double halfValue(double);
    extern "C" __declspec(dllexport) double changeValue(double, bool);
    double dblValue(double value)
    {
     return value * value;
    };
    double halfValue(double value)
    {
     return value / 2.0;
    }
    double changeValue(double value, bool whichOp)
    {
     return whichOp ? dblValue(value) : halfValue(value);
    }
    Ve výše uvedeném kódu, funkce changeValue je exportována a tedy je dostupná z volajících aplikací. Funkce dblValue a halfValue jsou interní a nemohou být volány z vnějšku DLL.
    V IDE C++ Builderu, můžeme vytvořit projekt nové DLL volbou File | New a na stránce New vybereme ikonu DLL. Je otevřeno editační okno a projektové volby jsou nastaveny na vytváření DLL (namísto EXE). Povšimněte si, že do zdrojového souboru DLL je vložen hlavičkový soubor VCL.H. Pokud naše DLL nebude používat komponenty VCL, pak tento řádek můžeme odstranit.
  3. Jednou z výhod DLL je to, že DLL vytvořená v jednom vývojovém prostředí může být nabídnuta k používání aplikacím vytvořeným v jiných vývojových nástrojích. Když naše DLL obsahuje komponenty VCL používané volající aplikací, pak je nutno poskytnout exportované rozhraní, které používá standardní volací konvence a nevyžaduje aby volající aplikace požadovala pro svou práci knihovnu VCL. K vytvoření komponent VCL, které mohou být exportovány používáme běhové balíčky.

  4. Např. předpokládejme, že chceme vytvořit DLL zobrazující následující jednoduché dialogové okno:

    Hlavičkový soubor tohoto formuláře je:
    // DLLMAIN.H
    //---------------------------------------------------------------
    #ifndef dllMainH
    #define dllMainH
    //---------------------------------------------------------------
    #include <vcl\Classes.hpp>
    #include <vcl\Controls.hpp>
    #include <vcl\StdCtrls.hpp>
    #include <vcl\Forms.hpp>
    //---------------------------------------------------------------
    class TYesNoDialog : public TForm
    {
    __published:    // IDE-managed Components
        TLabel *LabelText;
        TButton *YesButton;
        TButton *NoButton;
        void __fastcall YesButtonClick(TObject *Sender);
        void __fastcall NoButtonClick(TObject *Sender);
    private:        // User declarations
        bool returnValue;
    public:         // User declarations
        virtual __fastcall TYesNoDialog(TComponent* Owner);
        bool __fastcall GetReturnValue();
    };
    // exportovaná funkce rozhraní
    extern "C" __declspec(dllexport) bool InvokeYesNoDialog();
    //---------------------------------------------------------------
      extern TYesNoDialog *YesNoDialog;
    //---------------------------------------------------------------
    #endif
    Následuje výpis zdrojového souboru dialogového okna:
    // DLLMAIN.CPP
    //----------------------------------------------------------------
    #include <vcl\vcl.h>
    #pragma hdrstop
    #include "dllMain.h"
    //----------------------------------------------------------------
    #pragma resource "*.dfm"
    TYesNoDialog *YesNoDialog;
    //----------------------------------------------------------------
    __fastcall TYesNoDialog::TYesNoDialog(TComponent* Owner)
      : TForm(Owner)
    {
       returnValue = false;
    }
    //---------------------------------------------------------------
    void __fastcall TYesNoDialog::YesButtonClick(TObject *Sender)
    {
        returnValue = true;
        Close();
    }
    //---------------------------------------------------------------
    void __fastcall TYesNoDialog::NoButtonClick(TObject *Sender)
    {
        returnValue = false;
        Close();
    }
    //---------------------------------------------------------------
    bool __fastcall TYesNoDialog::GetReturnValue()
    {
        return returnValue;
    }
    //---------------------------------------------------------------
    // exportovaná funkce rozhraní, která volá VCL
    bool InvokeYesNoDialog()
    {
      bool returnValue;
      TYesNoDialog *YesNoDialog = new TYesNoDialog(NULL);
      YesNoDialog->ShowModal();
      returnValue = YesNoDialog->GetReturnValue();
      delete YesNoDialog;
      return returnValue;
    }
    Kód v tomto příkladě zobrazí dialogové okno a v soukromé složce returnValue uloží, kterým tlačítkem okno bylo uzavřeno. Veřejná funkce GetReturnValue vrací současnou hodnotu returnValue.
    K vyvolání dialogu a určení, které tlačítko bylo stisknuto, volající aplikace volá exportovanou funkci InvokeYesNoDialog. Tato funkce je deklarována v hlavičkovém souboru jako exportovaná funkce používající sestavení C (k zabránění komolení jmen a používání standardních volacích konvencí C). Funkce je definována ve zdrojovém souboru. Tím dosáhneme to, že DLL může být volána z libovolné aplikace (nemusí být vytvořena v C++ Builderu). Funkčnost VCL vyžadovaná DLL je sestavena do samotné DLL a volající aplikace o VCL nepotřebuje nic znát. Když vytváříme DLL, které používají VCL, pak vyžadované komponenty VCL jsou sestaveny do DLL. To zvětšuje DLL a je tedy vhodné do jedné DLL uložit více komponent. Vytvořte DLL s dialogovým oknem popsaným v tomto zadání.
  5. Můžeme nastavit volby sestavovacího programu pro naši DLL na stránce Linker dialogového okna Project Options. Na této stránce je také značkou možno určit, zda bude pro naši DLL vytvářena importní knihovna. Pokud překládáme z příkazového řádku, pak sestavovací program vyvoláváme s volbou -Tpd. Např.

  6. ilink32 /c /aa /Tpd c0d32.obj mydll.obj, mydll.dll, mdll.map,
      import32.lib cw32mt.lib
    Pokud importní knihovnu nemáme, pak použijeme také volbu -Gi k jejímu vygenerování.
    Importní knihovnu lze také vytvořit programem IMPLIB. Např.
    implib mydll.lib mydll.dll
    Více informací o různých volbách pro sestavování DLL a jejich používání s různými moduly, které jsou staticky nebo dynamicky sestavovány s knihovnou běhu programu nalezneme v nápovědě.
  7. Vytvořte aplikaci, která použije DLL vytvořenou v zadání 2. DLL sestavte staticky.
  8. Vytvořte aplikaci, která použije DLL vytvořenou v zadání 2. Tentokrát DLL zavádějte dynamicky za běhu aplikace.
  9. Pro podporu VCL, C++ Builder implementuje, překládá nebo přepisuje mapování mnoha datových typů Object Pascalu, konstrukcí a jazykových koncepcí do jazyka C++. Mnoho zajímavých datových typů Object Pascalu je implementováno v C++ Builderu pomocí typedef na přirozený typ C++. Tyto definice nalezneme v sysdefs.h. Je ale vhodnější používat přirozený typ C++ namísto typu Object Pascalu (např. je sice možno používat datový typ Integer, ale je vhodnější používat int).

  10. Některé datové typy Object Pascalu a jazykové konstrukce, které nejsou v C++, jsou implementovány jako třídy nebo struktury. Někdy jsou také použity šablony tříd (např. šablona set pro implementaci datového typu množina). Tyto deklarace nalezneme v hlavičkových souborech: dstring.h, sysdefs.h, variant.hpp a wstring.h.
    Parametry volané odkazem a netypové parametry Object Pascalu nejsou přirozené v C++. C++ i Object Pascal mají koncepci předávání parametru odkazem. Jsou to modifikovatelné parametry. Syntaxe v Object Pascalu je:
    procedure myFunc(var x : integer);
    V C++ lze předávat tento typ parametrů odkazem:
    void myFunc(int& x);
    Parametry předávané odkazem a ukazatelem v C++ mohou být použity k modifikaci objektu. Odkaz C++ neodpovídá přesně parametru předávanému odkazem Object Pascalu, ale oboje umožňuje měnit hodnotu odkazu.
    Object Pascal má také parametry nespecifikovaného typu. Tyto parametry jsou předány funkci s nedefinovaným typem. Funkce musí přetypovat parametr na známý typ před použitím parametru. C++ Builder interpretuje netypové parametry jako ukazatel na void. Funkce musí přetypovat ukazatel na void na ukazatel na požadovaný typ. Např.
    int myfunc(void* MyName)
    {
     // Přetypování ukazatele na požadovaný typ; potom jeho dereference
     int* pi = static_cast<int*>(MyName);
     return 1 + *pi;
    }
  11. Object Pascal má konstrukci "otevřené pole", která umožňuje předávat funkcím pole nespecifikované velikosti. V C++ toto může být vyřešeno předáním ukazatele na první prvek pole a hodnotou posledního indexu pole (počet prvků pole mínus jedna). Např. funkce Mean v math.hpp má v Object Pascalu deklaraci:

  12. function Mean(Data: array of Double): Extended;
    Deklarace C++ je:
    Extended __fastcall Mean(const double * Data, const int Data_Size);
    Následující kód ilustruje volání této funkce v C++.
    double d[] = { 3.1, 4.4, 5.6 };
    // explicitní specifikace posledního indexu
    long double x = Mean(d, 2);
    // lepší: použití sizeof pro výpočet počtu prvků pole
    long double y = Mean(d, (sizeof(d) / sizeof(d[0])) - 1);
    // použití makra z sysdefs.h
    long double z = Mean(d, ARRAYSIZE(d) - 1);
    Když používáme sizeof, makro ARRAYSIZE nebo makro EXISTINGARRAY k výpočtu počtu prvků v poli, pak nesmíme použít ukazatel na první prvek pole, ale je nutno předat jméno pole:
    double d[] = { 3.1, 4.4, 5.6 };
    ARRAYSIZE(d) == 3;
    double *pd = d;
    ARRAYSIZE(pd) == 0; // Chyba!
    sizeof pole neodpovídá sizeof ukazatele. Např. při deklaraci:
    double d[3];
    double *p = d;
    získáme velikost pole výrazem:
    sizeof(d)/sizeof d[0]
    ale použití následujícího výrazu je chybné
    sizeof(p)/sizeof(p[0])
    Object Pascal umožňuje funkcím předávat nepojmenovaná dočasná otevřená pole. V C++ pro toto neexistuje syntaxe (je možno to vyřešit několika příkazy a pojmenováním pole). Zápis v Object Pascalu
    Result := Mean([3.1, 4.4, 5.6]);
    lze v C++ zapsat jako
    double d[] = { 3.1, 4.4, 5.6 };
    return Mean(d, ARRAYSIZE(d) - 1);
    K omezení rozsahu pojmenovaného dočasného pole je možno použít zápis:
    long double x;
    {
     double d[] = { 4.4, 333.1, 0.0 };
     x = Mean(d, ARRAYSIZE(d) - 1);
    }
    Object Pascal podporuje jazykovou koncepci nazvanou array of const. Tento typ parametru je stejný jako předávání otevřeného pole TVarRec hodnotou. Např. segment kódu Object Pascalu:
    function Format(const Format: string; Args: array of const): string;
    V C++ se jedná o následující prototyp:
    AnsiString __fastcall Format(const AnsiString Format;
               TVarRec const *Args, const int Args_Size);
    Funkce je volána jako libovolná jiná funkce, která přebírá otevřené pole:
    void show_error(int error_code, AnsiString const &error_msg)
    {
      TVarRec v[] = { error_code, error_msg };
      ShowMessage(Format("%d: %s", v, ARRAYSIZE(v) - 1));
    }
    Makro OPENARRAY definované v sysdefs.h může být použito jako alternativa k použití pojmenované proměnné pro předání dočasného otevřeného pole funkci, která přebírá otevřené pole hodnotou. Použití makra vypadá takto:
    OPENARRAY(T, (value1, value2, value3)) // až 19 hodnot
    kde T je typ vytvářeného otevřeného pole. Např.
    void show_error(int error_code, AnsiString const &error_msg)
    {
     ShowMessage(Format("%d: %s",OPENARRAY(TVarRec,error_code,error_msg)));
    }
    Makro OPENARRAY může být použito pro předání až 19 hodnot. Pro větší pole musí být definována explicitní proměnná.
    Makro EXISTINGARRAY definované v sysdefs.h může být použito k předání existujícího pole, když je očekáváno otevřené pole. Použití makra vypadá takto:
    long double Mean(const double *Data, const int Data_Size);
    double d[] = { 3.1, 3.14159, 2.17128 };
    Mean(EXISTINGARRAY (d));
  13. Některé typy jsou v Object Pascalu a v C++ definovány různě. Jedná se o logické datové typy a typ char. S odchylkami v těchto typech se nyní seznámíme.

  14. True pro datové typy ByteBool, WordBool a LongBool Object Pascalu je reprezentováno hodnotou -1 a False hodnotou 0. Ostatní logické datové typy mají pro True hodnotu 1 (False je vždy 0). C++ převádí tyto typy správně, ale problémy mohou nastat při používání funkcí Win API.
    Typ char je v C++ znaménkový typ, zatímco v Object Pascalu se jedná o bezznaménkový typ. Tato odchylka může někdy způsobit problémy při sdílení kódu.
  15. Jestliže máme kód v jednotce Pascalu, který používá zdroje řetězců, pak překladač Pascalu (DCC32) generuje globální proměnnou a odpovídající makro preprocesoru pro každý zdroj řetězce při generování hlavičkového souboru. Makra jsou používány k automatickému zavádění zdrojů řetězců a jsou určena pro použití v našem C++ kódu na místech, kde se odkazujeme na zdroje řetězců. Např. sekce resourcestring v kódu Object Pascalu může obsahovat:

  16. unit borrowed;
    interfaceS
    resourcestring
     Warning = 'Be careful when accessing string resources.';
    implementation
    begin
    end.
    Odpovídající kód generovaný překladačem Pascalu pro C++ Builder bude:
    extern System::Resource ResourceString _Warning;
    #define Borrowed_Warning System::LoadResourceString(&Borrowed::_Warning)
    To umožňuje použít exportované zdroje řetězců Object Pascalu bez nutnosti explicitního volání LoadResourceString.
  17. Překladač Pascalu nyní akceptuje implicitní parametry pro kompatibilitu s rozlišováním konstruktorů C++. Na rozdíl od C++, konstruktory Object Pascalu mají stejný počet a typy parametrů. K rozlišení konstruktorů v Object Pascalu jsou použity prázdné parametry (při generování hlavičkových souborů C++). Např. pro třídu TInCompatible, jsou v Object Pascalu konstruktory:

  18. constructor Create(AOwner: TComponent);
    constructor CreateNew(AOwner: TComponent);
    které jsou nesprávně (nelze je rozlišit) přeloženy na tyto konstruktory v C++:
    __fastcall TInCompatible(Classes::TComponent* Owner); // Create
    __fastcall TInCompatible(Classes::TComponent* Owner); // CreateNew
    Při použití implicitních parametrů v Object Pascalu lze např. vytvořit konstruktory:
    constructor Create(AOwner: TComponent);
    constructor CreateNew(AOwner: TComponent; Dummy: Integer = 0);
    které je možno již správně přeložit na:
    __fastcall TCompatible(Classes::TComponent* Owner); // Create
    __fastcall TCompatible(Classes::TComponent* Owner, int Dummy); // CreateNew
  19. Object Pascal má jazykové konstrukce pracující s RTTI (identifikace typu za běhu). Něco obdobného je i v C++. Např. konstrukce Object Pascalu

  20. if Sender is TButton...
    je mapována v C++ na
    if (dynamic_cast <TButton*> (Sender))...
    Obdobě
    b := Sender as TButton;
    je mapováno na
    TButton& ref_b = dynamic_cast <TButton&> (*Sender);
    Další metody týkající se RTTI jsou zaobaleny v TObject, např. metody ClassNameIs, ClassName, ClassParent apod.
  21. Starý 6-ti slabikový formát pohyblivé řádové čárky Object Pascalu se nyní nazývá Real48. V C++ není ekvivalent pro tento typ. Nelze tedy použít kód Object Pascalu s tímto kódem v kódu C++.

  22. V Object Pascalu funkce mohou vracet pole. Např. syntaxe pro funkci GetLine vracející 80 znaků je:
    type Line_Data = array[0..79] of char;
    function GetLine: Line_Data;
    V C++ funkce nemohou vracet pole. VCL nemá žádnou vlastnost, která je polem, i když to Object Pascel umožňuje. Protože vlastnosti mohou používat čtecí metody, které vracejí hodnoty typu vlastnosti, není v C++ Builderu vlastnost typu pole.
20. Používání DLL