Qt DLL files/hu: Difference between revisions

From Qt Wiki
Jump to navigation Jump to search
No edit summary
No edit summary
Line 1: Line 1:
[toc align_right="yes" depth="3"]
[toc align_right="yes" depth="3"]


= Qt és a DLL fájlok =
= Qt és a DLL fájlok =
Line 9: Line 9:
== Saját DLL fájlok létrehozása Qt-ben ==
== Saját DLL fájlok létrehozása Qt-ben ==


Egy saját dll fájl létrehozásához nincsen semmi más dolgunk, mint a Qt Creatorban létrehozni a File / New File menüpont segítségével. A név megadásán (mi a példában "test" nevet fogjuk használni) kívül sok dolgunk nincs (a jelenlegi kis bemutatóhoz nem szükséges a QtCore modul, így kiszedhetjük mellőle a pipát).
Egy saját dll fájl létrehozásához nincsen semmi más dolgunk, mint a Qt Creatorban létrehozni a File / New File menüpont segítségével. A név megadásán (mi a példában "test" nevet fogjuk használni) kívül sok dolgunk nincs (a jelenlegi kis bemutatóhoz nem szükséges a QtCore modul, így kiszedhetjük mellőle a pipát).


[[Image:http://huxi.uw.hu/hidden/Qt/new.jpg|Új dll létrehozása]]
[[Image:http://huxi.uw.hu/hidden/Qt/new.jpg|Új dll létrehozása]]
Line 15: Line 15:
Látjuk, hogy szépen létrejött 4 darab fájlunk. A .pro fájlunk egy hétköznapi Qt projekttől a TEMPLATE = lib sorban fog csak eltérni, itt látszik hogy egy megosztott könyvtárat fogunk létrehozni. Ezen kívül kapunk még két header és egy cpp fájlt, most vizsgáljuk meg ezeket tüzetesebben:
Látjuk, hogy szépen létrejött 4 darab fájlunk. A .pro fájlunk egy hétköznapi Qt projekttől a TEMPLATE = lib sorban fog csak eltérni, itt látszik hogy egy megosztott könyvtárat fogunk létrehozni. Ezen kívül kapunk még két header és egy cpp fájlt, most vizsgáljuk meg ezeket tüzetesebben:


Kezdetben a dll fájlunk egyetlen eljárást fog tartalmazni, ami nem csinál mást, mint kiírja nekünk azt hogy "Helló világ!". Első körben töröljük ki azt a test.h header fájlból az osztályt(class TEST2SHARED_EXPOR Test{…}), ami a fájl generálásakor létrejött, illetve ne felejtsük el a cpp fájlból sem kitörölni a Test::Test() konstruktort. A függvényünk így fog kinézni:
Kezdetben a dll fájlunk egyetlen eljárást fog tartalmazni, ami nem csinál mást, mint kiírja nekünk azt hogy "Helló világ!". Első körben töröljük ki azt a test.h header fájlból az osztályt(class TEST2SHARED_EXPOR Test{…}), ami a fájl generálásakor létrejött, illetve ne felejtsük el a cpp fájlból sem kitörölni a Test::Test() konstruktort. A függvényünk így fog kinézni:


<code>#ifndef TEST_H<br />#define TEST_H
<code>#ifndef TEST_H<br />#define TEST_H


#include &lt;QDebug&amp;gt;
#include <QDebug>


extern &quot;C&amp;quot; _''declspec(dllexport) void hello() { qDebug() &lt;&lt; &quot;Helló világ!&quot; &lt;&lt; endl;}
extern "C" _''declspec(dllexport) void hello() { qDebug() << "Helló világ!" << endl;}
<br />#endif // TEST_H</code>
<br />#endif // TEST_H</code>
<br />''(megj.: Használhatnánk a cout parancsot is a kiíratáshoz, de több mindent kellene beállítanunk, a qDebug() használata a mi példánkban egyszerűbb. (Az automatikusan generált header fájlban lesz még egy #include &quot;test_global.h&amp;quot; sor is, jelenleg ez nekünk nem kell még.)_
<br />''(megj.: Használhatnánk a cout parancsot is a kiíratáshoz, de több mindent kellene beállítanunk, a qDebug() használata a mi példánkban egyszerűbb. (Az automatikusan generált header fájlban lesz még egy #include "test_global.h" sor is, jelenleg ez nekünk nem kell még.)_


Látszik, hogy a függvény fejében a visszatérési érték előtt sok csúnyaság szerepel. Vegyük ezek szemügyre:
Látszik, hogy a függvény fejében a visszatérési érték előtt sok csúnyaság szerepel. Vegyük ezek szemügyre:


* &lt;code&amp;gt;extern &quot;C&amp;quot;&lt;/code&amp;gt; - Ez egy C++ parancs, ami azt mondja meg a C++ fordítónak, hogy az adott függvényt C nyelven írták, illetve hogy az adott fájlon kívül máshonnan is el lehet érni.
* <code>extern "C"</code> - Ez egy C++ parancs, ami azt mondja meg a C++ fordítónak, hogy az adott függvényt C nyelven írták, illetve hogy az adott fájlon kívül máshonnan is el lehet érni.
* &lt;code&amp;gt;__declspec(dllexport)&lt;/code&amp;gt; - Szintén a fordítónak szól ez az utasítás is, tulajdonképpen ezzel mondjuk meg, hogy az adott adatot, függvényt, osztályt vagy osztályon belüli függvényt (amelyik elé ezt beírjuk) exportálni szeretnénk majd.
* <code>__declspec(dllexport)</code> - Szintén a fordítónak szól ez az utasítás is, tulajdonképpen ezzel mondjuk meg, hogy az adott adatot, függvényt, osztályt vagy osztályon belüli függvényt (amelyik elé ezt beírjuk) exportálni szeretnénk majd.


Ha szeretnénk megkapni a dll fájlunkat nincs is más dolgunk, mint hogy buildeljük a projektünket, és a debug mappába rögtön létre fog jönni a test.dll fájlunk (a két másik fájlról majd később szólunk).
Ha szeretnénk megkapni a dll fájlunkat nincs is más dolgunk, mint hogy buildeljük a projektünket, és a debug mappába rögtön létre fog jönni a test.dll fájlunk (a két másik fájlról majd később szólunk).


A továbbiak megértéséhez szükséges a függvénymutatók minimális ismerete, röviden összefoglalom a lényegét, hosszan magyarul &quot;itt&amp;quot;:http://www.freeweb.hu/kr-c/files/05.html és angolul &quot;itt&amp;quot;:http://www.newty.de/fpt/intro.html.<br />Ugyan úgy, ahogyan egy változóhoz létrehozhatunk egy mutatót, hasonlóan létre tudunk hozni olyan mutatókat, amelyek függvényekre mutatnak. Egy függvény mutatót az alábbi módon definiálhatunk:
A továbbiak megértéséhez szükséges a függvénymutatók minimális ismerete, röviden összefoglalom a lényegét, hosszan magyarul "itt":http://www.freeweb.hu/kr-c/files/05.html és angolul "itt":http://www.newty.de/fpt/intro.html.<br />Ugyan úgy, ahogyan egy változóhoz létrehozhatunk egy mutatót, hasonlóan létre tudunk hozni olyan mutatókat, amelyek függvényekre mutatnak. Egy függvény mutatót az alábbi módon definiálhatunk:


<code>&lt;visszatérési_érték&amp;gt;(*név)(formális_paraméter_lista)<br />// például:<br />typedef void (*fp)();<br />typedef int ('''fnctptr)(double,double)</code>
<code><visszatérési_érték>(*név)(formális_paraméter_lista)<br />// például:<br />typedef void (*fp)();<br />typedef int ('''fnctptr)(double,double)</code>
<br />Tegyük fel, hogy deklarálva vannak a következő függvényeink / eljárásaink:
<br />Tegyük fel, hogy deklarálva vannak a következő függvényeink / eljárásaink:
<br /><code><br />void foo1();<br />void foo2();<br />void foo3(int);<br />int bar1(double,double);<br />int bar1(double,char''');<br />void bar1(double,double);</code>
<br /><code><br />void foo1();<br />void foo2();<br />void foo3(int);<br />int bar1(double,double);<br />int bar1(double,char''');<br />void bar1(double,double);</code>
Line 50: Line 50:
Mindjárt meglátjuk, hogy miért lesz ez nekünk jó.
Mindjárt meglátjuk, hogy miért lesz ez nekünk jó.


Legyen egy tetszőleges, másik Qt projektünk, amiben szeretnénk ezt a rendkívül hasznos dll-t felhasználni. Abban az osztályban, ahol használni szeretnénk a dll-t, a &lt;code&amp;gt;QLibrary&amp;lt;/code&amp;gt; segítségével tudjuk betölteni:
Legyen egy tetszőleges, másik Qt projektünk, amiben szeretnénk ezt a rendkívül hasznos dll-t felhasználni. Abban az osztályban, ahol használni szeretnénk a dll-t, a <code>QLibrary</code> segítségével tudjuk betölteni:


<code>typedef void ('''fp)();<br />QLibrary library(&quot;&lt;eléri_útvonal&amp;gt;/test.dll&amp;quot;);<br />library.load();<br />if(library.isLoaded()){<br /> fp p = (fp)library.resolve(&quot;hello&amp;quot;);<br /> p();<br />}<br />else{<br /> qDebug() &lt;&lt; &quot;Nem sikerült betölteni a dll-t!&quot; &lt;&lt; endl;<br />}<br /></code>
<code>typedef void ('''fp)();<br />QLibrary library("<eléri_útvonal>/test.dll");<br />library.load();<br />if(library.isLoaded()){<br /> fp p = (fp)library.resolve("hello");<br /> p();<br />}<br />else{<br /> qDebug() << "Nem sikerült betölteni a dll-t!" << endl;<br />}<br /></code>
<br />Első lépésben betöltjük a dll-ünket, majd meggyőződünk a &lt;code&amp;gt;library.isLoaded()&lt;/code&amp;gt; függvénnyel, hogy tényleg sikerült-e betöltenünk. Ha nem, akkor nagy valószínűséggel rossz elérési útvonalat adtunk meg. Fontos ezt megvizsgálni, mert ha rossz helyre irányítjuk a függvénymutatónkat az 5. sorban, akkor annak katasztrofális végeredménye lehet (jobb esetben a &lt;code&amp;gt;library.resolve&amp;lt;/code&amp;gt; ha nem találja az adott függvényt, akkor NULL-al tér vissza, és a p() <s>re egyből meghal a programunk, rosszabb esetben akár valami értelmes helyre is mutathat, ilyenkor a következmények teljesen megjósolhatatlanok.)<br />Tehát egy dll fájl betöltése két lépésben történik: Megadjuk a '''pontos, teljes''' elérési útvonalát, és meghívjuk rá a &lt;code&amp;gt;load()&lt;/code&amp;gt; eljárást. Ha sikeres volt a betöltés, akkor a következő lépésben egy függvénymutatót rá kell állítanunk a használni kívánt függvényre. Ez a &lt;code&amp;gt;resolve(&quot;&lt;függvénynév&amp;gt;&quot;)&lt;/code&amp;gt; segítségével tehető meg. Ezek után pedig nincs is más dolgunk, mint a 6. sorban meghívni a függvényünket, és gyönyörködni a végeredményben.
<br />Első lépésben betöltjük a dll-ünket, majd meggyőződünk a <code>library.isLoaded()</code> függvénnyel, hogy tényleg sikerült-e betöltenünk. Ha nem, akkor nagy valószínűséggel rossz elérési útvonalat adtunk meg. Fontos ezt megvizsgálni, mert ha rossz helyre irányítjuk a függvénymutatónkat az 5. sorban, akkor annak katasztrofális végeredménye lehet (jobb esetben a <code>library.resolve</code> ha nem találja az adott függvényt, akkor NULL-al tér vissza, és a p() -re egyből meghal a programunk, rosszabb esetben akár valami értelmes helyre is mutathat, ilyenkor a következmények teljesen megjósolhatatlanok.)<br />Tehát egy dll fájl betöltése két lépésben történik: Megadjuk a '''pontos, teljes''' elérési útvonalát, és meghívjuk rá a <code>load()</code> eljárást. Ha sikeres volt a betöltés, akkor a következő lépésben egy függvénymutatót rá kell állítanunk a használni kívánt függvényre. Ez a <code>resolve("<függvénynév>")</code> segítségével tehető meg. Ezek után pedig nincs is más dolgunk, mint a 6. sorban meghívni a függvényünket, és gyönyörködni a végeredményben.
<br />Ez mind nagyon szép és jó, de mi van akkor, ha szeretnénk komplett C++ osztályokat betölteni dll-ből?<br />Térjünk vissza a test.h fájlunkra, és módosítsuk azt az alábbiak szerint:
<br />Ez mind nagyon szép és jó, de mi van akkor, ha szeretnénk komplett C++ osztályokat betölteni dll-ből?<br />Térjünk vissza a test.h fájlunkra, és módosítsuk azt az alábbiak szerint:
<br /><code><br />#ifndef TEST_H<br />#define TEST_H
<br /><code><br />#ifndef TEST_H<br />#define TEST_H
<br />#include &lt;QDebug&amp;gt;<br />#include &quot;test_global.h&amp;quot;
<br />#include <QDebug><br />#include "test_global.h"


<br />class TESTSHARED_EXPORT Test{
<br />class TESTSHARED_EXPORT Test{
<br />public:
<br />public:
<br /> Test(){}<br /> void hello() { qDebug() &lt;&lt; &quot;Hello dll&amp;quot; &lt;&lt; endl;}
<br /> Test(){}<br /> void hello() { qDebug() << "Hello dll" << endl;}
<br />};
<br />};
<br />extern &quot;C&amp;quot; Q_DECL_EXPORT Test * create(){ return new Test(); }
<br />extern "C" Q_DECL_EXPORT Test * create(){ return new Test(); }
<br />#endif // TEST_H</code>
<br />#endif // TEST_H</code>


<br />Mint látható megváltozott pár dolog, lássuk ezeket:<br />* &lt;code&amp;gt;TESTSHARED_EXPORT&amp;lt;/code&amp;gt;: Ez a(z egyetlen) test_global.h fájlunkban definiált makró a korábbi &lt;code&amp;gt;_''declspec(dllexport) &lt;/code&amp;gt;csúnyaságot (+ &lt;code&amp;gt;''_declspec(dllimport))&lt;/code&amp;gt; takarja, azt jelzi, hogy az osztályunkat szeretnénk majd exportálni.<br />* a &lt;code&amp;gt;create()&lt;/code&amp;gt; függvény: Ennek a függvények a segítségével fogjuk tudni az osztályunkat példányosítani egy másik fájlban.
<br />Mint látható megváltozott pár dolog, lássuk ezeket:<br />* <code>TESTSHARED_EXPORT</code>: Ez a(z egyetlen) test_global.h fájlunkban definiált makró a korábbi <code>_''declspec(dllexport) </code>csúnyaságot (+ <code>''_declspec(dllimport))</code> takarja, azt jelzi, hogy az osztályunkat szeretnénk majd exportálni.<br />* a <code>create()</code> függvény: Ennek a függvények a segítségével fogjuk tudni az osztályunkat példányosítani egy másik fájlban.
<br />Abban az osztályban, ahol használni szeretnénk, a kód e szerint módosul:
<br />Abban az osztályban, ahol használni szeretnénk, a kód e szerint módosul:
<br /><code>#include &quot;&lt;a_dll_fájl_headerje&amp;gt;&quot; // mi esetünkben #include &quot;test.h&amp;quot;<br /> …<br /> // a dll betöltése változatlan, ez a if(library.isLoaded()) részen belül van!
<br /><code>#include "<a_dll_fájl_headerje>" // mi esetünkben #include "test.h"<br /> …<br /> // a dll betöltése változatlan, ez a if(library.isLoaded()) részen belül van!
<br /> typedef Test * ('''gettest)();<br /> gettest gt = (gettest) library.resolve(&quot;create&amp;quot;);
<br /> typedef Test * ('''gettest)();<br /> gettest gt = (gettest) library.resolve("create");
<br /> Test''' t = gt();<br /> if (t){<br /> t</s>&gt;hello();<br /> }<br /> else<br /> qDebug() &lt;&lt; &quot;Nem sikerült létrehozni az objektumot!&quot; &lt;&lt; endl;<br /></code>
<br /> Test''' t = gt();<br /> if (t){<br /> t->hello();<br /> }<br /> else<br /> qDebug() << "Nem sikerült létrehozni az objektumot!" << endl;<br /></code>
<br />Mit is csináltunk most? Definiáltunk egy függvénymutatót, aminek a visszatérési értéke egy Test objektumra mutató (gt), illetve egy külön Test objektumra mutatót(t) (mindezt azért tehettük meg, mert beincludáltuk a test.h header fájlt). A gt függvénymutatót ráállítottuk a dll-ben lévő &lt;code&amp;gt;create()&lt;/code&amp;gt; függvényre, ami nem csinál semmi mást, mint létrehoz egy új Test objektumot, és visszaad az objektumra mutató mutatót. Ezt a visszatérési értéket fogjuk eltárolni a t mutatóban, amit ezek után természetes módon használhatunk, például ahogyan a 10. sorban látszik meghívhatjuk nyugodtan a &lt;code&amp;gt;hello()&lt;/code&amp;gt; függvényét.<br />Ha nem szeretnénk az egész osztályt exportálni, csak néhány függvényt belőle, akkor nincsen más teendőnk, mint a &lt;code&amp;gt;TESTSHARED_EXPORT&amp;lt;/code&amp;gt; makrót elhagyjuk az osztály deklarálásánál, és minden olyan függvényt, amelyet exportálni szeretnénk ellátunk &lt;code&amp;gt;extern &quot;C&amp;quot; &lt;/code&amp;gt;és &lt;code&amp;gt;Q_DECL_EXPORT&amp;lt;/code&amp;gt; részekkel (ahogyan a példában a create() szerepel). Ilyenkor az egyetlen (viszont életveszélyes) dolog az, hogy azokat a függvényeket is látni fogjuk (nyílván, hiszen van hozzá header fájlunk) az osztályból, amelyek nincsen a dll-be exportálva, és ha azokat szeretnénk meghívni akkor gyönyörű szép memóriahibákat tudunk kapni. Ennek a problémának a megoldása is sajnos túlmutat a példáinkon.<br />Hogy még egyszerűbbé tegyük az életünket, használhatjuk a <code>Test''' t = new Test;<code> sort is, így teljesen megszabadítva magunkat a függvénymutatózás borzalmaitól.
<br />Mit is csináltunk most? Definiáltunk egy függvénymutatót, aminek a visszatérési értéke egy Test objektumra mutató (gt), illetve egy külön Test objektumra mutatót(t) (mindezt azért tehettük meg, mert beincludáltuk a test.h header fájlt). A gt függvénymutatót ráállítottuk a dll-ben lévő <code>create()</code> függvényre, ami nem csinál semmi mást, mint létrehoz egy új Test objektumot, és visszaad az objektumra mutató mutatót. Ezt a visszatérési értéket fogjuk eltárolni a t mutatóban, amit ezek után természetes módon használhatunk, például ahogyan a 10. sorban látszik meghívhatjuk nyugodtan a <code>hello()</code> függvényét.<br />Ha nem szeretnénk az egész osztályt exportálni, csak néhány függvényt belőle, akkor nincsen más teendőnk, mint a <code>TESTSHARED_EXPORT</code> makrót elhagyjuk az osztály deklarálásánál, és minden olyan függvényt, amelyet exportálni szeretnénk ellátunk <code>extern "C" </code>és <code>Q_DECL_EXPORT</code> részekkel (ahogyan a példában a create() szerepel). Ilyenkor az egyetlen (viszont életveszélyes) dolog az, hogy azokat a függvényeket is látni fogjuk (nyílván, hiszen van hozzá header fájlunk) az osztályból, amelyek nincsen a dll-be exportálva, és ha azokat szeretnénk meghívni akkor gyönyörű szép memóriahibákat tudunk kapni. Ennek a problémának a megoldása is sajnos túlmutat a példáinkon.<br />Hogy még egyszerűbbé tegyük az életünket, használhatjuk a <code>Test''' t = new Test;<code> sort is, így teljesen megszabadítva magunkat a függvénymutatózás borzalmaitól.


Mostanáig dinamikusan / futási időben töltöttük be a dll fájlainkat, mindig akkor amikor szükségessé váltak. Mi van akkor, ha tudjuk, hogy néhány erőforrásra a program minden egyes futtatásakor szükségünk lesz? Ilyenkor lehetőségünk van arra, hogy már fordítási / statikus időben hozzáfűzni a dll-ünket a programunkhoz. Ennek módja a következő:
Mostanáig dinamikusan / futási időben töltöttük be a dll fájlainkat, mindig akkor amikor szükségessé váltak. Mi van akkor, ha tudjuk, hogy néhány erőforrásra a program minden egyes futtatásakor szükségünk lesz? Ilyenkor lehetőségünk van arra, hogy már fordítási / statikus időben hozzáfűzni a dll-ünket a programunkhoz. Ennek módja a következő:
Line 76: Line 76:
A projektünk .pro fájljába ('''nem''' a test.pro[[Image:|Image:]]!) írjuk be az alábbi sorokat:
A projektünk .pro fájljába ('''nem''' a test.pro[[Image:|Image:]]!) írjuk be az alábbi sorokat:


</code>INCLUDEPATH ''= &lt;a &gt;
</code>INCLUDEPATH ''= <a >
<br />LIBS''= -L&amp;lt;dll-t tartalmazó mappa elérési útja&amp;lt; #ha van az elérési útvonalban space akkor LIBS ''= -L&amp;quot;&lt;dll-t tartalmazó könyvtár elérési útja&amp;gt;&quot;<br />LIBS''= <s>ltest<code>
<br />LIBS''= -L<dll-t tartalmazó mappa elérési útja< #ha van az elérési útvonalban space akkor LIBS ''= -L"<dll-t tartalmazó könyvtár elérési útja>"<br />LIBS''= -ltest<code>


<br />Az &lt;code&amp;gt;INCLUDEPATH&amp;lt;/code&amp;gt; tulajdonképpen arra szolgál, hogy ne kelljen a test.h header fájl teljes elérési útvonalát beírni minden #include során, ami számunkra feltétlenül fontos az a &lt;code&amp;gt;LIBS&amp;lt;/code&amp;gt;.
<br />Az <code>INCLUDEPATH</code> tulajdonképpen arra szolgál, hogy ne kelljen a test.h header fájl teljes elérési útvonalát beírni minden #include során, ami számunkra feltétlenül fontos az a <code>LIBS</code>.
<br />Ezen sorok felvétele után a betöltés a kódból teljesen elhagyható:
<br />Ezen sorok felvétele után a betöltés a kódból teljesen elhagyható:
<br /></code>#include &quot;&lt;a_dll_fájl_headerje&amp;gt;&quot; // mi esetünkben #include &quot;test.h&amp;quot;<br /> …<br /> // a dll betöltése ELHAGYHATÓ[[Image:|Image:]][[Image:|Image:]][[Image:|Image:]]!
<br /></code>#include "<a_dll_fájl_headerje>" // mi esetünkben #include "test.h"<br /> …<br /> // a dll betöltése ELHAGYHATÓ[[Image:|Image:]][[Image:|Image:]][[Image:|Image:]]!
<br /> Test * t = new Test;<br /> if (t){<br /> t</s>&gt;hello();<br /> }<br /> else<br /> qDebug() &lt;&lt; &quot;Nem sikerült létrehozni az objektumot!&quot; &lt;&lt; endl;<br /><code>
<br /> Test * t = new Test;<br /> if (t){<br /> t->hello();<br /> }<br /> else<br /> qDebug() << "Nem sikerült létrehozni az objektumot!" << endl;<br /><code>


Már csak egy kérdésünk maradt hátra: mi a helyzet az olyan dll-ekkel, amelyek Qt osztályokat tartalmaznak?<br />A válasz pofonegyszerű: minden ugyan így, változtatások nélkül működik!<br />(A következő kódrészeknél feltételezzük, hogy statikusan betöltjük a dll-ünket!)
Már csak egy kérdésünk maradt hátra: mi a helyzet az olyan dll-ekkel, amelyek Qt osztályokat tartalmaznak?<br />A válasz pofonegyszerű: minden ugyan így, változtatások nélkül működik!<br />(A következő kódrészeknél feltételezzük, hogy statikusan betöltjük a dll-ünket!)
Line 90: Line 90:
</code><br />#ifndef TEST_H<br />#define TEST_H
</code><br />#ifndef TEST_H<br />#define TEST_H


#include &lt;QObject&amp;gt;<br />#include &lt;QDebug&amp;gt;<br />#include &quot;test_global.h&amp;quot;
#include <QObject><br />#include <QDebug><br />#include "test_global.h"


class TESTSHARED_EXPORT Test : public QObject{
class TESTSHARED_EXPORT Test : public QObject{
Line 98: Line 98:
public:
public:


Test(){}<br /> void hello() { qDebug() &lt;&lt; &quot;Hello dll&amp;quot; &lt;&lt; endl;}<br /> void setX(int x){ m_x = x;}<br /> int getX(){return m_x;}
Test(){}<br /> void hello() { qDebug() << "Hello dll" << endl;}<br /> void setX(int x){ m_x = x;}<br /> int getX(){return m_x;}


private slots:<br /> void printX(){ qDebug() &lt;&lt; m_x &lt;&lt; endl;}
private slots:<br /> void printX(){ qDebug() << m_x << endl;}


private:<br /> int m_x;<br />};
private:<br /> int m_x;<br />};
Line 106: Line 106:
#endif // TEST_H<code>
#endif // TEST_H<code>


Hívó osztály átírva:<br /></code>#include &lt;QtGui/QApplication&amp;gt;<br />#include &quot;mainwindow.h&amp;quot;<br />#include &lt;QLibrary&amp;gt;<br />#include &lt;QDir&amp;gt;<br />#include &lt;QDebug&amp;gt;
Hívó osztály átírva:<br /></code>#include <QtGui/QApplication><br />#include "mainwindow.h"<br />#include <QLibrary><br />#include <QDir><br />#include <QDebug>


#include &quot;test.h&amp;quot;
#include "test.h"


int main(int argc, char '''argv[])<br />{<br /> QApplication a(argc, argv);<br /> MainWindow w;<br /> w.show();  
int main(int argc, char '''argv[])<br />{<br /> QApplication a(argc, argv);<br /> MainWindow w;<br /> w.show();  
<br /> Test''' t = new Test;<br /> QObject::connect(&amp;w, SIGNAL (destroyed()), t, SLOT (printX()));
<br /> Test''' t = new Test;<br /> QObject::connect(&amp;w, SIGNAL (destroyed()), t, SLOT (printX()));


if (t){<br /> t-&gt;hello();<br /> t-&gt;setX(3);<br /> qDebug() &lt;&lt; t-&gt;getX() &lt;&lt; endl;<br /> t-&gt;setX(4);<br /> }<br /> else<br /> qDebug() &lt;&lt; &quot;Nem sikerült betölteni az objektumot!&quot; &lt;&lt; endl;
if (t){<br /> t->hello();<br /> t->setX(3);<br /> qDebug() << t->getX() << endl;<br /> t->setX(4);<br /> }<br /> else<br /> qDebug() << "Nem sikerült betölteni az objektumot!" << endl;


return a.exec&amp;amp;#40;&amp;#41;;<br />}<br /><code>
return a.exec();<br />}<br /><code>


A példát lefuttatva látjuk, hogy minden további nélkül használhatóak a Qt-s osztályaink, illetve a signal / slot rendszer is hibátlanul működik így is.
A példát lefuttatva látjuk, hogy minden további nélkül használhatóak a Qt-s osztályaink, illetve a signal / slot rendszer is hibátlanul működik így is.


[[Category:Hungarian]]
[[Category:Hungarian]]

Revision as of 14:23, 24 February 2015

[toc align_right="yes" depth="3"]

Qt és a DLL fájlok

Ebben a tutorialban bemutatom, hogy hogyan lehet a Qt Creator(2.1) segítségével dll fájlokat létrehozni, illetve az általunk létrehozott, vagy esetleg egy teljesen máshonnan származó dll fájlt betölteni a programunkba és használni.

Mik is azok a dll-ek, és mire is használhatóak? A dll az angol Dinamic Link Library rövidítése, magyarul talán a leggyakrabban megosztott könyvtáraknak nevezik őket. Ezek a fájlok segédfájlok, amelyeket Windows környezetben futó alkalmazások használhatnak. Egy dll fájl tulajdonképpen C függvényeket és eljárásokat (esetleg C++ osztályokat) tartalmaz, amiket bármely program meghívhat és használhat. Előnye, hogy csak akkor töltődnek be a memóriába, amikor ténylegesen meghívjuk őket, így például nem szükséges egy hatalmas, 50 MB-os exe fájlt létrehoznunk, és azt minden induláskor behúzni a memóriába. Segítségükkel néhány függvényünket, esetleg C++ osztályunkat kimenthetünk dll-be, és csak akkor töltjük be őket, amikor tényleg szükségünk van rájuk. Hátrányuk viszont, hogy kizárólag Windows alatt használhatóak, ugyanis Unix környezetben 'so' kiterjesztésű fájlokat használhatunk csak (vannak még 'sl' és 'a' kiterjesztésűek is, de az so a leggyakoribb), ráadásul nem csak platform hanem fordító függő is (nem biztos, hogy egy gcc fordítóval készített dll kompatibilis az msvc fordítóval és viszont, ráadásul bejönnek a 'lib' fájlok is a képbe…).

Saját DLL fájlok létrehozása Qt-ben

Egy saját dll fájl létrehozásához nincsen semmi más dolgunk, mint a Qt Creatorban létrehozni a File / New File menüpont segítségével. A név megadásán (mi a példában "test" nevet fogjuk használni) kívül sok dolgunk nincs (a jelenlegi kis bemutatóhoz nem szükséges a QtCore modul, így kiszedhetjük mellőle a pipát).

Új dll létrehozása

Látjuk, hogy szépen létrejött 4 darab fájlunk. A .pro fájlunk egy hétköznapi Qt projekttől a TEMPLATE = lib sorban fog csak eltérni, itt látszik hogy egy megosztott könyvtárat fogunk létrehozni. Ezen kívül kapunk még két header és egy cpp fájlt, most vizsgáljuk meg ezeket tüzetesebben:

Kezdetben a dll fájlunk egyetlen eljárást fog tartalmazni, ami nem csinál mást, mint kiírja nekünk azt hogy "Helló világ!". Első körben töröljük ki azt a test.h header fájlból az osztályt(class TEST2SHARED_EXPOR Test{…}), ami a fájl generálásakor létrejött, illetve ne felejtsük el a cpp fájlból sem kitörölni a Test::Test() konstruktort. A függvényünk így fog kinézni:

#ifndef TEST_H<br />#define TEST_H

#include <QDebug>

extern "C" _''declspec(dllexport) void hello() { qDebug() << "Helló világ!" << endl;}
<br />#endif // TEST_H


(megj.: Használhatnánk a cout parancsot is a kiíratáshoz, de több mindent kellene beállítanunk, a qDebug() használata a mi példánkban egyszerűbb. (Az automatikusan generált header fájlban lesz még egy #include "test_global.h" sor is, jelenleg ez nekünk nem kell még.)_

Látszik, hogy a függvény fejében a visszatérési érték előtt sok csúnyaság szerepel. Vegyük ezek szemügyre:

  • extern "C"
    
    - Ez egy C++ parancs, ami azt mondja meg a C++ fordítónak, hogy az adott függvényt C nyelven írták, illetve hogy az adott fájlon kívül máshonnan is el lehet érni.
  • __declspec(dllexport)
    
    - Szintén a fordítónak szól ez az utasítás is, tulajdonképpen ezzel mondjuk meg, hogy az adott adatot, függvényt, osztályt vagy osztályon belüli függvényt (amelyik elé ezt beírjuk) exportálni szeretnénk majd.

Ha szeretnénk megkapni a dll fájlunkat nincs is más dolgunk, mint hogy buildeljük a projektünket, és a debug mappába rögtön létre fog jönni a test.dll fájlunk (a két másik fájlról majd később szólunk).

A továbbiak megértéséhez szükséges a függvénymutatók minimális ismerete, röviden összefoglalom a lényegét, hosszan magyarul "itt":http://www.freeweb.hu/kr-c/files/05.html és angolul "itt":http://www.newty.de/fpt/intro.html.
Ugyan úgy, ahogyan egy változóhoz létrehozhatunk egy mutatót, hasonlóan létre tudunk hozni olyan mutatókat, amelyek függvényekre mutatnak. Egy függvény mutatót az alábbi módon definiálhatunk:

<visszatérési_érték>(*név)(formális_paraméter_lista)<br />// például:<br />typedef void (*fp)();<br />typedef int ('''fnctptr)(double,double)


Tegyük fel, hogy deklarálva vannak a következő függvényeink / eljárásaink:


<br />void foo1();<br />void foo2();<br />void foo3(int);<br />int bar1(double,double);<br />int bar1(double,char''');<br />void bar1(double,double);

Akkor a következők igazak:

<br />fp p;<br />p = foo1; // OK<br />p = &amp;foo2; // OK<br />p = foo3; // NEM OK, a foo3 egy int-et vár paraméterként, a fp-t viszont úgy definiáltuk, hogy nem vár paramétert

fnctptr p2;<br />p2 = bar1; // OK<br />p2 = bar2; // NEM OK, formális paraméterlista nem egyezik<br />p2 = bar1; // NEM OK, visszatérési érték nem egyezik<br />

Egy függvénymutatót az alábbi módon hívhatunk meg:

<br />fp p;<br />p = foo1;<br />p=(); // ennek hatására végre fog hajtódni a foo1() eljárás<br />

Mindjárt meglátjuk, hogy miért lesz ez nekünk jó.

Legyen egy tetszőleges, másik Qt projektünk, amiben szeretnénk ezt a rendkívül hasznos dll-t felhasználni. Abban az osztályban, ahol használni szeretnénk a dll-t, a

QLibrary

segítségével tudjuk betölteni:

typedef void ('''fp)();<br />QLibrary library("<eléri_útvonal>/test.dll");<br />library.load();<br />if(library.isLoaded()){<br /> fp p = (fp)library.resolve("hello");<br /> p();<br />}<br />else{<br /> qDebug() << "Nem sikerült betölteni a dll-t!" << endl;<br />}<br />


Első lépésben betöltjük a dll-ünket, majd meggyőződünk a

library.isLoaded()

függvénnyel, hogy tényleg sikerült-e betöltenünk. Ha nem, akkor nagy valószínűséggel rossz elérési útvonalat adtunk meg. Fontos ezt megvizsgálni, mert ha rossz helyre irányítjuk a függvénymutatónkat az 5. sorban, akkor annak katasztrofális végeredménye lehet (jobb esetben a

library.resolve

ha nem találja az adott függvényt, akkor NULL-al tér vissza, és a p() -re egyből meghal a programunk, rosszabb esetben akár valami értelmes helyre is mutathat, ilyenkor a következmények teljesen megjósolhatatlanok.)
Tehát egy dll fájl betöltése két lépésben történik: Megadjuk a pontos, teljes elérési útvonalát, és meghívjuk rá a

load()

eljárást. Ha sikeres volt a betöltés, akkor a következő lépésben egy függvénymutatót rá kell állítanunk a használni kívánt függvényre. Ez a

resolve("<függvénynév>")

segítségével tehető meg. Ezek után pedig nincs is más dolgunk, mint a 6. sorban meghívni a függvényünket, és gyönyörködni a végeredményben.


Ez mind nagyon szép és jó, de mi van akkor, ha szeretnénk komplett C++ osztályokat betölteni dll-ből?
Térjünk vissza a test.h fájlunkra, és módosítsuk azt az alábbiak szerint:


<br />#ifndef TEST_H<br />#define TEST_H
<br />#include <QDebug><br />#include "test_global.h"

<br />class TESTSHARED_EXPORT Test{
<br />public:
<br /> Test(){}<br /> void hello() { qDebug() << "Hello dll" << endl;}
<br />};
<br />extern "C" Q_DECL_EXPORT Test * create(){ return new Test(); }
<br />#endif // TEST_H


Mint látható megváltozott pár dolog, lássuk ezeket:
*

TESTSHARED_EXPORT

: Ez a(z egyetlen) test_global.h fájlunkban definiált makró a korábbi

_''declspec(dllexport)

csúnyaságot (+

''_declspec(dllimport))

takarja, azt jelzi, hogy az osztályunkat szeretnénk majd exportálni.
* a

create()

függvény: Ennek a függvények a segítségével fogjuk tudni az osztályunkat példányosítani egy másik fájlban.


Abban az osztályban, ahol használni szeretnénk, a kód e szerint módosul:


#include "<a_dll_fájl_headerje>" // mi esetünkben #include "test.h"<br /> …<br /> // a dll betöltése változatlan, ez a if(library.isLoaded()) részen belül van!
<br /> typedef Test * ('''gettest)();<br /> gettest gt = (gettest) library.resolve("create");
<br /> Test''' t = gt();<br /> if (t){<br /> t->hello();<br /> }<br /> else<br /> qDebug() << "Nem sikerült létrehozni az objektumot!" << endl;<br />


Mit is csináltunk most? Definiáltunk egy függvénymutatót, aminek a visszatérési értéke egy Test objektumra mutató (gt), illetve egy külön Test objektumra mutatót(t) (mindezt azért tehettük meg, mert beincludáltuk a test.h header fájlt). A gt függvénymutatót ráállítottuk a dll-ben lévő

create()

függvényre, ami nem csinál semmi mást, mint létrehoz egy új Test objektumot, és visszaad az objektumra mutató mutatót. Ezt a visszatérési értéket fogjuk eltárolni a t mutatóban, amit ezek után természetes módon használhatunk, például ahogyan a 10. sorban látszik meghívhatjuk nyugodtan a

hello()

függvényét.
Ha nem szeretnénk az egész osztályt exportálni, csak néhány függvényt belőle, akkor nincsen más teendőnk, mint a

TESTSHARED_EXPORT

makrót elhagyjuk az osztály deklarálásánál, és minden olyan függvényt, amelyet exportálni szeretnénk ellátunk

extern "C"

és

Q_DECL_EXPORT

részekkel (ahogyan a példában a create() szerepel). Ilyenkor az egyetlen (viszont életveszélyes) dolog az, hogy azokat a függvényeket is látni fogjuk (nyílván, hiszen van hozzá header fájlunk) az osztályból, amelyek nincsen a dll-be exportálva, és ha azokat szeretnénk meghívni akkor gyönyörű szép memóriahibákat tudunk kapni. Ennek a problémának a megoldása is sajnos túlmutat a példáinkon.
Hogy még egyszerűbbé tegyük az életünket, használhatjuk a

Test''' t = new Test;<code> sort is, így teljesen megszabadítva magunkat a függvénymutatózás borzalmaitól.

Mostanáig dinamikusan / futási időben töltöttük be a dll fájlainkat, mindig akkor amikor szükségessé váltak. Mi van akkor, ha tudjuk, hogy néhány erőforrásra a program minden egyes futtatásakor szükségünk lesz? Ilyenkor lehetőségünk van arra, hogy már fordítási / statikus időben hozzáfűzni a dll-ünket a programunkhoz. Ennek módja a következő:

A projektünk .pro fájljába ('''nem''' a test.pro[[Image:|Image:]]!) írjuk be az alábbi sorokat:

INCLUDEPATH = <a >
LIBS= -L<dll-t tartalmazó mappa elérési útja< #ha van az elérési útvonalban space akkor LIBS = -L"<dll-t tartalmazó könyvtár elérési útja>"
LIBS= -ltest

<br />Az <code>INCLUDEPATH

tulajdonképpen arra szolgál, hogy ne kelljen a test.h header fájl teljes elérési útvonalát beírni minden #include során, ami számunkra feltétlenül fontos az a

LIBS

.


Ezen sorok felvétele után a betöltés a kódból teljesen elhagyható:
#include "<a_dll_fájl_headerje>" // mi esetünkben #include "test.h"

// a dll betöltése ELHAGYHATÓ[[Image:|Image:]][[Image:|Image:]][[Image:|Image:]]!


Test * t = new Test;
if (t){
t->hello();
}
else
qDebug() << "Nem sikerült létrehozni az objektumot!" << endl;

Már csak egy kérdésünk maradt hátra: mi a helyzet az olyan dll-ekkel, amelyek Qt osztályokat tartalmaznak?<br />A válasz pofonegyszerű: minden ugyan így, változtatások nélkül működik!<br />(A következő kódrészeknél feltételezzük, hogy statikusan betöltjük a dll-ünket!)

Módosított test.h:


#ifndef TEST_H
#define TEST_H

  1. include <QObject>
    #include <QDebug>
    #include "test_global.h"

class TESTSHARED_EXPORT Test : public QObject{

Q_OBJECT

public:

Test(){}
void hello() { qDebug() << "Hello dll" << endl;}
void setX(int x){ m_x = x;}
int getX(){return m_x;}

private slots:
void printX(){ qDebug() << m_x << endl;}

private:
int m_x;
};

  1. endif // TEST_H
    Hívó osztály átírva:<br />
    
    #include <QtGui/QApplication>
    #include "mainwindow.h"
    #include <QLibrary>
    #include <QDir>
    #include <QDebug>
  1. include "test.h"

int main(int argc, char argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();

Test t = new Test;
QObject::connect(&w, SIGNAL (destroyed()), t, SLOT (printX()));

if (t){
t->hello();
t->setX(3);
qDebug() << t->getX() << endl;
t->setX(4);
}
else
qDebug() << "Nem sikerült betölteni az objektumot!" << endl;

return a.exec();
}

A példát lefuttatva látjuk, hogy minden további nélkül használhatóak a Qt-s osztályaink, illetve a signal / slot rendszer is hibátlanul működik így is.