czwartek, 28 maja 2009

Uwaga na strumienie (i metodę File.OpenWrite)

Promuj

Tym razem będzie o czymś bardzo prostym, chociaż przyznam, że mnie trochę zdziwiło. Zwykle metody w .NET Framework'u działają tak jak podpowiada mi moja intuicja tym razem było trochę inaczej, a ponieważ po rozmowie w gronie znajomych okazało się, że i inni dali się na to złapać, więc opiszę to jako ostrzeżenie ;).

Zacznijmy więc od początku: Jak można w najprostszy sposób wpisać coś do pliku? Chyba najłatwiej jest stworzyć strumień do zapisu, przy pomocy metody File.OpenWrite, a następnie wpisać do niego dowolne dane. Oczywiście jeśli dany plik nie istnieje ta funkcja sama zadba by go sobie utworzyć, proste?, nieprawdaż? Przeanalizujmy jednak poniższy przykład:

static void Main( string[] args )
{
  //mamy dwa bufory
  //pierwszy dłuższy (15 bajtów/znaków)
  byte[] buffer1 = new byte[ 15 ];
  //drugi krótszy (10 bajtów/znaków)
  byte[] buffer2 = new byte[ 10 ];
  for ( int i = 0; i < buffer1.Length; i++ )
    buffer1[ i ] = 65; //litera: A
  for ( int j = 0; j < buffer2.Length; j++ )
    buffer2[ j ] = 66; //litera: B
  // otwieramy plik (tworzony jest jeśli go
  // jeszcze nie ma)
  using ( Stream file = File.OpenWrite( "myfile.txt" ) )
  {
    file.Write( buffer1, 0, buffer1.Length );
  }
  //ponowny raz otwieramy ten plik do zapisu
  using ( Stream file = File.OpenWrite( "myfile.txt" ) )
  {
    file.Write( buffer2, 0, buffer2.Length );
  }
  // kolejny raz otwieramy ten plik (tym razem do odczytu
  using ( StreamReader reader = new StreamReader( "myfile.txt" ) )
  {
    //wypisujemy na ekran to co odczytujemy z pliku
    Console.WriteLine( reader.ReadToEnd() );
  }
  Console.ReadLine();
}

Jak myślicie co się pojawi na ekranie? A) AAAAAAAAAAAAAAA
B) BBBBBBBBBB
C) wyjątek
D) żadna z powyższych

Czy już znacie odpowiedź? ..... oczywiście to odpowiedź.... D

Czy wszyscy podali taką odpowiedź? bo ja dałem się złapać (myślałem że prawidłowa jest odpowiedź B), natomiast na ekranie pojawi się: BBBBBBBBBBAAAAA. Dlaczego? ano dlatego, że za drugim razem otwieramy już istniejący plik, ustawiamy się na jego początku (bynajmniej nie kasujemy starego) i wpisujemy odpoczątku 10 liter B. Pozostała zawartość pliku jest nie zmieniona.

Skąd się wziął mój błąd, ano myślałem, że działa to podobnie jak strumienie w konsoli:
type plik.txt >plik2.txt - przepisuje zawartość plik.txt do plik2.txt (jeśli plik2.txt istniał zostaje on wykasowany)
type plik.txt >>plik2.txt - dopisuje zawartość plik.txt na końcu plik2.txt (jeśli plik2.txt nie istnieje, to go wcześniej tworzy)

Dodatkowo w MSDN nie ma nic na temat takiego zachowania (np. krótkiego "remark" z ostrzeżeniem).

Ktoś z Was (czytelników) też dał się na to złapać?

Promuj

środa, 27 maja 2009

CodeProject: Creation of Word 2007 document using Open XML Format SDK

Ostatnio opublikowałem na portalu CodeProject artykuł na temat Open XML Format SDK i tworzenia przy jego pomocy dokumentu typu Word 2007.

http://www.codeproject.com/KB/office/OpenXML-SDK-HelloWorld.aspx

Zagadnienia poruszone w tym artykule, pokrywają się częściowo już z materiałem dostępnym na tym blogu, ale są i pewne dodatkowe treści (związane m.in. z samym językiem WordprocessingML).

Zapraszam do lektury.

wtorek, 26 maja 2009

SyntaxHighlighter na Blogger'rze (optymalizacja)

