2010-10-09

Konwersja pomiędzy double, float, bytes

Kod:

[TestMethod]
public void CheckDF()
{
    // Float

    var bytes = new byte[4];
    var random = new Random();

    for (int i = 0; i < 5000000; i++)
    {
        random.NextBytes(bytes);

        // Convert bytes to float.
        float float_from_bytes = BitConverter.ToSingle(bytes, 0);
                
        // Convert float to bytes.
        var bytes_from_float = BitConverter.GetBytes(float_from_bytes);

        if (!bytes.SequenceEqual(bytes_from_float))
        {
            Assert.IsTrue(Single.IsNaN(float_from_bytes));

            float[] floats = new float[1];
            Buffer.BlockCopy(bytes, 0, floats, 0, 4);

            Assert.IsTrue(Single.IsNaN(floats[0]));

            byte[] bytes_from_floats = new byte[4];
            Buffer.BlockCopy(floats, 0, bytes_from_floats, 0, 4);

            CollectionAssert.AreEqual(bytes, bytes_from_floats);

            Assert.IsFalse(float_from_bytes == floats[0]);

            float f1 = floats[0]; 
            Assert.IsTrue(Single.IsNaN(f1));

            Assert.IsTrue(f1 != float_from_bytes);
            Assert.IsTrue(f1 != floats[0]);
            Assert.IsTrue(float_from_bytes != floats[0]);

            var bytes1 = BitConverter.GetBytes(f1);

            CollectionAssert.AreNotEqual(bytes, bytes1);
            CollectionAssert.AreNotEqual(bytes_from_floats, bytes1);

            float ff1 = f1 + 0.0f;
            float ff2 = float_from_bytes + 0.0f;
            float ff3 = floats[0] + 0.0f;

            Assert.IsTrue(Single.IsNaN(ff1));
            Assert.IsTrue(Single.IsNaN(ff2));
            Assert.IsTrue(Single.IsNaN(ff3));

            byte[] bb1 = BitConverter.GetBytes(ff1);
            byte[] bb2 = BitConverter.GetBytes(ff2);
            byte[] bb3 = BitConverter.GetBytes(ff3);

            CollectionAssert.AreEqual(bb1, bb2);
            CollectionAssert.AreEqual(bb2, bb3);

            CollectionAssert.AreEqual(bytes1, bb1);
        }
    }
}

Istnieje wiele kombinacji bajtów, które tworzą liczbę typu NaN i ta liczba jeśli tylko przejdzie przez rejestr FPU jest zmieniana na jedyną poprawną liczbę NaN. Przykładowo dzieje się tak w linii 35. Podobnie w 47, 48, 49.

Mam system x64. Problem ten występuje zarówno w wersji Release jak i Debug podczas kompilacji do x86. W x64 w linii 35 liczba nie przechodzi przez FPU, więc nie jest ona zmieniana. Ale oczywiście samo dodawanie poprawi ją.

Przy okazji nigdzie nie porównuje liczb NaN ze sobą, gdyż takie porównanie zawsze zwróci false.

Podobnie dzieje się w przypadku liczb double.

Wniosek: tworzenie liczb zmiennoprzecinkowych z losowych sekwencji bajtów nie jest dobrym pomysłem, zwłaszcza jeśli wynikiem są liczby NaN.

Ja tak niestety robiłem i ostatnio się zdziwiłem jak kod testowy algorytmów hashujących, które przyjmują prawie zawsze sekwencję bajtów na wyjście nagle zaczął losowo zwracać błędy.

No i najlepsze ale ciężko mi to potwierdzić. Teraz zaczął zawodzić prawie każdy test (sekwencje bajtów są losowane). Poprzednio, a testy nie zmieniły się może od pół roku nigdy nie było problemów. Ciężko mi to potwierdzić czy coś nie zmieniło się w kompilatorze JIT, może w samej platformie .NET.

No aby tytułowi stało się zadość w kodzie widzimy dwie możliwości jak konwertować pomiędzy tablicą bajtów, a liczbą zmiennoprzecinkową. Mamy do dyspozycji dla pojedynczych liczb BitConverter oraz bardziej właściwy dla tablic Buffer.BlockCopy.

2010-10-06

Mutex with FIFO order and CancellationToken support

This is simple mutex implementation which releasing threads in FIFO manner. Normally it's race. Its also support CancellationToken.

public class QueuedMutex
{
    private Object m_lock = new Object();
    private Queue m_queue = new Queue();
    private bool m_firstGo = false;

    public void WaitOne(CancellationToken a_token)
    {
        ManualResetEvent mre = null;

        lock (m_lock)
        {
            if (m_firstGo)
            {
                mre = new ManualResetEvent(false);
                m_queue.Enqueue(mre);
            }
            else
                m_firstGo = true;
        }

        if (mre != null)
        {
            while (!mre.WaitOne(100))
            {
                if (a_token.IsCancellationRequested)
                {
                    lock (m_lock)
                    {
                        if (mre.WaitOne(0))
                        {
                            ReleaseMutex();
                        }
                        else if (m_queue.Contains(mre))
                        {
                            List list = new List();
                            while (m_queue.Peek() != mre)
                                list.Add(m_queue.Dequeue());
                            m_queue.Dequeue();
                            while (list.Count != 0)
                            {
                                m_queue.Enqueue(list.Last());
                                list.RemoveLast();
                            }
                        }
                    }
                    a_token.ThrowIfCancellationRequested();
                }
            }
        }
    }

    public void ReleaseMutex()
    {
        lock (m_lock)
        {
            if (m_queue.Count != 0)
                m_queue.Dequeue().Set();

            if (m_queue.Count == 0)
                m_firstGo = false;
        }
    }
}

ReadOnlyDictionary

This is simple implementation of read only Dictionary:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace ReadOnlyDictionaryLib
{
    public static class DictionaryExtensions
    {
        public static IDictionary<TKey, TValue> AsReadOnlyx<TKey, TValue>(this IDictionary<TKey, TValue> a_source)
        {
            return new ReadOnlyDictionary<TKey, TValue>(a_source);
        }
    }

    public sealed class ReadOnlyDictionary<TKey, TValue> : IDictionary<TKey, TValue>
    {
        private IDictionary<TKey, TValue> m_dictionary;

        public ReadOnlyDictionary(IDictionary<TKey, TValue> a_source)
        {
            m_dictionary = a_source;
        }

        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
        {
            foreach (var item in m_dictionary)
            {
                yield return item;
            }
        }

        public bool ContainsKey(TKey a_key)
        {
            return m_dictionary.ContainsKey(a_key);
        }

        public bool TryGetValue(TKey a_key, out TValue a_value)
        {
            return m_dictionary.TryGetValue(a_key, out a_value);
        }

        public bool Contains(KeyValuePair<TKey, TValue> a_item)
        {
            return m_dictionary.Contains(a_item);
        }

        public void CopyTo(KeyValuePair<TKey, TValue>[] a_array, int a_arrayIndex)
        {
            m_dictionary.CopyTo(a_array, a_arrayIndex);
        }

        public TValue this[TKey a_key]
        {
            get
            {
                return m_dictionary[a_key];
            }
        }

        public ICollection<TKey> Keys
        {
            get
            {
                return new ReadOnlyCollection<TKey>(new List<TKey>(m_dictionary.Keys));
            }
        }

        public ICollection<TValue> Values
        {
            get
            {
                return new ReadOnlyCollection<TValue>(new List<TValue>(m_dictionary.Values));
            }
        }

        public int Count
        {
            get
            {
                return m_dictionary.Count;
            }
        }

        public bool IsReadOnly
        {
            get 
            { 
                return true; 
            }
        }

        void IDictionary<TKey, TValue>.Add(TKey a_key, TValue a_value)
        {
            throw new NotSupportedException();
        }

        bool IDictionary<TKey, TValue>.Remove(TKey a_key)
        {
            throw new NotSupportedException();
        }

        TValue IDictionary<TKey, TValue>.this[TKey a_key]
        {
            get
            {
                return this[a_key];
            }
            set
            {
                throw new NotSupportedException();
            }
        }

        void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> a_item)
        {
            throw new NotSupportedException();
        }

        bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> a_item)
        {
            throw new NotSupportedException();
        }

        void ICollection<KeyValuePair<TKey, TValue>>.Clear()
        {
            throw new NotSupportedException();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}

2010-07-20

Visual C++ 2010 - wyrażenia Lambda

Wyrażenie lambda w C++ pozwala nam stworzyć obiekt (zmienną), który jest funkcją. Istnieje tutaj duże podobieństwo do wyrażeń Lambda z C#, które są tak naprawdę anonimowymi delegatami, a te są zwykłymi delegatami, które kompilator tworzy podczas generowania kodu. Można to zaobserwować używając narzędzia Reflector. Podobnie jest w wyrażeniami Lambda w C++. każde wyrażenie lambda jest tak naprawdę strukturą, która przeładowuje operator(). Mając do dyspozycji wyrażenia Lambda chcąc wywołać jakąś funkcję wymagającą jako parametru obiektu funkcyjnego nie musimy tworzyć odpowiedniej struktury albo klasy, tylko możemy zapisać to w jednej linijce kodu definiując wyrażenie Lambda jako parametr funkcji. Wyrażenia Lambda pojawiły się w VC2010, jako efekt tworzenia się nowego standardu C++0x. VC2010 implementuje wiele z elementów tego standardu, w tym wyrażenia Lambda.

Przykład prostego wyrażenia lambda:

auto lmb1 = [] (int a) { return a + 2; };int r1 = lmb1(6)

Słowo kluczowe auto to też nowy element C++0x. Jest on odpowiednikiem var z C#. Kompilator sam w tym przypadku ustali typ zmiennej. Auto pozwala na uproszczenie kodu, zwłaszcza jeśli jeśli jest to jakaś skomplikowana klasa szablonowa, której pełnej definicji nawet możemy nie poznać bez wgłębienia się w kod. Poza tym w tym przypadku nie mamy innej możliwości, typ jest nam nieznany, jest to anonimowa struktura wygenerowana przez kompilator:

'anonymous-namespace'::<lambda0>

Nasze wyrażenie lambda, o którym możemy myśleć jako o funkcji zwiększa liczbę o dwa. I teraz jeszcze jeden przykład:

int r2 = [] (int a) { return a + 2; }(6);

Blok [] to z dokumentacji MSDN lambda-parameter-declaration-list (referred to as parameter list later in this topic). Za polską wikipedią to domknięcie. Ja będę go nazywać blokiem przechwytującym. Więcej o tym dalej.

Za blokiem przechwytującym w nawiasach mamy listę parametrów wyrażenia Lambda. I tutaj mamy trzy ograniczenia: nie ma parametrów domyślnych, zmiennej liczby parametrów i parametrów nie nazwanych. W przypadku gdy nasza funkcja nie ma parametrów () możemy pominąć.

Dalej w nawiasach klamrowych znajduje się dowolny kod funkcji.

Powyższe wyrażenie zwraca int. Typ zwracany możemy zdefiniować bezpośrednio, albo jak wyżej pozostawić to domyślności kompilatora.
Lambda nie musi koniecznie zwracać wyniku:

[] (int a) { a + 2; }(4);

W tym wypadku zwracany jest void.

Co w takim wypadku:

auto L3 = [] (int x) { return (x % 2 == 0) ? (x * 2) : (x / 2.0); };

Wynikiem wyrażenia Lambda będzie int gdyż on został napotkany podczas kompilacji jako pierwszy. Double podlega automatycznej konwersji do int więc nie ma tutaj żadnego błędu. Osobiście nie podoba mi się takie zachowanie, według mnie taka niejednoznaczność powinna wygenerować błąd kompilacji i zmusić użytkownika do podania zwracanego rezultatu, a robi się to tak:
auto L3 = [] (int x) -> double { return (x % 2 == 0) ? (x * 2) : (x / 2.0); };
Jeszcze jeden przykład:

auto L2 = [](int x) { x = x + 1; return x; };

Tutaj też mamy błąd. Kompilator założył po analizie pierwszej linii, że funkcja zwraca void. Czyli tak naprawdę nie podawanie typu wyniku jest zarezerwowane dla najprostszych funkcji.

Kolejny błędny przykład:

auto L2 = [](int x) 
{ 
    if (x ==2) 
        return 3;
    else 
    { 
        if (x == 3) 
            return x + 2; 
        else
            return 5; 
    }
};

