Posts tagged ‘Windows’

Pakowanie w przestrzenie

2010-03-09 17:05

Jeśli w C++ piszemy jakąś bibliotekę (albo nawet ogólniej: kod wielorazowego użytku), to zgodnie z dobrymi praktykami powinniśmy jej symbole zamknąć przynajmniej w jedną osobną przestrzeń nazw (namespace). Dzięki temu zapobiegniemy zminimalizujemy możliwość kolizji identyfikatorów w kodzie, który z naszego dzieła będzie korzystał – a także potencjalnie z innych bibliotek.
Nie wszystkie z nich jednak mogą być tak ładnie napisane – choćby dlatego, że któraś może być przeznaczona oryginalnie dla języka C. Najbardziej typowy przykład? Windows API. Dołączenie windows.h zasypuje globalną przestrzeń nazw istną lawiną symboli odpowiadających tysiącom funkcji czy typów zadeklarowanych w tym nagłówku. Nie jest to specjalnie dobre.

Jak temu zaradzić? Bardzo prostą, ale nierozwiązującą wszystkich problemów metodą jest stworzenie własnego nagłówka “opakowującego” ten biblioteczny w nową przestrzeń nazw:

  1. #ifndef FOO_H
  2. #define FOO_H
  3.  
  4. namespace foo
  5. {
  6.     #include <foobar.h>
  7. }
  8.  
  9. #endif

Założenie jest takie, żeby wynikowego pliku nagłówkowego (foo.h) używać następnie w miejsce oryginalnego (foobar.h). Wtedy wszystkie symbole w nim zadeklarowane znajdą się wewnątrz nowej przestrzeni nazw, foo.

Wszystkie?… Nie! Pakując kod napisany w stylu C bezpośrednio do przestrzeni nazw nie osiągniemy bowiem wszystkich celów, którym namespace‘y przyświecają. Owszem, da się co nieco poprawić: jeśli np. wspomniany windows.h zamknęlibyśmy w przestrzeni win, to poniższy kod będzie jak najbardziej działał:

  1. win::HWND hNotepad;
  2. hNotepad = win::FindWindow("Notepad", 0);

podczas gdy wersja bez przedrostków win:: już niezupełnie. Jednak nie jest to całkowity – nomen omen – win, bo z kolei takie wywołanie:

  1. win::ShowWindow (hNotepad, win::SW_MINIMIZE);

skutkuje już niestety failem :) Nasza przestrzeń nie może bowiem zamknąć wszystkiego, gdyż nie podlegają jej dyrektywy preprocesora – a w szczególności #define. Pech polega na tym, że właśnie #define jest w C podstawowym sposobem definiowania stałych, więc użyta wyżej nazwa SW_MINIMIZE jest (w windows.h) określona po prostu jako:

  1. #define SW_MINIMIZE 6

Próba jej kwalifikowania powoduje zatem powstanie nieprawidłowego ciągu win::6 i słuszne narzekania kompilatora.

Nasz pojemnik (na nazwy) jest więc dziurawy i niestety nic z tym nie da się zrobić. Tak to już jest, gdy wciąż trzeba mieć do czynienia z API, które w tym przypadku liczy sobie – bagatelka – ponad 20 lat!

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

Szpieg++

2010-02-26 16:41

Pakiet Visual Studio to nie tylko samo środowisko programistyczne (IDE), ale i kilka mniejszych narzędzi. Większość z nich dotyczy albo programowania na platformy mobilne, albo .NET, ale jest przynajmniej jedno, które może okazać się przydatne dla każdego programisty Windows. Chodzi o program Spy++ (spyxx.exe).

Co ta aplikacja potrafi? Otóż pozwala ona na podgląd różnych obiektów w systemie, tj. okien, wątków i procesów wraz z ich właściwościami (jak na przykład wartości uchwytów czy ilość zaalokowanych bajtów). Na pierwszy rzut oka może nie wydawać się to jakoś wybitnie zachwycające, jako że zwykły Menedżer zadań potrafi (prawie) to samo, z dokładnością do mniejszej liczby szczegółów.
Wyróżniającym i znacznie przydatniejszym feature’em Spy++ jest bowiem co innego. Umożliwia on mianowicie podglądanie, filtrowanie i logowanie wszystkich lub wybranych komunikatów Windows (WM_PAINT, WM_LBUTTONDOWN, itd.) dochodzących do wskazanego okna lub grupy okien, wraz z ich parametrami (wParam, lParam) oraz wartościami zwróconymi przez okno w odpowiedzi na nie.

