Warsztat.GDCompo!ProjektyMediaArtykułyQ&AForumOferty pracyPobieranie

Opisz napotkaną sytuację, a redakcja niezwłocznie znajdzie rozwiązanie!

wyślij anuluj

Podstawy bibliotek DLL - ujęcie praktyczne

Tekst został importowany z Warsztatowych artykułów. Jego oryginalnym autorem jest Hubert "Koshmaar" Rutkowski. Jeżeli został importowany poprawnie, usuń ten szablon!

[[Kategoria:Programowanie C++]]

Podstawy bibliotek DLL – ujęcie praktyczne


Wstęp

Moim zamiarem jest przedstawienie krok po kroku prostego schematu tworzenia i wykorzystywania w swoim kodzie bibliotek łączonych dynamicznie, czyli osławionych DLLek. Nie będę zajmował się tłumaczeniem teorii, gdyż zostało to dużo lepiej zrobione na angielskiej Wikipedii [1], zakładam więc że przed przejściem dalej będziesz ją rozumiał. Ogólnie mówiąc, w sieci jest mnóstwo informacji poświęconych DLLkom (zobacz [2]), szczególnie na angielskich stronach. Jednak spora część z nich jest pisana specjalnie dla totalnych begginerów którym trzema krok po kroku tłumaczyć np. jak utworzyć projekt ("Najedź na pozycję New z menu File, wybierz Project...") lub go skompilować.

Nie uśmiecha mi się aż taki poziom szczegółowości, dlatego moją docelową grupą czytelników są koderzy mający już nieco doświadczenia, być może grę albo dwie w dorobku, jednak nie umiejący jeszcze samemu tworzyć i wykorzystywać DLLek. Przy pomocy informacji które się znajdzie w sieci można się tego nauczyć, jednak z mojego skromnego doświadczenia wynika że nie jest to takie proste jakby się wydawało... ja sam mając już ciutkę doświadczenia w programowaniu byłem mocno zdziwiony że zajęło mnie to ładnych kilka godzin, głównie z powodu niewielkich błędów albo niedopowiedzeń w tutorialach, które jednak sprawiały że trzeba było samemu kombinować metodą prób i błędów. Pamiętając o tym wszystkim zdecydowałem się napisać artykuł, dzięki któremu inni koderzy nie będą musieli już wyrywać sobie włosów z głowy czy rzucać myszką po ścianach. Skupię się przy tym na takim kodzie i ustawieniach środowiska które umożliwią efektywną i przyjemną pracę z DLLkami. Tak przy okazji, jest to mój pierwszy art wysłany na Warsztat, więc proszę o wyrozumiałość :-)


Początek

Dobra, koniec przydługiego wstępu, uzgodnijmy jeszcze w czym będziemy rzeźbić: C++, Windows, Visual Studio 2005. Dwa pierwsze są najważniejsze, trzeci można bez problemu wymienić na dowolną inną wersję Visuala albo inne IDE (DevCpp / Code :: Blocks / CokolwiekInnego). Przejście np. na MinGW ([4]) nie powinno sprawiać problemów, różnice występują praktycznie tylko przy tworzeniu projektów, ustawieniach IDE albo szczegółach składni. Następna sprawa: przedstawię sposób tworzenia DLLek łączonych jawnie, na przykładzie eksportowania funkcji oraz klasy.

Pierwsze co trzeba zrobić, to stworzyć projekt biblioteki oraz programu który będzie jej używał – zacznijmy od tego pierwszego. Stwórz więc nowy (pusty) projekt typu Win32 Project, ustaw DLL w opcjach, nazwij go np. DLLTest_Lib. Teraz mając gotowy szkielet możemy przystąpić do tworzenia własnych plików :-) pierwszy będzie to plik nagłówkowy, którego zadaniem jest wyliczenie eksportowanych funkcji i klas. Oto on (plik myDLL_Test.h):


#ifndef MY_DLL_TEST_H // 1
#define MY_DLL_TEST_H


#ifdef DLLTEST_LIB_EXPORTS // 2
 #define MY_DLL __declspec(dllexport)
#else
 #define MY_DLL __declspec(dllimport)
#endif


MY_DLL int GetFive(); // 3