W tym wypadku ponieważ każda ścieżka przepływu zwraca int-a wydaje się, że kompilator powinien się domyśleć zwracanego wyrażenia.

Kolejny blok throw() o którym tylko wspomnę:

auto L3 = [] (int x) throw() -> int { test(); return (x % 2 == 0) ? (x * 2) : throw 4; };

Takie wyrażenie jest błędne, zadeklarowaliśmy, że Lambda nie będzie wyrzucać wyjątków, a to robi. Więcej informacji: Exception Specifications

Do omówienia pozostał nam jeszcze blok przechwytujący. Funkcja Lambda w swoim zasięgu ma tylko zmienne i funkcje globalne (w tym statyczne klas).

Przykład:

class Test
{
    private:    

        static int w;
        int z;

    public:

        void test()
        {
            auto L2 = [](int x) { return x + z; };
            auto L3 = [](int x) { return x + w; };
        }
};

int w;

void  main()
{ 
    int z;
    auto L2 = [](int x) { return x + z; };

    int* p = new int[5];
    auto L4 = [](int x) { return x + p[1]; };

    auto L3 = [](int x) { return x + w; };
};

Poprawne są tylko wyrażenia starające się odwołać do zmiennych w. Wyrażenie lambda to anonimowa struktura, jej operator(), który zawiera ciało naszej funkcji nie ma dostępu do nie-globalnych składników. Chcąc wykorzystać w wyrażeniu Lambda zmienne nie-globalne musimy je przechwycić. Są one przechwytywane do pól lokalnych struktury podczas konstruowania jej.

Przechwytywać możemy przez wartość i przez referencję:

int z;
int* p = new int[5];

auto L3 = [&](int x) { return x + z + p[1]; };
auto L4 = [&, z](int x) { return x + z + p[1]; };
//auto L5 = [&, &z](int x) { return x + z + p[1]; }; // Błąd
auto L5 = [&, z, p](int x) { return x + z + p[1]; };
auto L6 = [=](int x) { return x + z + p[1]; };
//auto L7 = [=, p](int x) { return x + z + p[1]; }; // Błąd
auto L8 = [=, &p](int x) { return x + z + p[1]; };
auto L9 = [=, &p, &z](int x) { return x + z + p[1]; };
auto L10 = [&p, z](int x) { return x + z + p[1]; };

Linia L3 Przechwytuje wszystko jako referencje zmiennych, zaś linia L6 przechwytuje wszystko jako wartości. Pozostałe przypadki są kombinacją przechwytywania przez referencje i wartość. Np. linia L5 przechwytuje wszystko przez referencje, za wyjątkiem dwóch innych zmiennych. W linii L10 trzeba podać wszystkie zmienne przechwytywane. Przypadki błędne to swego rodzaju tożsamości, pierwszy błąd mówi przechwyć wszystko przez referencje i z przez referencję.

Tak przechwytuje się dostęp do składników klasy:

class Test
{
    private:    

        int z;

        void test_priv()
        {
        }

    public:

        void test()
        {
            auto L2 = [this](int x) -> int { test_priv(); return x + z; };
        }
};

Przy okazji widzimy, że wyrażenie Lambda zdefiniowane we wnętrzu metody klasy ma dostęp do jego składników prywatnych (staje się przyjacielem klasy). Taki sam efekt daje zastosowanie [&] albo [=]. [&] oznacza, że this łapiemy przez wartość. Zapisanie [&this] jest błędem.

Jaka jest różnica pomiędzy przechwyceniem przez wartość i przez referencję?

void main()
{
    byte z = 0;
    byte* p = new byte[5];
    memset(p, 0, 5);
    byte* cp = p;
    printf("z: %d, p: %08X, cp: %08X, p[1]: %d, cp[1]: %d\n", 
            z, p, cp, p[1], cp[1]);
    auto L3 = [=](int x) mutable -> int { z = x; p[1] = x; p = new byte[5]; 
        memset(p, 11, 5); return x + z + p[1]; };
    L3(5);
    printf("z: %d, p: %08X, cp: %08X, p[1]: %d, cp[1]: %d\n", 
            z, p, cp, p[1], cp[1]);
    auto L4 = [&](int x) -> int { z = x; p[1] = x; p = new byte[5]; 
        memset(p, 12, 5); return x + z + p[1]; };
    L4(6);
    printf("z: %d, p: %08X, cp: %08X, p[1]: %d, cp[1]: %d\n", 
            z, p, cp, p[1], cp[1]);
};

Wyniki:

z: 0, p: 00251370, cp: 00251370, p[1]: 0, cp[1]: 0
z: 0, p: 00251370, cp: 00251370, p[1]: 5, cp[1]: 5
z: 6, p: 00251400, cp: 00251370, p[1]: 12, cp[1]: 6


Słowo mutable pozwala nam na zmienianie parametrów przechwyconych przez wartość.

Widzimy więc, że L3 operuje na kopiach, zaś L4 na referencjach.

Przechwycenie następuje w momencie zdefiniowania wyrażenia Lambda, nie jego wykonania, takie wyrażenie posiada swój stan, który może się zmieniać.

int z = 0;
auto L1 = [=] ()  { return z; };
z++;
auto L2 = [=] ()  { return z; };
z++;
int a1 = L1();
z++;
int a2 = L2();

Wyniki to a1 = 0 i a2 = 1.

int z = 0;
auto L1 = [&] ()  { return z; };
z++;
auto L2 = [&] ()  { return z; };
z++;
int a1 = L1();
z++;
int a2 = L2();

Wyniki to a1 = 2 i a2 = 3.

Przechwytując referencje albo jakiś wskaźnik zawsze trzeba uważać bo można łatwo coś namieszać. Staje się to tym ważniejsze jeśli wyrażenie Lambda zostanie zrównoleglone w czymś w rodzaju for_each_parallel.

Poza tym warto zauważyć, że definicja wyrażenia Lambda nie oznacza jego wykonania. Mówimy tutaj o opóźnionym wykonaniu, wyrażenie jest wykonywane dopiero w momencie gdy jest potrzebne. W momencie definiowania tworzona jest struktura, która stanowi obudowę wyrażenia Lambda i przechwytywane są zmienne do tej struktury.

Opóźnione wykonywanie rodzi tutaj problemy. Np. skasowanie p przed wywołaniem L3 spowoduje błąd. Czyli pierwszy rodzaj kłopotów to trwałość zmiennych alokowanych dynamicznie, a przechwyconych do wyrażenia Lambda i skasowanych przed wywołaniem wyrażenia Lambda. W językach z GC nie mamy takich problemów.

Drugi rodzaj możliwych błędów to zmienienie stanu obiektów przechwyconych przed wywołaniem wyrażenia Lambda, np. zamknięcie pliku.

Różnorakich błędów może być mnóstwo, trzeba po prostu uważać.

Spójrzmy na poniższy przykład zastosowania wyrażenia Lambda:

int _tmain(int argc, _TCHAR* argv[])
{
    vector<int> v;
    for (int i = 0; i < 10; ++i) 
        v.push_back(i);
    for_each(v.begin(), v.end(), [](int n) { cout << n << " "; } );

 return 0;
}

Przekazaliśmy do funkcji for_each jako parametr wyrażenie Lambda, która jest anonimowo zdefiniowaną strukturą. Tak naprawdę for_each jest funkcją szablonową i spodziewa się, że trzeci parametr ma przeładowany operator (). Co gdybyśmy chcieli zwrócić jako wynik funkcji wyrażenie Lambda. W C# możemy do tego wykorzystać delegaty, najlepiej w postaci Action i Func. W C++ mamy odpowiednik function.

using namespace std;
using namespace std::tr1;

void round(function<int(double)> round_impl)
{
    double x = 0.1;
    int y = round_impl(0.1);
}

int _tmain(int argc, _TCHAR* argv[])
{
    auto round_impl = [&] (double g ) { return (int)(g + 0.5); };
    function<int(double)> f = round_impl;

    round(round_impl);

 return 0;
}

Użycie function zwiększa naszą elastyczność, pozwalając nam przekazywać funkcje jako parametry, ale kosztem wydajności. Teraz na koniec możemy spóbować co się stanie jeśli przechwycimy przez referencję zmienną lokalną funkcji i spróbujemy jej użyć po wyjściu z jej zakresu w wyrażeniu Lambda.

function<int()> CreateF()
{
    int x = 111;
    return [&] () -> int { x++; return x; };
}

int _tmain(int argc, _TCHAR* argv[])
{
    int y = CreateF()();
    return 0;
}

Wartość zmiennej y jest nieustalona, samo wyrażenie Lambda modyfikuje pamięć poza wskaźnikiem stosu. W pewnych okolicznościach może to prowadzić do losowych i fatalnych błędów. I jeszcze jeden przykład:

[](){}();
[]{}();
function(5)();


Skompiluje się, ale podczas działania ostatni przykład wyrzuci wyjątek.

Podsumowanie

Wszystko co możemy zrobić za pomocą wyrażeń Lambda mogliśmy zrobić wcześniej używając obiektów funkcyjnych. Z tym, że teraz możemy to zrobić prościej i szybciej. A z tym wiąże się fakt: jeśli coś jest ciężkie do wykorzystania to się z reguły z tego nie skorzysta, bo się nie chce tyle pisać, zaciemniać kod, goni nas czas. Innymi słowy wyrażenia Lambda przybliżają nas do tego by programować bardziej funkcyjnie.

Korzystałem z:

Lambda Expressions in C++
Function Objects
Lambdas, auto, and static_assert: C++0x Features in VC10, Part 1
C++0x - Wikipedia Lambda Expression Syntax
Examples of Lambda Expressions

2010-07-19

Visual C++ - symbol already defined, one or more multiply defined symbols found

Wpis będzie dotyczyć Visual C++ 2010, ale prawie na pewno wszystko odnosi się też do poprzednich wersji Visual-a.

Oba tytułowe błędy występują zawsze razem.

Żeby zrozumieć istotę tych błędów trzeba mniej więcej wiedzieć jak budowany jest kod wynikowy w C++. Musimy wiedzieć co robi preprocesor z plikami h, jak powstają obj, co zawierają, co to są biblioteki statyczne, co robi linker z plikami obj i bibliotekami statycznymi. Więcej tutaj:
I jeszcze o sygnaturach:
Poniżej lista przyczyn, które do tytułowych błędów prowadzą. Nie jest ona pewnie kompletna, ale mam nadzieje, że zawiera wszystkie najpopularniejsze przyczyny.

Poniższe przypadki odnoszą się do błędów związanych z symbolami w naszym kodzie, dalej są omówione przypadki błędów związanych z symbolami z bibliotek CRT i MFC.

Podwójna definicja symbolu (funkcja, zmienna)


Linker poszukuje eksportu symbolu dla powiązania go z jego importem i znajduje wiele eksportów o takiej samej syngaturze. Mogą się one znajdować w obj-tach, albo importowanych lib-ach. Po pierwsze możemy oznaczyć symbole używane tylko lokalnie w plikach cpp jako static, dzięki temu nie będą one eksportowane. Po drugie możemy zmienić nazwę spornych symboli.

Deklaracja symbolu w pliku nagłówkowym


Problem pojawia się kiedy taki plik nagłówkowych zostanie dołączony do conajmniej dwóch różnych plików cpp. Rozwiązanie to pozostawienie deklaracji symbolu w pliku nagłówkowym i przeniesienie definicji do pliku cpp. W przypadku funkcji możemy w ostateczności oznaczyć je jako inline.

Plik nagłówkowy dołączany jest wielokrotnie


Dopisz na początku pliku nagłówkowego #pragma once.

Brak #include <afx.h>


Teraz załóżmy, że nasza solucja składa się z projektu pliku wykonywalnego i dołączanej do niej biblioteki statycznej z innego projektu w solucji. Taka przykładowa solucja jest używana dalej w artykule.

Opcje projektów General|Character Set są zgodne. Jeśli do projektu pliku wykonywalnego nie dołączymy afx.h to dostaniemy błędy w zależności od Character Set:

3>uafxcwd.lib(afxmem.obj) : error LNK2005: "void * __cdecl operator new(unsigned int)" (??2@YAPAXI@Z) already defined in LIBCMTD.lib(new.obj)
3>uafxcwd.lib(afxmem.obj) : error LNK2005: "void __cdecl operator delete(void *)" (??3@YAXPAX@Z) already defined in LIBCMTD.lib(dbgdel.obj)