Działa to przy tym prosto i intuicyjnie. Najpierw wybieramy sobie okno do podglądania (Spy > Log Messages lub Spy > Find Window), co możemy zrobić przy pomocy przeciągnięcia celownika myszą w jego obszar. Potem możemy określić, jakiego rodzaju komunikaty potrzebujemy przechwytywać oraz jakie informacje chcemy z nich wyciągnąć. Wynikiem będzie log mniej więcej takiej postaci:

<00694> 0002009C P WM_MOUSEMOVE fwKeys:0000 xPos:51 yPos:259
<00695> 0002009C P WM_MOUSELEAVE
<00696> 0002009C P WM_PAINT hdc:00000000
<00697> 0002009C P WM_TIMER wTimerID:5 tmprc:00000000
<00698> 0002009C P WM_TIMER wTimerID:2 tmprc:00000000

która to, jak sądzę, powinna być zrozumiała dla każdego średnio zaawansowanego programisty Windows :]

Po co nam jednak coś takiego?… Ano odpowiedź jest prosta: do debugowania :) Można oczywiście podchodzić do tego w “tradycyjny” sposób przy pomocy pracy krokowej tudzież breakpointów, ale często ogranicza nas tutaj swego rodzaju zasada nieoznaczoności, gdy samo debugowanie zmienia działanie programu – chociażby ze względu na ciągłe przełączenia między nim a IDE. To oczywiście znacznie utrudnia wykrycie i poprawienie usterek.
Jak nietrudno się domyślić, istnieją też inne programy oferujące podobną funkcjonalność co Spy++, jak np. Winspector. Z Visual Studio otrzymujemy jednak dobre narzędzie out-of-the-box, więc czegóż można by chcieć więcej? ;]

Hmm… pewnie tego, by dowiedzieć się mniej więcej, jak ono działa. O tym można przeczytać na blogu jego autora.

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

O obiektowości

2010-02-12 16:58

Kiedy programiści się nudzą, to często spierają się o terminologię. Przedmiotem takich sporów są zwykle nieco mgliste określenia, które czasami – zwykle niesłusznie – używane są jako atrybuty oceniające. Przykład? Choćby “obiektowość” – w zależności od punktu widzenia stwierdzenie, że dany kod/biblioteka/moduł/itp. są bardziej lub mniej obiektowe może stanowić albo zaletę, albo wadę. Zależy to głównie od ‘poglądów’ danej osoby na temat użyteczności podejścia OO w programowaniu.

Co to jednak znaczy, że dany kod jest obiektowy?… Nie uważam wcale, że należy go w tym celu napisać go w języku obiektowym. Twierdzę na przykład, że Windows API jest całkiem dobrym przykładem biblioteki realizującej obiektowy paradygmat, mimo tego że została ona stworzona w jak najbardziej strukturalnym C. Praktyczna różnica między poniższymi wywołaniami:

  1. foo->DoSomething (...);
  2. DoSomething (foo, ...);

jest bowiem właściwie żadna. Dodajmy do tego fakt, że z punktu widzenia programisty-użytkownika w WinAPI występuje zarówno dziedziczenie (rodzajów uchwytów), jak i polimorfizm (funkcje niezależne od typu uchwytów, np. CloseHandle), a więc bardzo obiektowe feature‘y.
Jeśli komuś wydaje się to naciągane i twierdzi, że w ten sposób pod obiektowość można podciągnąć właściwie każdy kod, to już spieszę z przykładem ukazującym, że tak nie jest. Otóż większa część biblioteki OpenGL obiektowa na pewno nie jest, zaś w tych nielicznych miejscach gdzie OOP jest niemal konieczny (jak zarządzanie teksturami czy buforami wierzchołków) pojawia się nieco dziwna koncepcja ‘indeksów’ używanych do identyfikowania poszczególnych obiektów.

