Posts from 2008

Triki z PowerShellem #6 – Mail z załącznikami

2008-07-03 20:05

Kiedy mamy komuś przesłać plik, możemy niekiedy użyć do tego zwykłej poczty e-mail. Nie jest to oczywiście możliwe zawsze: plik nie powinien być zbyt duży i zwykle nie może też należeć do żadnego z “niebezpiecznych” typów (np. aplikacji EXE), by serwery pocztowe mogły go przepchnąć bez narażania lub zatykania sieci.
Jeśli w naszym przypadku tak jest, to możemy wykonać całą operację na przykład przy pomocy poniższego skryptu PowerShella:

  1. # MailFile.ps1 - Wysyła podany plik e-mailem na podany adres
  2. # Parametr: nazwa pliku lokalnego
  3. param([string]$file = $(throw "File not specified"))
  4.  
  5. # Stałe
  6. $SERVER = "moj.server.pl"
  7. $LOGIN = "loginSmtp"
  8. $PASSWORD = "hasłoSmtp"
  9. $FROM_ADDRESS = "ja@server.pl"
  10.  
  11. # Odczytanie adresu docelowego
  12. $recipentAddress = Read-Host -Prompt "Recipent e-mail address"
  13. $recipent = New-Object Net.Mail.MailAddress @($recipentAddress)
  14.  
  15. # Złożenie maila
  16. $mail = New-Object Net.Mail.MailMessage
  17. $mail.From = New-Object Net.Mail.MailAddress @($FROM_ADDRESS)
  18. $mail.To.Add($recipent)
  19. $mail.Subject = (New-Object IO.FileInfo @($file)).Name
  20.  
  21. # Dodanie załącznika
  22. $attachment = New-Object Net.Mail.Attachment @($file,
  23.     [Net.Mime.MediaTypeNames+Application]::Octet)
  24. $cd = $attachment.ContentDisposition
  25. $cd.CreationDate = [IO.File]::GetCreationTime($file)
  26. $cd.ModificationDate = [IO.File]::GetLastWriteTime($file)
  27. $cd.ReadDate = [IO.File]::GetLastAccessTime($file)
  28. $mail.Attachments.Add($attachment)
  29.  
  30. # Łączenie z serwerem SMTP i wysłanie maila
  31. $smtp = New-Object Net.Mail.SmtpClient @($SERVER)
  32. $smtp.Credentials = New-Object Net.NetworkCredential @($LOGIN, $PASSWORD)
  33. # $smtp.EnableSsl = $true # Odkomentowujemy, jeśli serwer wymaga SSL
  34. $smtp.Send($mail)

Jak widać, nie jest to nic skomplikowanego. A jak wykorzystać go w praktyce?… Podobnie jak prezentowany wcześniej skrypt do uploadu na serwer FTP, można go wywołać komendą w rodzaju:

  1. powershell -Command . 'C:\Sciezka\Do\Skryptu\MailFile.ps1' '%1'

którą podpinamy do menu kontekstowego plików lub podmenu Wyślij do.
W ten prosty sposób możemy oszczędzić sobie uruchamiania całego klienta poczty, względnie przeglądarki z webmailem. Naturalnie sporo rzeczy – jak chociażby temat wysyłanej wiadomości czy jej treść – możemy w zaprezentowanym kawałku kodu zmienić lub uzupełnić.

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

Wyliczenia kwalifikowane

2008-07-01 23:26

W C++ typy wyliczeniowe deklaruje się zwykle poprzez coś podobnego do poniższego kawałka kodu:

  1. enum Sides { Left = -1, Middle = 0, Right = 1 };

Jego skutkiem jest jednak to, że nazwy stałych typu (tutaj: Left, Middle i Right) będą widoczne w całej przestrzeni nazw zawierającej daną deklarację enum. Jeśli więc przypadkiem jest ona globalna, to całkiem łatwo może ona spowodować konflikt chociażby z innym typem w rodzaju:

  1. enum Keys { Left, Right, Up, Down, /* ... */ };