class MY_DLL TestClass // 4 
 {
  public: 
  
   bool AmICool() const;
 };

#endif

Po kolei: (1) – standardowy straźnik nagłówkowy. Aby wyeksportować cokolwiek, musimy powiedzieć kompilatorowi co to mają być za funkcje, klasy itp. Robimy to poprzez oznaczenie danej funkcji (3) lub klasy (4) przy pomocy modyfikatora __declspec(dllexport). Jednak ponieważ plik myDLL_Test.h będzie również includowany poprzez aplikację kliencką, która z kolei musi wiedzieć co będziemy w niej importować (oznaczane przez __declspec(dllimport)), więc stosujemy tą sekwencję (2) instrukcji preprocesora: jeśli DLLTEST_LIB_EXPORTS jest zdefiniowane, podstaw pod MY_DLL ciąg __declspec(dllexport), w przeciwnym wypadku __declspec(dllimport). Ponieważ DLLTEST_LIB_EXPORTS będzie zdefiniowane tylko w projekcie biblioteki DLL, otrzymujemy prosty i efektywny mechanizm eksportowania / importowania. Teraz wystarczy tylko przed nazwą deklaracji wybranych funkcji oraz definicji klas (zauważ w którym miejscu) wstawić MY_DLL (czy co tam wybrałeś) oraz w opcjach kompilatora (w VS to C/C++ -> Preprocessor -> Preprocessor definitions) dodać DLLTEST_LIB_EXPORTS.

Krótki komentarz dot. nazwy DLLTEST_LIB_EXPORTS: jest ona dowolna, jak chcesz może to być np. DUPA_DUPA_DUPA, jednak VS domyślnie predefiniuje nazwę w stylu $(ProjectName)_EXPORTS, więc można tego używać. I jeszcze jedna uwaga na przyszłość: w projektach używających wielu DLLek jednocześnie musisz zadbać aby te nazwy były unikalne.

Więcej uwag nt. eksportowania klas w [3].

Teraz musimy zapewnić jeszcze implementację, zróbmy to w pliku np. myDLL_Test.cpp:


#include <windows.h> // 1
#include "myDLL_Test.h"

// 2
BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD ul_reason_for_call,
                       LPVOID lpReserved
                      )
 {
  return TRUE;
 }


int GetFive() { return 6; }

bool TestClass :: AmICool() const { return false; }

W (1) dołączamy standardowy plik nagłówkowy Windowsa, oczywiście można (i raczej trzeba) go dołączyć wykorzystując prekompilowane nagłówki aby kompilator ciągle nie przetwarzał tego samego (jak to zrobić? stwórz nowy projekt dllki z domyślnymi ustawieniami i zobacz jak VS to ustawił; jest to proste więc nie ma sensu rozpisywać się o tym tutaj). Dołączamy również plik myDLL_Test.h z deklarcjami danych które będziemy eksportować. Następnie widzimy (2), czyli DllMain, krótko mówiąc, jest to odpowiednik funkcji main(), dzięki niemu możemy kontrolować stan życia naszej DLLki, kto z niej w jaki sposób korzysta itp. Resztę szczegółów z łatwością znajdziesz np. w MSDNie, powiem jeszcze tylko że taka forma jaką tutaj widać (zwrócenie TRUE) zazwyczaj wystarcza. Na samym końcu mamy defincje eksportowanych funkcji (w tym jednej składowej klasy).

Ok, 99% roboty związanej z projektem biblioteki mamy z głowy, przejdźmy więc do aplikacji klienckiej ją wykorzystującej. Stwórz więc drugi pusty projekt Win32, tym razem niech to będzie Console Application. Dodaj nowy plik main.cpp o następującej treści:


#include <iostream>
#include "myDLL_Test.h"

using namespace std;


int main(int argc, char ** argv)
 {
  cout << "Hello world! Five is: " << GetFive() << endl;

  TestClass test;

                     
  // domyślnie cout wypisuje boole jako 1/0, dzięki                      
  // tej funkcji wypisze je jako true/false
  boolalpha( cout ); 

  cout << "Am I cool? " << test.AmICool() << endl;

  return 0;
 }