Dla niektórych (łącznie ze mną) taki interfejs nie jest szczytem marzeń, a preferowane jest wyraźnie obiektowe podejście DirectX. Absolutnie jednak nie zgadzam się z twierdzeniem, że DirectX jest lepszy, bo bardziej obiektowy – to trochę tak jak powiedzenie, że ten obrazek jest ładniejszy, bo bardziej zielony. W obu przypadkach jest to kwestia gustu i nie powinniśmy zakładać, że cechy pozytywne dla nas są tak samo dobrze odbierane przez innych.
A wyższość DirectX nad OpenGL da się przecież uargumentować na wiele innych, lepszych sposobów :)

Tags: , , ,
Author: Xion, posted under Programming, Thoughts » 3 comments

Siedem. Windows 7

2010-01-09 13:27

To, że przez kilka dni nie skrobnąłem żadnej nowej notki to prosta konsekwencja zasady, że skoro nie ma o czym pisać, to można nie pisać w ogóle. Ponieważ jednak stosowanie tej reguły na dłuższą metę będzie dla bloga fatalne, postanowiłem ją niniejszym zarzucić ;-)
"Logo" Windows 7No, a wyrażając się już nieco poważniej… Minęło już trochę czasu, od kiedy zainstalowałem Windows 7, więc mogę się już podzielić nie tylko pierwszymi wrażeniami z codziennego użytkowania tego systemu. W takich przypadkach zresztą te późniejsze wrażenia są ponadto znacznie ważniejsze.

Jakoś tak bowiem wychodzi, że wraz z upływem czasu każda instalacja systemu operacyjnego zaczyna tracić na wydajności. To takie informatyczne zastosowanie drugiej zasady termodynamiki. I chociaż entropię (czyli bałagan) na dysku da się jednak zmniejszyć – chociażby poprzez defragmentację – to ilość danych, z jaką musi się zmierzyć system, regularnie rośnie. Produkuje je bowiem każdy sposób korzystania z komputera i każde uruchomienie dowolnego programu.
Jak więc z okiełznaniem tego powiększającego się nieporządku radzi sobie Windows 7? Otóż nie najgorzej. Poza nieznacznym wydłużeniem czasu ładowania samego systemu, po ponad dwóch miesiącach jego użytkowania nie zauważam znaczących spadków wydajności w działaniu. Pomaga przy tym pewnie fakt, że Windows 7 koncentruje się głównie na optymalizowaniu często wykonywanych czynności, jak chociażby uruchamiania się programów na stałe “przypiętych” do paska zadań. Trudno to jednak uznać za coś złego.
Całkiem dobrze funkcjonuje też inny element wpływający na postrzeganie wydajności systemu, czyli scheduler, tj. moduł szeregujący procesy i wątki. Na tyle dobrze rozpoznaje on aplikacje, z którymi aktualnie pracuje użytkownik, że przełączanie między nimi nie sprawia problemów. Dotyczy to nawet przechodzenia między Pulpitem a pełnoekranowymi grami, w trakcie którego czasu totalnego “zaciemnienia” powodowanego zmianą rozdzielczości jest naprawdę minimalny.

Co poza tym? Właściwie nic specjalnego. Tak naprawdę Windows 7 jest tym, czym wcześniej powinna być Vista – systemem, gdzie cukierkowe GUI nie służy tylko przykryciu niedorobionego wnętrza. Jest tu oczywiście kilka miłych, przydatnych i cieszących oko drobiazgów, jak choćby nowy pasek zadań, o którym zdążyłem już wspomnieć. Jest również kilka feature‘ów mniej użytecznych – albo przynajmniej takich, których użyteczności nie udało mi się w porę zauważyć – jak choćby funkcja Aero Peek, dzięki której można “na chwilę” podejrzeć Pulpit (a raczej jego zapamiętany obraz). Da się je na szczęście wyłączyć :)