2>nafxcwd.lib(afxmem.obj) : error LNK2005: "void * __cdecl operator new(unsigned int)" (??2@YAPAXI@Z) already defined in LIBCMTD.lib(new.obj)
2>nafxcwd.lib(afxmem.obj) : error LNK2005: "void __cdecl operator delete(void *)" (??3@YAXPAX@Z) already defined in LIBCMTD.lib(dbgdel.obj)


Błędów może być mniej albo więcej, zależy ile potencjalnie konfliktowych symboli użyliśmy. Oczywiście aby uzyskać takie błędy projekt pliku wykonywalnego nie może korzystać z MFC.

Niezgodność opcji projektów


Podejrzane opcje projektów to General|Use of MFC, General|Character Set i C++|Code Generation|Runtime Library. W zależności od ich kombinacji błędy będą różne. Oto niektóre podejrzane komunikaty:

1>LINK : warning LNK4098: defaultlib 'nafxcwd.lib' conflicts with use of other libs; use /NODEFAULTLIB:library

1>msvcrtd.lib(ti_inst.obj) : error LNK2005: "private: __thiscall type_info::type_info(class type_info const &)" (??0type_info@@AAE@ABV0@@Z) already defined in LIBCMTD.lib(typinfo.obj)
1>msvcrtd.lib(ti_inst.obj) : error LNK2005: "private: class type_info & __thiscall type_info::operator=(class type_info const &)" (??4type_info@@AAEAAV0@ABV0@@Z) already defined in LIBCMTD.lib(typinfo.obj)
1>LINK : warning LNK4098: defaultlib 'msvcrtd.lib' conflicts with use of other libs; use /NODEFAULTLIB:library

1>LINK : warning LNK4098: defaultlib 'mfc100u.lib' conflicts with use of other libs; use /NODEFAULTLIB:library
1>LINK : warning LNK4098: defaultlib 'mfcs100u.lib' conflicts with use of other libs; use /NODEFAULTLIB:library
1>LINK : warning LNK4098: defaultlib 'msvcrt.lib' conflicts with use of other libs; use /NODEFAULTLIB:library


Dla uniknięcia tego rodzaju błędów wspomniane wcześniej opcje powinny zostać ujednolicone.

Trochę więcej informacji na temat dwóch ostatnich przyapdków A LNK2005 error occurs when the CRT library and MFC libraries are linked in the wrong order in Visual C++

2010-07-18

Unresolved External Symbol w Visual C++

Bardzo powszechny błąd podczas tworzenia programów w C++. Tutaj skupie się na Visual C++, konkretnie w wersji 2010, choć prawie wszystko będzie się odnosić również do poprzednich wersji.

Żeby zrozumieć istotę błędu trzeba mniej więcej wiedzieć jak budowany jest kod wynikowy w C++. Musimy wiedzieć co robi preprocesor z plikami h, jak powstają obj, co zawierają, co to są biblioteki statyczne, co robi linker z plikami obj i bibliotekami statycznymi. Więcej tutaj:
I jeszcze o sygnaturach:
Błąd Unresolved External Symbol oznacza, że kompilator nie napotkał wcale (albo o odpowiedniej sygnaturze) eksportu definicji, dla której deklaracji wygenerował import. Przyczyn takiego błędu może być wiele.

Czy ja się kiedyś doczekam takiej wersji Visual-a C++, który zasugeruje, że funkcja o takiej, a takiej nazwie jest zdefiniowana, ale jej sygnatura się różni od wymaganej. Albo jakaś możliwość podejrzenia jak wyglądają importowane i eksportowane sygnatury obj-tów i lib-ów. Większość błędów można by szybciej usunąć. Istotą tytułowego błędu jest to, że kompilator nic a nic nam nie pomoże w jego znalezieniu. Wszystko jest wypadkową naszego doświadczenia względem projektu, doświadczenia w ogóle i sprawdzenia wszystkiego tak by niczego nie przegapić. Niewdzięczny to błąd w istocie jest.

Po pierwsze: czytaj co jest generowane do Output Windows. Czasami można tam znaleźć użyteczne wskazówki.

Rebuild


Czasami to wystarczy, niezależnie od wersji Visual-a, od czasy do czasu coś się mu popieprzyć lubi, tak już ma.

Brak definicji, niezgodność sygnatur


Jest deklaracja, brak jest definicji. Nazwa definicji różni się od nazwy deklaracji, lub różnią się listą parametrów. Mam tutaj na myśli błędy, które wprowadzamy sami podczas procesu pisania. I z reguły wiemy gdzie ich szukać.

Niezgodność sygnatur - trochę bardziej skomplikowana ewentualność


Np. plik "test1.h" zawiera definicję funkcji void test(MY_TYPE t). Dołączamy go do test1.cpp gdzie jest jego definicja i do test2.cpp gdzie jest używany. Ale w obu plikach cpp przed #include "test1.h" tak mieszamy, że MY_TYPE dla obu plików cpp jest różny.

Niezgodność definicji preprocesora


W zasadzie jest to odmiana poprzedniego błędu. Z tym, że różnice w sygnaturach wywoływane są przez różne ustawienia opcji preprocesora w opcjach powiązanych projektów. Niektóre biblioteki statyczne mogą być na tyle skomplikowane, że wymagają konfiguracji, z reguły odbywa się to przez jeden plik nagłówkowy o np. wymownej nazwie config.h, a czasami przez ustawienie odpowiednich definicji preprocesora w opcjach projektu.

Definicja jest wyłączona z procesu kompilacji