Komentarz praktycznie zbędny: dodajemy plik nagłówkowy z deklaracjami importowanych funkcji (a nie jak w innych tutorialach na sieci, wpisujemy ich nazwy bezpośrednio w pliku main.cpp – powodzenia w utrzymywaniu czegoś takiego!), następnie w main() najnormalniej w świecie używamy importowanych funkcji i typów. Czy trzeba coś dodawać?

Tak :-) kod obu projektów jest w 100% kompletny, jednak ich ustawienia pozostawiają wiele do życzenia. Pierwsza sprawa: projekt aplikacji musi widzieć plik .lib którego będzie używać do importowania funkcji (w dużym uproszczeniu). Dodaj więc nazwę tej biblioteki do sekcji Linker->Command Line, np. u mnie jest to DLLTest_Lib.lib oraz w Linker->General->Additional Library Directories dodaj katalog z tą biblioteką do ścieżki poszukiwań (ew. możesz to zrobić globalnie z ustawieniach VS aby nie powtarzać tego kroku przy każdym następnym projekcie używającym tej biblioteki, ale IMHO przy większej ilość DLLek bardzo nieładnie zanieczyściła by się ta "przestrzeń nazw"), a ponieważ najprawdopodobniej będziesz używał zarówno wersji Debug jak i Release, więc dodaj oba te katalogi. Podobnie wygląda sprawa z includowaniem – plik myDLL_Test.h jest w katalogu lokalnym dla projektu bibliteki, więc musimy pomóc #includowi go odszukać – C++->General->Additional Include Directories.

Spróbuj teraz skompilować oba projekty. Jeśli wszystko dobrze poustawiałeś (i ja nie zapomniałem o czymś wspomnieć :->) powinny się skompilować, natomiast z uruchomieniem jeszcze będą problemy... "System nie znalazł wymaganego pliku dll" itp. - czytaj dalej. Jak pewnie wiesz aby uruchomić aplikację używającą DLLki, system musi być w stanie ją znaleźć – musi się ona znajdować albo w katalogu z .exekiem, w Windows/, Windows/System albo w katalogu będącym na liście zmiennej środowiskowej PATH.

Na koniec dodam więc jeszcze kilka uwag dotyczących umieszczania DLLki w jednym z powyższych miejsc. Ze swoim komputerem możesz robić co tylko chcesz, jednak nie polecam zaśmiecania katalogów systemowych setkami własnych DLLek – a już nawet nie myśl o tym by robić to z komputerem użytkownika. IMHO najsensowniejszym miejscem do umieszczania DLLek jest katalog z programem. Jeśli masz bibliotekę którą po jednorazowym skompilowaniu będziesz dalej używać w jednym projekcie, to gratulacje, wtedy możesz się zadowolić ręcznym kopiowaniem pliku .dll. Jednak jeśli w bibliotece mogą występować błędy i będziesz musiał ją często przekompilowywać, a na dodatek będzie ona wykorzystywana w wielu projektach, to myślę że ręczne kopiowanie znudzi ci się bardzo szybko. Co zrobić w takiej sytuacji? Visual Studio (oraz inne lepsze IDE, np. C::B) posiada coś takiego jak Build Events – są to polecenia (zwykłe konsolowe – wiesz, cmd.exe i te klimaty) wykonywane z okazji określonych zdarzeń, np. Post-Build Event jest wywoływany po poprawnym zbudowaniu całego projektu. Przejdź więc do opcji projektu biblioteki, Build Events->Post-Build Event, pole Command Line. Ja tu wpisałem:

copy $(TargetPath) E:\Tests\DLLTest_App\Debug
copy $(TargetPath) E:\Tests\DLLTest_App\Release

Komentarz znów praktycznie zbędny, z wyjątkiem jednej kwestii: $(TargetPath) jest to makro w VS które podstawia w to miejsce nazwę tego czegoś, co dany projekt produkuje (łopatologicznie mówiąc). W naszym przypadku jest to plik .dll – po każdorazowym poprawnym zbudowaniu biblioteki zostanie on skopiowany do odpowiednich katalogów. VS posiada jeszcze wiele podobnych makr, poprzeglądaj je sobie w Helpie aby zorientować się w czym mogą cię wyręczyć.

