Posts from 2009

Metody rozszerzające w C#

2009-09-11 12:28

Wspomnę dzisiaj o dość dziwnej funkcji, która została dodana w wersji 3.0 języka C#. Polega ona na możliwości dodania nowych metod do istniejących klas bez zmiany ich definicji. Odbywa się to poprzez zdefiniowanie tych dodatkowych metod jako statycznych (w innych klasach) i użyciu specjalnej składni dla jej pierwszego parametru. Oto przykład:

  1. public static class StringExtensions
  2. {
  3.     // zwraca liczbę znaków w ciągu
  4.     public static int WordCount(this String str)
  5.         { return str.Split(new char[] { ' ' }).Length; }
  6. }

Jeśli ktoś co nieco wie o tym, jak od środka działają metody obiektów w C++, to pewnie przypomina sobie, że to co wewnątrz metody jest wskaźnikiem this w rzeczywistości przekazywane jest metodzie jako jej pierwszy parametr. Pewnie ten fakt posłużył twórcom C# jako inspiracja przy ustalaniu składni metod rozszerzających.
Jak to działa? Otóż bardzo prosto. Dzięki powyższej definicji standardowa klasa System.String została teraz wzbogacona o nową metodę WordCount, której możemy użyć tak samo, jak każdej innej metody tej klasy:

  1. string s = "Ala ma kota";
  2. int words = s.WordCount();

Powód, dla którego fakt istnienia podobnego feature‘a nazwałem dość dziwnym, powinien się w tej chwili stać nieco jaśniejszy. Po krótkim zastanowieniu można bowiem stwierdzić, że niemal identyczny efekt można osiągnąć dwoma innymi sposobami:

  • Po pierwsze, możemy zdefiniować klasę pochodną po tej, której chcemy dodać metodę. Albo wręcz zdefiniować sobie po prostu zwykłą funkcję przyjmującą jeszcze zwyklejszy parametr i mieć coś w stylu StringUtils.WordCount(s). Jak widać, co najmniej jeden z tych sposobów można zastosować do każdej klasy – także takiej, której definicją nie dysponujemy (co w gruncie rzeczy jest banalne; na takich “sztuczkach” polega przecież całe programowanie obiektowe ;-]).
  • Po drugie, możemy zwyczajne pójść do deklaracji klasy i żądaną metodę dodać. Naturalnie, musi to być nasza własna klasa, której kodem dysponujemy i który możemy zmieniać.

Cechą wspólną obu tych “obejść” jest to, iż to my chcemy mieć tę nową metodę i to my ją definiujemy – zapewne dlatego, że to my chcemy jej potem używać. A skoro tak, to mamy wolność w ustaleniu sposobu jej wywoływania, ergo: nie ma znaczenia, jak ją zdefiniujemy. Któryś z tych dwóch/trzech sposobów będzie więc wystarczający.

Czyżby więc wychodziło na to, że C# w końcu dorobił się feature‘a, który rzeczywiście niczemu nie służy i jest kompletnie bez sensu? :) Prawie… Jak to z większością nowości w C# 3.0, przyczyna wprowadzenia metod rozszerzających streszcza się w – za przeproszeniem – czterech literach: LINQ.
LINQ opiera się bowiem na rozszerzeniu interfejsów pojemników (np. IEnumerable) o dodatkowe metody służące wykonywaniu na nich quasi-SQL-owych zapytań – jak np. Select czy Where. One właśnie zostały zaimplementowane jako metody rozszerzające. A że ponadto trafiły one do przestrzeni nazw System.Linq, to stają się dostępne dopiero po zadeklarowaniu użycia tej właśnie przestrzeni.
Reasumując: twórcy .NET-a dodali dwie nowości (metody rozszerzające oraz LINQ) po to, żeby ta pierwsza pozwalała na nieużywanie tej drugiej. Genialne, czyż nie? ;D

Tags: ,
Author: Xion, posted under Programming » 6 comments

Kawałek kodu w makrze

2009-09-01 19:52