Aby zapobiegać takim sytuacjom, w Javie i C# stałe wyliczeniowe muszą być kwalifikowane nazwą odpowiedniego typu – używa się więc Sides.Left i Keys.Left. W C++ jest jednak inaczej, gdyż blok enum sam w sobie nie tworzy zasięgu (w przeciwieństwie np. do bloków class).

Można temu częściowo zaradzić w następujący sposób:

  1. struct Sides
  2. {
  3.     enum _Enum { Left = -1, Middle = 0, Right = 1 };
  4. };
  5. typedef Sides::_Enum Side;

dzięki czemu możemy z naszego enuma korzystać tak:

  1. Side foo;
  2. foo = Sides::Left;  // OK
  3. foo = 1; // błąd - nie można przypisać liczby
  4. foo = Right; // błąd - Right nie jest w przestrzeni globalnej

Różnica względem wspomnianych dwóch języków polega na tym, że nazwa typu wyliczeniowego (Side) oraz kwalifikator stałych (Sides) nie są takie same. Wydaje się jednak (przynajmniej mi się tak wydaje :]), że w tym przypadku takie rozróżnienie jest logicznie poprawne i wygląda nawet czytelniej niż gdyby obie nazwy były identyczne.
Trik ten można naturalnie opakować w makro, które umożliwi łatwe tworzenie typów wyliczeniowych z kwalifikowanymi nazwami stałych. Nie poprawi to oczywiście funkcjonalności enumów w C++, ale przynajmniej sprawi, że będą ładniej wyglądały :D

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

Konstruktory bezparametrowe

2008-06-28 12:12

W C++ konstruktor domyślny jest generowany automatycznie, jeśli w klasie nie zostanie zdefiniowany żaden inny. W przeciwnym wypadku nie jst on tworzony, co może prowadzić do sytuacji, gdy klasa posiada jedynie takie konstruktory, które wymagają podania parametrów. Jest ona niekorzystna przynajmniej z dwóch powodów:

  1. Gdy dziedziczymy po takiej klasie, musimy jakoś zapewnić, że w klasie pochodnej zostanie wywołany konstruktor klasy bazowej. Zwykle robi się to za pomocą listy inicjalizacyjnej:
    1. Derived(int param1, int param2) : Base(param1, param2) { /*... */ }

    Kiedy jednak zmieni się postać konstruktora klasy bazowej, wówczas modyfikacja będzie musiała dotyczyć wszystkich bezpośrednich potomków tej klasy. W nowej wersji standardu C++ ma być aczkolwiek wprowadzone nowe zadanie dla słowa kluczowego using, które pozwoli na automatyczne utworzenie konstruktorów “przekaźnikowych” – takich, których jedyną rolą jest wywołanie konstuktorów bazowych z takimi samymi parametrami jak te otrzymane w konstruktorze pochodnym. Jeśli jednak przy okazji chcemy coś zmienić czy pominąć, to nie ma rady: trzeba całą tę “sztafetę” zakodować ręcznie.

  2. Jeszcze gorzej jest, kiedy klasa bez konstruktora domyślnego jest wirtualną klasą bazową. Wówczas o jej prawidłowej inicjalizacji muszą pamiętać nie tylko klasy bezpośrednio pochodne, ale w ogóle wszystkie potomne! Dokładnie tak: nawet w piątym czy dziesiątym pokoleniu hierarchii o korzeniu w wirtualnej klasie bazowej, klasy pochodne muszą w swoich konstruktorach wywoływać konstruktory owego korzenia. To oczywiście sprawia, że każda zmiana musi być rozpropagowana na całe to drzewo dziedziczenia (a właściwie graf, bo pewnie zawiera on cykle :>).