Czy warto więc w nową wersję Windows się zaopatrzyć? Jeśli dotąd wystarczała nam edycja XP i nie potrzebujemy chociażby wsparcia dla DX10+, to sprawa wymaga głębszego przemyślenia. Z kolei dla osób, którego z tego lub innych względów były dotąd “skazane” na Vistę, wybór Windows 7 będzie długo oczekiwanym upgrade’em :)

Tags: ,
Author: Xion, posted under Computer Science & IT » 8 comments

Triki z PowerShellem #13 – Prędkość czasu

2009-12-31 13:47

Ostatni dzień roku to dobry moment na różne dziwne przemyślenia na temat czasu. Kiedyś z tej okazji wymyśliłem zupełnie nowy system kalendarza, a dzisiaj przypomniałem sobie o niezupełnie poważnym pomyśle autorstwa Rega, ze zbliżonej dziedziny.
Chodzi o oryginalną wielkość “fizyczną”: prędkość czasu. Ma ona ilościowo mierzyć znany wszystkim fakt, że wrażenie upływającego czasu jest różne w różnych sytuacjach. Gdy w rzeczywistości upłynął pewien czas t, a nam wydawało się, że minął raczej czas t', to odczuwana przez nas prędkość czasu \tau wynosi

\displaystyle \tau = \frac{t}{t'}.

Dla przykładu: na nudnym wykładzie mogło minąć ledwie 15 minut, podczas gdy nam wydaje się, że siedzimy na nim już godzinę; wówczas \tau_1 = 0,25 / 1 = 0,25. Z kolei grając przez trzy godziny możemy mieć wrażenie, że upłynęło tylko 30 minut – wtedy \tau_2 = 3 / 0,5 = 6. Jak widać \tau_1 < \tau_2, zatem teoria zgadza się tutaj z intuicją, co pozwala wnioskować, że wielkość jest dobrze zdefiniowana :)