Po skompilowaniu obu projektów i poprawnym uruchomieniu, przeprowadź mały test: zmień wartości zwracane przez funkcje testowe i zrekompiluj projekt biblioteki, ale tylko jej! następnie skopiuj nową DLLkę do katalogu aplikacji testowej i spróbuj uruchomić starego exeka. Jeśli program się uruchomił i ujrzałeś nowe wartości, jest to oczywisty znak że mechanizm bibliotek ładowanych dynamicznie zadziałał perfekcyjnie i jesteś w stanie przejść do efektywnej pracy z DLLkami. Gratulacje!

Dla kompletu i łatwiejszej nauki dołączyłem pliki projektów stworzone w Visual Studio Pro 2005 abyś mógł zobaczyć jak to wszystko jest poustawiane u mnie, jednak wątpię abyś mógł ich użyć bezpośrednio u siebie (chociażby z powodu innych ścieżek...).


Koniec

Oczywiście w powyższym tutorialu zaledwie liznąłem początki podstaw tematu DLLek, starałem się tłumaczyć tylko sprawy wymagające najwięcej uwagi, a i to czyniłem skrótowo. Wierzę że z pozostałymi rzeczami domyślny czytelnik nie będzie miał problemów. Mam nadzieję że błędów żadnych nie popełniłem i że ten tekst przyda się komuś w praktyce. Powodzenia w programowaniu gier! :-)

                Koshmaar ([email protected])


Bibliografia

[1] - http://en.wikipedia.org/wiki/Dynamic-link_library

[2] - http://www.codeproject.com/dll/

[3] - Perełki Programowania Gier #2, rozdział 1.4

[4] - http://mingw.org/docs.shtml



[[Kategoria:C++]] files/articles/podstawy_bibliotek_dll/simple%20DLL%20tutorial.odt files/articles/podstawy_bibliotek_dll/simple%20DLL%20tutorial.pdf files/articles/podstawy_bibliotek_dll/simple%20DLLs%20tutorial.zip

Tekst dodał:
Hubert Rutkowski
10.09.2006 22:04

Ostatnia edycja:
Hubert Rutkowski
10.09.2006 22:04

Kategorie:

Aby edytować tekst, musisz się zalogować.

# Edytuj Porównaj Czas Autor Rozmiar
#1 edytuj 10.09.2006 22:04 Hubert Rutkowski 13.69 KB
Zwykły
Do sprawdzenia
Do akceptacji
  • ~RedHot 28 czerwca 2007 23:01
    Nie dośc ,że często chaotycznie i nie na temat, to mi jeszcze dzień popsułeś xD Na Devie nie da rady, linker się pluje bez powodu , sprawdzone na 2 niezależnych kompach u 2 niezależnych ludzi a środowiska nie zmieniam :\ Niby moja strata, ale coś tu kurna nie gra.
  • ~Nattfarinn 08 września 2007 11:48
    Koshmaar: wydaje mi sie ze Fff chodzilo o fragment "tworzenia i wykorzystywania w swoim kodzie bibliotek łączonych dynamicznie". Fff zrozumial to jako temat ninejszego artukulu a nie jako rozwiniecie skrotu DLL (Dynamic Linked Library).

    Co do dynamicznego ladowania DLL'ek, zdaje sie ze Cyfrowy Baron swojego czasu naskrobal calkiem niezly artykulik jak cos takiego zrobic.
  • ~Akenes 14 lutego 2008 03:19
    Dobry artykul - bo ta instrukcja z msdn'a niewiele wyjasnia - chociarz 2 ze sposobow (Run-Time Dynamic Linking) ladowania dll'i wydaje mi sie troche sensowniejszy ;)
  • sssso (@sssso) 23 lipca 2008 23:01
    RedHot: U mnie na devie działa i to miło :P
  • RedHot (@RedHot) 19 października 2008 23:49
    Co prawda napisałem tamtego komenta ponad rok temu ( O.o oj) , ale pamiętam, że było coś nie tak z #define.
  • Napisz komentarz:
    Aby dodać swój komentarz, musisz się zalogować.
Licencja Creative Commons

Warsztat używa plików cookies. | Copyright © 2006-2017 Warsztat · Kontakt · Regulamin i polityka prywatności
build #ff080b4740 (Tue Mar 25 11:39:28 CET 2014)