Może się też zdarzyć, że w wyniku ustawień preprocesora, dana funkcja nie zostanie skompilowana (np. zostanie wyłączona poprzez #ifdef).

Plik lub projekt wyłączony jest z procesu budowania


Co do projektu sprawdź opcje solucji. Jeśli z procesu budowania wyłączony jest jeden plik będzie on oznaczony w specjalny sposób. Zmień jego właściwości ewentualnie. Plik może być też wyłączony z procesu budowania, gdyż nie został dodany do projektu.

Odpowiednia biblioteka statyczna nie jest dołączona


Jeśli nasza funkcja jest zadeklarowana w innej bibliotece statycznej albo DLL-ce sprawdź czy odpowiednia biblioteka statyczna jest dołączona w opcjach projektu Properties|Linker|Input|Additional Dependencies lub dla VS2010 Properties|Framework And References.

Niektóre projekty korzystają z #pragma comment(lib, ...) zwalniając nas tym samym z ustawiania różnych lib-ów w zależności od opcji kompilacji, np. debug i release, ansi i unicode, itp. Jedyne co musimy ustawić to ścieżkę do katalogu z lib-ami.

Zgodność konfiguracji projektów dla konfiguracji solucji


Po pierwsze wielokrotnie zdarzało mi się, że konfiguracje rozjeżdżały się, szczególnie podczas dodawania i usuwania konfiguracji. Sprawdź np.: że w opcjach solucji dla konfiguracji solucji debug, wszystkie projekty mają wybrane debug. Podobny problem tyczy się platformy. No i czasami także checkbox-y Build mogą się pomieszać. Tego rodzaju błędy są w VS2010. Parę razy musiałem ręcznie edytować pliki projektu, gdyż GUI się sypało.

Po drugie ustawiając np. konwencje wywołań projektu upewnij się, że robisz to dla wszystkich konfiguracji. Domyślnie modyfikujesz opcje wybranej konfiguracji, a z reguły nie o to nam chodzi.

Zmienna lub metoda globalna jest zadeklarowana jako static.


Słowo kluczowe static w tym wypadku oznacza dostępne tylko z danej jednostki kompilacji. Funkcja lub zmienna nie jest wystawiana na zewnątrz pliku obj.

Funkcje globalne szablonowe.


Zarówno deklaracja jak i definicja funkcji szablonowej musi się znajdować podczas linkowania w tej samej jednostce obj. Taki kod jest poprawny:
template <class T>
void test(T t);

void main()
{
    test(5);
}

template <class T>
void test(T t)
{
}
Ale nie praktyczny. Zdefiniowanie funkcji test() w innym pliku cpp i dołączenie jej definicji poprzez plik nagłówkowy spowoduje błąd linkowania. A ponieważ funkcje szablonowe tworzy się po to by wykorzystywać je wiele razy musimy dołączyć definicję funkcji szablonowej do jej deklaracji. I takiego zapisu powinniśmy się zawsze trzymać.

Gdybyśmy tak postąpili względem zwykłej globalnej funkcji możemy dostać błąd linkera, jeśli plik nagłówkowy z funkcją dołączymy do dwóch różnych plików cpp. Wyjątkiem są tutaj funkcje inline. Dlaczego nic takiego nie dzieje w przypadku funkcji szablonowej ? Bo to jest szablon funkcji, ona w tym miejscu nie istnieje w postaci kodu.

Klasy szablonowe, metody klas szablonowe.


To samo odnosi się zarówno do metod statycznych klasy i jak i metod obiektów. Zarówno kiedy te metody są szablonowe jak i metoda zawiera parametry szablonowe klasy, albo to i to jednocześnie. Definicja i deklaracja powinna być w tym samym miejscu.

Zmienne statyczne klasy

class test_t1
{
    public: 

        static int x;
};
To nie wystarczy. Zapisaliśmy jakby:

extern test_t1::x

Musimy więc dopisać:

int test_t1::x = 5;

Zmiennej nie trzeba inicjalizować. A definicję tą powinniśmy zamieścić w pliku cpp z takich samych powodów jak dla zwykłych zmiennych.

Mieszanie kodu C++ i C


Visual C++ niezależnie od rozszerzenia pliku może go skompilować jako C albo jako C++. Dla każdego pliku z osobna i dla każdego projektu możemy ustawić Properies|C++|Advanced|Compile As. Mamy trzy możliwości kompiluj zgodnie z rozszerzeniem, kompiluj jako C i kompiluj jako C++.

Jeszcze się nie spotkałem, żeby ktoś ustawił tą opcję dla pliku z osobna (jak i inne wspomniane w artykule). Generalnie nie powinno się tego robić. Co jeśli plików są tysiące, co jeśli autor błędu siedzi obok mnie, co jeśli ty nim jesteś, to może się źle skończyć.

Funkcje C i C++ mają różne sygnatury. Kompilujemy kod C++, kompilator napotyka na definicję funkcji, generuje w obj import dla funkcji C++. Tymczasem eksport jest typu C. Musimy kompilatorowi powiedzieć, że wspomniana funkcją ma eksport typu C. Np. w taki sposób:
#ifdef __cplusplus
#define MY_EXTERN extern "C"
#else
#define MY_EXTERN
#endif
Wtedy w pliku nagłówkowym możemy zapisać:

MY_EXTERN void test();

Jeśli wspomniany plik nagłówkowy jest dołączany do pliku C, całe extern "C" jest pomijane, gdyż taki zapis C rozpoznaje jako błędny, funkcja jest eksportowana z sygnaturą C. Dla pliku cpp definiowany jest automatycznie symbol __cplusplus i funkcja jest oznaczana jako extern "C", czyli importowana jest z sygnaturą C.

Konwencja wywołania


Ustawiana w opcjach projektu Properies|C++|Advanced|Calling Convention. Ich zgodność lub niezgodność dla kilku powiązanych projektów niczego nie oznacza, musisz upewnić się czy sporne funkcje nie zostały oznaczone konkretną konwencją wywołania. Jeśli konwencja jest zdefiniowana dyrektywą preprocesora np. NAZWA_PORJEKTU_EXTERN warto spojrzeć co się za tym kryje.

Treat wchar_t as Built-in Type


Ustawiane w opcjach projektu Properies|C++|Language|Treat wchar_t as Built-in Type. Tak jak poprzednio sprawdzamy zgodność dla powiązanych projektów. Jeśli wszystkie nasze nierozwiązane funkcje zawierają w sobie parametry typu wchar_t albo jako unsigned short może to wskazywać na ten rodzaj błedu.

DLL


Linkowanie z DLL-ką jest specyficzne dla linkera, a później dla systemu, który aplikację uruchamia. Musimy w sposób specjalny powiedzieć kompilatorowi, że ta funkcja znajduje się w DLL-ce. Dla eksportu używamy __declspec(dllexport), zaś dla importu __declspec(dllimport). Dekoracji takiej możemy także użyć w stosunku do klas. Metody klasy to odpowiednio nazwane funkcje biblioteczne, których jednym z parametrów jest wskaźnik na obiekt.

Z reguły mamy plik nagłówkowy z definicjami eksportowanych/importowanych funkcji. Niestety Visual C++ nie jest tutaj zbyt domyślny. Jeśli nasz drugi projekt to plik wykonywalny, ciągle domaga się definiowania importu, tak jakby dało się eksportować funkcję z exe-ca. Rozwiązanie powszechnie stosowane to dodanie na początku takiego pliku nagłówkowego czegoś takiego:
#ifdef LIBRARY_EXPORTS
#    define LIBRARY_API __declspec(dllexport)
#else
#    define LIBRARY_API __declspec(dllimport)
#endif
Musimy więc dodać do definicji preprocesora do naszego projektu LIBRARY_EXPORTS.

Specyficzny błąd dla VC2010


VC2010 pozwala w opcjach projektu ustawić inne projekty od których on zależy. Dzięki temu będą one kompilowane przed naszym projektem. Jeśli są to lib-y możemy zaznaczyć ich włączenie do naszego projektu. Znacznie upraszcza do zarządzanie dużą solucją. Problem który napotkałem można streścić tak: dla niektórych lib-ów, jeśli lib A włączamy do referencji lib-u B (zaznaczamy opcję Use library dependency inputs, ten zaś do exe-ka C, to mamy błąd. Jeśli do C dołączymy A to błędu nie ma. Nie wiem o co chodziło, nie umiałem tego rozgryźć.

Podsumowanie


To zapewne nie wszystko, w opcjach linkera jest jeszcze parę ustawień, które mogą zamieszać, jednakże jako, że nigdy nie miałem z nimi problemu nie opisałem ich.

2010-07-07

Visual C++ Compiler Intrinsics - funkcje wbudowane

Muszę się przyznać, że nigdy moja uwaga specjalnie nie skupiła się na tym zagadnieniu. Dopiero ostatnio kiedy istotne znaczenie miało dla mnie szybkie rotowanie bitów oraz konwersja little-endian na big-endian natrafiłem na opis tych funkcji.

Funkcje wbudowane są specjalnymi funkcjami kompilatora. Przeważnie funkcja wbudowana jest zastępowana zbiorem instrukcji procesora (nie jest to dokładnie funkcja inline). W debug zaś wywoływana jest z reguły niezoptymalizowana wersja biblioteczna. W przypadku funkcji dostępnych pod podanym wcześniej adresem większość z nich w wersji zoptymalizowanej to pojedyncze instrukcje procesora. Część z nich można w debug zastąpić wersjami bibliotecznymi (np: _byteswap_ulong), a część nie (obsługa wirtualizacji). Instrukcje SIMD nie mają one odpowiedników bibliotecznych. Niektóre funkcje wbudowane przeznaczone są na konkretną architekturę procesora. Niektóre działają na dowolnej, co nie oznacza, że w zależności od architektury nie zostanie wygenerowany inny kod. Np. używając funkcji wbudowanych SIMD na architekturę x86 wcale nie jest powiedziane, że wszystkie będą działać zarówno na procesorach AMD jak i Intel. Sprawdzenie tego leży po naszej stronie. Kompilator zastąpi funkcję odpowiednimi instrukcjami procesora. Kompilator w przypadku funkcji wbudowanych może w zależności od sytuacji wygenerować różny kod, funkcja nie jest nigdzie zapisana, nie jest to zwykła funkcja inline. Użycie funkcji wbudowanych czyni nasz kod trudno przenoszalnym.

Wracając do głównego tematu: rotowanie i zmiana bajtów w słowie. Bardzo często spotykana implementacja tych zadań wygląda tak:

#define ROT32a(x,n) (((x >> n) | (x << (32 - n))))
#define ROT32b(x,n) _rotr(x, n)
#define BYTESWAPa(x) ((x << 24) + (( x & 0x0000FF00) << 8) + \ 
                     ((x & 0x00FF0000) >> 8) + (x >> 24))

Kompilator nie jest w stanie zorientować się o co nam chodzi i wygenerować jedną instrukcję procesora, które obie definicje posiadają.

Możemy to oczywiście zrobić sami używając __asm, co już czyni nasz kod przenoszalnym pomiędzy kompilatorami, ale ciągle uwiązanym do danej architektury.

Wersja tych makr z wykorzystaniem funkcji wbudowanych:

#define ROT32b(x,n) _rotr(x, n)
#define BYTESWAPb(x) _byteswap_ulong(x)

No i teraz najważniejsze. Czas wykonania:

void test()
{
    const int size = 100000000;

    int* memory = new int[size];

    std::srand(GetTickCount());

    for (int i=0; i<size; i++)
        memory[i] = std::rand();

    {
        Helpers::StopWatch sw;

        for (int i=0; i<size; i++)
            memory[i] = ROT32a(i, i);

        std::wprintf(L"ROT32a: %d [ms]\n", sw.GetElapsedTime());
    }

    {
        Helpers::StopWatch sw;

        for (int i=0; i<size; i++)
        {
            int sum = memory[i];
            for (int j=0; j<64; j++)
            {
                int x = sum + i;
                sum+= ROT32a(x, i);
            }
            memory[i] = sum;
        }

        std::wprintf(L"ROT32a: %d [ms]\n", sw.GetElapsedTime());
    }

    {
        Helpers::StopWatch sw;

        for (int i=0; i<size; i++)
            memory[i] = ROT32b(i, i);

        std::wprintf(L"ROT32b: %d [ms]\n", sw.GetElapsedTime());
    }

    {
        Helpers::StopWatch sw;

        for (int i=0; i<size; i++)
        {
            int sum = memory[i];
            for (int j=0; j<64; j++)
            {
                int x = sum + i;
                sum+= ROT32b(x, i);
            }
            memory[i] = sum;
        }

        std::wprintf(L"ROT32b: %d [ms]\n", sw.GetElapsedTime());
    }

    {
        Helpers::StopWatch sw;

        for (int i=0; i<size; i++)
            memory[i] = BYTESWAPa(i);

        std::wprintf(L"BYTESWAPa: %d [ms]\n", sw.GetElapsedTime());
    }

    {
        Helpers::StopWatch sw;

        for (int i=0; i<size; i++)
        {
            int sum = memory[i];
            for (int j=0; j<64; j++)
            {
                int x = sum + i;
                sum+= BYTESWAPa(x);
            }
            memory[i] = sum;
        }

        std::wprintf(L"BYTESWAPa: %d [ms]\n", sw.GetElapsedTime());
    }

    {
        Helpers::StopWatch sw;

        for (int i=0; i<size; i++)
            memory[i] = BYTESWAPb(i);

        std::wprintf(L"BYTESWAPb: %d [ms]\n", sw.GetElapsedTime());
    }

    {
        Helpers::StopWatch sw;

        for (int i=0; i<size; i++)
        {
            int sum = memory[i];
            for (int j=0; j<64; j++)
            {
                int x = sum + i;
                sum+= BYTESWAPb(x);
            }
            memory[i] = sum;
        }

        std::wprintf(L"BYTESWAPb: %d [ms]\n", sw.GetElapsedTime());
    }

    std::getchar();
}

Rozmiar tablicy jest dużo większy niż pamięć cache. Dodatkowo druga wersja mieli trochę daną z pamięci, sprawiając, że oczekiwanie na dane z pamięci nie stanowi o prędkości programu. Wyniki:

ROT32a: 209 [ms]
ROT32a: 17807 [ms]
ROT32b: 191 [ms]
ROT32b: 8874 [ms]
BYTESWAPa: 270 [ms]
BYTESWAPa: 26587 [ms]
BYTESWAPb: 187 [ms]
BYTESWAPb: 16022 [ms]


Widać wyraźnie, że w przypadku gdy "szybko" lecimy po obszarze danych nie mieszczącym się w pamięci cache różnice są minimalne. Druga wersja pokazuje prawdziwą różnicę w czasie wykonania, kiedy czas wykonania przekracza czas napływu danych.

Po zmniejszeniu rozmiaru tablicy do 0.5 MB:

ROT32a: 0 [ms]
ROT32a: 89 [ms]
ROT32b: 0 [ms]
ROT32b: 45 [ms]
BYTESWAPa: 1 [ms]
BYTESWAPa: 149 [ms]
BYTESWAPb: 0 [ms]
BYTESWAPb: 80 [ms]


Widzimy, że różnice dla mielących daną wersjami zostały utrzymane. Wniosek to nie pamięć tak spowalniała tylko dość długi kod pętli, który sprawił, że procesor nie mógł efektywnie przetworzyć kodu w potoku. Powiedzmy sobie szczerze, ja optymalizację niskopoziomową uważam za rodzaj sztuki i magii zarazem, nigdy nie wiadomo co nam wyjdzie.

Czasy zostały zdjęte na procesorze x64 i kodzie x32. Kod x64 daje podobne rezultaty.

Na koniec tak wygląda wymiana bajtów w wersji makrowej:

memory[i] = BYTESWAPa(i);
00F059BD mov edx,eax
00F059BF and edx,0FF00h
00F059C5 add edx,ecx
00F059C7 mov edi,eax
00F059C9 sar edi,8
00F059CC and edi,0FF00h
00F059D2 shl edx,8
00F059D5 add edx,edi
00F059D7 mov edi,eax
00F059D9 sar edi,18h
00F059DC add edx,edi
00F059DE mov dword ptr [esi+eax*4],edx


, zaś tak w wersji wbudowanej:

00F05A80 mov ecx,eax
00F05A82 bswap ecx
00F05A84 mov dword ptr [esi+eax*4],ecx


A czas wykonania przy sprzyjających okolicznościach może być taki sam.

2010-07-05

Simplify CString formatting

There is no static method such as CString::Format(). So instead of:
CString str;
str.Format(_T("%d"), i);
use:
CString StringFormat(LPCTSTR a_str, ...)
{
    va_list argptr;
    va_start(argptr, a_str);

    CString str;
    str.FormatV(a_str, argptr);

    return str;
}
Funny thing its stops working when you change LPCTSTR to CString.

Yet another high-resolution timer in C++

Simple high-resolution timer in C++.

class StopWatch 
{
    private:

        LARGE_INTEGER start;
        LARGE_INTEGER stop;
        LARGE_INTEGER frequency;
        bool started;
        bool stopped;

    public:

        StopWatch(bool autoStart = true)
        {
            start.QuadPart = 0;
            stop.QuadPart = 0; 
            started = false;
            stopped = false;

            QueryPerformanceFrequency(&frequency);

            if (autoStart)
                StartTimer();
        }

        void StartTimer() 
        {
            if (started)
                AfxThrowNotSupportedException();

            started = true;
            stopped = false;

            QueryPerformanceCounter(&start);
        }

        void StopTimer(bool traceResult = true) 
        {
            QueryPerformanceCounter(&stop);

            if (!started)
                AfxThrowNotSupportedException();

            started = false;
            stopped = true;

            if (traceResult)
                AfxMessageBox(Helpers::StringFormat(L"Time [ms]: %d\n", 
                              GetElapsedTime()));
        }

        int GetElapsedTime() 
        {
            if (started)
                AfxThrowNotSupportedException();
            if (!stopped)
                AfxThrowNotSupportedException();

            return (int)((stop.QuadPart - start.QuadPart) * 1000 / 
                       frequency.QuadPart);
        }
};

2010-06-04

Compiling WinUAE 2.1.0

Requirements:
  • Visual Studio 2010
  • installed the most recent WDK (also on XP SP3)
  • installed the most recent DirectX SDK
Here's a note. It is worth to all the files *.h in the WDK and the DirectX SDK switch to read-only. Let's check content of $(USERPROFILE)\AppData\Local\Microsoft\MSBuild\v4.0\Microsoft.Cpp.Win32.user. Its should look like this:


$(ExecutablePath);C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin;C:\Program Files (x86)\NASM
$(IncludePath);C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Include;C:\Program Files (x86)\Microsoft DirectX SDK (February 2010)\Include;C:\DDK_7.1\inc\api
$(ReferencePath)
$(LibraryPath);C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Lib;C:\Program Files (x86)\Microsoft DirectX SDK (February 2010)\Lib\x86;C:\DDK_7.1\lib\wxp\i386
$(SourcePath)
$(ExcludePath);C:\Program Files\Microsoft SDKs (x86)\Windows\v7.0A\Include


The idea is to place VS2010 path $(Path*) always at the very beginning. At this stage, you should properly set up the path to the WDK and DirectX SDK. Best to do it here, so you will not have to configure each project individually. Download NASM, install it and add it to ExecutablePath Grab the source of WinUAE. Create a directory winuae-base. Extract the sources to directory winuae. Also grab the source of WinUAE from the GIT repository http://github.com/tonioni/WinUAE.git (see the official site of the project, Downloads section.) I used TortoiseGIT. Requires msysgit. During the installation of msysgit select Run Git From Windows Command Promp. Copy the directory winuae-git/prowizard, which is missing in official source 2.1.0.
Time to get a lot of packages needed to compile WinUAE. Here is an excellent source of knowledge on that subject. Things such as OpenAL, networking, printers, or the functionality which these packages provide, usually requires that WinUAE has access to the appropriate DLLs. They are not required at start time, instead they are loaded dynamically if necessary. WinUAE is prepare for situation when they do not exists. I am concentrating only at compiling WinUAE with minimum number of changes. There is possibility to compile WinUAE without all those packages. Basic elements such as CD, HDD, games, sound will work. However, this requires lot of work. OpenAL Download OpenAL 1.1 Core SDK. Install it. Create a directory winuae-base/openal and copy there al.h, alc.h, OpenAL32.lib. We can uninstall OpenAL now. zlib Download a source package, extract to winuae-base/zlib unrar Download UnRAR.dll and to directory winuae-base/unrar extract unrar.h. WinPcap Download Developer's Pack and extract to directory winuae-base/winpcap directories Include and Lib. FreeType Download Binaries and exract to directory winuae-base/freetype directories Include and Lib. From the directory tree eliminates freetype2. libpng Download source and extract to directory winuae-base/libpng. TVicPort Download, install, copy to directory winuae-base/tvicport file TVicPort.h. And in my case, uninstall. Catweasel Download the latest version Catweasel ISA/MK3/MK4/MK4plus. Extract to directory winuae-base/catweasel file catweasl_usr.h. Ghostscritp Download source and extract to directory winuae-base/ghostscript files errors.h, iapi.h, ierrors.h. PortAudio Download version v19. Extract to directory winuae-base/portaudio. fdrawcmd Downsload this file and save to winuae-base/fdrawcmd. G15 LCD monitor Download drivers. Install them. Look for LCDSDK_3.04.143.zip. Extract to directory winuae-base/logitechlcd files lglcd.h and lglcd.lib. Drivers can be uninstalled now. WinIO Extract to winuae-base/winio content of the directory Source. ParaPort Save content of the header file to winuae-base/paraport/paraport.h (preferably copy it from page's source). wnaspi32 Writes it to winuae-base/wnaspi32/wnaspi32.h enet Extract to directory winuae-base/enet.
To directory winuae-base/winuae/od-win32/winuae_msvc10/ copy solution file from GIT repository. Open solution. Remove the project unpackers. Remove files *.obj from the project WinUAE. In project create folder asm and add the files *.asm, which is obj's you generate. Select them all and open properties. Select all configurations. As Item Type select Custom Build Tool. Click apply. In Custom Build Tool set: Command Line: nasm.exe -O1 -f win32 -Xvc -o "$(IntDir)%(Filename).obj" "%(RootDir)%(Directory)\%(Filename).asm" -w-orphan-labels Description: Compiling asm file %(Filename).asm Outputs: $(IntDir)%(Filename).obj;%(Outputs) For projects build68k, genblitter, gencpu, gencomp for Pre-Link Event and Post-Build Event unify entries for all configurations. For build68k change generated file cpudefs.c to cpudefs.cpp. Same in project genblitter and genlinetoscr. Set dependencies between projects:
  • winuae depends on genblitter, gencpu, gencomp, genlinetoscr, prowizard.
  • gencpu and gencomp depends on build68k.