Jeśli staramy się pisać kod zgodnie z dobrymi praktykami programowania w C/C++, to niezbyt często korzystamy z dyrektywy #define. W większości przypadków ogranicza się to zresztą do zdefiniowania jakiegoś symbolu służącego do kompilacji warunkowej (np. WIN32_LEAN_AND_MEAN). Od czasu do czasu bywa jednak tak, że chcemy użyć #define w jego – teoretycznie – głównym zastosowaniu, czyli do zastępowania jednego kawałka kodu innym.

Uzasadnione przypadki takiego postępowania (do których nie należą np. makra udające funkcje, czyli sławetne #define min/max) to tylko takie sytuacje, w których bez #define rzeczywiście musielibyśmy wiele razy pisać (prawie) to samo. Moim ulubionym przykładem czegoś takiego jest wychodzenie z funkcji w wielu miejscach, gdzie przed każdym returnem musimy coś jeszcze zrobić – np.:

  1. bool SprobujCosObliczyc(const data& parametry, int* const wynik)
  2. {
  3.     if (parametryNieSaDobre) { *wynik = 0; return false; }
  4.     /* ... */
  5.     if (cosObliczylismyAleJestZle) { *wynik = 0; return false; }
  6.     /* ... */
  7.     if (tutajCosSiePopsulo) { *wynik = 0; return false; }
  8.     /* ... */
  9.     if (juzPrawieKoniecAleNiestetyLipa) { *wynik = 0; return false; }
  10.     /* ... */
  11.     return true;
  12. }

Zwyczajne ustawienie *wynik = 0; na początku funkcji będzie nieakceptowalne, jeśli chcemy, by w przypadku rzucenia wyjątku w którymś z /*... */ podana do funkcji zmienna pozostała bez zmian.
Prostym rozwiązaniem wydaje się więc makro:

  1. #define RETURN { *wynik = 0; return false; }

definiowane przed i #undefowane tuż po funkcji. Pomysł byłby dobry, gdyby nie to, że w tej postaci makro to powodowałoby błędy składniowe. Kiedy? Chociażby w takim ifie:

  1. if (wyjsc) RETURN; else { /* ... */ }

Tutaj bowiem kompilator nie znalazłby instrukcji if pasującej do else, co jest naturalnie błędem. Powodem tego jest średnik po wystąpieniu makra, generujący pustą instrukcję i sygnalizujący (przedwczesny) koniec klauzuli if.

Jak ominąć ten problem? Odpowiedź jest może zaskakująca: instrukcje trzeba zamknąć w… pętlę:

  1. #define RETURN do { *wynik = 0; return false; } while (0)

Jak nietrudno zauważyć, taka pętla wykona się dokładnie raz (i co więcej, kompilator o tym doskonale wie, zatem może ją zoptymalizować – tj. wyrzucić wszystko poza zawartością). Działanie jest więc takie same. Różnica polega na tym, że teraz takie makro może być używane w dowolnym miejscu bez żadnych niespodziewanych efektów.

Tags: , ,
Author: Xion, posted under Programming » 12 comments

Trzeci dodatek do WoW-a

2009-08-22 21:53

Przez ostatni tydzień byłem na małych wakacjach, więc nie zajmowałem się niczym na tyle pożytecznym, by dało się o tym tutaj napisać ;] Ale na szczęście w międzyczasie zdarzyło się coś, o czym zdecydowanie warto wspomnieć.

