Archive for Programming

Stare pliki INI

2010-06-24 15:47

Jest coś takiego jak aplikacje typu portable. Charakteryzują się tym, że nie wymagają instalacji i nie zostawiają żadnych “śladów” w systemie poza swoim własnym folderem. Takie programy są praktyczne, jeśli musimy korzystać z wielu różnych, nieswoich komputerów – wtedy można je przenieść na nośniku typu pendrive i uruchamiać bezpośrednio z niego.

Oczywiście nasze własne aplikacje nie muszą należeć do tej kategorii. Inaczej musielibyśmy na przykład zrezygnować z możliwości zapisywania ustawień programu w Rejestrze… Hmm, ale czy akurat tutaj jest czego żałować? :)
Ano niekoniecznie. Dawno, dawno temu popularnym sposobem przechowywania konfiguracji były pliki o rozszerzeniu .ini. Są to bardzo proste pliki tekstowe, zawierające pary klucz-wartość pogrupowane w sekcje. Składnia takiego pliku wygląda następująco:

  1. [Sekcja1]
  2. Klucz1=42
  3. Klucz2="wartość"
  4.  
  5. [Sekcja2]
  6. Klucz="Ala ma kota"

W Windows API wciąż istnieje interfejs pozwalający na odczytywanie i zapisywanie danych z takich plików, chociaż jest on trzymany podobno tylko dla kompatybilności. Obejmuje on funkcje specjalizujące się w obsłudze konkretnego pliku – mianowicie win.ini z głównego katalogu Windows – ale także dowolnego pliku .ini o podanej przez nas nazwie. Tymi bardziej elastycznymi funkcjami są:

  • do odczytywania wartości różnych typów: GetPrivateProfileInt, GetPrivateProfileString, GetPrivateProfileStruct
  • do zapisywania wartości: WritePrivateProfileString, WritePrivateProfileStruct
  • do pobierania listy sekcji w pliku .ini (GetPrivateProfileSectionNames) oraz listy kluczy w sekcji (GetPrivateProfileSection)

Ich użycie jest dość proste. Jeśli na przykład chcielibyśmy pobrać wartość liczbową z powyższego pliku, to wystarczy poniższe wywołanie, o ile jest on zapisany w bieżącym katalogu jako plik.ini:

  1. int value = GetPrivateProfileInt("Sekcja1", "Klucz1", 0, "plik.ini");

Dość podobnie wygląda korzystanie z pozostałych funkcji operujących na pojedynczych wartościach.

Dzisiaj pliki .ini są już trochę reliktem przeszłości, ale wydaje mi się, że do niektórych prostych zastosowań nadają się całkiem dobrze. A jeśli zdarzy się ten niefortunny przypadek, iż w którejś z przyszłych wersji Windows support dla nich zostanie porzucony, to cóż… składniowo są one na tyle proste, że ich parsowanie nie powinno być problemem ;-)

Tags: ,
Author: Xion, posted under Applications, Programming » 8 comments

Własne kontrolki w Windows Forms

2010-06-18 22:06

Gdy tworzymy aplikacje okienkowe w .NET z użyciem Windows Forms, możemy korzystać z mnóstwa (co najmniej kilkudziesięciu) typów kontrolek dostępnych out-of-the-box. Nie wszystkie nawet mają rację bytu jako rzeczywiste kontrolki, ale w wielu przypadkach fakt, że nimi są, ułatwia korzystanie z API, które się kryje za taki komponentami. (Przykładem jest kontrolka BackgroundWorker).
Nie oznacza to jednak, że nie istnieje czasami potrzeba stworzenia własnego rodzaju kontrolki, specyficznej dla pisanego programu. Nawet jeśli miałaby ona być użyta tylko w jednym egzemplarzu, wyodrębnienie jej logiki poza zasadniczy kod aplikacji powinno wyjść im obu na dobre. W tym sensie jest ten rodzaj dodatkowej warstwy abstrakcji, który zazwyczaj opłaca się stworzyć.