Pozostaje jeszcze kwestia jej pomiaru. W swoim oryginalnym artykule (który w tajemniczy sposób zniknął z sieci) Reg zaproponował, że można by go przeprowadzić za pomocą odpowiedniego programu. Miałby on działać w tle, losować pewien interwał czasu i po jego upływie pytać użytkownika, ile czasu według niego upłynęło od ostatniego pytania. Z dokładnością do małych błędów pomiarowych (np. czasu potrzebnego na wpisanie odpowiedzi) metoda wydaje się dobra – czemu więc by jej nie zaimplementować? :]
To właśnie uczyniłem, posługując się oczywiście swoim ulubionym narzędziem na takie okazje, czyli PowerShellem. Pewnym problemem było to, jak w skrypcie konsolowym postarać się o mierzenie czasu w sposób, który byłby niezauważalny dla użytkownika. Da się to jednak łatwo zrobić, posługując się… wbudowanym w system Harmonogramem zadań (Task Scheduler). Istnieje bowiem do niego dobrze udokumentowane, ładne i przejrzyste COM-owe API (dostępne od Visty wzwyż), którego użycie w PowerShellu wygląda zresztą kilka razy lepiej niż w C++ ;P
Wynikowy skrypt wygląda więc następująco:

  1. # timespeed.ps1
  2. # Skrypt do mierzenia prędkości czasu
  3. param ([int]$minutes = 0)
  4.  
  5. # Stałe
  6. $TASK_NAME = "TimeSpeed_Task"   # Nazwa zadania w Harmonogramie
  7.  
  8. # Pobieramy obiekt harmonogramu zadań
  9. $ts = New-Object -ComObject Schedule.Service
  10. $ts.Connect($null, $null, $null, $null)
  11. $tf = $ts.GetFolder($(New-Object String @([char]92, 1))) # "\"
  12.  
  13. if ($minutes -eq 0)
  14. {
  15.     # Pierwsze uruchomienie
  16.     $task = $ts.NewTask(0)
  17.     $task.Data = ""
  18.     $continue = "T"
  19. }
  20. else
  21. {
  22.     # Kolejne uruchomienia
  23.     $task = $tf.GetTask($TASK_NAME).Definition
  24.    
  25.     # Pytamy użytkownika i liczymy prędkość czasu
  26.     $userMinutes = Read-Host -Prompt "Liczba minut od ostatniego razu"
  27.     $d_tau = $minutes / $userMinutes
  28.     if (-not [String]::IsNullOrEmpty($task.Data))
  29.     {
  30.         $timeData = $task.Data.Split(',')
  31.         $totalTime = [int]$timeData[0]
  32.         $totalUserTime = [int]$timeData[1]
  33.     }
  34.     $totalTime += $minutes
  35.     $totalUserTime += $userMinutes
  36.     $tau = $totalTime / $totalUserTime
  37.     $task.Data = $totalTime.ToString() + "," + $totalUserTime.ToString()
  38.    
  39.     # Wyświetlamy aktualne wyniki
  40.     "Rzeczywista liczba minut: " + $minutes | Out-Host
  41.     "Chwilowa prędkość czasu: " + $d_tau | Out-Host
  42.     "Całkowita prędkość czasu: " + $tau | Out-Host
  43.    
  44.     # Pytamy użytkownika, czy chce kontynuować
  45.     $continue = Read-Host -Prompt "Wykonać następny pomiar (T/n)?"
  46. }
  47.  
  48. if ($continue -ine "n")
  49. {
  50.     # Losujemy nowy interwał czasowy (od 5 do 60 minut, co 5)
  51.     $rand = New-Object System.Random
  52.     $nextMinutes = ($rand.Next(0, 12) + 1) * 5
  53.    
  54.     # Dodajemy trigger dla zadania (czas: za X minut)
  55.     $task.Triggers.Clear()
  56.     $trigger = $task.Triggers.Create(1) # 1 - trigger na określony czas
  57.     $nextTime = [DateTime]::Now.AddMinutes($nextMinutes)
  58.     $trigger.StartBoundary = $nextTime.ToString("o")
  59.    
  60.     # Dodajemy akcję (uruchomienie skryptu)
  61.     $task.Actions.Clear()
  62.     $action = $task.Actions.Create(0)   # 0 - uruchomienie polecenia
  63.     $action.Path = "powershell"
  64.     $action.Arguments = "-Command . '" + $MyInvocation.MyCommand.Path
  65.     $action.Arguments += "' -minutes " + $nextMinutes.ToString()
  66.    
  67.     # Dodajemy zadanie
  68.     # (6 - stwórz lub uaktualnij)
  69.     # (3 - zadanie uruchamiane tylko przy zalogowanym użytkowniku)
  70.     $tf.RegisterTaskDefinition($TASK_NAME, $task, 6, $null, $null, 3) | Out-Null
  71. }
  72. else
  73. {
  74.     # Koniec - usuwamy zdarzenie
  75.     try { $tf.DeleteTask($TASK_NAME, 0) } catch { }
  76. }

Przy swoim pierwszym uruchomieniu tworzy on nowe zadanie w Harmonogramie, które polega na jego ponownym odpaleniu po upływie losowej liczby minut (5, 10, 15, itd. aż do maks. 60 minut). Wtedy użytkownik jest pytany o to, ile czasu według niego upłynęło, a następnie wyświetlają się częściowe wyniki pomiaru. Wówczas można zdecydować o jego kontynuowaniu lub zakończeniu. (Zawsze można też ręcznie usunąć zadanie z Harmonogramu, rzecz jasna).

Skrypt w PSh do mierzenia prędkości czasu

I to w sumie tyle. Można już przystąpić do badań określających dokładnie, jak bardzo dłuży nam się czas do północy :))

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

Odczytywanie tekstu z gwiazdek

2009-12-29 12:24

Pole tekstowe hasłaAplikacje wymagające podania od użytkownika jakichś haseł zwykle ukrywają ich znaki, zastępując je “gwiazdkami”, czyli np. znakami asterisk (*) lub dużymi kropkami. Dzięki temu lekko zwiększa się poziom bezpieczeństwa programu, gdyż osoba niepowołana nie może tak po prostu odczytać hasła, patrząc na ekran.
Niezbyt imponujące określenie ‘lekko’ jest tu jednak bardzo na miejscu, bo istnieje całe mnóstwo narzędzi, które potrafią takie “zagwiazdkowane” hasła bez trudu odczytać. W jaki sposób to robią?