Wczoraj mianowicie potwierdziły się spekulacje odnośnie kolejnego, trzeciego już dodatku do gry World of Warcraft. Rzecz była w sumie do przewidzenia, bowiem – podobnie jak dwa poprzednie – i to rozszerzenie zostało oficjalnie ogłoszone podczas BlizzConu. Co tam więc nowego ma zostać dodane do świata Azeroth?…
Ano odpowiedź brzmi: prawie nic albo niemal wszystko. W przeciwieństwie do poprzednich dodatków, ten nie doda żadnego nowego, dużego kontynentu do zwiedzania przez wysokopoziomowych graczy, a jedynie kilka nowych, mniejszych lokacji rozsianych po świecie. Tenże świat ma być jednak gruntownie przekształcony w wyniku tajemniczej i niezwykle brzemiennej w skutkach katastrofy – kataklizmu. Taki jest zresztą tytuł rozszerzenia – World of Warcraft: Cataclysm. Sprawcą całego zamieszania ma tu być Deathwing – pewien niezbyt przyjemny czarny smok, którego niektórzy może pamiętają z Warcrafta 2.
Jak widać, fabularnie prezentuje się to tak sobie, ale ciężko oczekiwać po tylu latach jakiejś wielkiej kreatywności :) To co bardziej interesujące, to oczywiście zmiany w samej grze, które dodatek ma przynieść. A są one następujące:

  • podniesienie maksymalnego poziomu postaci do 85
  • mniejsze lub większe zmiany w większości lokacji na dwóch starych kontynentach (tu coś zalane, tam jakiś wulkan wybuchnie, itp. ;]) oraz dodanie kilku nowych lokacji; częścią zmian będzie miedzy innymi umożliwienie swobodnego latania w “starym” świecie
  • dwie nowe rasy: Gobliny dla Hordy i Worgeni (takie wilkołaki) dla Sojuszu
  • wprowadzenie nowych kombinacji ras, które wcześniej nie były dostępne, np. Troll Druid czy Nocny Elf Mag
  • nowa poboczna profesja – Archeologia, pozwalająca na szukanie starożytnych skarbów i otrzymywanie za nich różnych nagród
  • nowe ścieżki rozwoju nie tylko dla postaci, ale i dla gildii – łącznie z Osiągnięciami (Guild Achievements), a nawet talentami
  • uproszczenie statystyk przedmiotów, usuwające z nich niektóre drugorzędne atrybuty, jak Attack Power czy Spell Power

Część zmian wydaje się drobna, gdyż tak naprawdę są fragmentem dość długiej listy niewielkich, acz ciekawych modyfikacji, które dodatek ma przynieść; wybrałem te, które wydały mi się najważniejsze. Widać oczywiście, że są też i większe nowości, ale nietrudno zauważyć, że brak wśród nich jednej z bardziej oczekiwanych – nowej klasy. Jej w Kataklizmie nie uświadczymy.

   

Tak w dużym skrócie całość się przedstawia i trudno byłoby zaprzeczyć, że wygląda to całkiem ciekawie. Osobiście wydaje mi się, że to właśnie rozszerzenie może być decydujące dla dalszych losów gry. Jeśli okaże się, że lifting starych, liczących sobie już prawie 5 lat krain wypadł pomyślnie, może to przyciągnąć jeszcze większą liczbę graczy i zapewnić WoW-owi pozycję lidera na kolejne kilka lat – czyli mniej więcej na tyle, na ile rozciągają się prawdopodobne plany kolejnych 2-3 dodatków. (Tę liczbę można wywnioskować z tempa podnoszenia maksymalnego poziomu postaci i niedwuznacznych sugestii Blizzarda, że poziom 100 będzie ostatnim). Nie muszę pewnie dodawać, że szansa na to, iż będzie inaczej Cataclysm okaże się komercyjną klapą, są – znając Blizzarda – baaardzo małe :)

Jak będzie w rzeczywistości, zobaczymy pewnie gdzieś w okolicach końcówki przyszłego roku. Ja mam tylko nadzieję, że Starcraft 2 będzie wydany wcześniej ;D

Tags:
Author: Xion, posted under Games » 3 comments

Gdzie jest pasek zadań

2009-08-10 15:11