Dlatego też klasy będące przede wszystkim klasami bazowymi w dziedziczeniu dobrze jest wyposażać w konstruktory bezparametrowe. Może to aczkolwiek wymagać wyróżnienia w obiekcie stanów Zainicjowany-Niezainicjowany, które powinny być sprawdzane w jego metodach i w razie potrzeby odpowiednio sygnalizowane.

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

Bo to zła instrukcja była…

2008-06-24 21:56

Ostatnio na forum Warsztatu wyrosła dyskusja wokół niepozornej instrukcji break. Zaczęło się od zasłyszanej opinii, że korzystanie z niej jest objawem złego stylu programowania i że jest generalnie niezalecane. Argumentem na rzecz takiego twierdzenia jest to, że break jest podobny w swoim działaniu do etykiet i “wyklętej” instrukcji goto:

  1. while (WarunekPetli())
  2. {
  3.     if (CosSieStalo()) goto ZaPetla;   // prawie jak break
  4. }
  5. ZaPetla:
  6.     // ...

W sumie wyszła z tego całkiem długa polemika, która ujawniła kilka ciekawych faktów. Po pierwsze: przejrzystość kodu jest w całkiem dużym stopniu kwestią subiektywną i o ile jednemu może przeszkadzać brak (lub niewystarczająca ilość) komentarzy, to dla drugiego o wiele ważniejsze może być używanie lub nieużywanie określonych konstrukcji językowych. Po drugie: oprócz wspomnianego już goto czy wywołanego we wspomnianym wątku breaka, do instrukcji potencjalnie niepożądanych swobodnie można zaliczyć bardzo wiele innych rozwiązań – zależnie od upodobań, poczucia ‘słuszności’, bieżącej fazy księżyca i pewnie mnóstwa jeszcze innych kryteriów. I tak dostało się chociażby pętlom “nieskończonym” (typu while(true) lub for(;;)), wspomniano coś o wyjątkach, a sam napomknąłem o instrukcji continue lub “przedwczesnym” return:

  1. void Funkcja()
  2. {
  3.     if (/* warunek wyjścia */) return;
  4.     // reszta instrukcji
  5. }

Teoretycznie można by wszystkie te konstrukcje usunąć w imię ideologicznej czystości programowania strukturalnego, w którym każdy blok ma dokładnie jedno wejście i wyjście. Ale tu na szczęście wkracza wniosek trzeci: języki programowania używane w praktyce nie są żadnymi doskonałymi modelami, nad którymi należy się zachwycać. To narzędzia i jako takie powinny być przede wszystkim wygodne w użytkowaniu.
Więc nie warto drzeć kotów o sens stosowania break czy nawet goto (która to instrukcja też ma swoje zastosowania) i wydawać ogólne sądy na temat ich ‘nieprofesjonalności’. Bo nawet jeśli ta czy inna instrukcja rzekomo jest zła, to korzystający z niej kod nadal może być dobry, czytelny i łatwy do modyfikacji… Łącznie z refaktoringiem, który tego czy innego breaka może przecież usunąć ;P

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

Znaki białe i bielsze

2008-06-22 22:23

W językach programowania i opisu (np. HTML lub XML) ważną rolę odgrywają tzw. białe znaki (whitespaces). Nie są one nigdy wypisywane ani drukowane jako symbole, ale zajmują miejsce na ekranie i czasem powodują dodatkowe efekty (jak na przykład przejście do nowego wiersza w przypadku znaku końca linii – line feed). W wielu przypadkach parsery tekstowe ignorują większą ilość tego typu znaków, pozwalając dzięki temu na dowolne formatowanie kodu. Ale czasami jest wręcz odwrotnie: istnieje mianowicie ezoteryczny język programowania – zwany oryginalnie Whitespace – w którym to właśnie znaki niedrukowane (a dokładniej spacja, tabulator i znak końca wiersza) są jedynymi, które są interpretowane jako kod.