In the project winuaee properties add reference for prowizard and set Output File to the default value for all configurations. Select configuration Debug for build. In the solution properties in section Configuration Properties for all configurations check for build only build68k, gencpu, gencomp genblitter, genlinetoscr, prowizard, winuae. Build build68k, genblitter - should be no problems. Build gencomp, there are some errors. In sysdeps.h before #include <tchar.h> place #include <windows.h>. Build again. Build gencpu - without problems. Build genlinetoscr. There are some errors. For projects build68k, gencpu, gencomp, genlinetoscr, genblitter for all configurations set Output Directory and Intermediate Directory to $(Configuration)\, and Output File set to $(OutDir )\..\$(TargetName)$(TargetExt). Build genlinetoscr again. Build prowizard. Errors. Copy value of Additional Include Directories from Release to Debug. Build again. Unifies content of all Additional Include Directories in each project. Because I couldn't find lib for wacom tablet so I had to disable tablet functionality. In short just comment #include <wintab.h>, #include <pktdef.h> and follow the errors. Instead of commenting or removing code you may add new preprocessor definition to sysconfig.h. Remove from Linker/Additional Dependencies entry wintab32.lib for all configurations. Remove from Delay Loaded DLLs entry wintab32.dll for all configurations. For project winuae set the Output Directory and Intermediate Directory to $(Configuration)\ for all configurations. Set Output File to the default value for all configurations. Build project winuae. All errors are associated with lack of headers. Add to Additional Include Directories: ..\..\..\catweasel;..\..\..\enet\include;..\..\..\fdrawcmd;..\..\..\freetype\include;..\..\..\libpng;..\..\..\logitechlcd;..\..\..\openal;..\..\..\paraport;..\..\..\portaudio\include;..\..\..\tvicport;..\..\..\unrar;..\..\..\winio\Dll;..\..\..\winpcap\include;..\..\..\wnaspi32;..\..\..\zlib;%(AdditionalIncludeDirectories) Two errors remained: syntax error : identifier 'PARAPORT_INFO' Remove: typedef BOOL (*getPortInfo)(HANDLE, PARAPORT_INFO*); static getPortInfo pp_getportinfo; pp_getportinfo = (getPortInfo)GetProcAddress (para, "getPortInfo"); None of these are in use. As to the second error: 'Pa_OpenStream' : cannot convert parameter 7 from 'int (__cdecl *)(const void *,void *,unsigned long,const PaStreamCallbackTimeInfo *,PaStreamCallbackFlags,void *)' to 'PaStreamCallback (__stdcall *)' In portaudio.h decorate declaration of PaStreamCallback with __cdecl. Build again winuae. Now it's just linking errors left. Unifies Linker/Additional Dependencies and Delay Loaded DLLs for all configuration for project winuae. Add to Additional Library Directories for all configurations: ..\..\..\logitechlcd;..\..\..\winpcap\Lib;..\..\..\openal;..\..\..\freetype\lib Add to solution WinIo.vcproj. Add to references of winuae project WinIO. Remove winio.lib from Linker/Additional Dependencies. For project WinIO set the Output Directory and Intermediate Directory to $(Configuration)\ for all configurations. Make sure that for solution proper configurations were set up, it's going mess very often, specially after adding project or changing configuration (adding, removing, renaming). Build WinIO. Build again winuae. Now zlib. Do exactly as before in WinIO. Project name is zlibstat.vcxproj. Build zlibstat. Build winuae. Now libpng. Edit libpng.vcxproj. Remove <import project="$(SolutionDir)\zlib.props">. Add the project to the solution. Set Output Directory and Intermediate Directory to $(Configuration)\ for all configurations. And Target Name to $(ProjectName) for all configurations. Set Additional Include Directories to ..\..\..\..\Zlib for all configurations. In solution properties set proper Debug Library and Release Library for the corresponding solution configuration. Add to references of winuae project libpng. Remove libpng.lib from Linker/Additional Dependencies. Build libpng. Build winuae. Time for portaudio. Add to solution portaudio.vcproj. Add to references of winuae project portaudio. Set Output Directory and Intermediate Directory to $(Configuration)\ for all configurations. Set Output File the default value for all configurations. Remove portaudio_x86.lib from Linker/Additional Dependencies for all configurations. Build PortAudio. Remove directory ASIO from disk like from the project Build. Remove portaudio.def from the project. Build. Clear for all configurations Module Definition File. Add the preprocessor symbols PA_NO_ASIO for all configurations. Change in Delay Loaded DLLs from portaudio_x86.dll to portaudio.dll. Build. Note: After you changed dll's name you can't use original dll. Build winuae Time for enet. Add to solution enet.dsp. Add to references of winuae project enet. Set Output Directory and Intermediate Directory to $(Configuration)\ for all configurations. Set Output File to the default value for all configurations. Remove enet.lib from Linker/Additional Dependencies for all configurations. Build enet. Build winuae. Remove prowizard.lib from Linker/AdditionalDependencies for all configurations. Build winuae. Time for errors like Unresolved externals. For project enet, libpng, prowizard change calling convention to stdcall for all configurations. Project portaudio does not export any symbols. Add header files to the project portaudio. Place at the top of portaudio.h: #ifdef _USRDLL #define DELC_EXTERN __declspec(dllexport) #else #define DELC_EXTERN __declspec(dllimport) #endif Decorate with DELC_EXTERN each function in portaudio.h. Add __cdecl to each function. Clear property Linker/Advanced/Import Library for all configurations. Open pcap.h, packet32.h, remote-ext.h and every function that is reported as Unresolved external decorate with __cdecl (and for sure all others too). At this point only errors like multiply symbol define left. For projects enet, winuae, portaudio, prowizard, libpng, zlibstat, winio for all configurations set up properly Runtime Library. Build winuae. And we have the executable file. When it comes to Release. Add to the project zlibstat to Pre_Build Event: cd ..\..\masmx86 bld_ml32.bat

2010-06-01

Kompilacja WinUAE 2.1.0

Wymagania:
  • Visual Studio 2010
  • zainstalowane najlepiej najnowsze WDK (także pod XP SP3)
  • zainstalowane najlepiej najnowsze DirectX SDK
Tutaj jedna uwaga. Warto sobie wszystkie pliki *.h w WDK i DirectX SDK przestawić na tylko do odczytu. Sprawdźmy plik $(USERPROFILE)\AppData\Local\Microsoft\MSBuild\v4.0\Microsoft.Cpp.Win32.user. Jego zawartość powinna wyglądać mniej więcej tak:


$(ExecutablePath);C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin;C:\Program Files (x86)\NASM
$(IncludePath);C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Include;C:\Program Files (x86)\Microsoft DirectX SDK (February 2010)\Include;C:\DDK_7.1\inc\api
$(ReferencePath)
$(LibraryPath);C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Lib;C:\Program Files (x86)\Microsoft DirectX SDK (February 2010)\Lib\x86;C:\DDK_7.1\lib\wxp\i386
$(SourcePath)
$(ExcludePath);C:\Program Files\Microsoft SDKs (x86)\Windows\v7.0A\Include