Jak to się robi? Przede wszystkim należy wiedzieć, że w .NET kontrolki definiowane przez programistę mogą być jednego z trzech rodzajów. Stosunkowo najprostszym jest user control, które to stanowi po prostu pojemnik na kilka innych kontrolek zebranych w całość. Mogą to być na przykład dwa obiekty typu NumericUpDown opatrzone etykietami “Od” i “Do”, jeśli w naszym programie wielokrotnie i w różnych miejscach zachodzi potrzeba podawania przedziału liczbowego. Właściwości takiej customowej kontrolki są najczęściej bezpośrednio mapowane na właściwości kontrolek składowych, a zdarzenia są sygnalizowane w odpowiedzi na niektóre ze zdarzeń pochodzących z tychże kontrolek. W sumie więc można powiedzieć, że trochę tak jak funkcje i klasy pozwalają na wielokrotne wykorzystywanie kodu, tak user controls dają możliwość ponownego wykorzystania pewnych fragmentów UI.

Drugim typem są kontrolki dziedziczone (inherited controls). Nazwa wskazuje, że powstają one przez odziedziczenie już istniejącego rodzaju (klasy) kontrolki i dodanie do niej lub zmianę funkcjonalności. Może to obejmować dodanie nowych właściwości, częściowego lub całkowitego obsłużenia niektórych zdarzeń i inne tego typu modyfikacje zachowania kontrolki. Ważne jest tutaj to, iż efekt, jaki chcemy osiągnąć, jest na tyle zbliżony do jednej z istniejących kontrolek, że można ją wykorzystać i nie zaczynać od zera.
No ale nie zawsze tak jest i czasem naprawdę trzeba zacząć od podstaw. Jeżeli bazujemy na zupełnie abstrakcyjnym typie z góry hierarchii interfejsu – jak Control czy Component – to mamy do czynienia z kontrolką typu owner-draw. Dla niej musimy ręcznie rysować całą zawartość przy pomocy instrukcji GDI+ (stąd nazwa), opierając się przy tym na prostych zdarzeniach w rodzaju kliknięć myszy. Nie jest to więc łatwe i szybkie do wykonania zadanie.

Bywa aczkolwiek i tak, że jest ono koniecznie. Zanim się do niego zabierzemy, lepiej jednak przejrzeć alternatywy w postaci kontrolek odziedziczonych lub user controls. Możliwe, że w ten sposób zaoszczędzimy sobie pracy.

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

Równoczesne zastępowanie kilku fraz w tekście

2010-06-12 23:22

Łańcuchy znaków w językach programowania udostępniają zwykle operację zamiany wszystkich wystąpień jednego podciągu na inny. Nawet jeśli tak nie jest (co dotyczy chociażby C++, jak zwykle), możliwe jest jej łatwe zaimplementowanie przy pomocy prostszych funkcji (jak choćby std::string::find).
Gorzej jest z nieco bardziej skomplikowaną procedurą zastępowania kilku fraz naraz. Zaznaczam od razu, że nie jest to wcale równoważne wywołaniu funkcji typu Replace parę razy dla różnych argumentów. Jeśli bowiem zastąpimy w tekście X1 przez Y1, a w wyniku X2 przez Y2, to możemy otrzymać inny rezultat niż przy dokonywaniu obu zamian równocześnie – wystarczy, że Y1 będzie w sobie zawierał ciąg X2. W celu uzyskania poprawnego wyniku należy wszystkie zamiany przeprowadzać jednocześnie.

Prosta implementacja takiej operacji może się sprowadzać do przeglądania tekstu w poszukiwaniu któregoś z podciągów do zastąpienia, a następnie dokonania faktycznej zamiany dla tego z nich, który został wykryty najwcześniej w stosunku do aktualnej pozycji w tekście. Należy następnie przesunąć się tuż za znalezioną frazę i działanie powtórzyć – i tak aż do końca tekstu. W C++ wygląda to wszystko mniej więcej tak:

  1. typedef map<string,string> StringMap;
  2. string ConcurrentReplace(const string& text, const StringMap& phrases)
  3. {
  4.     if (text.empty() || phrases.empty()) return text;
  5.  
  6.     stringstream ss;
  7.     for (size_t i = 0; i < text.length(); )
  8.     {
  9.         StringMap::const_iterator phraseIt = phrases.end();
  10.         size_t minPhrasePos = 0;
  11.         for (StringMap::const_iterator it = phrases.begin();
  12.             it != phrases.end(); ++it)
  13.         {
  14.             size_t phrasePos = text.find(it->first, i);
  15.             if (phrasePos == string::npos) continue;
  16.             if (phraseIt == phrases.end() || phrasePos < minPhrasePos)
  17.                 { phraseIt = it; minPhrasePos = phrasePos; }
  18.         }
  19.         if (phraseIt == phrases.end())   { ss << text.substr(i); break; }
  20.  
  21.         ss << text.substr(i, minPhrasePos - i);
  22.         ss << phraseIt->second;
  23.         i = minPhrasePos + phraseIt->first.length();
  24.     }
  25.     return ss.str();
  26. }

