Jakub Florczyk - Blog o programowaniu .NET i Android

Programista praktyczny

Month: June, 2009

Konkurs z Pda.pl

Dziś ruszył konkurs na stronach Pda.pl. Do wygrania TomTom XL z IQ Route 2.0. Zasady są bardzo proste, wystarczy opisać swój pomysł na program na Windows Mobile Professional. Szczegóły konkursu na stronach Pda.pl.

Konkurs

Wydajność GDI w .NET Compact Framework – rysowanie napisów

Treść tego wpisu mógłbym zawrzeć w jednej sentencji: “nie używaj DrawString” i zasadniczo mógłbym zakończyć, ale postaram się udowodnić dlaczego.

Zacznijmy od testów:

e.Graphics.MeasureString("Testowy string", base.Font);
e.Graphics.DrawString("Testowy string", base.Font, new SolidBrush(Color.Black), 0, 0);
e.Graphics.DrawImage(img, 0, 0);

Trzy metody; pierwsza mierzy nasz napis, druga rysuje a trzecia jest dodatkowo dla porównania, ale do niej zaraz wrócimy.
Uzyskane wyniki w trzech próbach przedstawiają się tak (ticks | miliseconds):

MS – MeasureString
DS – DrawString
DI – DrawImage

MS: 647855 | 37
DS: 862750 | 34
DI: 26575 | 1

MS: 2483094 | 101
DS: 705654 | 28
DI: 310075 | 12

MS: 650258 | 36
DS: 880659 | 34
DI: 43816 | 1

Wyniki są rozbieżne w różnych próbach ale nie to jest ważne, chodzi o ogólne proporcje między nimi. Na pierwszy rzut oka widać od razu, że najwolniejsza metoda odpowiada za pomiar napisów, druga co do powolności jest metoda odpowiadająca za rysowanie.

Wiemy już co jest wolne ale jak wyeliminować te metody w naszych projektach na Windows Mobile. Odpowiedź jest dziecinnie prosta, cache i jeszcze raz cache. Najbardziej trywialny sposób to jest dekorator który będzie zawierał dodatkowe grafiki napisów, np coś takiego:

internal class MessageDecorator : Message, ICloneable
{
    private Image _bodyImage;

    internal Image BodyImage
    {
        get { return _bodyImage; }
        set { _bodyImage = value; }
    }
}

Jeżeli BodyImage jest nullem przy pomocy prostego helpera może stworzyć sobie cache:

internal static Bitmap Draw(this String s, Brush background, Font font, Brush brush, RectangleF rectangle)
{
    Bitmap buffer = new Bitmap((int)rectangle.Width, (int)rectangle.Height);

    using (Graphics g = Graphics.FromImage(buffer))
    {
        g.FillRectangle(background, 0, 0, buffer.Width, buffer.Height);
        g.DrawString(s, font, brush, new RectangleF(0, 0, buffer.Width, buffer.Height));
    }

    return buffer;
}

W przypadku gdy nie jest nullem grafikę body wrysowujemy w miejsce gdzie powinna się znajdować przy pomocy DrawImage(Image, x, y). Ważne jest to aby nie używać innych metod abyśmy czasem nie skalowali naszych napisów. Dlatego cache tworzymy w skali 1:1.

Na koniec pamiętajmy jeszcze o skasowaniu cachy gdy na telefonie zmieni się orientacja ekranu.

protected override void OnResize(EventArgs e)
{
    if (_messages != null)
    {
        foreach (MessageDecorator message in _messages)
        {
            message.BodyImage = null;
        }

        Refresh();
    }

    base.OnResize(e);
}

Kolejne odpalenie OnPaint spowoduje przerysowanie napisów w nowych rozmiarach. Oczywiście kasowanie ma tylko sens, jeżeli napisy są dokładnie wpasowywane w wielkości ekranu. W przypadku pojedynczych, jednowierszowych napisów nie ma to sensu.

RilNET – Radio Interface Layer (RIL) .NET wrapper

Długo się zanosiłem z wrapperem na RIL-a i zawsze mi brakowało czasu, ale może od początku…

Dla niezorientowanych Radio Interface Layer jest warstwą łączącą hardware telefonu z oprogramowaniem. Pisząc hardware mam na myśli część telefonu Windows Mobile odpowiedzialną za wykonywanie połączeń i transfer danych do stacji komórkowych (Radio). Z punktu widzenia programisty nic poniżej RIL-a już nie ma poza samym sprzętem. Więcej o samym systemie można poczytać na http://msdn.microsoft.com/en-us/library/aa920475.aspx.

Poniżej schemat architektury w Windows Mobile (RIL występuje także w Windows CE):

RIL WM Architecture

