User Tools

Site Tools


No ACL setup yet! Denying access to everyone.
pl:edu:zmitac:n1z:ja:lab3

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
pl:edu:zmitac:n1z:ja:lab3 [2021/04/10 17:16] – [Powtórka z teorii: Przekazywanie danych do funkcji] pczekalskipl:edu:zmitac:n1z:ja:lab3 [2022/04/23 16:03] (current) – [Konfiguracja środowiska] pczekalski
Line 62: Line 62:
   * w celu poinformowania Garbage Collectora o zakazie relokacji zmiennej na czas wywołania funkcji w assemblerze należy użyć słowa kluczowego ''fixed'' po stronie kodu .NET (C#) ((https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/fixed-statement)) - fixed należy używać równolegle z ''unsafe'';    * w celu poinformowania Garbage Collectora o zakazie relokacji zmiennej na czas wywołania funkcji w assemblerze należy użyć słowa kluczowego ''fixed'' po stronie kodu .NET (C#) ((https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/fixed-statement)) - fixed należy używać równolegle z ''unsafe''; 
  
-<note warning>Błędy związane z brakiem zastosowania powyższych reguł (relokacją pamięci przez Garbage Collector i następującym po nim odwołaniem z poziomu języka assembler) są niezwykle trudne do wykrycia, ponieważ aplikacja zachowuje się nieprzewidywalnie: raz działa, innym razem nie. Wynika to z faktu, że Garbage Collector może, ale nie musi przesunąć zmiennej, zatem kod w takim przypadku może działać "przez przypadek", ponadto jego uruchomienie jest zależne od wielu czynników, w tym np. od obciążenia pamięci operacyjnej przez inne procesy, co oznacza że dwukrotne uruchomienie aplikacji może powodować różne działanie.</note>+<note warning>Błędy związane z brakiem zastosowania powyższych reguł (relokacją pamięci przez Garbage Collector i następującym po nim odwołaniem z poziomu języka assembler) są niezwykle trudne do wykrycia, ponieważ aplikacja zachowuje się nieprzewidywalnie: raz działa, innym razem nie. Wynika to z faktu, że Garbage Collector może, ale nie musi przesunąć zmiennej, zatem kod w takim przypadku może działać "przez przypadek". Działanie ''Garbage Collectora'' jest zależne od wielu czynników, w tym np. od obciążenia pamięci operacyjnej przez inne procesy, co oznacza że dwukrotne uruchomienie aplikacji może powodować jej różne działanie.</note>
  
 ==== Dołączanie biblioteki DLL do kodu w C# ==== ==== Dołączanie biblioteki DLL do kodu w C# ====
Line 93: Line 93:
 <note important>Uwaga! Należy zapewnić, aby skompilowana biblioteka DLL (tutaj ''DLLAsm.dll'') znalazła się w tym samym folderze, w którym znajdzie się skompilowana aplikacja .NET. W przeciwnym wypadku podczas uruchamiania zostanie rzucony wyjątek o braku możliwości zainstalowania biblioteki. W kolejnym rozdziale znajduje się infomacja, jak skonfigurować projekty, aby generowały wyniki do wspólnego folderu.</note> <note important>Uwaga! Należy zapewnić, aby skompilowana biblioteka DLL (tutaj ''DLLAsm.dll'') znalazła się w tym samym folderze, w którym znajdzie się skompilowana aplikacja .NET. W przeciwnym wypadku podczas uruchamiania zostanie rzucony wyjątek o braku możliwości zainstalowania biblioteki. W kolejnym rozdziale znajduje się infomacja, jak skonfigurować projekty, aby generowały wyniki do wspólnego folderu.</note>
  
-<note tip>Można również pozostawić bibliotekę w pierwotnej lokalizacji o zmodyfikować dyrektywę ''DLLImport'' podając ścieżkę dostępu przed nazwą pliku DLL, względną lub absolutną. W przypadku użycia tej ostatniej, nie będzie można zmienić lokalizacji biblioteki.</note>+<note tip>Można również pozostawić bibliotekę w pierwotnej lokalizacji i zmodyfikować dyrektywę ''DLLImport'' podając ścieżkę dostępu przed nazwą pliku DLL, względną lub absolutną. W przypadku użycia tej ostatniej, nie będzie można zmienić lokalizacji biblioteki, a w przypadku skopiowania biblioteki w inne miejsce na dysku, przestanie ona działać.</note>
  
 Uwaga na przekazywane i zwracane typy! Informacja o tym, gdzie znaleźć zmienne po stronie wywoływanej funkcji znajduje się w opisie do [[pl:edu:zmitac:n1z:ja:lab1|Laboratorium JA ćw. 1]].\\ Uwaga na przekazywane i zwracane typy! Informacja o tym, gdzie znaleźć zmienne po stronie wywoływanej funkcji znajduje się w opisie do [[pl:edu:zmitac:n1z:ja:lab1|Laboratorium JA ćw. 1]].\\
Line 178: Line 178:
 <note warning>Przed wykonaniem pierwszej kompilacji folder nie zostanie założony, zatem nie będzie można go wybrać za pomoca przycisku [Browse...]. W związku z tym należy wykonać pustą kompilację wyłącznie projektu DLL w celu utworzenia folderu wynikowego.</note> <note warning>Przed wykonaniem pierwszej kompilacji folder nie zostanie założony, zatem nie będzie można go wybrać za pomoca przycisku [Browse...]. W związku z tym należy wykonać pustą kompilację wyłącznie projektu DLL w celu utworzenia folderu wynikowego.</note>
 <note warning>Powyższa konfiguracja zapisuje na stałe ścieżkę w konfiguracji projektu. Oznacza to, że zmiana z trybu Debug na Release wymaga zmiany ww. ścieżki!</note> <note warning>Powyższa konfiguracja zapisuje na stałe ścieżkę w konfiguracji projektu. Oznacza to, że zmiana z trybu Debug na Release wymaga zmiany ww. ścieżki!</note>
 +
 +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>
  
 === Konfiguracja na potrzeby kodu unsafe oraz debuggowania === === Konfiguracja na potrzeby kodu unsafe oraz debuggowania ===
 Do ustawienia pozostają jeszcze dwie opcje: Do ustawienia pozostają jeszcze dwie opcje:
   * Włączenie użycia kodu "unsafe", o ile dyrektywa "unsafe" jest wymagana. Dyrektywy tej nie trzeba używać m.in. w przypadku wykorzystywania struktury ''IntPtr'' do przekazywania wskaźników oraz w przypadku przekazywania argumentów przez wartość, niemniej jednak zalecane jest jej włączenie. W tym celu we właściwościach projektu w języku C# należy w sekcji "Build" w grupie"General" zaznaczyć opcję "Allow unsafe code" (rysunek {{ref>asm3_13}});   * Włączenie użycia kodu "unsafe", o ile dyrektywa "unsafe" jest wymagana. Dyrektywy tej nie trzeba używać m.in. w przypadku wykorzystywania struktury ''IntPtr'' do przekazywania wskaźników oraz w przypadku przekazywania argumentów przez wartość, niemniej jednak zalecane jest jej włączenie. W tym celu we właściwościach projektu w języku C# należy w sekcji "Build" w grupie"General" zaznaczyć opcję "Allow unsafe code" (rysunek {{ref>asm3_13}});
-  * Włączenie debuggowania kodu natywnego, co pozwoli na płynne przełączanie pomiędzy kodem wywołującym w C# i wywoływanym w assemblerze w jednej sesji debuggera. W tym celu we właściwościach projektu w jezyku C# należy wybrać sekcję "Debug", a następnie w grupie opcji "Debugger engines" zaznaczyć opcję "Enable natie code debugging" (rysunek {{ref>asm3_14}});+  * Włączenie debuggowania kodu natywnego, co pozwoli na płynne przełączanie pomiędzy kodem wywołującym w C# i wywoływanym w assemblerze w jednej sesji debuggera. W tym celu we właściwościach projektu w jezyku C# należy wybrać sekcję "Debug", a następnie w grupie opcji "Debugger engines" zaznaczyć opcję "Enable natie code debugging" (rysunki {{ref>asm3_14}},{{ref>asm3_14_2}});
  
 <figure asm3_13> <figure asm3_13>
Line 190: Line 203:
 <figure asm3_14> <figure asm3_14>
 {{:pl:edu:zmitac:n1z:ja:virtualbox_msedge_-_win10_06_04_2021_22_48_59.png?600|}} {{:pl:edu:zmitac:n1z:ja:virtualbox_msedge_-_win10_06_04_2021_22_48_59.png?600|}}
-<caption>Włączenie debuggowania kodu niezarządzalnego</caption>+<caption>Włączenie debuggowania kodu niezarządzalnego (VS do wersji 2019 włącznie, .NET do wersji 5)</caption> 
 +</figure> 
 +<figure asm3_14_2> 
 +{{:pl:edu:zmitac:n1z:ja:virtualbox_msedge_-_win10_-_ja_nsi_23_04_2022_16_00_45.png?600|}} 
 +<caption>Włączenie debuggowania kodu niezarządzalnego (VS od wersji 2022 włącznie, .NET 6.0)</caption>
 </figure> </figure>
  
-==== Przekazywanie wskaźników z poziomi C# do funkcji w assemblerze ====+==== Przekazywanie wskaźników z poziomu C# do funkcji w assemblerze ====
 Przekazywanie typów złożonych (tablic, obiektów, struktur) jest możliwe wyłącznie za pomocą wskaźników. Wskaźniki te są przekazywane jako typy int, 64-bitowe, w rejestrach. Ich interpretacja po stronie funkcji wywoływanej należy do programisty. Nieniej jednak po stronie języka C# nie jest to tak oczywiste.  Przekazywanie typów złożonych (tablic, obiektów, struktur) jest możliwe wyłącznie za pomocą wskaźników. Wskaźniki te są przekazywane jako typy int, 64-bitowe, w rejestrach. Ich interpretacja po stronie funkcji wywoływanej należy do programisty. Nieniej jednak po stronie języka C# nie jest to tak oczywiste. 
  
Line 221: Line 238:
 W powyższym przykładzie pominięto przekazywanie do funkcji długości tablicy, gdyż jej rozmiar (6 elementów) jest z góry znany. W przypadku, gdy rozmiar tablicy jest zmienny, należy przekazać do funkcji w assemblerze jej długość jako osobny parametr. W powyższym przykładzie pominięto przekazywanie do funkcji długości tablicy, gdyż jej rozmiar (6 elementów) jest z góry znany. W przypadku, gdy rozmiar tablicy jest zmienny, należy przekazać do funkcji w assemblerze jej długość jako osobny parametr.
  
-Przykładowy kod w assemblerze, który zwraca wskazany element tablcy może wyglądać następująco:+<note important>Uwaga! Tablice po stronie kodu w C# są alokowane liniowo. Ponieważ jednak w C# nie da się w prosty sposób otrzymać wskaźnika do obiektu, który nie jest typem prostym (a tablica nim nie jest), używamy pewnego obejścia polegającego na tym, że pobieramy adres do pierwszego elementu tablicy, zamiast do całego obiektu tablicy. Ostatecznie, w przypadku tablicy typów prostych, adresy będą takie same. Elementy pojedynczej tablicy są alokowane bez wyrównania do 64 bitów. Dopiero po ostatnim elemencie może pojawić się wyrównanie (o ile będzie konieczne). Wyrównanie widać w kolejnym przykładzie, gdzie korzystamy ze struktury.</note> 
 + 
 +Przykładowy kod w assemblerze, który zwraca wskazany element tablcy, może wyglądać następująco:
 <code asm> <code asm>
 ProcAsm2 proc ProcAsm2 proc
Line 353: Line 372:
 W szczególności, do wykonania są następujące zadania: W szczególności, do wykonania są następujące zadania:
   - Zsumowanie wektora danych typu uint (zadeklarowanych po stronie ,NET w postaci tablicy jednowymiarowej o wymiarze 20 zmiennych). Funkcja ma zwracać pojedynczą wartość (przez rejestr). Wskaźnik na tablicę źródłową oraz jej długość należy podać do funkcji w assemblerze poprzez rejestry.   - Zsumowanie wektora danych typu uint (zadeklarowanych po stronie ,NET w postaci tablicy jednowymiarowej o wymiarze 20 zmiennych). Funkcja ma zwracać pojedynczą wartość (przez rejestr). Wskaźnik na tablicę źródłową oraz jej długość należy podać do funkcji w assemblerze poprzez rejestry.
-  - Obliczenie wartości średnich dla zestawu zmiennych przechowywanych w postaci dwóch wektorów o długości 16, typu float, pozycja po pozycji, wynikiem ma być wektor średnich (o długości 16). Funkcja nie zwraca żadnej wartości. Wyniki są umieszczane w tablicy zadeklarowanej po stronie ,NET i przekazanej do funkcji jako wskaźnik. Sugerowane jest użycie struktury do przekazania danych do funkcji. Docelową tablicę należy zadeklarować po stronie języka C#.+  - Obliczenie wartości średnich dla zestawu zmiennych przechowywanych w postaci dwóch wektorów o długości 16, typu float, pozycja po pozycji, wynikiem ma być wektor średnich (o długości 16). Funkcja nie zwraca żadnej wartości. Wyniki są umieszczane w tablicy zadeklarowanej po stronie ,NET i przekazanej do funkcji jako wskaźnik. Sugerowane jest użycie struktury do przekazania danych do funkcji. Docelową tablicę należy zadeklarować po stronie języka C#. Zalecane jest wykonanie operacji za pomocą pętli, tj. obliczanie osobno dla każdej pary. Można skorzystać albo z funkcji koprocesora arytmetycznego, albo z MMX/SSE.
  
 Powyższe operacje są od siebie niezależne, a zatem powinny być zrealizowane w ramach dwóch, niezależnych funkcji assemblerowych. Powyższe operacje są od siebie niezależne, a zatem powinny być zrealizowane w ramach dwóch, niezależnych funkcji assemblerowych.
pl/edu/zmitac/n1z/ja/lab3.1618067780.txt.gz · Last modified: by pczekalski