Jeśli tekst, na którym operujemy, ma długość n, a fraz do wyszukania i zastąpienia jest k, to w najgorszym przypadku całość może zabrać czas O(kn). Wymagałoby to jednak dość specyficznych danych, więc w praktyce nie musi być aż tak źle :) Dla lepszych rezultatów – zwłaszcza przy “obrabianiu” większej liczby tekstów tym samym słownikiem fraz – należałoby pewnie użyć specjalistycznych struktur danych, np. drzew prefiksowych (trie).

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

C# i zbiory

2010-06-08 16:39

Dłuższy czas temu popełniłem notkę o tym, że w C# (lub szerzej: w .NET) brakuje pewnych struktur danych, które niekiedy bywają przydatne. Jedną z nich jest bardzo prosty rodzaj pojemnika: zbiór.
Zbiory (w programowaniu) to kontenery, które przechowują elementy niepowtarzające się i umożliwiają szybkie sprawdzenie, czy jakaś wartość do danego zbioru należy. ‘Szybkie’ oznacza tu złożoność logarytmiczną (względem rozmiaru pojemnika) lub lepszą. Podstawowa różnica w stosunku do zbiorów matematycznych jest natomiast taka, iż te drugie mogą zawierać elementy różnych rodzajów, podczas struktura danych o tej nazwie przechowuje obiekty jednego typu.

W C++ zbiór implementuje STL-owa klasa std::set. W C# z kolei – jak napisałem na początku – jej odpowiednika nie ma. Takowy trzeba by dopiero napisać, co w przypadku tego języka jest raczej zaskakujące :) Tym niemniej da się to zrobić w miarę prosto, używając do tego… klasy Dictionary. Pomysł polega na tym, żeby całkowicie zignorować tę jej część, która kluczom w słowniku pozwala przypisywać wartości. Zamiast tego interesują nas wyłącznie same klucze jako elementy naszego zbioru. Zarys klasy opartej o tę ideę może wyglądać choćby tak:

  1. public class Set<T> : ICollection<T>, IEnumerable<T>
  2. {
  3.     private Dictionary<T, object> set;
  4.  
  5.     public Set() { set = new Dictionary<T, object>(); }
  6.  
  7.     public void Add(T item) { set.Add(item, null); }
  8.     public void Clear() { set.Clear(); }
  9.     public bool Contains(T item) { return set.ContainsKey(item); }
  10.     public void CopyTo(T[] array, int arrayIndex)
  11.         { set.Keys.CopyTo(array, arrayIndex); }
  12.     public int Count    { get { return set.Count; } }
  13.     public bool IsReadOnly  { get { return false; } }
  14.     public bool Remove(T item) { return set.Remove(item); }
  15.  
  16.     public IEnumerator<T> GetEnumerator()
  17.         { return set.Keys.GetEnumerator(); }
  18.     IEnumerator IEnumerable.GetEnumerator()
  19.         { return (set.Keys as IEnumerable).GetEnumerator(); }
  20. }

Skorzystanie ze słownika (klasy Dictionary) sprawia, że kluczowa operacja Contains (sprawdzenie przynależności) jest bardzo szybka. MSDN podaje, że wydajność odpowiadającej jej operacji słownikowej ContainsKey “zbliża się do O(1)”. Mamy tu też oczywiście niezbędne metody do dodawania i usuwania elementów, a także kilka innych związanych z implementowanymi interfejsami ICollection i IEnumerable, które pozwalają na przykład na iterowanie po zbiorze pętlą foreach.
Można naturalnie jeszcze ulepszyć tę klasę, dodając nowe konstruktory, metody i implementując następne interfejsy (na przykład ISerializable). Nie jest to trudne, bo polega głównie na wywoływaniu odpowiadających metod obiektu Dictionary lub jego kolekcji kluczy. Dopiero operacje matematyczne – jak suma i iloczyn zbiorów – wymagałoby nieco większej ilości kodu.
Ale przecież dla chcącego nic trudnego :)

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