Chodzi o to by ścieżki dołączane przez VS2010 typu $(*Path) były na samym początku. Na tym etapie powinniśmy poustawiać odpowiednio ścieżki do WDK i DirectX SDK. Najlepiej zrobić to tutaj, dzięki czemu nie będziemy musieli konfigurować każdego projektu z osobna. Pobierz NASM, zainstaluj go i dodaj go do ExecutablePath Pobieramy źródła WinUAE. Utwórz katalog winuae-base. W nim katalog winuae do którego wypakuj źródła. Pobieramy także źródła WinUAE z repozytorium GIT http://github.com/tonioni/WinUAE.git (patrz oficjalna strona projektu, sekcja Download). Ja użyłem do tego TortoiseGIT. Wymaga do działania msysgit. Podczas instalacji msysgit wybierz Run Git From Windows Command Promp. Kopiujemy katalog winuae-git/prowizard, którego brakuje w oficjalnych źródłach 2.1.0.
Czas na pobranie mnóstwa pakietów wymaganych do poprawnej kompilacji WinUAE. Tutaj doskonałe źródło wiedzy na ten temat. Skorzystanie np. z OpenAL, obsługi sieci, drukarek, czyli funkcjonalności której te pakiety dostarczają, wymaga z reguły by WinUAE miał dostęp do odpowiednich bibliotek DLL. Nie są one wymagane w momencie startu, są ładowane dynamicznie w razie potrzeby. WinUAE radzi sobie dobrze z obsługą sytuacji gdy ich nie ma. Ja skupiam się w tylko na skompilowaniu WinUAE z minimalną liczbą zmian. Można i to z sukcesem skompilować WinUAE bez tych wszystkich pakietów. Podstawowe elementy takie jak CD, HDD, gry, dźwięk będą działać. Wymaga to jednak dużego nakładu pracy. OpenAL Pobieramy OpenAL 1.1 Core SDK. Instalujemy. Tworzymy katalog winuae-base/openal i kopiujemy tam al.h, alc.h, OpenAL32.lib. Możemy odinstalować. zlib Pobieramy paczkę źródeł, wypakowujemy do winuae-base/zlib UnRAR Pobieramy UnRAR.dll i do katalogu winuae-base/unrar wypakowujemy unrar.h. WinPcap Pobieramy Developer's Pack i do wypakowujemy do katalogu winuae-base/winpcap katalogi Include i Lib. FreeType Pobieramy Binaries, do katalogu winuae-base/freetype wypakowujemy katalogi Include i Lib. Ze struktury katalogów eliminujemy freetype2. libpng Pobieramy źródła i wypakowujemy do katalogu winuae-base/libpng. TVicPort Pobieramy, instalujemy, do katalogu winuae-base/tvicport kopiujemy TVicPort.h. I w moim przypadku odinstalowujemy. Catweasel Pobieramy najnowszą wersję Catweasel ISA/MK3/MK4/MK4plus. Do katalogu winuae-base/catweasel wypakowujemy catweasl_usr.h. Ghostscritp Pobieramy źródła i wypakowujemy do katalogu winuae-base/ghostscript pliki errors.h, iapi.h, ierrors.h. PortAudio Pobieramy wersję v19. Wypakowujemy do katalogu winuae-base/portaudio. fdrawcmd Pobieramy ten plik i zapisujemy do winuae-base/fdrawcmd. G15 LCD Pobieramy sterowniki. Instalujemy je. Odnajdujemy LCDSDK_3.04.143.zip. Do katalogu winuae-base/logitechlcd wypakowujemy lglcd.h i lglcd.lib. Sterowniki możemy odinstalować. WinIO Do katalogu winuae-base/winio wypakowujemy zawartość katalogu Source. ParaPort Zawartość pliku nagłówkowego zapisujemy do winuae-base/paraport/paraport.h (najlepiej skopiować go ze źródeł strony). wnaspi32 Zapisuje do winuae-base/wnaspi32/wnaspi32.h enet Wypakowujemy do katalogu winuae-base/enet.
Do katalogu winuae-base/winuae/od-win32/winuae_msvc10/ kopiujemy plik solucji z repozytorium GIT. Otwieramy solucje. Usuwamy projekt unpackers. Usuwamy pliki *.obj z projektu winuae. Tworzymy w projekcie folder asm i dodajemy do niego pliki *.asm, które te obj-ty generowały. Zaznaczamy je wszystkie i wybieramy ich właściwości. Wybieramy wszystkie konfiguracje. Jako Item Type wybieramy Custom Build Tool. Naciskamy zastosuj. W Custom Build Tool ustawiamy: Command Line: nasm.exe -O1 -f win32 -Xvc -o "$(IntDir)%(Filename).obj" "%(RootDir)%(Directory)\%(Filename).asm" -w-orphan-labels Description: Compiling asm file %(Filename).asm Outputs: $(IntDir)%(Filename).obj;%(Outputs) Dla projektów build68k, genblitter, gencpu, gencomp dla Pre-Link-Event i Post-Build Event ujednolicamy wpisy dla wszystkich konfiguracji. Dla build68k zmieniamy by zamiast cpudefs.c generowany był plik cpudefs.cpp. Podobnie robimy dla projektu genblitter i genlinetoscr. Ustawiamy zależności pomiędzy projektami: winuae zależy od genblitter, gencpu, gencomp, genlinetoscr, prowizard oraz gencpu i gencomp zależą od build68k. We właściwościach projektu winuae do referencji dodajemy prowizard i ustawiamy Output File na domyślne dla wszystkich konfiguracji. Wybieramy konfiguracje Debug. W właściwościach solucji w Configuration Properties dla wszystkich konfiguracji włączamy w proces budowania tylko build68k, gencpu, gencomp, genblitter, genlinetoscr, prowizard, winuae. Budujemy build68k, genblitter - bez problemów. Budujemy gencomp, mamy błędy. W sysdeps.h dodajemy przed #include <tchar.h> wpis #include <windows.h>. Budujemy ponownie. Budujemy gencpu bez problemu. Budujemy genlinetoscr z problemami. Dla projektów build68k, gencpu, gencomp, genlinetoscr, genblitter dla wszystkich konfiguracji ustawiamy Output Directory i Intermediate Directory na $(Configuration)\, zaś Output File na $(OutDir)\..\$(TargetName)$(TargetExt). Budujemy genlinetoscr ponownie. Budujemy prowizard. Błędy. Kopiujemy z Release do Debug zawartość Additional Include Directories. Budujemy ponownie. Ujednolicamy dla wszystkich projektów zawartość Additional Include Directories. Z braku możliwości znalezienia lib-a do obsługi tabletu musiałem jego obsługę wyłączyć. W skórcie komentujemy #include <wintab.h>, #include <pktdef.h> i eliminujemy reszte błędów. Można także alternatywnie dodać nową definicje preprocesora do sysconfig.h. Usuwamy z Linker/Additional Dependencies wpis wintab32.lib dla wszystkich konfiguracji. Usuwamy z Delay Loaded Dlls wpis wintab32.dll dla wszystkich konfiguracji. Dla projektu winuae ustawiamy Output Directory i Intermediate Directory na $(Configuration)\ dla wszystkich konfiguracji. Ustawiamy Output File na domyślne dla wszystkich konfiguracji. Budujemy projekt winuae. Same błędy związane z brakiem plików nagłówkowych. Uzupełniamy Additional Include Directories o: ..\..\..\catweasel;..\..\..\enet\include;..\..\..\fdrawcmd;..\..\..\freetype\include;..\..\..\libpng;..\..\..\logitechlcd;..\..\..\openal;..\..\..\paraport;..\..\..\portaudio\include;..\..\..\tvicport;..\..\..\unrar;..\..\..\winio\Dll;..\..\..\winpcap\include;..\..\..\wnaspi32;..\..\..\zlib;%(AdditionalIncludeDirectories) Pozostały nam 2 błędy kompilacji: syntax error : identifier 'PARAPORT_INFO' d:\programowanie\c++\moje programy\winuae-base\winuae\od-win32\ioport.cpp Usuwamy: typedef BOOL (*getPortInfo)(HANDLE, PARAPORT_INFO*); static getPortInfo pp_getportinfo; pp_getportinfo = (getPortInfo)GetProcAddress (para, "getPortInfo"); Nic z tego nie korzysta. Co do drugiego błędu: 'Pa_OpenStream' : cannot convert parameter 7 from 'int (__cdecl *)(const void *,void *,unsigned long,const PaStreamCallbackTimeInfo *,PaStreamCallbackFlags,void *)' to 'PaStreamCallback (__stdcall *)' d:\programowanie\c++\moje programy\winuae-base\winuae\od-win32\sounddep\sound.cpp Uzupełniamy w portaudio.h deklarację PaStreamCallback o __cdecl. Budujemy ponownie winuae. Teraz to już tylko błędy linkowania. Ujednolicamy Linker/Additional Dependencies i Delay Loaded Dlls dla wszystkich konfiguracji projektu winuae. Uzupełniamy Additional Library Directories dla wszystkich konfiguracji o: ..\..\..\logitechlcd;..\..\..\winpcap\Lib;..\..\..\openal;..\..\..\freetype\lib Dodajemy do solucji WinIo.vcproj. Dodajemy do referencji winuae projekt WinIO. Usuwamy winio.lib z Linker/Additional Dependencies. Dla projektu WinIO ustawiamy Output Directory i Intermediate Directory na $(Configuration)\ dla wszystkich konfiguracji. Upewnijmy się w solucji że odpowiednie konfiguracje zostały wybrane. Bardzo często się zdarza, że np. dla konfiguracji solucji Release WinIO zostanie ustawiony na Debug. Budujemy WinIO. Budujemy ponownie winuae. Teraz zlib. Robimy dokładnie to co poprzednio w przypadku WinIO. Projekt to zlibstat.vcxproj. Budujemy zlibstat. Budujemy winuae. Teraz libpng. Edytujemy libpng.vcxproj. Usuwamy <import project="$(SolutionDir)\zlib.props">. Dodajemy projekt do solucji. Ustawiamy Output Directory i Intermediate Directory na $(Configuration)\ dla wszystkich konfiguracji. Target Name zaś na $(ProjectName) dla wszystkich konfiguracji. Ustawiamy Additional Include Directories na ..\..\..\..\zlib dla wszystkich konfiguracji. W właściwościach solucji ustawiamy Debug Library i Release Library dla odpowiednich konfiguracji. Dodajemy do referencji winuae projekt libpng. Usuwamy libpng.lib z Linker/Additional Dependencies. Budujemy libpng. Budujemy winuae. Czas na portaudio. Dodajemy do solucji portaudio.vcproj. Dodajemy do referencji winuae projekt portaudio. Ustawiamy Output Directory i Intermediate Directory na $(Configuration)\ dla wszystkich konfiguracji. Ustawiamy Output File na domyślne dla wszystkich konfiguracji. Usuwamy portaudio_x86.lib z Linker/Additional Dependencies dla wszystkich konfiguracji. Budujemy portaudio. Usuwamy z dysku jak i z projektu katalog ASIO. Budujemy. Usuwamy portaudio.def z projektu. Budujemy. Usuwamy dla wszystkich konfiguracji Module Definition File. Dodajmy do symboli preprocesora dla wszystkich konfiguracji PA_NO_ASIO. Zmieniamy w Delay Loaded Dlls portaudio_x86.dll na portaudio.dll. Budujemy. Uwaga: po zmianie nazwy dll-ki nie skorzystamy już z oryginalnej. Budujemy winuae. Czas na enet. Dodajemy do solucji enet.dsp. Dodajemy do referencji winuae projekt enet. Ustawiamy Output Directory i Intermediate Directory na $(Configuration)\ dla wszystkich konfiguracji. Ustawiamy Output File na domyślne dla wszystkich konfiguracji. Usuwamy enet.lib z Linker/Additional Dependencies dla wszystkich konfiguracji. Budujemy enet. Budujemy winuae. Usuwamy prowizard.lib z Linker/Additional Dependencies dla wszystkich konfiguracji. Budujemy winuae. Czas na błędy typu unresolved externals. Dla projektu enet, libpng, prowizard zmieniamy konwencje wywołań na stdcall dla wszystkich konfiguracji. Projekt portaudio nie eksportuje żadnych symboli. Dodajemy pliki nagłówkowe do projektu portaudio. Dodajemy na początek portaudio.h #ifdef _USRDLL #define DELC_EXTERN __declspec(dllexport) #else #define DELC_EXTERN __declspec(dllimport) #endif Dodajemy DELC_EXTERN do każdej funkcji w portaudio.h. Dodajemy _cdecl do każdej funkcji. Czyścimy dla wszystkich konfiguracji Linker/Advanced/Import Library. Otwieramy pcap.h, packet32.h, remote-ext.h i każdą funkcję raportowaną jako unresolved external dekorujemy __cdecl (a najlepiej wszystkie z tych plików). W tym momencie jedyne błędy linkera jakie zostały to multiply symbol define. Dla projektów enet, winuae, portaudio, prowizard, libpng, zlibstat, winio dla wszystkich konfiguracji poustawiajmy prawidłowo Runtime Library. Budujemy winuae. I mamy plik wykonywalny. Jeszcze jeśli idzie o Release. Dodajemy do projektu zlibstat do Pre_Build Event: cd ..\..\masmx86 bld_ml32.bat

2010-03-12

ListBox.SelectAll()

