BETA
Aby się zalogować, najpiew wybierz portal.
Aby się zarejestrować, najpiew wybierz portal.
Podaj słowa kluczowe
Słowa kluczowe muszą mieć co najmniej 3 sąsiadujące znaki alfanumeryczne
Pole zawiera niedozwolone znaki

Baza wiedzy











Serializacja danych w Compact Framework 2.0

16-05-2007 07:38 | User 84677
Poniższy artykuł przedstawia serializację danych w Compact Framework 2.0 przy użyciu biblioteki CompactFormatter.Autor świadomie prowadzi Czytelnika za rękę, tak aby jak najmniej musiał błądzić podczas samodzielnej implementacji. Artykuł ten nie ma za zadanie przekazać wiadomości technicznych na temat serializacji – jest raczej małym poradnikiem – How To Do, dla osób które szukają konkretnego rozwiązania. CompactFormatter to biblioteka, która z pewnością powinna zainteresować każdego, kto pragn

Serializacja danych w Compact Framework 2.0

Streszczenie

 

Poniższy artykuł przedstawia serializację danych w Compact Framework 2.0 przy użyciu biblioteki CompactFormatter.

Autor świadomie prowadzi Czytelnika za rękę, tak aby jak najmniej musiał błądzić podczas samodzielnej implementacji. Artykuł ten nie ma za zadanie przekazać wiadomości technicznych na temat serializacji – jest raczej małym poradnikiem – How To Do, dla osób które szukają konkretnego rozwiązania.

  CompactFormatter to biblioteka, która z pewnością powinna zainteresować każdego, kto pragnie serializować dane.
 Nie tylko w Compact Framework.

 

Główne zalety stosowania CompactFormatter’a

- jest szybszy niż BinaryFormatter

- mniej nadmiarowych danych podczas serializacji

- w pełni napisany w kodzie zarządzalnym .Net

- interfejs bardzo podobny do formaterów Binary/SOAP używanych w .Net

- można stosować we wszystkich framework’ach

 

