-
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.
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.
-
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.
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í.
-
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ř.
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ě.
-
Vytvořte aplikaci, která použije DLL vytvořenou v zadání 2. DLL sestavte
staticky.
-
Vytvořte aplikaci, která použije DLL vytvořenou v zadání 2. Tentokrát DLL
zavádějte dynamicky za běhu aplikace.
-
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).
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;
}
-
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:
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));
-
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.
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.
-
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:
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.
-
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:
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
-
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
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.
-
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++.
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.