Szablony klas i funkcje zaprzyjaźnione

2010-06-04 21:18

Chcę dzisiaj napisać o pewnego rodzaju “ciekawostce”, związanej z dwoma pojęciami z tytułu notki, na którą natknąłem się jakiś czas temu. Po zredukowaniu nadmiaru szczegółów można przyjąć, że rzecz dotyczy sytuacji, w której mamy szablon klasy oraz zadeklarowaną wewnątrz niego funkcję zaprzyjaźnioną – na przykład przeciążony operator strumieniowy <<:

  1. #include <iostream>
  2.  
  3. template <typename T> class Foo
  4. {
  5.     private: T x;
  6.     public:
  7.         explicit Foo(T _x) : x(_x) { }
  8.         friend std::ostream& operator << (std::ostream& out, const Foo<T>& foo);
  9. };

Funkcja jest zadeklarowana, lecz nie jest od razu zaimplementowana wewnątrz bloku class. Zamiast tego umieszczamy jej kod osobno:

  1. template <typename T> std::ostream& operator << (std::ostream& out, const Foo<T>& foo)
  2.     { return out << foo.x; }&#91;/cpp]
  3. co jest może trochę osobliwe, ale wydaje się poprawne. No właśnie; wydaje się. Próba użycia tak zdefiniowana operatora:
  4. &#91;cpp]Foo<int> foo(42);
  5. std::cout << foo << std::endl; // bum[/cpp]
  6. nie przeszkadza wprawdzie kompilatorowi, jednak linker ma poważne zastrzeżenia. Okazuje się, że funkcja <code>operator &lt;&lt;</code> w wymaganej postaci nie jest zdefiniowana - czyli mamy zwyczajny <em>unresolved external</em>, aczkolwiek o niecodziennych przyczynach.
  7.  
  8. Przyznać muszę teraz, że nie jestem do końca pewien co do ich natury, jako że konsultacja z zaufanymi źródłami (w postaci książki <a href="http://helion.pl/ksiazki/c_szablony_vademecum_profesjonalisty_david_vandevoorde_nicolai_m_josuttis,cpszav.htm"><em>C++. Szablony</em></a> panów Vandevoorde'a i Josuttisa) nie dała jednoznacznej odpowiedzi, co tutaj tak naprawdę się dzieje. Przypuszczam, że ma tu miejsce pewien dziwnej interakcji między konkretyzacją szablonu klasy a deklaracją <code>friend</code>, która przy okazji jest też deklaracją funkcji <code>operator &lt;&lt;</code>. Mimo że nie stanowi ona części szablonu, owej konkretyzacji <strong>podlega</strong>, czego skutkiem jest stworzenie <strong>zwykłej</strong> (nieszablonowej) deklaracji:
  9. [cpp]std::ostream& operator << (std::ostream& out, const Foo<int>& foo);

Nie ma więc ona nic wspólnego z szablonem funkcji operator <<, który jest zdefiniowany później! W rzeczywistości wygląda zresztą tak, że ów szablon jest całkowicie pominięty przy kompilacji jako niewykorzystywany; to zapewne z powodu reguł przeciążania funkcji w C++, nakazujących najwyraźniej wybranie raczej funkcji nieszablonowej (o powyższej deklaracji) zamiast szablonu funkcji. A że przypadkiem funkcja ta nie ma implementacji… Cóż, kompilator nie ma obowiązku się o to martwić :)