Jakie jednak znaki należy uważać za niedrukowane?… Sprawa nie jest taka prosta, gdyż, podobnie jak rozróżnienie liter wielkich i małych, zależy ona od ustawień lokalnych języka. Większość języków programowania dysponuje aczkolwiek odpowiednią metodą sprawdzenia. W C++ jest to na przykład standardowa funkcja isspace. Ponieważ jednak w grę wchodzą ustawienia kulturowe, rezultaty zależą od języka systemu operacyjnego (przynajmniej teoretycznie).
W Unicode mamy na szczęście standardowo zdefiniowane, które znaki są traktowane jako białe. Te właśnie są sprawdzane przez metody w rodzaju java.lang.Character.isWhitepace w Javie czy System.Char.IsWhiteSpace w .NET. Działają one tak samo niezależnie od kontekstu kulturowego.

A co mamy zrobić, jeśli optujemy za ANSI? Wówczas możemy ustalić sobie swój własny zbiór znaków interpretowanych jako białe. Według mnie najlepszym zestawem jest następujący:

  1. bool IsCharWhitespace(char c)
  2. {
  3.     return (c == 0x09 || c == 0x0A || c == 0x0D || c == 0x20);
  4. }

Te znaki to kolejno: tabulator (poziomy) (\t), line feed (\n), znak powrotu karetki (carriage return, \r) i zwykła spacja. Za takim wyborem przemawia chociażby fakt, że właśnie te znaki są uważane w XML za białe i że obejmują różne standardy kodowania końca wiersza na różnych platformach. Dwa inne znaki: tabulator pionowy (0x0B) i form feed (0x0C) są jeszcze uważane za białe w C, ale stosuje się je chyba zbyt rzadko, aby trzeba się było nimi przejmować :)

Tags: ,
Author: Xion, posted under Programming » Comments Off on Znaki białe i bielsze

Triki z PowerShellem #5 – Skróty

2008-06-20 13:53

Pisząc skrypty w PowerShellu mamy dostęp do niemal całej platformy .NET, zawierającej grubo ponad dziesięć tysięcy klas, dlatego nieczęsto będziemy potrzebowali czegoś spoza tego wielkiego bogactwa. Ale takie przypadki też istnieją. Na szczęście PSh umożliwia też dostęp do innych narzędzi niż .NET – jak choćby do starych (nie)dobrych obiektów COM.
Przy ich pomocy można zrobić całkiem sporo, zwłaszcza jeśli chodzi o sterowanie aplikacjami, które udostępniają na zewnątrz odpowiednie interfejsy (a robi tak wiele większych programów, nie tylko Microsoftu). Ciekawsze jest chyba jednak łączenie technologii: “starego” COM-a i nowego .NET-a, aby razem zrobić coś użytecznego. Oto prosty przykład:

  1. # shortcuts.ps1
  2. # Szuka skrótów do nieistniejących plików w podanym katalogu
  3.  
  4. # Parametr: ścieżka do przeszukiwanego katalogu
  5. param([string]$path = ".")
  6.  
  7. # Szukamy plików skrótów
  8. $allLinks = [IO.Directory]::GetFiles($path, "*.lnk", [IO.SearchOption]::AllDirectories)
  9.  
  10. # Przeglądamy listę i szukamy nieistniejących
  11. $wsh = New-Object -ComObject WScript.Shell
  12. $invalidLinks = New-Object Collections.ArrayList
  13. foreach ($linkFile in $allLinks)
  14. {
  15.     $lnk = $wsh.CreateShortcut($linkFile)
  16.     if (-not ([IO.File]::Exists($lnk.TargetPath) -or [IO.Directory]::Exists($lnk.TargetPath)))
  17.         { $invalidLinks.Add($linkFile) | Out-Null }
  18. }
  19. return [string[]]$invalidLinks.ToArray()