W .NET nie ma takiej metody, a przydałaby się.
public static void SelectAll(this ListBox a_listBox)
{
    a_listBox.BeginUpdate();
    try
    {
        int topIndex = a_listBox.IndexFromPoint(0, 0);
        int selectedIndex = a_listBox.SelectedIndex;
 
        for (int i = 0; i < a_listBox.Items.Count; i++)
            a_listBox.SetSelected(i, true);
 
        if (selectedIndex != -1)
        {
            a_listBox.SetSelected(selectedIndex, false);
            a_listBox.SetSelected(selectedIndex, true);
        }

        a_listBox.TopIndex = topIndex;
    }
    finally
    {
        a_listBox.EndUpdate();
    }
}

2010-03-06

Kowariancja i kontrawariancja w C#

W C# 4.0 rozszerzono możliwości języka o kowariancję i kontrawariancję (zwana dalej KiK) szablonów interfejsów i delegatów. Rozszerzono, czyli KiK jako zjawisko istniało wcześniej. Zanim przejdziemy do omówienia rozszerzenia KiK w C# 4.0 warto dokładniej zapoznać się z pojęciem KiK. Całe poniższe omówienie dotyczy wersji .NET przed 4.0. Dopiero na samym końcu zostaną omówione rozszerzenie dodane w .NET 4.0. Warto też wspomnieć że opisane tutaj problemy mają tylko języki z silnym typowaniem. Źródła na postawie których powstał ten wpis: Fabulous Adventures In Coding - Covariance and Contravariance Wikipedia Covariance and Contravariance FAQ Pojęcie kowariancji i kontrawariancji Mówimy, że operator konwersji (niejawny) jest kowariantny jeśli zawsze zachowuje porządek typów, kontrawariantny jeśli zawsze nie zachowuje porząde typów, niezmienny (invariant) jeśli żadne z poprzednich nie zachodzi. Oznaczmy tą relację porządku jako B<=A, i zdefiniujmy jako: możliwa jest konwersja zmiennej typu B do zmiennej typu A. Przy czym nie ma to nic wspólnego z hierarchią klas. Konwersja jest bezpośrednia (implicity), związku z tym generalnie typy wartościowe nie podpadają pod tą definicję, jako, że mogą one zajmować różną ilość bajtów w pamięci (dokładniej jest to omówione dalej). Generalnie przyjmujemy, że o ile nie zostanie powiedziane inaczej dalej mówimy tylko o typach referencyjnych. Tablice są kowariantne Przykład:
class A { }
class B : A { }

void Test()
{
    A[] a1 = new A[1];
    A[] a2 = new B[1];
    //B[] b1 = new A[1] Błąd kompilacji
    B[] b2 = new B[1]
}
Zachodzi relacja: B<=A i możemy zapisać: dla A=A => A[] = A[] dla B<A => B[] < A[] dla B=B => B[] = B[] ogólnie dla B<=A mamy B[] <= A[], czyli jest to kowariancja. W przypadku tablic typ B musi się wywodzić z lub być równy typowi A. Z drugiej strony B[] nie wywodzi się z A[], oba są podklasami System.Array. Kowariancja tablic ma też swoje wady:
void Test()
{
    A[] a = new B[1];
    a[0] = new A();
}
Druga linijka spowoduje wyjątek, jako, że do tablicy elementów typu B próbujemy podstawić element typu A. Tego rodzaju błąd jest wykrywany w czasie działania programu i z pewnością w pewnym stopniu go spowalnia. Język C# jest językiem silnego typowania, a więc wszelkie niekompatybilności typów powinny być sprawdzane w czasie kompilacji, a nie w trakcie wykonywania programu. Dlaczego więc zdecydowano się na takie rozwiązanie w nie tyle C# 1.0, co w samym CLR. Bo Java to miała, a twórcy CLR chcieli uzyskać zgodność z Javą. Zgryźliwie można by zauważyć, że fajnie by było jakby jeszcze w paru rzeczach też powzorowali się na Javie. Problem ten nie istniałby gdyby tablice były niezmienne (immutable), ale nie są... Konwersja grupy metod do delegatów jest kowariantna w sensie rezultatu Co oznacza nazwa grupa metod:
void A() { }
void A(int x) { }
void A(object x) { }
void A<T>(T x) { }

void B()
{
    Action a1 = A;
    Action<int> a2 = A;
    Action<object> a3 = A;
    Action<int> a4 = A<int>;
}
Do delegata podstawiamy tak naprawdę (za wyjątkiem ostatniego przykładu) nazwę metody. Dopasowanie właściwej zostawiamy regułom kompilatora. Pod nazwą metody może kryć się tak naprawdę wiele metod o różnych sygnaturach, taką grupę nazywamy grupą metod. W dalszym tekście będę się posługiwał delegatami zadeklarowanymi przy użyciu szablonów Action<> i Func<>, które są wygodniejsze w użyciu. Zostały one dodane w .NET 3.0. Oczywiście wszystko poniższe jest też prawdziwe dla delegatów zdefiniowanych bez użycia szablonów. Przykład:
class Animal
{
}

class Dog : Animal
{
}

static Dog Func_Dog()
{
   return null;
}

static Animal Func_Animal()
{
   return null;
}

static void Test1()
{
   Func<Dog> a1 = Func_Dog;
   //Func<Dog> a2 = Func_Animal; Błąd kompilacji.

   Func<Animal> a3 = Func_Dog;
   Func<Animal> a4 = Func_Animal;
}
Mamy: Dog == Dog => Func<Dog> == Func<Dog> Animal > Dog => Func<Animal> > Func<Dog> Animal = Animal => Func<Animal> = Func<Animal> czyli ogólnie dla Dog >= Animal mamy Func<Dog> >= Func<Animal>, czyli jest to kowariancja. Konwersja grupy metod do delegatów jest kontrawariantna w sensie parametrów Przykład:
static void Action_Animal(Animal animal)
{
}

static void Action_Dog(Dog dog)
{
}

static void Test2()
{
   Action<Dog> a1 = Action_Dog;
   Action<Dog> a2 = Action_Animal;

   //Action<Animal> a3 = Action_Dog; Błąd kompilacji.
   Action<Animal> a4 = Action_Animal;
}
Mamy: Dog == Dog => Action<Dog> == Action<Dog> Dog < Animal => Action<Dog> > Action<Animal> Animal = Animal => Action<Animal> = Action<Animal> czyli ogólnie: dla Dog <= Animal mamy: Action<Dog> >= Action<Animal>, czyli jest to kontrawariancja. Dlaczego ref i out nie mogą być KiK Przykład:
class A 
{
    public void TestA() { }
}

class B : A 
{
    public void TestB() { }
}

class Program
{
    A a = new A();
    B b = new B();

    void RefA(ref A a)
    {
        a.TestA();
        a = new A();
    }

    void RefB(ref B b)
    {
        b.TestB();
        b = new B();
    }

    void OutA(out A a)
    {
        a = new A();
    }

    void OutB(out B b)
    {
        b = new B();
        b.TestB();
        RefA(ref a);
        b.TestB();
    }

    void Test()
    {
        RefA(ref a);
        //RefA(ref b); // Błąd kompilacji
        //RefB(ref a); // Błąd kompilacji
        RefB(ref b);

        OutA(out a);
        //OutA(out b); // Błąd kompilacji
        //OutB(out a); // Błąd kompilacji
        OutB(out b);

        a.TestA();
        b.TestB();
    }

    static void Main(string[] args)
    {
        new Program().Test();
    }
}
Skupmy się na przypadkach powodujących błąd kompilacji. Pierwszy podstawia pod zmienną typu B zmienną typ A. Drugi wywróci się na wywołaniu metody A.TestB(). Trzeci spowoduje podstawienie pod zmienną typu B zmiennej typu A. W czwartym drugie wywołanie TestB zawiedzie, będzie to wywołanie A.TestB(). Wniosek: ref i out nie mogą być KiK. siebie takich obiektów. Musiałyby mieć one takie same sygnatury (mieć taką samą reprezentacje w pamięci). Chwila refleksji Zauważmy, że kowariancji jest poprawna tylko dla rezultatu wyjściowego, zaś kontrawariancji dla parametrów wejściowych. Inaczej mówiąc rzutowanie parametrów wyjściowych spełnia definicje kowariancji, rzutowanie parametrów wejściowych spełnia definicje kontrawariancji. Samo pojęcie KiK opisuje zachowanie, nie definiuje go. Pojęcie KiK jest więc w pewnym sensie sztuczne. Na samym początku wspomnieliśmy, że o KiK rozszerzono szablony interfejsów i delegatów. Upraszcza to kod i daje nam możliwość dokonywania bezpośrednich konwersji tam gdzie widzimy, że mogą one zajść bez problemów, a kompilator krzyczy nam błąd. Dodanie takiej możliwości bez rozszerzenia składni prowadziłoby do takich samych problemów jak z tablicami. To dlatego typy generyczne dodane w .NET 2.0 w przeciwieństwie do tablic nie są KiK (aż do .NET 4.0, gdzie KiK stały się szablony interfejsów). Czyli po pierwsze ich dodanie wiąże się z rozszerzeniem składni. I tak jak w przypadku tablic, możliwy jest taki kod który może wygenerować nam wyjątki. Mamy dwie możliwości rozwiązania tego, sprawdzanie na etapie kompilacji, czy kod może generować problemy, lub sprawdzanie wyjątków w czasie działania (jak w tablicach). Ale jeśli wybierzemy drugą opcję, czy w ogóle potrzebujemy specjalnej składni ? Pierwsza opcja gwarantuje nam zachowanie szybkości kodu. Niewątpliwą zaletą KiK jest uproszczenie kodu. Naturalne wydaje się też uczynienie KiK obecnych w bibliotece .NET typów. Alternatywne rozwiązanie w postaci inaczej nazwanych albo znajdujących się w innej przestrzeni nazw typów komplikuje zbytnio sprawę. Ale czy czyniąc dotychczasowe interfejsy w bibliotece .NET KiK nie spowodujemy, że obecny kod przestanie się kompilować lub co gorsza zacznie działać błędnie. Rozbijmy to na przypadki. Szablony interfejsów i delegatów - przykład zastosowania w .NET 4.0 Popatrzmy na przykład:
interface IZoo<out T> where T : Animal
{
   T[] GetAnimals();
}

interface ICage<in T> where T : Animal
{
   void Trap(T animal);
}

class Zoo<T> : IZoo<T> where T : Animal
{
   public T[] GetAnimals()
   {
       return null;
   }
}

class Cage<T> : ICage<T> where T : Animal
{
   public void Trap(T animal)
   {
   }
}

void Test()
{
   IZoo<Dog> zoo1 = new Zoo<Dog>();
   Dog[] a1 = zoo1.GetAnimals();
   //IZoo<Dog> zoo2 = new Zoo<Animal>(); Błąd kompilacji
   //Dog[] a = zoo2.GetAnimals() Błąd kompilacji
   IZoo<Animal> zoo4 = new Zoo<Animal>();

   ICage<Dog> cage1 = new Cage<Dog>();
   ICage<Dog> cage2 = new Cage<Animal>();
   //ICage<Animal> cage3 = new Cage<Dog>(); Błąd kompilacji
   //cage3.Trap(new Animal()); Błąd kompilacji
   ICage<Animal> cage4 = new Cage<Animal>();
}
Identyczny przykład moglibyśmy stworzyć dla szablonów delegatów. Zauważmy, że kłopoty które mamy przy tablicach zostały tutaj uniknięte przez odpowiednie zaprojektowanie reguł. Może nie do końca. O regułach które zapobiegają tym błędom dalej. Błędów niespójności tablic nie unikniemy w dalszym ciągu: zoo3.GetAnimals()[0] = new Animal(); i mamy wyjątek. Uproszczenie kodu osiągane dzięki KiK Przykład na kowariancję: C# 3.0:
class A { }
class B : A { }

class Program
{
    static void Process(IEnumerable<A> e)
    {
    }

    static void Main(string[] args)
    {
        List<A> la = new List<A>();
        List<B> lb = new List<B>();

        Process(la);
        Process(lb.Cast<A>());
    }
}
Moglibyśmy uczynić metodę Process szablonem i ewentualnie użyć where dla dostępu w niej do metod klasy A. W C# 4.0 kod może być prostszy, szybszy, bardziej naturalny dzięki zrezygnowaniu z Cast<T>(). Nawet nie używając KiK w swoich klasach interakcja z metodami z biblioteki .NET w wielu przypadkach będzie szybsza i bardziej naturalna. Tak sobie myślę, że KiK dodano do C# by usprawnić i przyspieszyć LINQ, w którym często zdarzało mi się używać Cast<T>(), teraz wszystko jest bardziej naturalne. Niewątpliwie potrzeba KiK narastała, aż przekroczyła masę krytyczną i dodaną ją do specyfikacji. Na samym końcu jest poruszona sprawa kiedy KiK dodano do specyfikacji IL. Przykład na kontrawariancję: Kod w C# 3.5:
class A 
{
    public int Value = 0;
}