Programy działające w tle (np. komunikatory) często używają różnego rodzaju “wyskakujących” powiadomień, które w założeniu mają pojawiać się tuż obok zasobnika systemowego (system tray), gdzie wspomniane programy mają swoje ikonki. W rzeczywistości pozycja tych komunikatów jest często hardcode‘owana w okolicach prawego dolnego rogu ekranu. Jeśli więc użytkownik przypadkiem ma pasek zadań gdzie indziej, to wtedy cały misterny plan idzie w… las ;]
Dlatego lepszym rozwiązaniem jest sprawdzanie, gdzie tak naprawdę ów pasek się znajduje. Przez długi czas można było to zrobić poprzez zwyczajne pobranie jego uchwytu (HWND) i odczytanie wymiarów tak, jak każdego innego okna – np. przez GetWindowRect:

  1. HWND hTaskBar = FindWindow(TEXT("Shell_TrayWnd"), NULL);
  2. // "Shell_TrayWnd" to klasa okna specyficzna dla paska zadań
  3. RECT rc; GetWindowRect (hTaskBar, &rc);

Począwszy od Windows Vista nie jest to jednak możliwe, gdyż ze względu na bezpieczeństwo (tzw. izolacja sesji 0) nie da się już tak po prostu pobrać uchwytu do paska zadań ze zwykłej aplikacji. Istnieje na szczęście przenośny sposób na pobranie jego pozycji; w tym celu trzeba się posłużyć funkcją powłoki systemowej SHAppBarMessage (z nagłówka shellapi.h i biblioteki shellapi.lib):

  1. UINT GetTaskBarPos(LPRECT rc)
  2. {
  3.     APPBARDATA abd = { sizeof(APPBARDATA), NULL };
  4.     if (!SHAppBarMessage(ABM_GETTASKBARPOS, &abd)) return 0;
  5.     if (rc) memcpy (rc, &abd.rc, sizeof(RECT));
  6.     return abd.uEdge;
  7. }

Tak możemy otrzymać nie tylko wymiary prostokąta okalającego pasek, ale też informację o tym, przy której krawędzi się on znajduje. Jest ona zawarta w polu uEdge struktury zwracanej przez funkcję SHAppBarMessage i równa którejś ze stałych ABE_BOTTOM, ABE_TOP, itd.

Mając te dwie informacje możemy nie tylko wyświetlać nasze powiadomienia w odpowiednim miejscu, ale też zapewnić, żeby w razie ich nagromadzenia “rosły” one w odpowiednim kierunku.

Tags: ,
Author: Xion, posted under Programming » 10 comments

Typ zwracany funkcji wirtualnej

2009-08-04 16:42

Kiedy w klasie pochodnej piszemy nową wersję funkcji wirtualnej, to w C++ powinniśmy zawsze uważać na to, żeby jej nagłówek był identyczny z tym, który zdeklarowano w klasie bazowej. To dlatego, że składnia języka nie wymaga w tej sytuacji żadnego słowa kluczowego w rodzaju override. Jeśli więc coś nieopatrznie zmienimy, to nie uzyskamy nowej wersji funkcji, tylko zupełnie nową funkcję przeciążoną:

  1. class Foo
  2. {
  3.     public: virtual void Do(int x, int y, int z) { }
  4. };
  5.  
  6. class Bar : public Foo
  7. {
  8.     // to *nie* jest nowa wersja Foo::Do, tylko zupełnie inna funkcja
  9.     public: void Do(int x, int y, string z) { }
  10. };

Taki efekt jest jest w gruncie rzeczy oczywisty i łatwy do zrozumienia. Pewnie więc będzie nieco zaskakujące dowiedzieć się, że tak naprawdę postać funkcji wirtualnej w klasie pochodnej może się trochę różnić, a mimo to wciąż będziemy mówili o tej samej funkcji.

Jak to jest możliwe?… Dopuszcza się mianowicie różnicę w typie zwracanym przez nową wersję funkcji wirtualnej. Może on być inny od typu zwracanego przez wersję z klasy bazowej – ale tylko pod warunkiem, że:

  • pierwotny typ był wskaźnikiem lub referencją do klasy (a nie chociażby typem podstawowym, jak int)
  • nowy typ zwracany jest – odpowiednio – wskaźnikiem lub referencją do klasy pochodnej od tej z typu pierwotnego
  • nowy typ posiada tę samą kwalifikację const/volatile co typ pierwotny