Tyle moja teoria. Niezależnie od tego, czy jest ona prawdziwa czy nie, dobrze byłoby wiedzieć, jak z problemu wybrnąć. Naturalnym wyjściem jest przenieść kod funkcji tam, gdzie jego miejsce – czyli z deklaracji funkcji w klasie (opatrzonej słówkiem friend) uczynić też jej definicję. Faktycznie, to działa; najwyraźniej powstaje wtedy nieszablonowa funkcja o nagłówku danym wyżej, razem ze swoją implementacją – czyli wszystko jest w porządku.
Drugie rozwiązanie to opatrzenie deklaracji friend klauzulą template:

  1. template <typename T>
  2.     friend std::ostream& operator << (std::ostream& out, const Foo<T>& foo);

W tej wersji – jak sądzę – następuje zaprzyjaźnienie każdej specjalizacji klasy Foo z odpowiadającą jej specjalizacją metody operator <<. Oba szablony podlegają niezależnym konkretyzacjom, ale linker potrafi prawidłowo rozwiązać relację między nimi.

Uff, trochę to skomplikowane. Jak widać, z zaprzyjaźnianiem bywają problemy – i to nie tylko dlatego, iż “In C++ friends touch each other’s private parts.” ;) Dziwne efekty związane z szablonami i konkretyzacją potrafią bowiem skonfudować nawet doświadczonych programistów.

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

Dwukierunkowe funkcje i obiekty proxy

2010-06-01 16:07

Jedną z pierwszych rzeczy poznawanych w trakcie nauki programowania jest sposób działania funkcji. Mam tu na myśli zupełnie elementarny fakt, iż funkcje przyjmują zero lub więcej argumentów na wejściu i produkują co najwyżej jeden rezultat na wyjściu. Niezachwiana pewność w tę własność funkcji może być potem mocno nadwątlona, jeśli poznamy języki o bardziej egzotycznym sposobie działania, jak na przykład Prolog; tam wszystkie parametry funkcji mogą przekazywać dane w obu kierunkach, dzięki czemu np. za dzielenie i łączenie ciągów lub list odpowiada jedna i ta sam funkcja.
Nie trzeba jednak uciekać tak daleko od starego dobrego programowania imperatywnego, żeby zaobserwować odstępstwa od reguły. Parametry mogą przecież służyć do zwracania wartości – dzieje się to przy pomocy słów kluczowych ref/out w C# lub zwykłych wskaźników/referencji w C++. Często zdarza się wtedy, że “normalny” rezultat zwracany przez funkcję służy jedynie przekazaniu informacji o ewentualnym błędzie.

Rzadsza sytuacja polega na tym, że wynik funkcji staje się jej argumentem wejściowym. Technicznie nie jest nawet możliwe, ale można tak traktować sytuacje, gdy rezultatem jest l-wartość (l-value), tj. obiekt, do którego można przypisywać:

  1. vector<int> v(10);
  2. v.at(5) = 25;

Typowo jest to referencja (tutaj int&), a powyższa konstrukcja – poprzez swojej podobieństwo do indeksowania zwykłej tablicy – nie należy do wielce zaskakujących. Co jednak można powiedzieć o tej:

  1. Dim S as String = "Ala ma kota"
  2. Mid(S, 4, 2) = "nie ma" ' Ala nie ma kota

poza widocznym od razu faktem, że pochodzi ona z języka, którego rozwlekłość składni przyprawia o niestrawność? :) Mianowicie tutaj nie ma już oczywistych przesłanek co do tego, czym jest lewa strona. Widać bowiem, że możemy jej przypisać dłuższy podciąg niż oryginalny i zostanie on mimo wszystko poprawnie zastąpiony – nie jest to więc żaden prosty “wyrób wskaźnikopodobny”. Może więc to po prostu feature akurat tego języka, którego nie da się zreplikować?…

Odpowiedź jest – jak można się domyślać, skoro o tym piszę – oczywiście negatywna :) Ażeby podobny efekt osiągnąć w C++, konieczne jest jednak zastosowanie techniki znanej jako obiekt pośredniczącyproxy. Powinien on zachowywać się jak zwykły rezultat funkcji, ale w razie potrzeby dawać również możliwość przypisywania do siebie. Naturalnie efekt takiego działania jest specyficzny dla konkretnej funkcji, która swoją drogą może być często zredukowana do samego tworzenia obiektu proxy:

  1. inline Mid_Proxy Mid(std::string& s, size_t off, size_t len)
  2.     { return Mid_Proxy(s, off, len); }