Jeżeli czytasz ten tekst pewnie zadasz pytanie “po co to komu?”. Z punktu widzenia CF mamy dostępne wrappery Microsoftu pozwalające na wykonywanie połączeń, wysyłanie SMS-ów, obsługę książki telefonicznej itp. Z punktu widzenia telefonu posiadamy jeszcze Tapi oraz ExTapi. A więc po co?

Otóż tak jak napisałem wcześniej RIL jest najniższą warstwą a więc pozwala na rzeczy na które wyższe warstwy nigdy nie zezwolą. Przy użyciu RIL-a możemy pobrać informację o  wieży komórkowej, możemy zarządzać kartą SIM, możemy wysyłać komendy AT, itp.

RIL pozwala na dwa sposoby odwołań:

  • Podłączenie się do systemu notyfikacji, przez co dostaniemy każde zdarzenie które wygeneruje Radio
  • Wysyłanie zapytań do systemu, przez co możemy uzyskać dowolną informację z Radio

Poniżej prosty przykład użycia:

int hrCo, hrCti, hrEi;

int hr = Ril.Initialize(1, new RILRESULTCALLBACK(RilResultCallback), new RILNOTIFYCALLBACK(RilNotifyCallback), RIL_NCLASS.ALL, 0, out hRil);

// you can bind only to RilResultCallback
// int hr = Ril.Initialize(1, new RILRESULTCALLBACK(RilResultCallback), null, 0, 0, out hRil);
// or only to or RilNotifyCallback
// int hr = Ril.Initialize(1, null, new RILNOTIFYCALLBACK(RilNotifyCallback), RIL_NCLASS.ALL, 0, out hRil);

hrCti = Ril.GetCellTowerInfo(hRil);
hrCo = Ril.GetCurrentOperator(hRil, RIL_OPFORMAT.LONG);
hrEi = Ril.GetEquipmentInfo(hRil);
hr = Ril.Deinitialize(hRil);

// after call some method remember to save returned HRESULT and compare it to hrCmdID in RilResultCallback

private void RilResultCallback(
    uint dwCode,
    int hrCmdID,
    IntPtr lpData,
    uint cbData,
    uint dwParam)
{
    if (hrCo == hrCmdID)
    {
        //Ril.GetCurrentOperator
        RILOPERATORNAMES pOperatorNames = (RILOPERATORNAMES)Marshal.PtrToStructure(lpData, typeof(RILOPERATORNAMES));

        if ((pOperatorNames.dwParams & RIL_PARAM_ON.LONGNAME) == RIL_PARAM_ON.LONGNAME) // check that LongName member is valid
        {
            string longName = Encoding.ASCII.GetString(pOperatorNames.szLongName, 0, pOperatorNames.szLongName.Length).Replace("\0", "");
        }
    }

    if (hrCti == hrCmdID)
    {
        //Ril.GetCellTowerInfo
        RILCELLTOWERINFO pCellTowerInfo = (RILCELLTOWERINFO)Marshal.PtrToStructure(lpData, typeof(RILCELLTOWERINFO));
    }

    if (hrEi == hrCmdID)
    {
        //Ril.GetEquipmentInfo
        RILEQUIPMENTINFO pEquipmentInfo = (RILEQUIPMENTINFO)Marshal.PtrToStructure(lpData, typeof(RILEQUIPMENTINFO));

        if ((pEquipmentInfo.dwParams & RIL_PARAM_EI.MANUFACTURER) == RIL_PARAM_EI.MANUFACTURER)
        {
            string manufacturer = Encoding.ASCII.GetString(pEquipmentInfo.szManufacturer, 0, pEquipmentInfo.szManufacturer.Length).Replace("\0", "");
        }

        if ((pEquipmentInfo.dwParams & RIL_PARAM_EI.MODEL) == RIL_PARAM_EI.MODEL)
        {
            string model = Encoding.ASCII.GetString(pEquipmentInfo.szModel, 0, pEquipmentInfo.szModel.Length).Replace("\0", "");
        }

        if ((pEquipmentInfo.dwParams & RIL_PARAM_EI.REVISION) == RIL_PARAM_EI.REVISION)
        {
            string revision = Encoding.ASCII.GetString(pEquipmentInfo.szRevision, 0, pEquipmentInfo.szRevision.Length).Replace("\0", "");
        }

        if ((pEquipmentInfo.dwParams & RIL_PARAM_EI.SERIALNUMBER) == RIL_PARAM_EI.SERIALNUMBER)
        {
            string serialNumber = Encoding.ASCII.GetString(pEquipmentInfo.szSerialNumber, 0, pEquipmentInfo.szSerialNumber.Length).Replace("\0", "");
        }
    }
}