Przykładowo więc, jeśli w klasie bazowej funkcja wirtualna zwraca const A*, to w klasie pochodnej może zwracać const B*, o ile klasa B dziedziczy publicznie po A. Nie może za to zwracać samego B* (niestałego) lub const X*, gdy klasa X jest niezwiązana z A.

Do czego taki “myk” może się przydać? Najczęściej chodzi tutaj o sytuacje, gdy mamy do czynienia z równoległym dziedziczeniem kilku klas, które na każdym poziomie są związane ze sobą. Mogę sobie na przykład wyobrazić ogólny menedżer zasobów w grze, którego metoda wirtualna Get zwraca wskaźnik na bazową klasę Resource, a następnie bardziej wyspecjalizowany TextureManager, który w tej sytuacji podaje nam wskaźnik na Texture. (Oczywiście klasa Texture w założeniu dziedziczy tu po Resource). Czasami coś takiego może być potrzebne, aczkolwiek takie równoległe hierarchie dziedziczenia nie są specjalnie eleganckie ani odporne na błędy.
Lepszym przykładem jest wirtualny konstruktor kopiujący: metoda, która pozwala na wykonanie kopii obiektu bez dokładnej znajomości jego typu. Zwyczajowo nazywa się ją Clone:

  1. class Object
  2. {
  3.     public:
  4.         virtual Object* Clone() const { return new Object; }
  5. };
  6.  
  7. class Foo : public Object
  8. {
  9.     public:
  10.         // nowa wersja metody Object::Clone
  11.         Foo* Clone() const { return new Foo; }
  12. };

Dzięki temu że metoda jest wirtualna, można ją wywołać nie znając rzeczywistego typu obiektu (co nie jest możliwe w przypadku zwykłych konstruktorów, które w C++ wirtualne być nie mogą). W wyniku jej wywołania otrzymamy jednak tylko ogólny wskaźnik Object* na powstałą kopię obiektu.
Gdybyśmy teraz nie zmienili typu zwracanego przez metodę w klasie pochodnej, to klonowanie podobne do tego:

  1. void Function(Foo* foo)
  2. {
  3.     Foo* copy = foo->Clone();
  4.     // ...
  5. }

wymagałoby dodatkowego rzutowania w górę, by mogło zostać przepuszczone przez kompilator.

Tags: , , , , ,
Author: Xion, posted under Programming » Comments Off on Typ zwracany funkcji wirtualnej

PlaySound bez ścięć

2009-07-31 17:03

Chcąc w prosty sposób odtworzyć dźwięk w programie pod Windows, możemy skorzystać z funkcji WinAPI o nazwie PlaySound. Wystarczy podać jej nazwę pliku dźwiękowego, by ten został odegrany:

  1. PlaySound (TEXT("C:\\Windows\\Media\\ding.wav"), NULL, 0);

Jak to jednak bywa w przypadku metod najprostszych, nie jest to rozwiązanie doskonałe. Jedną z jego wad jest bowiem to, że próbka dźwiękowa jest tutaj wczytywana z dysku tuż przed jej pierwszym odtworzeniem. W większości przypadków trwa to chwilę, którą da się zauważyć jako krótkie opóźnienie przed usłyszeniem dźwięku.
W grach (i nie tylko) jest to oczywiście niedopuszczalne, więc zwykle najlepszym wyjściem jest użycie wyspecjalizowanych bibliotek dźwiękowych. Jeśli jednak nie chcemy dołączać dodatkowych plików DLL lub wykorzystywać DirectX-a, możemy temu opóźnieniu zaradzić.

Funkcja PlaySound pozwala bowiem na odtworzenie dźwięku nie tylko z pliku, ale też z pamięci operacyjnej:

  1. PlaySound ((LPCTSTR)pSound, NULL, SND_MEMORY);

