| Both sides previous revisionPrevious revisionNext revision | Previous revision |
| pl:edu:zmitac:n1z:ja:lab4 [2021/04/11 15:40] – [WPF] pczekalski | pl:edu:zmitac:n1z:ja:lab4 [2021/04/24 17:07] (current) – [Ćwiczenie 3: Integracja kodu niezarządzalnego, tworzonego w języku Assembler wraz z aplikacją typu WPF w .NET C#, instrukcje SIMD] pczekalski |
|---|
| ===== Ćwiczenie 3: Integracja kodu niezarządzalnego, tworzonego w języku Assembler wraz z aplikacją typu WPF w .NET C#, instrukcje SIMD ===== | ===== Ćwiczenie 4: Integracja kodu niezarządzalnego, tworzonego w języku Assembler wraz z aplikacją typu WPF w .NET C#, instrukcje SIMD ===== |
| |
| |
| W ramach niniejszego laboratorium użyjemy projektu analogicznego do projektu w laboratorium nr [[pl:edu:zmitac:n1z:ja:lab3|3]], przy czym zamiast aplikacji konsolowej w języku C# zostanie wykorzystany frontend w modelu WPF (aplikacja okienkowa). | W ramach niniejszego laboratorium użyjemy projektu analogicznego do projektu w laboratorium nr [[pl:edu:zmitac:n1z:ja:lab3|3]], przy czym zamiast aplikacji konsolowej w języku C# zostanie wykorzystany frontend w modelu WPF (aplikacja okienkowa). |
| |
| ==== WPF ==== | ==== WPF i assembler ==== |
| Windows Presentation Fundation (WPF) to w skrócie sposób tworzenia interfejsów użytkownika dla systemu Windows z wykorzystaniem środowiska .NET (C#, F#, VB.NET). WPF zastępuje poprzednie Windows Forms, związane z kodem niezarządzalnym i językiem C++. WPF cechuje się tym, że widok (layout, okienka i układ kontrolek) jest odseparowany od kodu zarządzającego zawartością i interakcją. Wygląd jest zapisany w postaci osobnych plików z rozszerzeniem ''.xaml'', a kod reprezentujący okienko jest w dużej mierze generowany "w locie" podczas edycji wyglądu w edytorze Visual Studio (tzw "Code Behind"). Odseparowanie wyglądu od kodu pozwala na tworzenie modyfikacji wyglądu przez grafików, bez udziału programistów (tworzenie tzw. skórek). Dla każdego widoku (okienka) są zatem tworzone co najmniej dwa pliki: jeden z kodem, drugi z plikiem XML (''.xaml'') reprezentującym położenie i cechy graficzne. WPF jest skalowalny i posiada możliwość tworzenia inerfejsów użytkownika, które automatycznie dostosowują się do proporcji i rozmiarów ekranu oraz ich rozdzielczości i wymiarów (DPI). Więcej informacji można znaleźć w oficjalnym portalu Microsoft((https://docs.microsoft.com/en-us/dotnet/desktop/wpf/getting-started/walkthrough-my-first-wpf-desktop-application?view=netframeworkdesktop-4.8)) lub na wielu innych stronach. WPF na dzień dzisiejszy jest wiodącym środowiskiem tworzenia aplikacji okienkowych dla Windows w środowisku .NET. | Windows Presentation Fundation (WPF) to w skrócie sposób tworzenia interfejsów użytkownika dla systemu Windows z wykorzystaniem środowiska .NET (C#, F#, VB.NET). WPF zastępuje poprzednie Windows Forms, związane z kodem niezarządzalnym i językiem C++. WPF cechuje się tym, że widok (layout, okienka i układ kontrolek) jest odseparowany od kodu zarządzającego zawartością i interakcją. Wygląd jest zapisany w postaci osobnych plików z rozszerzeniem ''.xaml'', a kod reprezentujący okienko jest w dużej mierze generowany "w locie" podczas edycji wyglądu w edytorze Visual Studio (tzw "Code Behind"). Odseparowanie wyglądu od kodu pozwala na tworzenie modyfikacji wyglądu przez grafików, bez udziału programistów (tworzenie tzw. skórek). Dla każdego widoku (okienka) są zatem tworzone co najmniej dwa pliki: jeden z kodem, drugi z plikiem XML (''.xaml'') reprezentującym położenie i cechy graficzne. WPF jest skalowalny i posiada możliwość tworzenia inerfejsów użytkownika, które automatycznie dostosowują się do proporcji i rozmiarów ekranu oraz ich rozdzielczości i wymiarów (DPI). Więcej informacji można znaleźć w oficjalnym portalu Microsoft((https://docs.microsoft.com/en-us/dotnet/desktop/wpf/getting-started/walkthrough-my-first-wpf-desktop-application?view=netframeworkdesktop-4.8)) lub na wielu innych stronach. WPF na dzień dzisiejszy jest wiodącym środowiskiem tworzenia aplikacji okienkowych dla Windows w środowisku .NET. |
| |
| |
| Jeśli konwersja nie może zostać dokonana, ponieważ nie da się "przetłumaczyć" łańcucha na zmienną określowego typu, zostanie zgłoszony wyjątek i wykonywanie programu zostanie przerwane. Oczywiście istnieją mechanizmy walidacji, które pozwalają na wykrycie tego typu sytuacji oraz ich obsługę, niemniej jednak wykraczają one poza ramy niniejszej instrukcji. | Jeśli konwersja nie może zostać dokonana, ponieważ nie da się "przetłumaczyć" łańcucha na zmienną określowego typu, zostanie zgłoszony wyjątek i wykonywanie programu zostanie przerwane. Oczywiście istnieją mechanizmy walidacji, które pozwalają na wykrycie tego typu sytuacji oraz ich obsługę, niemniej jednak wykraczają one poza ramy niniejszej instrukcji. |
| | |
| | Aby zaprezentować wynik, należy wykonać operację odwrotną: zamienić zmienną numeryczną na łańcuch znaków (''String'') i przypisać wartość polu ''Text'' wskazanej kontrolki. W tym celu należy użyć funkcji ''ToString()'' wywoływanej "na" wskazanej zmiennej, np. tak: |
| | <code c> |
| | ... |
| | tbC4.Text = aResults[3].ToString(); |
| | ... |
| | </code> |
| | |
| | === Pozostałe ustawienia solucji === |
| | W projekcie WPF należy włączyć możliwość kompilacji kodu typu ''unsafe'' oraz debbugowanie kodu natywnego, analogicznie jak to opisano w instrukcji laboratoryjnej [[pl:edu:zmitac:n1z:ja:lab3|Ćwiczenie 3: Integracja kodu niezarządzalnego, tworzonego w języku Assembler wraz z kodem zarządzalnym .NET C#]]. |
| | |
| | Należy też pamiętać o wybraniu wspólnego folderu do generowania wynikowego kodu z projektu w assemblerze i projektu w języku C#, analogicznie jak to opisano w instrukcji laboratoryjnej [[pl:edu:zmitac:n1z:ja:lab3|Ćwiczenie 3: Integracja kodu niezarządzalnego, tworzonego w języku Assembler wraz z kodem zarządzalnym .NET C#]]. W przypadku projektów .NET, folder, do którego wykonuje się kompilację ustawia się we właściwościach projektu (prawym przyciskiem myszy należy kliknąć nagłówek projektu, a następnie wybrać "Properties" ("Właściwości"), i dalej w sekcji "Build" w grupie "Output path:" należy wskazać docelową ścieżkę (rysunek {{ref>asm4_10}}). Aby móc wybrać folder, musi on istnieć na dysku, zatem wskazane jest przeprowadzenie "pustej" kompilacji wyłącznie projektu DLL, aby folder ten został utworzony. |
| | |
| | <figure asm4_10> |
| | {{:pl:edu:zmitac:n1z:ja:buildtargetwpf.png?600|}} |
| | <caption>Ustawienie docelowego folderu na wyniki kompilacji projektu WPF</caption> |
| | </figure> |
| | |
| | W przypadku, gdyby pomimo ustawienia wspólnego folderu wynikowego nadal były zgłaszane błędy braku biblioteki DLL, istnieje możliwość, że włączony jest system tworzenia dodatkowych podfolderów, jak to opisano poniżej w notce z uwagami. Aby usunąć ten problem, konieczne jest ręczna edycja pliku konfiguracyjnego projektu.\\ Aby otworzyć plik konfiguracyjny projektu, należy dwukrotnie klinąć nazwe projektu w drzewie. Otworzy się plik typu XML, z rozszerzeniem ''.csproj''. |
| | |
| | <note warning>Uwaga! W przypadku projektu WPF w folderze wynikowym może pojawić się podfolder z wersją frameworku .NET, co w praktyce spowoduje, że plik wykonywalny z frontside (GUI) zostanie uruchomiony w innym folderze (w podfolderze) w stosunku do wybranego w ustawieniach, skutkiem czego biblioteka DLL nie zostanie załadowana i zostanie zgłoszony wyjątek. Podfoldery te są dodawane automatycznie. Aby usunąć tę niedogodność, w pliku projektu WPF należy mieścić dodatkową sekcję((https://docs.microsoft.com/en-us/visualstudio/ide/how-to-change-the-build-output-directory?view=vs-2019)): |
| | <code xml> |
| | |
| | <PropertyGroup> |
| | <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> |
| | <AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath> |
| | </PropertyGroup> |
| | |
| | </code> |
| | </note> |
| | |
| | |
| | |
| | === Assembler === |
| | Do solucji należy dodać projekt w assemblerze. Kod w assemblerze należy skompilować do biblioteki DLL, a następnie dołączyć ją dynamicznie do rozwiązania w C#. Jest to dokładnie ten sam proces, który został opisany w [[pl:edu:zmitac:n1z:ja:lab3|Ćwiczenie 3: Integracja kodu niezarządzalnego, tworzonego w języku Assembler wraz z kodem zarządzalnym .NET C#]] w zakresie tworzenia i konfiguracji części assemblerowej oraz jej udostępniania po stronie C#, natomiast zamiast aplikacji konsolowej występuje aplikacja WPF. |
| ==== Zadanie do wykonania ==== | ==== Zadanie do wykonania ==== |
| Należy utworzyć prosty kalkulator, który przeliczy wektorowo dla 4 zmiennych wyniki dla operacji dodawania, odejmowania, mnożenia i dzielenia (akcje na osobnych przyciskach). Wymagane jest przeliczenie więcej niż jednego zestawu danych w ramach jednej instrukcji dodawania (trzeba skorzystać z SIMD). Każde zadanie należy wykonać w dwóch wariantach: z nasyceniem i bez nasycenia. | Należy utworzyć prosty kalkulator, który przeliczy wektorowo dla 4 zmiennych wyniki dla operacji dodawania, odejmowania, mnożenia i dzielenia (akcje na osobnych przyciskach). Wymagane jest przeliczenie więcej niż jednego zestawu danych w ramach jednej instrukcji dodawania (trzeba skorzystać z SIMD). Zadanie należy wykonać na typach zmiennoprzecinkowych, pojedynczej precyzji (32 bity, po stronie C# typ ''float''). |
| |
| Należy zadeklarować po jednej funkcji assemblerowej dla każdej operacji. | Należy zadeklarować po jednej funkcji assemblerowej dla każdej operacji. |
| Wartości najwygodniej będzie przekazać do funkcji assemblerowych wpisując je po stronie kodu w C# do dwóch tablic źródłowych, a na wyniki zaalokować tablicę trzecią. Liczba zmiennych jest znana (4) i stała, zatem długości tablic nie trzeba przekazywać do funkcji assemblerowej. Wskaźniki na tablice można przekazać poprzez rejestry lub poprzez wskaźnik na strkturę zawierającą wskaźniki do ww. tablic (do wyboru). | Wartości najwygodniej będzie przekazać do funkcji assemblerowych wpisując je po stronie kodu w C# do dwóch tablic źródłowych, a na wyniki zaalokować tablicę trzecią. Liczba zmiennych jest znana (4) i stała, zatem długości tablic nie trzeba przekazywać do funkcji assemblerowej. Wskaźniki na tablice można przekazać poprzez rejestry lub poprzez wskaźnik na strkturę zawierającą wskaźniki do ww. tablic (do wyboru). |
| |
| | Tablice po stronie C# można zadeklarować np następująco: |
| | <code c> |
| | ... |
| | public partial class MainWindow : Window |
| | { |
| | public float[] aArg1 = { 0, 0, 0, 0 }; //argument 1 |
| | public float[] aArg2 = { 0, 0, 0, 0 }; //argument 2 |
| | public float[] aResults = { 0, 0, 0, 0 }; //wynik |
| | ... |
| | </code> |
| | |
| | <note tip>Powyżej dołączono kontekst: zmienne zostały umieszczone w klasie okienka. Słowo kluczowe ''partial'' w deklaracji typu klasy MainWindow oznacza, że w pliku zaimplementowana jest jedynie część klasy. Pozostała część kodu znajduje się w pliku zarządzanym automatycznie przez edytor GUI okienka. Dla dewelopera w praktyce ten mechanizm jest przezroczysty, a cały kod należy umieszczać w tym pliku (''.xaml.cs'').</note> |
| | |
| | <note important>Część instrukcji SIMD (SSE, AVX) wymaga specyficznego ułożenia danych w pamięci, w szczególności wyrównania (umieszczenia zmiennej pod adresem podzielnym przez 16). C# nie daje gwarancji umieszczenia początkowego elementu w pamięci pod adresem zgodnym z wymaganiami kodu niezarządzalnego (pomimo wyrównywania zmiennych do 64 bitów). W związku z tym, przy dostępie do pamięci, nie należy stosować instrukcji assemblerowych, wymagających wyrównania, w szczególności np. nie należy stosować ''MOVAPS'', tylko jej wolniejszą wersję, nie wymagającą specyficznego wyrównania zmiennych w pamięci, czyli ''MOVUPS''. </note> |
| | |
| | <note warning>Użycie instrukcji wymagającej wyrównania może powodować, że program będzie losowo działał i rzucał wyjątek (nawet w kolejnych uruchomieniach będzie zachowywał się niepowtarzalnie), ponieważ może się tak zdarzyć, że zmienna akurat będzie poprawnie ułożona pod adresem podzielnym przez 16, przez przypadek.</note> |
| ==== Metoda weryfikacji wyników ==== | ==== Metoda weryfikacji wyników ==== |
| Sprawdzenie debuggerem wraz z podglądem rejestrów / stanu pamięci oraz obserwacja wyników i sprawdzenie obliczeń. | Sprawdzenie debuggerem wraz z podglądem rejestrów / stanu pamięci oraz obserwacja wyników i sprawdzenie obliczeń. |
| ==== Pomocne zasoby ==== | ==== Pomocne zasoby ==== |
| Visual Studio 2019: [[https://visualstudio.microsoft.com/vs/]] | Visual Studio 2019: [[https://visualstudio.microsoft.com/vs/]] |