public void RilNotifyCallback(
    uint dwCode,
    IntPtr lpData,
    uint cbData,
    uint dwParam)
{
    RIL_NCLASS dwClass = ((RIL_NCLASS)dwCode & RIL_NCLASS.ALL);

    Debug.WriteLine("NotifyCallback: " + dwClass.ToString());

    switch ((RIL_NOTIFY_RADIOSTATE)dwCode)
    {
        case RIL_NOTIFY_RADIOSTATE.RADIOEQUIPMENTSTATECHANGED:
        {
            RILEQUIPMENTSTATE pState = (RILEQUIPMENTSTATE)Marshal.PtrToStructure(lpData, typeof(RILEQUIPMENTSTATE));

            Debug.WriteLine(String.Format("Radio Support: {0}; equipment State: {1}; ready State: {2}",
                           pState.dwRadioSupport, pState.dwEqState, pState.dwReadyState));

            break;
        }
        case RIL_NOTIFY_RADIOSTATE.RADIOPRESENCECHANGED:
        {
            RIL_RADIOPRESENCE dwPresence = (RIL_RADIOPRESENCE)Marshal.ReadInt32(lpData);

            switch (dwPresence)
            {
                case RIL_RADIOPRESENCE.NOTPRESENT:
                    Debug.WriteLine("Radio module is not present");
                    break;
                case RIL_RADIOPRESENCE.PRESENT:
                    Debug.WriteLine("Radio module is present");
                    break;
            }

            break;
        }
    }
}

W przypadku gdy podłączamy się do notyfikacji musimy określić jakie notyfikacje nas interesują oraz musimy zdefiniować funkcję która będzie je odbierać. Po odebraniu zdarzenia musimy przy pomocy enumeratorów “rozwiązać” typ zdarzenia oraz zinterpretować dane. System jest tak zorganizowany, że z danymi przychodzi wskaźnik który w większości przypadków wskazuje na strukturę w której dodatkowo są zapisane informacje przy pomocy “flagowego” enumeratora o poprawności poszczególnych pól w tej strukturze.

W przypadku gdy wysyłamy zapytania po odwołaniu do funkcji musimy zapamiętać uchwyt, który funkcja zwraca a następnie w metodzie obsługującej musimy go porównać z uchwytem który uzyskujemy od urządzenia. Po tej operacji wskaźnik możemy skonwertować do odpowiedniego typu danych i podobnie jak poprzednio, odpowiednie pole wskazuje poprawność poszczególnych pól w strukturze.

Dla wszystkich osób zainteresowanych określaniem pozycji według stacji nadawczych mam złe wiadomości. Otóż w mojej sieci Orange, system zwraca błąd o braku wsparcia ze strony sieci. Może to wina miejsca w którym mieszkam a może tak jest wszędzie. Jak przetestuje to w kilku lokalizacjach to udostępnie podsumowanie.

Projekt dostępny jest oczywiście na CodePlex RilNET

Nieoficjalny .NET 3.7 z nieoficjalnego Windows Mobile 6.5

Jak podaje pda.pl pewne osoby wydostały z nieoficjalnego Windows Mobile 6.5 nową wersję .NET – 3.7. Niestety wersja 3.7 nie jest jeszcze oficjalna, ale będzie dostępna na pewno w Windows Mobile 6.5. Trudno powiedzieć, czy pojawi się ona w oddzielnej postaci tak, by każdy mógł sobie ją zainstalować.

O nowym .NET już jest głośno na XDA i PPCGeeks. Osoby które go zainstalowały zauważyły przede wszystkim lepszą wydajność. Samą paczkę można pobrać tutaj.

To co mnie najbardziej interesuje to nowe elementy jakie się pojawiły w releasie. Od razu rzuca się w oczy obecność całego pakietu bibliotek OpenNETCF, co wg. mnie jest odrobinę “dziwne”, jednak trudno mi coś więcej napisać bo nie znam źródła ROM-u. Jednak najbardziej ciekawa jest obecność biblioteki TapiLib.dll od OpenNETCF!

Tak czy inaczej po inspekcji Reflectorem nie odnalazłem znaczącej rewolucji. Ucieszyło mnie kilka nowych metod na Graphics-ie, których zawsze mi brakowało w stosunku do wersji desktopowej.

public void DrawString(string s, Font font, Brush brush, RectangleF layoutRectangle)
public void DrawString(string s, Font font, Brush brush, RectangleF layoutRectangle, StringFormat format)

Nie wydaje mi się aby osoby na forach odnalazły jakieś rewolucyjne zmiany w bibliotekach, na pewno będzie w nich sporo kosmetyki. Podsumowując nadal czekamy na .NET 4.0 i oby ziściły się “zapowiedzi” Microsoftu o pełnym wsparciu dla Silverlight 2.0.