Pomimo, że z tego co widać na głównej stronie biblioteki (http://www.freewebs.com/compactFormatter/News.htm) prace nad CompactFormatter’em zostały przerwane, myślę że warto zbliżyć się do niej i poznać ją… a nóż ktoś z was pobierze kod źródłowy i będzie rozwijał dalej to piękne dzieło!

 

Przebieg ćwiczenia

  1. Utworzenie aplikacji Device Application
  2. Dodanie do solucji projektu bilblioteki w której znajdują się klasy zdolne do serializacji przy użyciu CompactFormatter’a.
  3. Zserializowanie obiektu i zapis do pliku.
  4. Utworzenie aplikacji Windows Application.
  5. Dodanie biblioteki ( jej dll’ki ) do projektu Windows Application.
  6. Wykorzystanie tej biblioteki do deserializacji obiektu z pliku.

 

Wstęp

Z pewnością nie jeden z was spotkał się z problemem serializacji po stronie Compact Framework 2.0. Niestety Compact Framework 2.0 nie udostępnia nam mechanizmów do binarnej serializacji ( http://www.eggheadcafe.com/articles/20040311.asp ). Do dyspozycji pozostaje XMLSerializer, który jest wolny(zasobożerny) co można przeczytać na głównej stronie zespołu rozwijającego Compact Framework 2.0 (http://blogs.msdn.com/netcfteam/archive/2005/05/04/414820.aspx )

 

Z pomocą przychodzi CompactFormatter – opensource’owa biblioteka napisana dla nas wszystkich, która pozwala zserializować dowolny obiekt i odczytać go po stronie aplikacji w „pełnym” Framework’u. Ten artykuł omówi, w jaki sposób zserializować dowolny obiekt przy użyciu tej biblioteki, zapisać go do pliku a następnie odczytać ten plik za pomocą aplikacji napisanej w pełnym Framework’u.

Do dzieła!

Ad1.

Tworzymy aplikację Device Application. Film

Ad2.

  • Dodajemy do istniejącej solucji projekt o nazwie TestModel FILM
  • Zmieniamy nazwę klasy na Description. Klasa, której obiekt będziemy serializować to zestaw pewnych parametrów innego obiektu.

screen1.jpg
 

Jak widać mamy trzy pola, każde z nich jest słownikiem. Kod tej klasy obejrzenia znajduje się w pliku zawierającym źródła rozwiązania dołączonym do ćwiczenia.

Tak utworzoną klasę chcemy zserializować.

Aby to uczynić musimy przejść przez dwa etapy:

·         dostosować bibliotekę do serializacji przy użyciu CompactFormatter’a

·         odpowiednio opakować ją, aby serializacja była intuicyjna i łatwa.

Krok 1.

Dołączamy bibliotekę CompactFormatter’a do projektu biblioteki.

Aby to zrobić pobieramy ją z Compact Formatter

Celowo nie dodaję nowego „using’a” aby można było z kodu dostrzec skąd pochodzi każdy interfejs oraz zmienna pobierana z CompactFormatter’a.

 

Krok 2.

Dodajemy nad naszą klasą parametr:

[CompactFormatter.Attributes.Serializable(Custom = true)]

- który mówi, że sami będziemy definiować sposób serializacji naszej klasy.

 

Krok 3.

Nasza klasa musi implementować interfejs CompactFormatter.Interfaces.ICSerializable, dlatego dodajemy odpowiedni wpis do nagłówka klasy.

Po kliknięciu prawym przyciskiem myszki na interfejsie wybieramy „implement interface”. Otrzymujemy dzięki temu dwie metody.

 

[Kod C#]

        #region ICSerializable Members

 

        public void ReceiveObjectData(CompactFormatter.CompactFormatter parent, System.IO.Stream stream)

        {

            throw new Exception("The method or operation is not implemented.");

        }

 

        public void SendObjectData(CompactFormatter.CompactFormatter parent, System.IO.Stream stream)

        {

            throw new Exception("The method or operation is not implemented.");

        }

 

        #endregion

 

[Kod C#]

ReciveObjectData jest to metoda wykorzystywana przez silnik CompactFormattera do deseralizowania ciągu bajtów na pola klasy. Używana jest podczas procesu deserializacji. W strumieniu, który otrzymujemy znajduje się ciąg bajtów do zdeserializowania.

 

SendObjectData używana jest podczas procesu serializacji. W strumień, który otrzymujemy, jako argument wpisujemy zamienione na bajty pola naszej klasy.

 

Te dwie powyższe metody stanowią rdzeń naszego mechanizmu serializacji. To tutaj określamy, co wysyłamy i czytamy. Logiczne jest, że to samo, co wysłaliśmy również musimy później odczytać w procesie deserializacji. Nie można dopuścić do sytuacji, w której przykładowo podczas serializacji zapisujemy wszystkie pola, a podczas deserializacji czytamy tylko jedno z nich. Spowoduje to prędzej czy później błąd gdyż ze złego miejsca w strumieniu będziemy czytać złe dane.

 

Krok 4.

Implementacja metody SendObjectData.

Popatrzmy na pola, które chcemy zserializować – mamy trzy słowniki. Jak je teraz umieścić w strumieniu bajtów?

Wykorzystujemy do tego obiekt klasy BinaryWriter z przestrzeni nazw System.IO.

Aby móc odtworzyć później stan słownika należy zapisać do strumienia ilość elementów znajdujących się w nim. Dzięki temu podczas procesu odczytu mamy od razu warunek stopu pętli czytającej ze strumienia kolejne elementy słownika.

Tworzymy więc obiekt klasy BinaryWriter, który jest podłączony do strumienia wyjściowego.

[Kod C#]

        public void SendObjectData(CompactFormatter.CompactFormatter parent, System.IO.Stream stream)

        {

            BinaryWriter bW = newBinaryWriter(stream);

 

     }

[Kod C#]

 

Teraz trzeba wykorzystać go do wysłania słowników. Zacznijmy od słownika files.

 

[Kod C#]

 

            if (files != null)

            {

                bW.Write(files.Count);

                foreach (KeyValuePair<string, Guid> file in files)

                {

                    bW.Write(file.Key);

                    bW.Write(file.Value.ToString());

                }

            }

            else bW.Write((Int32)0);

[Kod C#]

Należy zawsze sprawdzać czy aby obiekt nie istnieje. Następnie wpisujemy rozmiar słownika (na przyszły użytek). Potem pętla foreach wpisuje nam kolejne elementy słownika. W przypadku braku słownika, wpisujemy do strumienia wartość zero, która mówi nam podczas procesu deserializacji, że mamy do czynienia z pustym słownikiem. Rzutowanie na Int32wynika jedynie z faktu, że podczas deserializacji wybieramy jaki rozmiar Integer’a jest odczytywany. Lepiej mieć pewność, że jest to Int32 zamiast przez przypadek odczytać Int16 i mieć inną wartość w liczniku.

 

Podobny algorytm stosujemy dla pozostałych dwóch słowników.

 

Krok 5.

Implementacja metody ReciveObjectData.

Jest to odwrotność metody SendObjectData. Ma ona za zadanie zdeserializować to co zserializowaliśmy w SendObjectData. Ważne jest abyśmy zachowali tę samą kolejność, co w serializacji – najpierw słownik files, potem icons a na końcu grades. Do odczytu ze strumienia posłuży nam klasa BinaryReader

 

[Kod C#]

            BinaryReader bR = newBinaryReader(stream);

            int filesDictCounter = bR.ReadInt32();

            if (filesDictCounter > 0)

            {

                string key;

                Guid guid;

                files = newDictionary<string, Guid>();

                for (int i = 0; i < filesDictCounter; i++)

                {

                    key = bR.ReadString();

                    guid = newGuid(bR.ReadString());

                    files.Add(key, guid);

                }

         }

[Kod C#]

Powyższy kod ilustruje mechanizm deserializacji. Najpierw pobieramy licznik elementów w słowniku files. Jeśli jest różny od zera oznacza to, że słownik nie był pusty i można go stworzyć. Następnie w pętli for pobieramy kolejne elementy ze strumienia w tej samej kolejności, w jakiej przedtem ( w metodzie SendObjectData) serializowaliśmy ten słownik. Ten sam mechanizm stosujemy do pozostałych dwóch słowników.

 

Krok 6.

Mamy nasz rdzeń. Teraz trzeba go ładnie opakować, aby serializacja i deserializacja była jak najprostsza dla użytkownika naszej biblioteki. Standardowo, aby zserializować obiekt należy utworzyć zawsze obiekt klasy CompactFormatter następnie odpowiednio go użyć.

[Kod C#]

 

            FileStream file = newFileStream(@"Storage Card\serialization_test.bin", FileMode.Create);

            TestModel.Description obj = new TestModel.Description();

            obj.Files = newDictionary<string, Guid>();

            obj.Files.Add("file1", Guid.NewGuid());

            obj.Files.Add("file2", Guid.NewGuid());

            CompactFormatter.CompactFormatter cf = new CompactFormatter.CompactFormatter();

         cf.Serialize(file, obj);

 

[Kod C#]

Powyższy kod tworzy plik na urządzeniu mobilnym. Następnie tworzy obiekt klasy Description. Wypełniamy go przykładowymi danymi i serializujemy.

W pliku serialization_test.bin mamy następujące dane:

 

$   T e s t M o d e l %*   T e s t M o d e l . D e s c r i p t i o n ! [1]   file1$f747b5bc-3edb-4484-98ca-e796e4e01648file2$982b5f72-cb5e-4407-ad12-2facb9e5217f     

 

Deserializacja jest bardzo podobna.

[Kod C#]

            CompactFormatter.CompactFormatter cf = new CompactFormatter.CompactFormatter();

         Object obj = cf.Deserialize(stream);

[Kod C#]

Jako argument podajemy strumień bajtów, który poddajemy deserializacji. Funkcja Deserialize zwraca obiekt, który następnie rzutujemy na to, czego się spodziewamy po deserializacji.

 

Zauważyliście pewnie, że aby serializować i deserializować trzeba zawsze utworzyć obiekt CompactFormatter. Aby, nasza biblioteka była ładna, zróbmy klasę abstrakcyjną, która ukryje przed użytkownikiem mechanizm tworzenia obiektu CompactFormatter’a.

 

screen5.jpg

Postać klasy Serializator

[Kod C#]

    public abstract class Serializator

    {

        public static object Deserialize(System.IO.Stream stream)

        {

            CompactFormatter.CompactFormatter cf = new CompactFormatter.CompactFormatter();

            return cf.Deserialize(stream);

        }

 

        public static void Serialize(System.IO.Stream stream, object Value)

        {

            CompactFormatter.CompactFormatter cf = new CompactFormatter.CompactFormatter();

            cf.Serialize(stream, Value);

        }

    }

[Kod C#]

 

Nasza klasa Description dziedziczy po Serializator. Dzięki temu nie musimy ujawniać w kodzie, że korzystamy z obiektu CompactFormatter’a.

Porównanie

Bez klasy abstrakcyjnej

Z klasą abstrakcyjną

CompactFormatter.CompactFormatter cf = new CompactFormatter.CompactFormatter();

cf.Serialize(streamIn, obj);

CompactFormatter.CompactFormatter cf2 = new CompactFormatter.CompactFormatter();

TestModel.Description objRecived = (TestModel.Description)cf2.Deserialize(streamOut);

 

TestModel.Description.Serialize(streamIn, obj);
TestModel.Description objRecived =

(TestModel.Description)TestModel.Description.Deserialize

(streamOut);

 

Dzięki zastosowaniu klasy abstrakcyjnej, użytkownik nie musi wiedzieć, że korzysta z CompactFormatter’a a mechanizm serializacji jest całkowicie zaszyty wewnątrz klasy.

 

Ad 3.

Dla tych, którzy nie wiedzą gdzie jesteśmy – zrobiliśmy właśnie bibliotekę, która ma zaimplementowany mechanizm serializacji i jest gotowa do wykorzystania. Teraz trzeba ją wykorzystać w projekcie.

screen4.jpg

 

Krok 2.

Po dodaniu referencji do bibliotek możemy przystąpić do pisania kodu.

[Kod C#]

            FileStream fileWrite = newFileStream(@"Storage Card\serialization_test.bin", FileMode.Create);

            TestModel.Description obj = new TestModel.Description();

            obj.Files = newDictionary<string, Guid>();

            obj.Files.Add("file1", Guid.NewGuid());

            obj.Files.Add("file2", Guid.NewGuid());

            obj.Grades = newDictionary<byte, int>();

            obj.Grades.Add(5, 1);

            obj.Grades.Add(4, 3);

            obj.Grades.Add(3, 1);

            obj.Grades.Add(2, 1);

            obj.Icons = newDictionary<string, Guid>();

            obj.Icons.Add("icon1", Guid.NewGuid());

            obj.Icons.Add("icon2", Guid.NewGuid());

            TestModel.Description.Serialize(fileWrite, obj);

            fileWrite.Close();

            FileStream fileRead = newFileStream(@"Storage Card\serialization_test.bin", FileMode.Open);

TestModel.Description objReaded = (TestModel.Description)TestModel.Description.Deserialize(fileRead);

[Kod C#]

Powyższy kod tworzy plik o nazwie serialization_test.bin w folderze Storage Card który tak naprawdę jest folderem współdzielonym z naszym desktopem. Dzięki temu plik zapisany w emulatorze Pocket PC 2003 zostanie zapisany na nasz dysk twardy. Jak skonfigurować folder współdzielony? FILM

Tworzymy następnie obiekt klasy Description i wypełniamy go danymi. Kolejnym krokiem jest serializacja a następnie deserializacja. W objReaded mamy obiekt zdeserializowany z pliku serialization_test.bin

 

Ad 4.

Teraz utworzymy aplikację w pełnym Frameworku aby przy jej użyciu pokazać jak odczytać z pliku zserializowane dane za pomocą CompactFormatter’a. FILM

 

Ad 5.

Dodanie do naszego projektu biblioteki CompactFormatter oraz TestModel. FILM

 

Ad 6.

Teraz pozostaje nam tylko napisać parę linii odpowiedzialnych za zdeserializowanie danych z pliku serialization_test.bin

[Kod C#]

            FileStream fileRead = newFileStream(@"C:\serialization_test.bin", FileMode.Open);

 TestModel.Description objReaded = (TestModel.Description)TestModel.Description.Deserialize(fileRead);

[Kod C#]

Jak widzicie powyższy kod jest identyczny z tym stosowanym po stronie Compact Framework’a i taka jest w istocie. Mechanizm tworzenia i pracy z CompactFormatter’em jest taki sam po obu stronach Framework’a.

Aby jeszcze przekonać Was do tego, że naprawdę odczytaliśmy zdeserializowane dane dodam do projektu jednego ListBox’a i wyświetlę w nim zawartość wszystkich słowników z deserializowanego obiektu.

Po dodaniu kilku pętli oczom ukarze się Wam taki obraz :

screen3.png

Wyjątki

Czyli to, co może spotkać każdego z Was podczas pisania bibliotek w CompactFormatter.

 

"Unable to serialize TestModel.Description type, it's not marked with Serializable attribute and no surrogate or overriders are registered for it"

     Oznacza to, że nie masz nad klasą którą serializujesz parametru:

     [CompactFormatter.Attributes.Serializable(Custom = true)]

"FormatException"

     Wyszedłeś poza strumień danych podczas deserializacji. Za dużo chcesz odczytać a za mało zapisałeś
     (podczas serializacji nie starczyło miejsca lub źle jest zaimplementowana metoda SendObjectData)

"No parameterless constructor defined for this object"

Klasa, której obiekt chcesz zserializować nie zawiera konstruktora bezargumentowego. Jeśli nie tworzysz żadnych konstruktorów, automatycznie zostanie wygenerowany konstruktor bezargumentowy. Gorzej, jeśli zrobisz konstruktor z określoną listą argumentów a tego bez argumentowego nie utworzysz. Wówczas podczas deserializacji CompactFormatter nie ma jak utworzyć instancji naszej klasy ( zanim wywoła metodę ReciveObjectData ) gdyż nie wie jakie argumenty przekazać do konstruktora. Lekarstwo – jeśli tworzysz konstruktor z argumentami, utwórz również konstruktor bezargumentowy.

     Zakończenie

Powyższy artykuł miał na celu przybliżyć wam mechanizm serializacji za pomocą CompactFormatter’a. Artykuł ten z pewnością nie wyczerpuje tematyki serializacji w CompactFramework’u – to nie był jego cel. Celem było zaprezentować rozwiązanie, które jest szybkie proste i wygodne. Mam nadzieję, że to mi się udało.

Załączniki:

Komentarze 0

pkt.

Zaloguj się lub Zarejestruj się aby wykonać tę czynność.