W tym celu posłużyć się trzeba flagą SND_MEMORY i podać wskaźnik do bloku pamięci, w którym znajduje się nasza próbką dźwiękowa. Skąd go wziąć? Ano chociażby wczytać plik dźwiękowy z dysku do pamięci:

  1. const void* LoadSound(const TCHAR* fileName)
  2. {
  3.     // uwaga: brak obsługi błędów
  4.     HANDLE file = CreateFile(fileName, GENERIC_READ, FILE_SHARE_READ,
  5.                              NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  6.     DWORD size = GetFileSize(file, NULL);    // max. 2GB
  7.  
  8.     char* sample = new char [size];
  9.     char* p = sample;
  10.     DWORD bytesRead;
  11.     do
  12.     {
  13.         ReadFile (file, p, size, &bytesRead, NULL);
  14.         p += bytesRead;
  15.     } while (bytesRead > 0);
  16.  
  17.     CloseHandle (file);
  18.     return sample;
  19. }

“Trik” polega na tym, że operację wczytywania możemy przeprowadzić wcześniej, a więc w trakcie ładowania całej gry, pojedynczego etapu, pokoju, krainy, itp. – czyli wtedy, gdy jej potencjalnie długi czas nie będzie nikomu przeszkadzał. Potem zaś da się ją odtworzyć z pamięci i nie doświadczać żadnych opóźnień.
Trzeba tylko pamiętać, żeby na koniec zwolnić pamięć przeznaczoną na wczytaną próbkę.

Tags: ,
Author: Xion, posted under Programming » 6 comments

Mało znane tagi HTML

2009-07-25 18:40

Prawie każdy potrafi sklecić prostą stronę WWW, nie mówiąc już o napisaniu nieskomplikowanego tekstu w formacie HTML. Do tego celu wystarczy zwykle użyć od kilku do kilkunastu standardowych tagów, odpowiedzialnych za podstawowe formatowanie (pogrubienie, kursywa, itp.) oraz wstawianie obiektów w rodzaju obrazków czy tabel.
W rzeczywistości istnieje jednak znacznie więcej znaczników, z których wiele jest bardzo mało znanych. Oto kilka spośród nich:

  • <abbr> i <acronym> służą do wstawiania akronimów (odpowiednio: literowych i głoskowych) wraz z rozwinięciem (podawanym przy pomocy atrybutu title). Przykładami mogą być chociażby NATO czy PZU. W większości przeglądarek owo rozwinięcie pojawi się po najechaniu kursorem na dany akronim.
  • <dl> pozwala na stworzenie tzw. listy definicyjnej, czyli krótkiego słownika pojęć. Każdy termin należy w niej umieścić wewnątrz tagu <dt>, zaś jego rozwinięcie w tagu <dd>. W wyniku możemy otrzymać coś takiego:
    jeden
    liczba o jeden mniejsza niż dwa
    dwa
    liczba o jeden większa niż jeden
  • Tagi <em>, <code>, <kbd> oraz kilka podobnych to próba dodania do HTML sposobu na określenie znaczenia (a nie tylko formatowania) poszczególnych fragmentów dokumentu. W jaki sposób te tagi są interpretowane przez wyszukiwarki pozostaje raczej niejasne, ale można podejrzewać, że większość nie przywiązuje do nich dużej wagi, bo ich używanie na stronach nie jest specjalnie częste. Jeśli jednak zdecydujemy się na konsekwentne korzystanie z nich, to zaletą takiego postępowania będzie łatwość sformatowania np. wszystkich kawałków kodu na naszej stronie przy pomocy odpowiedniego stylu zaaplikowanego do znaczników <code>. Trudno aczkolwiek nie zauważyć, że klasy w CSS działają właściwie tak samo…
  • Analogicznie tagi <ins> i <del> mogą służyć do oznaczania zmian w dokumencie: tekstu wstawionego lub usuniętego, łącznie z możliwością podania daty dokonania zmiany. Żadna z głównych przeglądarek nie obsługuje jednak tego feature‘u, więc póki co przydatność tych tagów jest bliska zeru.

Oczywiście lista ta jest daleka od bycia kompletną, bo zasób wszystkich znaczników HTML liczy sobie dobrych kilkadziesiąt pozycji. Warto go przejrzeć, bo istnieje duża szansa, że natrafimy na coś przydatnego :)

Tags:
Author: Xion, posted under Internet » 8 comments
 


© 2023 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.