Otóż pola tekstowe dla haseł często są po prostu zwykłymi editboksami. Różnicą jest to, że mają przy tym ustawioną specjalną flagę: styl ES_PASSWORD; sprawia ona, że zawartość pola jest wyświetlana w sposób zamaskowany. Zakładając, że znamy jego uchwyt (HWND) – pozyskany na przykład z pozycji myszy i funkcji WindowFromPoint – możemy ów styl po prostu wyłączyć:

  1. SetWindowLong (hEditBox, GWL_STYLE,
  2.     GetWindowLong(hEditBox, GWL_STYLE) & ~ES_PASSWORD);

a następnie ustawić jeszcze znak zastępczy (“gwiazdkę”) na \0:

  1. SendMessage (hEditBox, EM_SETPASSWORDCHAR, 0, 0);

Wtedy kontrolka powinna zacząć normalnie wyświetlać swoją zawartość bez zamaskowania, więc użytkownik będzie w stanie ją odczytać. Jeśli natomiast chcielibyśmy programowo się do niej dostać, trzeba wysłać do kontrolki komunikat WM_GETTEXT. (Funkcja GetWindowText nie zadziała, jeśli editbox jest w innym procesie – a w tym przypadku właśnie tak jest).

Czy można swój własny program zabezpieczyć przed takim “hackiem”? Oczywiście można – wystarczy nadpisać domyślny sposób obsługi wspomnianego wyżej komunikatu WM_GETTEXT, by nie zwracał on rzeczywistego tekstu zapisanego w polu tekstowym. Da się to zrobić przy pomocy techniki subclassingu, czyli podmiany procedury zdarzeniowej okna (tutaj: pola tekstowego). Trzeba tylko pamiętać, żeby zapewnić przy okazji jakiś sposób, żeby ten tekst jednak jakoś pobierać – np. poprzez zupełnie nową wiadomość:

  1. const DWORD WM_GETREALTEXT = WM_USER + 0x0177;

Najpewniejszą metodą jest aczkolwiek zupełne zrezygonowanie z przechowywania sensownej zawartości w samym editboksie i użycie do tego jakiegoś zewnętrznego bufora w postaci zmiennej. To jednak wymaga obsługi komunikatów związanych z całym procesem edycji, aby ten zewnętrzny bufor uaktualniać.

Istnieją oczywiście gotowe implementacje kontrolek, które działają właśnie w ten sposób. Czy warto ich używać? Sądzę, że niekoniecznie – z dwóch powodów.
Po pierwsze, pole tekstowe do wprowadzania hasła nie musi być wcale najwęższym gardłem bezpieczeństwa aplikacji. Niewiele przyjdzie z nawet najlepszego zabezpieczenia go, jeśli potem wpisane doń hasło jest np. przesyłane otwartym tekstem przez sieć lub zapisywane w pliku w postaci hasha MD5.
Po drugie, użytkownicy są generalnie przyzwyczajeni do jednokrotnego wpisywania haseł, które są następnie zapisywane między sesjami. Tak się dzieje chociażby na stronach WWW, gdzie w tym celu korzysta się z ciasteczek (cookies). Hasła więc bywają zapominane, lecz w sieci nie jest to wielkim problemem ze względu na powszechne mechanizmy przypominania. Tradycyjne aplikacje ich nie posiadają, więc istnienie jakieś metody pozwalającej użytkownikowi na odzyskanie z nich haseł (czyli użycie “odgwiazdkowujących” programów) nie jest wcale rzeczą złą.

A że przy okazji może to zrobić ktoś niepowołany? No cóż… Jeśli posiada on już dostęp do komputera ofiary oraz prawa administratora w jej systemie (bo to jest potrzebne, by wymienione wyżej triki działały), to jest wątpliwe, żeby poprzestał tylko na zabawie z gwiazdkami w editboksach ;-)

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

Unikalne łańcuchy znaków

2009-12-19 12:25