Minimum, jakie rzeczony obiekt musi zapewniać, to operator przypisania oraz jakiś operator rzutowania, który pozwoli na “wyciągnięcie” rezultatu funkcji, gdy jest ona użyta w zwykły sposób:

  1. class Mid_Proxy
  2. {
  3.     private:
  4.         std::string& s;
  5.         size_t off, len;
  6.     public:
  7.         Mid_Proxy(std::string& _s, size_t _off, size_t _len)
  8.             : s(_s), off(_off), len(_len) { }
  9.  
  10.         void operator = (const std::string& str)
  11.             { s.replace (off, len, str); }
  12.         operator std::string () const
  13.             { return s.substr(off, len); }
  14. };

Widzimy tutaj, że przy takim proxy nasza funkcja Mid użyta w zwykły sposób zachowuje się jak metoda substr. Gdy jednak umieścimy jej wywołanie po lewej stronie przypisania, zadziała metoda replace, służąca do zastępowania podciągu innym. W sumie więc poniższy kod:

  1. std::string s("Ala ma kota");
  2. Mid(s, 4, 2) = "nie ma";

będzie działał analogicznie do prezentowanego wyżej fragmentu w języku Visual Basic.

Opisana tu sztuczka nie jest oczywiście doskonała. Do pełni możliwości potrzebna jest jeszcze wersja read-only funkcji Mid, przyjmująca stałą referencję do stringa i wywołująca substr bezpośrednio, z pominięciem obiektu proxy. To jednak da się łatwo zauważyć.
Mniej widoczny jest natomiast fakt, że obiekt proxy, dodając kolejną warstwę niejawnych konwersji (tutaj: z siebie na string) może spowodować kłopoty tam, gdzie jedna niestandardowa konwersja jest już wykorzystywana. Dobry przykład to interakcja ze strumieniem:
std::cout << Mid(s, 0, 3) << std::endl;[/cpp] która nie powiedzie, bo operator strumienowy << nie posiada wersji dla lewego prawego argumentu będącego std::stringiem, a jedynie dla const char* (co swoją drogą jest dość dziwne). Rozwiązaniem jest napisanie takowego dla samego obiektu proxy.

Zależności między projektami w Visual Studio

2010-05-29 0:40

Zdarza się, że pracuje nad złożonym systemem, na który składa się kilka osobnych projektów. IDE znają dobrze takie przypadki i potrafią je obsługiwać – stąd chociażby pojęcie solution (dawniej workspace) w Visual Studio. Dla pojedynczych aplikacji i bibliotek wydaje się ono zbędne, jednak staje się nieodzowne wtedy, gdy nasze projekty zależą od siebie.

Typowa sytuacja to wspólna biblioteka (framework, engine czy co jeszcze kto woli) rozwijana razem z programami, które z niej korzystają. (W najprostszym przypadku to może być po prostu jakaś aplikacja testowa). Wówczas pojawiają się zależności między projektami na etapie ich budowania: wynik szeroko pojętej “kompilacji” jednego jest wejściem do procesu budowania innego. Jeśli nie poświęcimy temu faktowi należytej uwagi, to mogą nas czekać kłopoty. W najlepszym razie jest to konieczność wciskania F7 (Build Solution) więcej niż raz, aż do zbudowania wszystkich projektów. W gorszym – uruchamianie (i debugowanie!) aplikacji korzystającej z nieaktualnej, bo nieprzekompilowanej wersji biblioteki.

Zależności między projektami w procesie budowania da się na szczęście określić. W Visual Studio służy do tego opcja Project Dependencies z menu – a jakże – Project. Możemy w niej określić dla każdego projektu, z jakimi innymi projektami z tego samego solution jest on powiązany, czyli które z nich powinny być już wcześniej od niego zbudowane. Na podstawie tak podanej sieci zależności da się następnie określić właściwą kolejności “kompilacji” dla wszystkich projektów w danym solution. VS oczywiście to czyni, używając do tego zapewne sortowania topologicznego w analogiczny sposób jak dla kompilacji jednego projektu składającego się z wielu plików.

Tags: , , ,
Author: Xion, posted under Applications, Programming » 1 comment
 


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