class B : A 
{ 
}

class Comparer_A : IComparer<A>
{
    public int Compare(A x, A y)
    {
        return x.Value - y.Value;
    }
}

class Comparer_B : IComparer<B>
{
    public int Compare(B x, B y)
    {
        return x.Value - y.Value;
    }
}

class Program
{
    static void Main(string[] args)
    {
        List<A> la = new List<A>();
        List<B> lb = new List<B>();

        la.Sort(new Comparer_A());
        lb.Sort(new Comparer_B());
    }
}
W C# 4.0 możemy zapisać jako:
class Program
{
    static void Main(string[] args)
    {
        List<A> la = new List<A>();
        List<B> lb = new List<B>();

        la.Sort(new Comparer_A());
        lb.Sort(new Comparer_A()); 
    }
}
Dlaczego wybrano taki sposób oznaczania składni O tym czy w ogóle potrzebujemy specjalnej składni dla KiK mówię dalej. Tutaj skupmy się na tym jakie mamy możliwości oznaczenia, potencjalni kandydaci:
  • poprzez atrybuty nałożone na szablon typu lub parametry typu
  • poprzez ograniczenie szablonów where
  • poprzez odpowiednie udekorowanie parametrów szablonu
Tak jak wspomniałem wcześniej terminy KiK słabo się kojarzą i ciężko je odróżnić, stąd jako oznaczenia odpadły w przebiegach. Padały propozycje na składnie z + i -, na zapisy typu iEnumerable<T:*>. Ostatecznie stanęło na in i out (może kiedyś dowiemy się jakie były kulisy, może po zażartych bojach postanowiono losować?). Dlaczego? Słowo kluczowe out w szablonie oznacza, że dopuszczamy w szablonach wywodzących się z tego szablonu, by rezultaty metod mogły być zwracane jako klasy bazowe rezultatu. Słowo kluczowe in oznacza, że dopuszczamy, by nasza metoda mogła przyjmować jako parametry klasy pochodne. Jeszcze większego znaczenia in i out nabierają później, kiedy omówimy jakie są restrykcje na parametry szablonów oznaczone jako KiK. Czy potrzebujemy rozszerzenia składni Niewątpliwie brak rozszerzenia składni oznacza pojawienie się takich samych problemów jak przy tablicach. Każda operacja zarówno zwracanie wyniku metody, jak i wywołanie metody wymagałaby sprawdzania typu, byłoby to kłopotliwe i spowalniałoby działanie aplikacji. Ale czy tylko ? Kod może także przestać działać poprawnie lub przestać się kompilować. Więcej o tym później. Rozszerzenie składni dają programiście szansę przejście na .NET 4.0 bez zaprzątania sobie głowy tematem KiK w własnym kodzie. Aczkolwiek kod korzystający z biblioteki standardowej gdzie zastosowano KiK może zacząć błędnie działać. Więcej o tym później. Poniższy fragment warto zostawić sobie na drugie czytanie. Przypuśćmy, że nie ma rozszerzenia składni. Jeśli nie chcemy opierać się na wyjątkach w wielu przypadkach musielibyśmy zgadnąć jakiego rodzaju są parametry szablonu (wnioskując z kontekstu). Oznaczałoby to analizę kodu przed podjęciem decyzji (wydłużenie czasu kompilacji) i poza tym nie jest zbyt mądre. Dotychczasowo język C# został tak zaprojektowany, że jak kompilator musi zgadywać, to mamy błąd kompilacji i musimy mu ręcznie podpowiedzieć (tutaj przy użyciu rozszerzenia składni). Samo to zgadywanie jest problematyczne. Wyobraźmy sobie, że że stworzyliśmy obiekt który wykorzystujemy w sposób kowariancyjny. Teraz dodajemy metodę i wykorzystujemy go w sposób kontrawariancyjny. Prowadzi to do konfliktu. Mamy do wybory inaczej napisać ostatnio dodaną metodę, albo zmienić te poprzednie. Dosyć problematyczne. Decydujemy się jednak na wyjątki. Wtedy zawsze 4 z 4 możliwych podanych w różnych przykładach konwersji byłyby możliwe. Wyjątki o niezgodności typów mielibyśmy wtedy zarówno przy wywoływaniu metody jak i zwracaniu wyniku. O ile tym pierwszym zajęłoby się ciało metody i to w tym drugim?. Sprawdzać za każdym razem? Osobna metoda na to sprawdzanie? Dodatkowy ukryty parametr do metody? Poza tym jakie metody mamy tym oznaczyć? W przypadku szablonów klas wystarczy analiza statyczna kodu, ale w przypadku delegatów chyba w całym kodzie musielibyśmy prowadzić takie sprawdzanie. Same problemy... Czy możemy błędy sprawdzać w czasie wykonywania Tak więc dodatkowa składnia jest potrzebna. Ale ciągle możemy przecież sprawdzać błędy w czasie działania, a nie w czasie kompilacji. A dlaczego? Po to jest ta składnia, aby niczego nie musieć sprawdzać. Czy dotychczasowy kod może zacząć błędnie działać Może. W wyniku modyfikacji parametrów szablonu o in albo out inaczej może zacząć działać is oraz określanie właściwej przeładowanej metody do wywołania w trakcie kompilacji. W pierwszym przypadku IZoo<Dog> staje się konwertowalne na IZoo<Animal> przez co operator is może inaczej zadziałać w czasie wykonywania. W drugim w czasie kompilacji może zostać wybrana inna przeładowana metoda niż dotychczas. Błędy te mogą się pojawić w naszym kodzie nawet jeśli nie wprowadzimy w nim zmian, gdyż wiele standardowych typów w .NET zostało wyposażonych w KiK. Oczywiście zostaje jeszcze nam sytuacja gdy na jakimś naszym typie danych dopuścimy kontrawariancję. Wtedy zupełnie legalnie, jakaś nasza metoda może otrzymywać jako parametry obiekty różnych typów (klasa bazowa dla której szablon został domknięty i jego pochodne). Kod może ale nie musi przestać działać. Kontrawariancja ref i out Słowa kluczowe ref i out dotyczą parametrów wejściowych metod, więc w tym przypadku możemy mówić tylko o kontrawariancji. W przypadku ref nie można przekazać do metody elementu klasy pochodnej:
void TestRef(ref Animal a)
{
   a = new Animal();
}

void Test3()
{
   Dog a2 = null;
   TestRef(a);
}
Zmienna typu Dog przyjęłaby typ Animal. Jeśli chodzi o out mamy dokładnie ten sam problem. Wniosek jaki z tego płynie dla szablonów interfejsów jest taki, że kontrawariantne parametry szablonu nie mogą być używane jako parametry metod oznaczone out i in. Błąd ten jest wykrywany jest podczas kompilacji. Jak realizowane jest sprawdzanie poprawności kodu na etapie kompilacji Używając słów kluczowych out i in możemy stworzyć niepoprawny kod:
interface IList1<T>
{
   void Add(T ele);
   T First();
}

interface IList2<out T>
{
   void Add(T ele);
   T First();
}

interface IList3<in T>
{
   void Add(T ele);
   T First();
}

interface IList4<in out T>
{
   void Add(T ele);
   T First();
}
W pierwszym przypadku lista może przyjmować tylko elementy klasy T. W przypadku drugim można ją skonwertować na listę klasy bazowej T, ale to oznacza, że taki element możemy dodać do listy, jest to ten sam problem to konwersją tablicy: metoda Add() staje się problematyczna. W trzecim przypadku lista może zostać skonwertowana na listę klasy pochodnej od T i tutaj metoda First() staje się problematyczna. Czwarty przypadek (niepoprawny składniowo: out i in nie może być użyte jednocześnie) jest problematyczny w obu metodach. Jaki z tego wniosek: kowariantny parametr szablonu interfejsu nie może być używany jak typ parametrów metod, zaś kowariantny jako typ rezultatu metod. Błędy tego typu zostaną wychwycone na etapie kompilacji. Dlatego właśnie kolekcje z metodami odczytu i wstawiania nie można uczynić kowariantnymi. Tutaj Immutability in C# Part Three: A Covariant Immutable Stack mamy przykład kowariantnej kolejki. Poniższy kod też prowadzi do błędu kompilacji:
interface IZoo<out T>
{
    T[] GetAnimals();
}

interface IZoo2<in T> : IZoo<T>
{
}
Raz zadeklarowane jako KiK parametry szablonu nie mogą zmieniać swojego rodzaju. Prowadziłoby to do takich samych błędów jak wspomniane wcześniej. Ten kod jest poprawny, jakkolwiek klasa ILT jest niezmienna (invariant).
interface IL<in T>
{
}

class ILT : IL<int>
{
}
Dzięki takiemu zachowaniu nie potrzebujemy dla typów wartościowych definiować osobnych typów szablonowych. Dlaczego szablony klas nie mogą być KiK Z dotychczasowych rozważań wynika, że nie powinno być z tym problemu. Przykład (niekompilujący się):
class A
{
}

class B : A
{
}

class Covariant<out T> 
{
    public event Action<T> e;
    public T a;
    public T aa { get; set; }
    public T Get() { return default(T); }
}

class Contravariant<in T>
{
    public event Func<T> e;
    public readonly T a;
    public T aa { get { return default(T); } }
    public T Get() { return default(T); }
    public void Set(T t) { }
}

void Test()
{
    Covariant<A> a = new Covariant<B>();
    A a1 = a.a;
    A a2 = a.aa;
    A a3 = a.Get();
    a.e += (aa) => { };

    Contravariant<B> b = new Contravariant<A>();
    b.Set(new B());
    b.e += () => { return new B(); };

}
Jak widzimy ograniczenia dla szablonów klas KiK są konsekwentnym rozszerzeniem ograniczeń dla szablonów interfejsów KiK. Co stało za tym, że szablony klas nie mogą być KiK nie wiem. Dlaczego nie typy wartościowe Tutaj wyjaśnienie jest prostsze. W przypadku typów które zajmują taki sam obszar pamięci i są konwertowalne bezpośrednio byłoby to możliwe, w innych przypadkach nie. Ostatecznie tak jak w przypadku tablic, zrezygnowano z KiK typów wartościowych. Jakie typy zostały rozszerzone w .NET 4.0 Interfejsy:
  • IEnumerable<out T>
  • IEnumerator<out T>
  • IQueryable<out T>
  • IGrouping<out TKey, out TElement>
  • IComparer<in T>
  • IEqualityComparer<in T>
  • IComparable<in T>
Delegaty:
  • Action<in T1, ... >
  • Func<out TResult>
  • Func<in T, ..., out TResult>
  • Predicate<in T>
  • Comparison<in T>
  • Converter<in TInput, out TOutput>
Zachowanie kowariancji i kontrawariancji podczas dziedziczenia Przykład:
interface IZoo1<out T>
{
    T GetFirstAnimal();
}

interface IZoo2<out T> : IZoo1<T>
{
}

interface IZoo3<T> : IZoo1<T>
{
}

class Zoo2 : IZoo2<Dog>
{
    public Dog GetFirstAnimal()
    {
        return null;
    }
}

class Zoo3 : IZoo3<Dog>
{
    public Dog GetFirstAnimal()
    {
        return null;
    }
}

static void Test()
{
    Zoo2 zoo2 = new Zoo2();
    IZoo2<Animal> zoo2a = zoo2;

    Zoo3 zoo3 = new Zoo3();
    //IZoo3<Animal> zoo3a = zoo3; Błąd kompilacji
}
IZoo2 jest kowariantne. IZoo3 jest niezmienne (invariant), pomimo tego, że interfejs bazowy jest kowariantny. Kowariancja i kontrawariancja w CLR Wsparcie dla KiK pojawiło się w CLR 2.0 wraz z nadejściem .NET 2.0. To prawie 5 lat temu. Dopiero teraz pojawiło się w C#. Do oznaczania KiK CLR używa oznaczeń + i -.