Okazjonalnie zachodzi potrzeba, aby do jakiegoś celu wygenerować niepowtarzalny ciąg znaków. Jednym z zastosowań może być tutaj nadanie plikowi nazwy gwarantującej brak kolizji z jakąkolwiek inną nazwą pliku w systemie albo chociaż w aktualnym katalogu. W przypadku gdy ów plik ma istnieć tylko przez chwilę – a więc być plikiem tymczasowym (tempfile), sprawa jest prosta i sprowadza się do wywołania GetTempFileName jako funkcji WinAPI lub metody klasy System.IO.Path z .NET.

Stworzona w ten sposób nazwa jest z definicji “krótko żyjąca”. Na drugim biegunie w zakresie unikalności znajdują się tzw. UUID-y, czyli specjalnie ustandaryzowane 128-bitowe identyfikatory konstruowane tak, by zapewnić, że prawdopodobieństwo wygenerowania duplikatów jest mikroskopijnie małe. Każdy, kto grzebał trochę w Rejestrze Windows lub miał do czynienia z technologią COM, zna na pewno ich kuzynów, czyli GUID-y, opartych w gruncie rzeczy na podobnych zasadach.
Ponieważ tworzenie nowych GUID-ów/UUID-ów jest proste, można je wykorzystać w celu uzyskania niepowtarzalnych ciągów znaków. W czystym Windows API unikalny string oparty na UUID-zie można otrzymać np. tak:

  1. #include <rpc.h>
  2. #pragma comment(lib, "rpcrt4.lib")
  3. string RandomStringViaUUID()
  4. {
  5.     UUID uuid;
  6.     if (UuidCreate(&uuid) != RPC_S_OK) return string();
  7.  
  8.     char* uuidStr;
  9.     if (UuidToStringA(&uuid, (unsigned char**)&uuidStr) != RPC_S_OK)
  10.         return string();
  11.     string res(uuidStr);
  12.     RpcStringFree (&uuidStr);
  13.     return res;
  14. }

Z kolei w .NET istnieje wyspecjalizowana klasa System.Guid, którą można wykorzystać w podobnym celu. W wyniku otrzymamy oczywiście teksty w stylu:

0DF44EAA-FF21-4412-828E-260A8728E7F1

co w wielu zastosowaniach może być strzelaniem z armaty do komara. Nie zawsze potrzebujemy przecież aż tak wielkiej unikalności, a UUID-y w formie tekstowej są długie i nieporęczne.

Są więc i inne rozwiązania, stosowalne jeśli nasz losowy ciąg musi być unikalny tylko przez stosunkowo niedługi czas, np. podczas jednego uruchomienia programu. W takim wypadku możemy użyć chociażby:

  • wartości zwracanych przez GetTickCount/QueryPerformanceCounter lub asemblerową instrukcję rdtsc. Jest to czas (lub liczba cykli procesora) od momentu rozruchu systemu, więc z definicji wartość taka nie powtórzy się w trakcie działania aplikacji. Warto wiedzieć aczkolwiek, że używanie rdtsc jest generalnie niezalecane.
  • aktualnego systemowego czasu – pobranego przez GetSystemTime z Windows API czy System.DateTime.Now z .NET – przekonwertowanego na string. Wadą jest tutaj wynikowa długość łańcucha i zbyt oczywisty format; prawdopodobnie więc należałoby jeszcze poddać go jakiemuś post-processingowi.
  • zwykłego ciągu losowych znaków. W zależności od jakości generatora liczb losowych oraz losowości początkowego ziarna seed, możemy otrzymać nawet całkiem dobre wyniki. Metoda jest dobra zwłaszcza wtedy, gdy generowanych identyfikatorów jest na tyle mało, że możemy je efektywnie przechowywać i po prostu sprawdzać, czy nowy string jeszcze nie wystąpił (przykładem mogą być uchwyty w menedżerze zasobów w silniku gry).

Najważniejsze jest to, aby właściwie dobrać metodę do sytuacji, w której potrzebujemy unikalnego łańcucha znaków. Często wystarczy coś w stylu:

  1. string s;
  2. while (rand() % MAX_LENGTH - s.length() > 0)
  3.     s += rand() % ('Z' - 'a') + 'a';

Nie myślmy nawet jednak, żeby tak stworzonego stringa używać do jakiejkolwiek formy komunikacji przez sieć. W takich przypadkach trzeba w zasadzie użyć UUID-ów.

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


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