Na temat SyntaxHighlighter i Bloggera pisałem już w jednym z moich poprzednich postów: Programowanie i Technologie (czyli C#, .NET, OPC, OPC UA i inne): SyntaxHighlighter na Blogger'rze (jak uruchomić?), zadowolony jestem że artykuł cieszył się powodzeniem, a nawet inni blogowicze zamieści linki do niego. Dziękuję czytelnikom (w szczególności Pawłowi Żubkiewiczowi i Tomaszowi Dziurko).

SyntaxHighlighter działa już na moim blogu od miesiąca, dlatego chyba najwyższy czas na optymalizację jego działania. Po uruchomieniu SyntaxHighlighter zgodnie ze wskazówkami opisanymi w moim poprzednim post'cie niestety zauważyłem, że często mój blog długo ładował się. Wiązało się to z koniecznością zaczytania dużej liczby skryptów z obcego serwera (w moim przypadku był to serwer autora SyntaxHighlighter).

Czy coś można z tym zrobić by poprawić wydajność? Oczywiście tak!

Oto zestaw kroków, które wykonałem tydzień temu i od tego czasu mój blog działa wyraźnie lepiej:

  1. Wybranie tylko tych skryptów i dla tych języków, które będą się pojawiały na moim blogu
  2. Przeniesienie z początku strony (przed końcem head) na koniec (przed końcem body) metod ładowania skryptów: shCore.js i innych w zależności od wybranych języków oraz uruchomienia samego SyntaxHighlighter'a. Po tej operacji na początku zostaje jedynie załadowanie arkuszy styli (shCore.css, shThemeDefault.css), później wczytuje się cała strona (dzięki temu jest od razu widoczna), natomiast na samym końcu wczytywane są i wykonywane są pozostałe skrypty

Na razie tyle zmian, mam jeszcze kolejny pomysł: przeniesienie skryptów na inny, szybszy serwer, ale jeszcze nie zdecydowałem na jaki, może ktoś ma jakieś sugestie?

poniedziałek, 25 maja 2009

Podpisywanie danych przy pomocy algorytmu RSA (przykłady w C#).

Promuj

Niniejszy, krótki artykuł poświęcony jest podstawom związanym z zabezpieczeniem przesyłanych informacji przy pomocy podpisu elektronicznego. Do wygenerowania podpisu zostanie wykorzystany algorytm RSA. Według Polskiej Wikipedii RSA, to pierwszy i obecnie jeden z dwóch najpopularniejszych (obok ElGamala) algorytmów kryptografii asymetrycznej. Został stworzony w 1978 przez zespół: Ronald Rivest, Adi Shamir, Leonard Adleman (nazwa RSA jest akronimem utworzonym z pierwszych liter nazwisk jego twórców). RSA opiera się na trudności faktoryzacji dużych liczb. Znalezienie szybkiej metody faktoryzacji doprowadziłoby do złamania RSA, aczkolwiek nie ma dowodu, że nie da się go złamać w inny sposób. Do generacji kluczy, podpisu i jego weryfikacji, zostaną wykorzystane klasy wbudowane w .NET Framework.

Generowanie kluczy

Przed przystąpieniem do podpisywania wiadomości, czy późniejszej weryfikacji potrzebna jest para kluczy: prywatny i publiczny. Klucze te mogą pochodzić z certyfikatu zainstalowanego w systemie, jak również można je wygenerować samodzielnie. Przed przystąpieniem do generacji kluczy, należy utworzyć bezpieczny pojemnik na klucze - wykorzystamy do tego klasę CspParameters, w której po inicjacji należy ustawić nazwę kontenera (KeyContainerName). Następnie tworzymy obiekt klasy RSACryptoServiceProvider, który jest dostawcą implementacji algorytmy RSA na platformie .NET. Z tak utworzonego obiektu możemy wyeksportować sobie klucze (publiczny i prywatny), format w jakim eksportujemy te klucze w zasadzie może być dowolny. W poniższym przypadku jako format przechowywania kluczy został wybrany format XML. Poniższy przykład generuje klucze, które ostatecznie zapisuje (w postaci XML) w dwóch string’ach. Pierwszy zwiera część prywatną i publiczną (zostanie wykorzystany do podpisania wiadomości), drugi zawiera tylko część publiczną (wykorzystany zostanie do weryfikacji podpisu).

// Declare a CspParameters variable, which will identify the 
// key container in which the recipients PRIVATE KEY is stored.
CspParameters csp = new CspParameters();
csp.KeyContainerName = "MyKeys";
// Initialize an RSACryptoServiceProvider object using
// the CspParameters object.
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(csp);
//XML representation of the key (public and private part is included):
string private_part = rsa.ToXmlString(true);
//XML representation of the key (only public part is included):
string public_part = rsa.ToXmlString(false);

Podpisywanie wiadomości

Wygenerowaliśmy wcześniej klucze, teraz można przejść do podpisania wiadomości. W tym celu wykorzystamy klasę RSACryptoServiceProvider, której obiekt zainicjujemy, a następnie zaimportujemy do niego wygenerowany wcześniej klucz (publiczny i prywatny) przy pomocy metody: FromXmlString.. Następnie wybieramy algorytm hash’ujący, do najpopularniejszych należą SHA1 i MD5. Kolejnym krokiem jest przygotowanie wiadomości do wysłania, zakładając że pierwotnie jest to string, zamieniamy go na ciąg byte’ów (np. przy pomocy metody GetBytes klasy ASCIIEncoding). Ostatecznie generujemy podpis przy pomocy SignData klasy RSACryptoServiceProvider.

RSACryptoServiceProvider rsa_with_private = new RSACryptoServiceProvider();
rsa_with_private.FromXmlString(private_part);
//selection the algorithm:
//const string algorithm = "SHA1";
const string algorithm = "MD5";
// Create some data to sign.
string message = @"Hello World!
Lorem ipsum dolor sit amet, consectetur adipiscing elit. 
Nulla nec euismod neque. Nam quam orci, ultrices vitae viverra vel, 
vehicula at purus. Integer tincidunt venenatis neque, eget commodo neque molestie at. 
Aenean porttitor elementum nisi, a scelerisque urna pulvinar vitae. ";
byte[] data = (new System.Text.ASCIIEncoding()).GetBytes(message);
// Sign the data using the Smart Card CryptoGraphic Provider.
byte[] sig = rsa_with_private.SignData(data, algorithm);

Weryfikacja podpisu

Podpisaliśmy wcześnie wiadomość, przesyłamy ją razem z podpisem, a teraz czas na weryfikację podpisu. W tym celu inicjujemy obiekt klasy RSACryptoServiceProvider, do którego przekazujemy (podobnie jak wcześniej) klucz (tym razem tylko jego część publiczną i weryfikujemy podpis przy pomocy metody VerifyData.

RSACryptoServiceProvider rsa_only_public =  new RSACryptoServiceProvider();
rsa_only_public.FromXmlString(public_part);
// Verify the data using the Smart Card CryptoGraphic Provider.
bool verified = rsa_only_public.VerifyData(data, algorithm, sig);

Podsumowanie

W tym artykule starałem się nie wchodzić za bardzo w szczegóły algorytmów, czy metod bezpiecznego przechowywania kluczy. Moim celem było pokazanie procedury podpisywania wiadomości w sposób jak najbardziej prosty. Mam nadzieję że to mi się udało.

Promuj

sobota, 23 maja 2009

Tabele w Open XML Format SDK (DOCX) (czyli przygotowujemy tabliczkę mnożenia dla Workd 2007)

W poprzednich częściach przedstawione były zagadnienia związane z przygotowaniem prostego dokumentu oraz formatowaniem tekstu. W tej części będzie o tworzeniu tabel przy pomocy Open XML Format SDK.

Dzięki wygodnemu API jakie oferuje Open XML Format SDK tworzenie tabel jest bardzo proste i polega na dodaniu do głównego dokumentu elementu typu Table, w którym następnie osadzamy elementy typu TableRow i TableCell. Najprostszy przykład takiej tabeli wygląda następująco:

Body body = new Body();
Table table = new Table(new TableRow(new TableCell(
  new Paragraph(new Run(new Text("Hello World!"))))));
body.Append(table);

Tabliczka mnożenia

W tym przykładzie stworzymy tabelkę z tabliczką mnożenia (zupełnie taką jak w szkole ;) ):

Multiplication table

*

1

2

3

4

5

6

7

8

9

10

1

1

2

3

4

5

6

7

8

9

10

2

2

4

6

8

10

12

14

16

18

20

3

3

6

9

12

15

18

21

24

27

30

4

4

8

12

16

20

24

28

32

36

40

5

5

10

15

20

25

30

35

40

45

50

6

6

12

18

24

30

36

42

48

54

60

7

7

14

21

28

35

42

49

56

63

70

8

8

16

24

32

40

48

56

64

72

80

9

9

18

27

36

45

54

63

72

81

90

10

10

20

30

40

50

60

70

80

90

100

We wstępie pokazałem jak przygotować prostą tabelkę. W tym przykładzie też będziemy osadzać w sobie obiekty TableRow i TableCell, ale dodatkowo zadbamy jeszcze o krawędzie i ustawimy by komórka w pierwszym wierszu tabeli rozszerzona była na wszystkie kolumny poniżej.

Zacznijmy więc do ustawienia obramowania, dla całej tabeli ustawimy proste pojedyncze obramowanie, w tym celu wykorzystujemy klasy: TableProperties i TableBorders. Przykład poniżej:

Table table = new Table();
TableProperties tblPr = new TableProperties();
TableBorders tblBorders = new TableBorders();
tblBorders.TopBorder = new TopBorder();
tblBorders.TopBorder.Val = new EnumValue<BorderValues>(BorderValues.Single);
tblBorders.BottomBorder = new BottomBorder();
tblBorders.BottomBorder.Val =new EnumValue<BorderValues>(  BorderValues.Single);
tblBorders.LeftBorder = new LeftBorder();
tblBorders.LeftBorder.Val = new EnumValue<BorderValues>(BorderValues.Single);
tblBorders.RightBorder = new RightBorder();
tblBorders.RightBorder.Val = new EnumValue<BorderValues>(BorderValues.Single);
tblBorders.InsideHorizontalBorder = new InsideHorizontalBorder();
tblBorders.InsideHorizontalBorder.Val = BorderValues.Single;
tblBorders.InsideVerticalBorder = new InsideVerticalBorder();
tblBorders.InsideVerticalBorder.Val = BorderValues.Single;
tblPr.Append(tblBorders);
table.Append(tblPr);

Teraz zajmijmy się pierwszym wierszem i jego komórką, dla której musimy ustawić cechę by była rozszerzona na 11 kolumn znajdujących się niżej. W tym celu ustawiamy odpowiednie właściwości komórki (TableCellProperties), a dokładniej właściwość GridSpan.

tr = new TableRow();
tc = new TableCell(new Paragraph(new Run(new Text("Multiplication table"))));
TableCellProperties tcp=new TableCellProperties();
GridSpan gridSpan=new GridSpan();
gridSpan.Val=11;
tcp.Append(gridSpan);
tc.Append(tcp);
tr.Append(tc);

Teraz zostają już do wykonania tylko proste wyliczenia i ostatecznie otrzymujemy funkcję

public void HelloWorld_table(string docName)
{
  // Create a Wordprocessing document. 
  using (WordprocessingDocument myDoc = WordprocessingDocument.Create(docName, WordprocessingDocumentType.Document))
  {
    // Add a new main document part. 
    MainDocumentPart mainPart = myDoc.AddMainDocumentPart();
    //Create DOM tree for simple document. 
    mainPart.Document = new Document();
    Body body = new Body();
    Table table = new Table();
    TableProperties tblPr = new TableProperties();
    TableBorders tblBorders = new TableBorders();
    tblBorders.TopBorder = new TopBorder();
    tblBorders.TopBorder.Val = new EnumValue(BorderValues.Single);
    tblBorders.BottomBorder = new BottomBorder();
    tblBorders.BottomBorder.Val =new EnumValue(  BorderValues.Single);
    tblBorders.LeftBorder = new LeftBorder();
    tblBorders.LeftBorder.Val = new EnumValue(BorderValues.Single);
    tblBorders.RightBorder = new RightBorder();
    tblBorders.RightBorder.Val = new EnumValue(BorderValues.Single);
    tblBorders.InsideHorizontalBorder = new InsideHorizontalBorder();
    tblBorders.InsideHorizontalBorder.Val = BorderValues.Single;
    tblBorders.InsideVerticalBorder = new InsideVerticalBorder();
    tblBorders.InsideVerticalBorder.Val = BorderValues.Single;
    tblPr.Append(tblBorders);
    table.Append(tblPr);
    TableRow tr;
    TableCell tc;
    //first row - title
    tr = new TableRow();
    tc = new TableCell(new Paragraph(new Run(new Text("Multiplication table"))));
    TableCellProperties tcp=new TableCellProperties();
    GridSpan gridSpan=new GridSpan();
    gridSpan.Val=11;
    tcp.Append(gridSpan);
    tc.Append(tcp);
    tr.Append(tc);
    table.Append(tr);
    //second row 
    tr = new TableRow();
    tc = new TableCell();
    tc.Append(new Paragraph(new Run(new Text("*"))));
    tr.Append(tc);
    for (int i = 1; i <= 10; i++)
    {
      tr.Append(new TableCell(new Paragraph(new Run(new Text(i.ToString())))));
    }
    table.Append(tr);
    for (int i = 1; i <= 10; i++)
    {
      tr = new TableRow();
      tr.Append(new TableCell(new Paragraph(new Run(new Text(i.ToString())))));
      for (int j = 1; j <= 10; j++)
      {
        tr.Append(new TableCell(new Paragraph(new Run(new Text((i*j).ToString())))));
      }
      table.Append(tr);
    }
    //appending table to body
    body.Append(table);
    // and body to the document
    mainPart.Document.Append(body);
    // Save changes to the main document part. 
    mainPart.Document.Save();
  }
}
Promuj

środa, 13 maja 2009

OPC, OPC UA, komunikacja - filmy instruktażowe [PL]

Ostatnio na tym blogu pojawiło się klika filmów instruktażowych na temat OPC, OPC UA i komunikacji. W ramach podsumowania (i dla ułatwienia nawigacji), przypomnę listę filmów:

  1. OPC: instalacja, uruchomienie i konfiguracja komunikacji pomiędzy dwoma komputerami - dwa filmy (w języku polskim) pokazujące instalację serwera OPC (na przykładzie CommServer), podłączenie się klientem OPC, konfigurację OPC i DCOM aby umożliwić komunikację pomiędzy dwoma komputerami
  2. Wymiana danych: OPC <-> DDE <-> Excel - film (w języku polskim) pokazuje przykład OPC serwera dla programu Excel (konfiguracja, uruchomienie i wykorzystanie)
  3. Komunikacja serwer OPC i sterownik PLC - dwa filmy (w języku angielskim) pokazujące konfigurację serwera OPC, aby ten odczytywał dane ze sterownika SAIA przy pomocy protokołów SBUS DataMode i IP/UDP
  4. Pozyskiwanie danych z procesu (do OPC) - pewnie i wydajnie (czyli redundancja i adaptacja) - dwa filmy (w języku angielskim) pokazujące zaawansowane algorytmy komunikacyjne: algorytm skanowania adaptacyjnego i redundację ścieżek komunikacyjnych
  5. Wstęp do modelowania przestrzeni adresowej OPC UA przy użyciu Address Space Model Designer - film/prezentacja (w języku angielskim) wprowadzająca w świat OPC UA i modelowania przestrzeni adresowej serwera

Zapraszam wszystkich do odwiedzenia kanału użytkownika CAS CommServer na portalu YouTube.

wtorek, 12 maja 2009

Wykorzystanie shella do wywołania klienta email (czyli mailto: z C#)

Każdy, kto liznął chociażby programowanie stron internetowych (nawet w czystym HTML'u) spotkał się z możliwością prostego wysyłania maili poprzez podanie adresu jako mailto:adresemail@na_pewnym_serwerze, czy można osiągnąć coś podobnego z poziomu aplikacji napisanej przy pomocy C#?

Oczywiście!

Wystarczy wykorzystać możliwości shella systemu operacyjnego windows i uruchomić proces (przy pomocy System.Diagnostics.Process.Start), który jako nazwę będzie miał dpowiednio skonstruowany ciąg "mailto:", czyli:

mailto:adres_email&subject=temat_wiadomosci&body=sugerowana_tresc_wiadomosci

Więcej informacji na temat składni "mailto:" można znaleźć tutaj: http://www.ianr.unl.edu/internet/mailto.html

Ostatecznie funkcja, która uruchamia domyślnego klienta email z zadanym adresatem, tematem i wiadomością, wygląda następująco:

/// <summary> 
 /// Opens the email client. 
/// </summary> 
/// <param name="EmailAddress">The email address.</param> 
/// <param name="MessageSubject">The message subject.</param> 
/// <param name="MessageBody">The message body.</param> 
public static void OpenEmailClient( string EmailAddress, string MessageSubject, string MessageBody ) 
{ 
  string request = String.Format( "mailto:{0}&subject={1}&body={2}", EmailAddress, MessageSubject, MessageBody ); 
  System.Diagnostics.Process.Start( request ); 
} 

W tym momencie mogą pojawić się komentarze, a dlaczego nie wykorzystać klas z przestrzeni System.Web.Mail, System.Net.Mail lub własnego serwera SMTP?
Powodów może być wiele:

  • jeśli chcemy wykorzystać własnego klienta SMTP, to musimy go skonfigurować! Trzeba ustawić serwer SMTP, nazwę użytkownika, hasło itp... dość skomplikowane i wymaga odczytania tych danych z systemu operacyjnego (hasła chyba się wyciągnąć nie da) lub zapytania się o nie użytkownika, czyli kłopoty..
  • jeśli chcemy wykorzystać własny serwer SMTP, to możemy mieć problemy z oprogramowaniem chroniącym przed spamem

Reasumując chyba warto sięgnąć czasami po tak prostą metodę.

poniedziałek, 11 maja 2009

Wstęp do modelowania przestrzeni adresowej OPC UA przy użyciu Address Space Model Designer

Na temat nowego i obiecującego standardu OPC Unified Architecture pisałem już na tym blogu wielokrotnie. Jedną z ważniejszych cech tego standardu jest możliwość pokazywania nie tylko danych z procesu, ale również wszystkich występujących w nim powiązań. Jest to możliwe dzięki zaawansowanym możliwością jakie oferuje przestrzeń adresowa OPC UA (OPC UA Address Space). Chciałbym wszystkich zachęcić do obejrzenia krótkiej prezentacji (nagranej jako film umieszcony w portalu YouTube), pt. :"Introduction to OPC UA Address Space modeling using CAS Address Space Model Designer". Film został nagrany w języku angielskim i dotyczy następujących zagadnień:

Zapraszam wszystkich do oglądania:

niedziela, 10 maja 2009

Style w Open XML SDK

Promuj

Po przeczytaniu artykułu pt. "Open XML Format SDK 2.0 - pierwsze wrażenia", czytelnik wie już jak napisać prostą aplikację, która przy pomocy pakietu Open XML SDK i w języku C# tworzy prosty dokument typu Word 2007 (docx), który zawiera prosty tekst "Hello World!". Tym razem pójdziemy nieco dalej i dołożymy elementy związane z formatowaniem tekstu, czyli zajmiemy się stylami.

Załóżmy, że chcemy przygotować dokument, który będzie się składał z dwóch linijek tekstu. Pierwsza linijka będzie nagłówkiem akapitu, druga tekstem akapitu. To jak przygotować zwykły tekst (tak jak tutaj w drugiej linijce) pisałem o tym już wcześniej, tym razem napiszę jak przygotować tą pierwszą (odpowiednio sformatowaną) linijkę.

Pierwszą czynnością jaką należy wykonać, to trzeba dodać do tworzonego dokumentu część związaną z definicją stylów (dodajemy ją do głównego elementu dokumentu):

WordprocessingDocument myDoc = WordprocessingDocument.Create(documentFileName, WordprocessingDocumentType.Document)
MainDocumentPart mainPart = myDoc.AddMainDocumentPart();
StyleDefinitionsPart stylePart = mainPart.AddNewPart<StyleDefinitionsPart>();

Definiujemy teraz czcionkę (Font), którą chcemy wykorzystać (w tym przykładzie: czerwony i pogrubiony Arial, rozmiaru 28):

// we have to set the properties
RunProperties rPr = new RunProperties(); 
Color color = new Color() { Val = "FF0000" }; // the color is red
RunFonts rFont = new RunFonts();
rFont.Ascii = "Arial"; // the font is Arial
rPr.Append(color);
rPr.Append(rFont);
rPr.Append(new Bold()); // it is Bold
rPr.Append(new FontSize() { Val = 28 }); //font size (in 1/72  of an inch) 

Teraz czas na definicję stylu, będzie się on nazywać "My Heading 1", a jego identyfikator (używany później w dokumencie) to "MyHeading1". Oto definicja:

//creation of a style
Style style = new Style();
style.StyleId = "MyHeading1"; //this is the ID of the style
style.Append(new Name() { Val = "My Heading 1" }); //this is name
style.Append(new BasedOn() { Val = "Heading1" }); // our style based on Normal style
style.Append(new NextParagraphStyle() { Val = "Normal" }); // the next paragraph is Normal type
style.Append(rPr);//we are adding properties previously defined

W tym momencie nadszedł czas na dodanie stworzonego stylu do dokumentu i zapisanie części związanej ze stylami:

// we have to add style that we have created to the StylePart
stylePart.Styles = new Styles();
stylePart.Styles.Append(style);
stylePart.Styles.Save(); // we save the style part

Styl mamy już stworzony, teraz trzeba go przypisać do jakiegoś paragrafu. Słyży do tego klasa ParagraphProperties, której trzeba przypisać odpowiednie ParagraphStyleId. Przykład poniżej:

Paragraph heading = new Paragraph();
Run heading_run = new Run();
Text heading_text = new Text("This is Heading");
ParagraphProperties heading_pPr = new ParagraphProperties();
heading_pPr.ParagraphStyleId = new ParagraphStyleId() { Val = "MyHeading1" }; // we set the style
heading.Append(heading_pPr);
heading_run.Append(heading_text);
heading.Append(heading_run);

Teraz można już cieszyć się efektem naszych prac (przykład jest na załączonym rysunku), na sam koniec zamieszczę całą procedurę która generuje ten dokument:

private void HelloWorld_withStyle(string documentFileName)
{
  // Create a Wordprocessing document. 
  using (WordprocessingDocument myDoc = WordprocessingDocument.Create(documentFileName, WordprocessingDocumentType.Document))
  {
    // Add a new main document part. 
    MainDocumentPart mainPart = myDoc.AddMainDocumentPart();

    //Add new style part 
    StyleDefinitionsPart stylePart = mainPart.AddNewPart();
    // we have to set the properties
    RunProperties rPr = new RunProperties();
    Color color = new Color() { Val = "FF0000" }; // the color is red
    RunFonts rFont = new RunFonts();
    rFont.Ascii = "Arial"; // the font is Arial
    rPr.Append(color);
    rPr.Append(rFont);
    rPr.Append(new Bold()); // it is Bold
    rPr.Append(new FontSize() { Val = 28 }); //font size (in 1/72  of an inch) 
    //creation of a style
    Style style = new Style();
    style.StyleId = "MyHeading1"; //this is the ID of the style
    style.Append(new Name() { Val = "My Heading 1" }); //this is name
    style.Append(new BasedOn() { Val = "Heading1" }); // our style based on Normal style
    style.Append(new NextParagraphStyle() { Val = "Normal" }); // the next paragraph is Normal type
    style.Append(rPr);//we are adding properties previously defined
    // we have to add style that we have created to the StylePart
    stylePart.Styles = new Styles();
    stylePart.Styles.Append(style);
    stylePart.Styles.Save(); // we save the style part

    //Create Document tree for simple document. 
    mainPart.Document = new Document();
    //Create Body (this element contains other elements that we want to include
    Body body = new Body();
    //Create paragraph
    Paragraph paragraph = new Paragraph();
    Run run_paragraph = new Run();
    // we want to put that text into the output document
    Text text_paragraph = new Text("Hello World!");
    //Append elements appropriately.
    run_paragraph.Append(text_paragraph);
    paragraph.Append(run_paragraph);
    Paragraph heading = new Paragraph();
    Run heading_run = new Run();
    Text heading_text = new Text("This is Heading");
    ParagraphProperties heading_pPr = new ParagraphProperties();
    heading_pPr.ParagraphStyleId = new ParagraphStyleId() { Val = "MyHeading1" }; // we set the style
    heading.Append(heading_pPr);
    heading_run.Append(heading_text);
    heading.Append(heading_run);
    body.Append(heading);
    body.Append(paragraph);
    mainPart.Document.Append(body);
    // Save changes to the main document part. 
    mainPart.Document.Save();
  }
}
Promuj

piątek, 8 maja 2009

Zapraszam na VII Warsztaty Nowych Technologii: OPC a pozyskiwanie danych procesowych

Szybki rozwój technologii OPC, standardu komunikacyjnego stosowanego w automatyce przemysłowej i systemach informatycznych, stał się jednym z powodów zainicjowania szkoleń pn. Warsztaty Nowych Technologii.

W bezpośrednich kontaktach z ludźmi związanymi z przemysłem zauważam często brak świadomości automatyków, czy inżynierów dotyczącej możliwości wykorzystania standardu OPC oraz płynących z tego powodu wymiernych korzyściach zarówno dla samych użytkowników, jak i całego przedsiębiorstwa. W tym kontekście dodatkowo jako lekturę polecam - artykuł prezesa OPC Foundation: „OPC Fights Recession by Maximizing Resources”.

Zapraszam zatem na kolejną, VII edycję szkolenia. Tym razem hasłem przewodnim spotkania będzie „OPC a pozyskiwanie danych procesowych” (oczywiście jestem jednym z prowadzących).

Wykładowcy: dr inż. Mariusz Postół, mgr inż. Maciej Zbrzezny, mgr inż. Mariusz Schabowski

Termin: 27 maj 2009 Łódź

Więcej informacji na stronie: http://www.commsvr.com/OPCWorkshops.aspx

Zapraszam!

czwartek, 7 maja 2009

Śledzimy w .NET dalej (dzisiaj uruchomimy własny podsłuch)

Być może tytuł trochę zadziwiający, ale już zaraz wszystko się wyjaśni. Ten artykuł jest kontynuacją wcześniejszego artykułu pod tytułem: "Śledzenie i logowanie zdarzeń (tracing and logging) na platformie .NET" - dlatego w tytule dalej, a co z tym podsłuchem? ... mhmmm... dziś opiszę jak stworzyć własnego Listener’a.

A więc do dzieła...

Założenia - tworzymy aplikację konsolową, której nazwa assembly to "MyApplication" (nazwa assembly jest ważna - zapamiętajmy ją). Aplikacja ma dostarczać własnego TraceListener’a, o nazwie MyCustomListener, który będzie wypisywał informacje do okienka konsoli (wiem, że taki listener jest w .NET, ale na potrzeby przykładu takie założenie będzie w sam raz). Postaram się, by wszystkie elementy były jak najprostsze, aby łatwo można było zrozumieć przykład.

Jak naproście zaimplementować naszego listener’a? Trzeba odziedziczyć po klasie TraceListener i zaimplementować dwie metody: Write i WriteLine, np. tak:

namespace MyApplicationNamespace
{
  class MyCustomListener : TraceListener
  {
    public override void Write(string message)
    {
      Console.Write(" Write: " + message);
    }
    public override void WriteLine(string message)
    {
      Console.WriteLine(" WriteLine: " + message);
    }
  }
}

Zauważmy, że oprócz samej wiadomości dodaję jeszcze na początku "Write:" lub "WriteLine:", czemu? Zobaczymy w końcowej aplikacji. Przejdźmy teraz do głównego programu, oto kod:

namespace MyApplicationNamespace
{
  class Program
  {
    static void Main(string[] args)
    {
      TraceSource mySource = new TraceSource("MySourceName");
      mySource.TraceEvent(TraceEventType.Information, 777, "My Information Message");
      Console.ReadLine(); //waiting for enter
    }
  }
}

Co się tu dzieje? Tworzone jest źródło: "MySourceName" (zapamiętajmy nazwę - będzie użyta w konfiguracji), następnie wrzucamy do logu informację o numerze 777 i treści My Information Message". Teraz konfiguracja:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.diagnostics>
    <sources>
      <source name="MySourceName" switchName="MySwitch" switchType="System.Diagnostics.SourceSwitch"  >
        <listeners>
          <add name="customListener"/>
          <remove name="Default"/>
        </listeners>
      </source>
    </sources>
    <switches>
      <add name="MySwitch" value="All" />
    </switches>
    <sharedListeners>
      <add name="customListener" type="MyApplicationNamespace.MyCustomListener,MyApplication">
        <filter type="System.Diagnostics.EventTypeFilter" initializeData="All" />
      </add>
    </sharedListeners>
  </system.diagnostics>
</configuration>

Na co trzeba zwrócić uwagę? Przede wszystkim ważna jest nazwa źródła: "MySourceName", musi ona być zgodna z tą w programie. Po drugie ważne jest w jaki sposób dodajemy listener’a, musimy tutaj użyć odpowiedniej konstrukcji (z nazwą assembly): <add name="nazwa_listenera" type="namespace_w_ktorym_jest_listener.nazwa_klasy_listenera,nazwa_naszego_assembly">.

Co się stanie po uruchomieniu? Powinniśmy otrzymać efekt:

Write: MySourceName Information: 777 :  WriteLine: My Information Message

Jak widać zostały wywołane obydwie metody naszego MyCustomListener’a, najpierw pojawiła się nazwa źródła i identyfikator (wypisana poprzez Write), a później (przy pomocy WriteLine) wypisana została właściwa wiadomość.

Proste prawda? ;)

wtorek, 5 maja 2009

Pozyskiwanie danych z procesu (do OPC) - pewnie i wydajnie (czyli redundancja i adaptacja)

Podczas konfiguracji połączenia pomiędzy procesem a aplikacjami warstw wyższych często spotykamy się z różnymi problemami. Do najczęstszych należą: ograniczona wydajność łącza lub urządzenia oraz przerwy w komunikacji. Chciałbym wszystkim czytelnikom przybliżyć dwa rozwiązania: adaptacyjne skanowanie i redundancję dróg komunikacyjnych, jako lekarstwo na dwie powyższe dolegliwości.

W przykładach wykorzystany zostanie CommServer i sterownik przemysłowy SAIA.

Algorytm Skanowania Adaptacyjnego (ASA), jest używany do:

  • dopasowywania czasu skanowania danych do bieżącego stanu procesu technologicznego,
  • utrzymania przepustowości pasma oraz kosztów transmisji na zadanym poziomie,
  • zapobiegania transmisji danych niepotrzebnych w danej chwili.

Redundancja tras komunikacyjnych (MPR), specjalny algorytm trasowania w czasie rzeczywistym pozwala na:

  • redundancję pozyskiwania danych z urządzenia zdalnego na poziomie różnych tras komunikacji,
  • redukcję kosztów transmisji poprzez wybór bardziej optymalnej trasy komunikacji
  • używanie w danej chwili wyłącznie jednej trasy do komunikacji z urządzeniem.

Zapraszam do oglądania:

  1. Algorym Skanowania Adaptacyjnego (ASA):
  2. Redundancja dróg komunikacyjnych (MPR):
    • Część pierwsza:
    • Część druga:

poniedziałek, 4 maja 2009

Komunikacja serwer OPC i sterownik PLC

Serwer OPC, by mógł udostępniać jakieś dane musi je najpierw w jakiś sposób odczytać. Pokazywałem już wcześniej w jaki sposób udostępnić dane z Excel'a poprzez OPC. Tym razem zaprezentuję jak skonfigurować serwer OPC, by ten pozyskiwał dane ze sterownika przemysłowego. W przykładzie zostanie wykorzystany OPC Server (CommServer) i sterownik przemysłowy SAIA PCD 3, protokół komunikacyjny to SBUS. Poniższe dwa filmy pokazują komunikację za pośrednictwem Ethernetu (i protokołu UDP/IP) oraz łącza szeregowego (RS). Ze względu na długość większą niż 10 min. każdy z filmów został podzielony na dwie części.

Zapraszam do oglądania:

  1. Komunikacja przy użyciu protokołu IP:
    • Część pierwsza:
    • Część druga:
  2. Komunikacja przy użyciu łącza szeregowego:
    • Część pierwsza:
    • Część druga:

niedziela, 3 maja 2009

COCOMO i estymacja kosztów oprogramowania

W poprzednim post’cie zajmowałem się rozważaniami na temat wyceny oprogramowania. Ponieważ widzę, że ten temat wzbudził zainteresowanie (dziękuję za uwagi w komentarzach i mailach) dziś chciałbym go kontynuować.

Oczywiście po przeczytaniu poprzedniego tekstu chyba każdemu nasunęły się następujące pytania:

  • Ja uzależnić opisywaną wycenę od języka programowania (przecież ilość linii zależy w dużym stopniu od języka)?
  • Jak uzależnić wycenę od umiejętności programisty (lub nawet całego zespołu)?
  • Co z faktem, że jeden projekt może być bardziej skomplikowany od innego?
  • Jak zliczać linie? (Czy liczyć puste? Czy liczyć linie zawierające tylko komentarze? Czy może pomijać pewne linie?)
  • Co z faktem, że w projekcie uczestniczą osoby z różnymi umiejętnościami, o różnym doświadczeniu i o różnych funkcjach?
Dziś spróbuję odpowiedzieć na te pytania.

Trochę teorii

Metoda COCOMO przy szacowaniu kosztu posługuje się oczywiście pewnymi wzorami, w najprostszym przypadku są to (wg Wikipedii):

  • Nakład pracy w osobomiesiącach: E = a*(KDSI)^b, gdzie KDSI ilość linii kodu w tysiącach (1000 linii to 1KDSI)
  • Czas potrzebny do wykonania projektu (time to develop): D = c*E^d
  • Liczba osób przy której projekt będzie najefektywniej realizowany: P = E/D

Natomiast współczynniki a, b, c, d są następujące:

Typ projektu

a

b

c

d

Organic

2.4

1.05

2.5

0.38

Semi-detached

3.0

1.12

2.5

0.35

Embedded

3.6

1.20

2.5

0.32

Typy projektu podane w tabeli, to:

  • Łatwy ("organic"), to projekt, w którym mały zespół posługuje się znanymi narzędziami pracy. Zna on sprzęt i oprogramowanie, z którymi rozwijany projekt będzie reagować. Presja czasu jest mała. Łatwe projekty są wielkości do max. 50 KDSI.
  • Średniej trudności projekt ("semi-detached"), jest to projekt, w którym np. jeden z czynników w projekcie nie jest znany (np. zespół nie zna sprzętu, który przyjdzie mu programować itp.) lub projekt rozmiarami przekracza wielkość projektu typu organic. Zwykle takie projekty są wielkości do 300 KDSI.
  • Trudny ("embedded"), to złożony projekt, w którym zwykle wiele czynników jest nieznanych lub należy uwzględnić szczególne procedury, np. w branży bankowej.

To jednak dopiero podstawy tej metody, które w angielskiej Wikipedii określone są mianem „Basic COCOMO”. Jeżeli wczytamy się dokładniej znajdziemy „Intermediate COCOMO”, w którym dodatkowo analizowane są dodatkowe cztery atrybuty:

  • Produktu
    • Wymagana czytelność stworzonego oprogramowania
    • Wielkość bazy danych
    • Skomplikowanie
  • Sprzętu
    • Ograniczenia związane z wydajnością
    • Czy tworzony system jest systemem czasu rzeczywistego
    • Ograniczenia pamięci
  • Personelu
    • Analiza możliwości
    • Doświadczenie w tworzeniu oprogramowania
    • Doświadczenie w tworzeniu oprogramowania danego typu
    • Doświadczenie w tworzeniu oprogramowania wykorzystującego dane środowisko, sprzęt czy język programowania
  • Projektu
    • Jakie narzędzia są potrzebne?
    • Jakie metody tworzenia oprogramowania będą wykorzystywane?
    • Jaki jest harmonogram projektu?
    • Jaka jest presja czasu?
Każdy z wymienionych atrybutów jest oceniany i w zależności od tego dobierane są wartości współczynników w odpowiednim równaniu.

Zobaczmy pewne przykłady

W ramach przykładów chciałbym wrócić do przytaczanego w poprzedniej części oprogramowania Argo UML, dla którego wyliczenia kosztu autorzy serwisu www.ohloh.net przyjęli następujące współczynniki (http://www.ohloh.net/wiki/project_codebase_cost): a=2.4 oraz b=1.05, dodatkowo dla płacy 55000$ rocznie wyszło im:

Wróćmy teraz do kalkulatora dostępnego pod adresem: http://www.cms4site.ru/utility.php?ecur=1.12&eafcur=1&utility=cocomoii&sloc=2%2C300&pph=50, tutaj dodatkowo oprócz podawanej liczby linii oraz kosztu roboczogodziny, wybieramy jeszcze współczynniki, których wartości zależą od poziomu skomplikowania projektu oraz poziomu zaawansowania grupy tworzącej oprogramowanie (przykład na rysunku poniżej).

Podsumowanie

W podsumowaniu chciałbym się zająć jeszcze jednym współczynnikiem, o którym nie wspominałem, a właśnie on ma znaczący wpływ na ostateczny koszt, a który trzeba uzależnić od języka programowania, umiejętności programisty, itp.… to oczywiście płaca. To właśnie tutaj można uwzględnić dodatkowe czynniki nie wymienione wcześniej, gdyż tak naprawdę opisywana wyżej metoda służy przede wszystkim do oszacowania czasu, który należy poświęcić (lub który poświęcono) na dany projekt.

Na samym końcu chciałbym jeszcze wspomnieć o jednej sprawie, mianowicie podane równania są prawidłowe, gdy ilość linii kodu w ocenianym oprogramowaniu wynosi kilka tysięcy (np. wspominany kalkulator wymaga minimalnej wartości 2300 linii kodu). Dlatego moja sugestia, co do liczenia ilości linii we wczorajszym programiku i szacowania jego kosztu jest bez sensu, ale mam nadzieję, że ten żart wam się spodobał.

piątek, 1 maja 2009

Ile powinno kosztować oprogramowanie?

Na pytanie zadane w tytule chyba najłatwiej byłoby odpowiedzieć:

Tyle, ile ktoś jest w stanie za nie zapłacić.

Jest to jednak odpowiedź jak w dowcipie z matematykiem:

Balonem na wycieczkę wybrali się fizyk z chemikiem, jednak w wyniku zażartej dyskusji na temat wyższości jednej nauki nad drugą, zgubili się. Lecieli tak już kilka godzin nie wiedząc gdzie są, wreszcie zobaczyli na ziemi człowieka. Postanowili się więc zapytać:
- Panie! Gdzie jesteśmy?
Po pewnej chwili padła odpowiedź:
- W balonie!
Na to fizyk do chemika:
- mmhm... Ten który nam odpowiedział to musi być matematykiem, odpowiedź jest w stu procentach przemyślana, w stu procentach prawidłowa i ... w stu procentach niepotrzebna!

Nie chciałbym więc, by mój artykuł został uznany za nie potrzebny i nie odpowiadający na postawione pytania. Dodatkowym utrudnieniem jest fakt, że zadane pytanie jest mało precyzyjne i trudno stwierdzić jakiej odpowiedzi może oczekiwać je zadający...

Zastanówmy się więc z jakimi kosztami możemy mieć do czynienia. Może to być koszt licencji jaka ma być udzielona kupującemu lub koszt wytworzenia oprogramowania. W pierwszym przypadku należy zrobić badania rynku, znaleźć podobne produkty, spróbować ocenić ile klient jest w stanie zapłacić, na podstawie tych informacji można oszacować cenę licencji na użytkowanie. Co jednak należy zrobić gdy chcemy sprzedać kod źródłowy, pomysł i licencję na dalszy rozwój tego oprogramowania? Wtedy znaczącym elementem wyceny jest oszacowanie kosztów wytworzenia oprogramowania. Jak to zrobić? Wydawałoby się to na pierwszy rzut oka proste - analizujemy ile przepracowaliśmy (my lub nasz team) nad tym oprogramowaniem: ile godzin, dni, czy miesięcy, mnożymy to przez pewną stawkę i mamy koszt. Takie szacowanie może być jednak trudne, zwłaszcza gdy tworzenie tego oprogramowania było dodatkowym zajęciem, skąd więc wziąć choćby przybliżony czas poświęcony na wytworzenie. Poza tym jak przekonać potencjalnego kupca, że to oprogramowanie tyle kosztuje?

Tutaj z pomocą przychodzą odpowiednie metody wyceny (to dobrze, że już ktoś przed nami miał taki problem ;) ). Jedną z tych metod jest COCOMO lub COCOMO II (COnstructive COst MOdel II) (ew. informacje w polskiej Wikipedii dostępne są tutaj: http://pl.wikipedia.org/wiki/COCOMO). Spotkałem się z wykorzystaniem tej metodologii na stronie udostępniające open source'owe oprogramowanie Argo UML, którego kod źródłowy (ponad 600 tys linii w Javie) został oszacowany na ponad 9 mln. dolarów. Ciekawe wyliczenie ;).

Jak posługiwać się więc tą metodologią? Najważniejszym jest fakt określenia ile linii kodu ma nasze oprogramowanie, później z wykorzystaniem pewnych równań i współczynników (takich jak komplikacja projektu, zaawansowanie grupy programistycznej, kosztu roboczo-godziny) wyliczamy ostateczny koszt.

W wyliczeniach może pomóc nam kalkulator, np. dostępny tutaj: http://www.cms4site.ru/utility.php?utility=cocomoii.

Co sądzicie o tej metodologii? (zapraszam do dyskusji przy pomocy komentarzy)

Zapraszam do przeczytania kontynuacji tego artykułu.

Na koniec chciałbym zadać jeszcze jedno pytanie: Jak wyznaczyć ilość linii w oprogramowaniu? (często jest to o tyle trudne, że trzeba zliczyć dane pochodzące z kilkuset plików).

Moją propozycją jest poniższy kod napisany w C# (domyślnie zlicza tylko pliki *.CS ale modyfikacja tego jest bardzo prosta). Jak go używać? Wystarczy tylko wskazać katalog w którym to oprogramowanie znajdzie wszystkie pliki *.CS i policzy w nich linie.

W ramach ćwiczenia zapraszam do policzenia linii w poniższym kodzie i wyznaczenie jego kosztu. Ja wolę nie liczyć, bo się jeszcze okaże, że rozrzutny jestem ;).

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Windows.Forms;

namespace CodeBaseCosts
{
  class Program
  {
    [STAThread]
    static void Main( string[] args )
    {
      FolderBrowserDialog folderBrowserDialog1 = new FolderBrowserDialog();
      if ( folderBrowserDialog1.ShowDialog() == DialogResult.OK )
      {
        ulong count = CountLines( folderBrowserDialog1.SelectedPath, ".cs" );
        Console.WriteLine( "directory:{0} - lines={1}", folderBrowserDialog1.SelectedPath, count );
      }
      Console.WriteLine( "end" );
      Console.ReadLine();
    }
    private static ulong CountLines( string directoryPath, string extension )
    {
      ulong count = 0;
      try
      {
        Console.Write( "." ); // progress bar ;)
        // 1. get the information of the directory
        DirectoryInfo directoryInfo = new DirectoryInfo( directoryPath );
        // 2. get content of the directory and go through each subdirectory
        foreach ( DirectoryInfo d in directoryInfo.GetDirectories() )
        {
          count += CountLines( d.FullName, extension );
        }
        //3. loop through each file in the directory, and add lines
        foreach ( FileInfo f in directoryInfo.GetFiles() )
        {
          if ( f.Extension == extension )
            using ( var reader = File.OpenText( f.FullName ) )
            {
              while ( reader.ReadLine() != null )
              {
                count++;
              }
            }
        }
      }
      catch
      {
        //it is possible that we receive an exception (e.g. directory cannot be accessed due to access privilidges
        // we do nothing, we just skip this node.
      }
      Console.WriteLine();
      return count;
    }
  }
}

Posty powiązane / Related posts