Dziwnym trafem najprostsze przykłady użycia COM są zwykle związane z tworzeniem skrótów (plików .lnk) przy pomocy metody CreateShortcut obiektu COM o identyfikatorze WScript.Shell. Obiekt ten był dawniej używany w skryptach hosta systemu Windows (Windows Scripting Host) i, jak widać, nadal zdarza mu się być użytecznym ;-)
Co jednak robi powyższy skrypt? Otóż nie tworzy żadnych nowych skrótów, a wręcz przeciwnie: wskazuje kandydatów do usunięcia, czyli skróty pokazujące na nieistniejące pliki lub katalogi. Na wyjściu dostajemy ich zgrabną listę, którą możemy przekierować na przykład do polecenia Remove-Item.
A tak możemy sobie wyczyścić Menu Start:

  1. $startMenu = [Environment]::GetFolderPath([Environment+SpecialFolder]::StartMenu)
  2. . ./shortcut.ps1 -path $startMenu | Remove-Item

I pomyśleć, że kiedyś napisałem do tego cały osobny program… :)

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

Debugowanie preprocesora

2008-06-18 22:48

Preprocesor w C++ to może i przestarzała, ale i całkiem fajna zabawka. Przynajmniej czasami – mimo dość ubogiej logiki – pozwala ona załatać choć niektóre braki języka. A niekiedy pozwala też na pewne sztuczki, jakie ciężko byłoby osiągnąć inaczej. W końcu, można go też używać do tego, w czym jest najlepszy: do automatycznego wklejania powtarzających się fragmentów kodu w wielu miejscach.
Ponieważ jednak preprocesor działa na kodzie jak na tekście, to rezultaty mogą być czasami zadziwiające – zwłaszcza dla kompilatora. Może on nam wówczas pokazać błąd w linijce, która jest całkowicie poprawna… Ale tylko z dokładnością do zawartego w niej makra, które po rozwinięciu generuje błąd składniowy lub semantyczny. Weźmy chociażby chyba najprostszy przypadek tego typu:

  1. #define COUNT 10;
  2. int buf[COUNT]; // błąd!

W nim łatwo o wykrycie pomyłki, lecz w rzeczywistych sytuacjach może to być zadanie nie lada. Zwłaszcza wtedy, gdy użyte makro jest skomplikowane, a my przecież nie możemy przełączyć preprocesor w tryb pracy krokowej i śledzić, jak jest ono rozwijane.

Możemy jednak przynajmniej podejrzeć ostateczne rezultaty. Wprawdzie domyślne każdy plik źródłowy po przetworzeniu przez preprocesor trafia od razu do kompilatora, jednak zachowanie to można łatwo zmienić. Możliwe jest na przykład zrzucenie wstępnie przetworzonego źródła do pliku, który to możemy potem przejrzeć i zobaczyć, w co ostatecznie nasze makra się zmieniły.
W Visual C++ takie zachowanie ustawia się we właściwościach projektu, na zakładce Configuration Properties > C/C++ > Preprocessor. Tam możemy wybrać, czy chcemy, by rezultaty działania preprocesora na plikach .cpp były zapisywane do osobnych plików (z rozszerzeniem .i). Wachlarz opcji umożliwia też określenie, czy mają być do nich dołączane numery linii lub komentarze.

Opcje preprocesora w Visual C++

Włączenie tych opcji bywa przydatne, jeśli dostajemy na linijkach kodu z użyciem makr dostajemy komunikaty o błędach kompilatora, które są zupełnie “z innej bajki”. Ponadto w ten sposób można też zobaczyć, jakie fragmenty kodu są kierowane do kompilacji za pomocą dyrektyw typu #ifdef, co też bywa przydatne.
Pamiętajmy jednak, że wygenerowane w ten sposób pliki .i są prawie zawsze bardzo, bardzo duże – rzędu kilku megabajtów – gdyż zawierają treść wszystkich dołączonych nagłówków. Dlatego, ze względu na szybkość budowania projektu, nie powinniśmy ich generować poza przypadkami debugowania preprocesora.

Tags: ,
Author: Xion, posted under Applications, Programming » Comments Off on Debugowanie preprocesora
 


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