Udostępnij za pośrednictwem


Migrowanie 32-bitowego kodu zarządzanego do 64-bitowego

 

Microsoft Corporation

Zaktualizowano maj 2005 r.

Dotyczy:
   Microsoft .NET
   Microsoft .NET Framework 2.0

Krótki opis: Dowiedz się, co jest związane z migracją 32-bitowych aplikacji zarządzanych do wersji 64-bitowej, problemy, które mogą mieć wpływ na migrację, oraz narzędzia, które są dostępne do ułatwienia. (17 drukowanych stron)

Zawartość

Wprowadzenie
Kod zarządzany w środowisku 32-bitowym
Wprowadź clR dla środowiska 64-bitowego
Migracja i wywołanie platformy
Migracja i współdziałanie modelu COM
Migracja i niebezpieczny kod
Migracja i marshaling
Migracja i serializacja
Podsumowanie

Wprowadzenie

W tym oficjalnym dokumencie omówiono następujące zagadnienia:

  • Co jest związane z migracją aplikacji zarządzanych z 32-bitowej do 64-bitowej
  • Problemy, które mogą mieć wpływ na migrację
  • Jakie narzędzia są dostępne, aby ci pomóc

Te informacje nie mają być normatywne; raczej jest przeznaczony do zapoznania się z różnymi obszarami, które są podatne na problemy podczas procesu migracji do 64-bitowej. W tym momencie nie ma konkretnego "podręcznika" kroków, które można wykonać i zapewnić, że kod będzie działać na 64-bitowej wersji. Informacje zawarte w tym oficjalnym dokumencie zaznajomią się z różnymi problemami i informacjami, które należy przejrzeć.

Jak wkrótce zobaczysz, jeśli zestaw zarządzany nie jest w 100% bezpiecznym kodem, musisz przejrzeć aplikację i jej zależności, aby określić problemy z migracją do 64-bitowej wersji. Wiele elementów, które zostaną przeczytane w następnych sekcjach, można rozwiązać za pomocą zmian programistycznych. W wielu przypadkach należy również poświęcić czas na zaktualizowanie kodu w celu poprawnego uruchomienia w środowiskach 32-bitowych i 64-bitowych, jeśli chcesz, aby był uruchamiany w obu tych środowiskach.

Microsoft .NET to zestaw technologii oprogramowania do łączenia informacji, osób, systemów i urządzeń. Od czasu wydania wersji 1.0 w 2002 r. organizacje pomyślnie wdrożyły program . Rozwiązania oparte na platformie NET, niezależnie od tego, czy są wbudowane, przez niezależnych dostawców oprogramowania (ISV), czy przez niektóre kombinacje. Istnieje kilka typów aplikacji .NET, które wypychają limity środowiska 32-bitowego. Te wyzwania obejmują, ale nie są ograniczone, potrzebę bardziej rzeczywistej pamięci i potrzeby zwiększenia wydajności zmiennoprzecinkowych. Procesory x64 i Itanium oferują lepszą wydajność dla operacji zmiennoprzecinkowych, niż można uzyskać na procesorze x86. Jednak możliwe jest również, że wyniki uzyskane na x64 lub Itanium będą inne niż wyniki uzyskane na x86. Platforma 64-bitowa ma na celu ułatwienie rozwiązania tych problemów.

Wraz z wydaniem .NET Framework w wersji 2.0 firma Microsoft obejmuje obsługę kodu zarządzanego działającego na platformach x64 i Itanium 64-bitowych.

Kod zarządzany to po prostu "kod", który udostępnia wystarczającą ilość informacji, aby umożliwić środowisku uruchomieniowemu języka wspólnego platformy .NET (CLR) dostarczanie zestawu podstawowych usług, w tym:

  • Opis własny kodu i danych za pomocą metadanych
  • Chodzenie stosem
  • Zabezpieczenia
  • Wyrzucanie elementów bezużytecznych
  • Kompilacja just in time

Oprócz kodu zarządzanego istnieje kilka innych definicji, które są ważne, aby zrozumieć podczas badania problemów z migracją.

Zarządzane dane — dane przydzielone na zarządzanym stosie i zbierane za pośrednictwem odzyskiwania pamięci.

Zestaw — jednostka wdrożenia, która umożliwia clR pełne zrozumienie zawartości aplikacji oraz wymuszanie reguł przechowywania wersji i zależności zdefiniowanych przez aplikację.

Kod bezpieczny typu — kod, który używa tylko zarządzanych danych i nie ma nieweryfikowalnych typów danych ani nieobsługiwanych operacji konwersji/przymusu typu danych (czyli niedyskryminowanych związków lub wskaźników struktury/interfejsu). Kod C#, Visual Basic .NET i Visual C++ skompilowany przy użyciu kodu bezpiecznego typu /clr:safe .

Niebezpieczny kod — kod, który może wykonywać takie operacje niższego poziomu, jak deklarowanie i działanie na wskaźnikach, wykonywanie konwersji między wskaźnikami i typami całkowitymi oraz pobieranie adresu zmiennych. Takie operacje umożliwiają współdziałanie z bazowym systemem operacyjnym, uzyskiwanie dostępu do urządzenia mapowanego w pamięci lub implementowanie algorytmu krytycznego czasowo. Kod natywny jest niebezpieczny.

Kod zarządzany w środowisku 32-bitowym

Aby zrozumieć złożoność związaną z migracją kodu zarządzanego do środowiska 64-bitowego, zapoznajmy się ze sposobem wykonywania kodu zarządzanego w środowisku 32-bitowym.

Gdy aplikacja, zarządzana lub niezarządzana, jest wybierana do wykonania, moduł ładujący systemu Windows jest wywoływany i odpowiada za podjęcie decyzji o tym, jak załadować, a następnie wykonać aplikację. Część tego procesu obejmuje wgląd wewnątrz nagłówka przenośnego wykonywania pliku wykonywalnego (PE), aby określić, czy clR jest wymagany. Jak już można się domyślić, istnieją flagi w środowisku PE, które wskazują kod zarządzany. W takim przypadku moduł ładujący systemu Windows uruchamia środowisko CLR, które jest następnie odpowiedzialne za ładowanie i wykonywanie aplikacji zarządzanej. (Jest to uproszczony opis procesu, ponieważ jest zaangażowanych wiele kroków, w tym określenie, która wersja środowiska CLR ma zostać wykonana, skonfigurowanie elementu AppDomain "piaskownica" itp.)

Współdziałanie

W miarę uruchamiania aplikacji zarządzanej może (przy założeniu odpowiednich uprawnień zabezpieczeń) korzystać z natywnych interfejsów API (w tym interfejsu API Win32) i obiektów COM za pośrednictwem możliwości współdziałania środowiska CLR. Bez względu na to, czy wywoływanie natywnego interfejsu API platformy, tworzenie żądania COM, czy kierowanie struktury, podczas uruchamiania całkowicie w środowisku 32-bitowym deweloper jest odizolowany od konieczności myślenia o rozmiarach typów danych i wyrównaniu danych.

Podczas rozważania migracji do wersji 64-bitowej niezbędne będzie zbadanie, jakie zależności ma aplikacja.

Wprowadź clR dla środowiska 64-bitowego

Aby kod zarządzany był wykonywany w środowisku 64-bitowym zgodnym ze środowiskiem 32-bitowym, zespół platformy .NET opracował środowisko uruchomieniowe języka wspólnego (CLR) dla systemów Itanium i x64 64-bitowych. Środowisko CLR musiało ściśle przestrzegać reguł infrastruktury języka wspólnego (CLI) i systemu typów języka wspólnego, aby zapewnić, że kod napisany w dowolnym z języków platformy .NET będzie mógł współdziałać tak jak w środowisku 32-bitowym. Ponadto poniżej znajduje się lista niektórych innych elementów, które również musiały być przeniesione i/lub opracowane dla środowiska 64-bitowego:

  • Biblioteki klas bazowych (System.*)
  • Kompilator just in time
  • Obsługa debugowania
  • Zestaw SDK programu .NET Framework

Obsługa 64-bitowego kodu zarządzanego

Procesory .NET Framework w wersji 2.0 obsługują procesory Itanium i x64 64-bitowe:

  • Windows Server 2003 z dodatkiem SP1
  • Przyszłe wersje klienta systemu Windows 64-bitowe

(Nie można zainstalować .NET Framework w wersji 2.0 w systemie Windows 2000. Pliki wyjściowe utworzone przy użyciu .NET Framework w wersji 1.0 i 1.1 będą działać w wersji WOW64 w 64-bitowym systemie operacyjnym).

Podczas instalowania .NET Framework w wersji 2.0 na platformie 64-bitowej nie tylko instalujesz całą infrastrukturę niezbędną do wykonywania kodu zarządzanego w trybie 64-bitowym, ale instalujesz niezbędną infrastrukturę do uruchomienia kodu zarządzanego w podsystemie Windows-on-Windows lub WoW64 (tryb 32-bitowy).

Prosta migracja 64-bitowa

Rozważmy aplikację platformy .NET, która jest w 100% bezpiecznym kodem. W tym scenariuszu można uruchomić plik wykonywalny platformy .NET na maszynie 32-bitowej i przenieść go do systemu 64-bitowego i pomyślnie go uruchomić. Dlaczego to działa? Ponieważ zestaw jest 100% bezpieczny, wiemy, że nie ma żadnych zależności od kodu natywnego lub obiektów COM i że nie ma "niebezpiecznego" kodu, co oznacza, że aplikacja działa całkowicie pod kontrolą CLR. ClR gwarantuje, że podczas gdy kod binarny generowany w wyniku kompilacji just in time (JIT) będzie różny między 32-bitowym i 64-bitowym, kod, który jest wykonywany, będzie semantycznie taki sam. (Nie można zainstalować .NET Framework w wersji 2.0 w systemie Windows 2000. Pliki wyjściowe utworzone przy użyciu .NET Framework w wersji 1.0 i 1.1 będą działać w wersji WOW64 w 64-bitowym systemie operacyjnym).

W rzeczywistości poprzedni scenariusz jest nieco bardziej skomplikowany z perspektywy załadowania aplikacji zarządzanej. Zgodnie z opisem w poprzedniej sekcji moduł ładujący systemu Windows jest odpowiedzialny za podjęcie decyzji o tym, jak załadować i wykonać aplikację. Jednak w przeciwieństwie do środowiska 32-bitowego, uruchomionego na 64-bitowej platformie Windows oznacza, że istnieją dwa (2) środowiska, w których można wykonać aplikację, w trybie natywnym 64-bitowym lub w WoW64.

Moduł ładujący systemu Windows musi teraz podejmować decyzje na podstawie tego, co odnajduje w nagłówku PE. Jak można się domyślić, w kodzie zarządzanym są flagi ustawialne, które pomagają w tym procesie. (Zobacz corflags.exe, aby wyświetlić ustawienia w środowisku PE). Poniższa lista reprezentuje informacje znajdujące się w środowisku PE, które ułatwiają proces podejmowania decyzji.

  • 64-bitowy — oznacza, że deweloper skompilował zestaw przeznaczony specjalnie dla procesu 64-bitowego.
  • 32-bitowy — oznacza, że deweloper skompilował zestaw przeznaczony specjalnie dla procesu 32-bitowego. W tym przypadku zestaw zostanie uruchomiony w WoW64.
  • Niezależne — oznacza, że deweloper skompilował zestaw za pomocą programu Visual Studio 2005 o nazwie "Whidbey". lub nowsze narzędzia i zestaw może działać w trybie 64-bitowym lub 32-bitowym. W tym przypadku 64-bitowy moduł ładujący systemu Windows uruchomi zestaw w wersji 64-bitowej.
  • Starsza wersja — oznacza, że narzędzia, które skompilowały zestaw, to "pre-Whidbey". W tym konkretnym przypadku zestaw zostanie uruchomiony w WoW64.

Uwaga W środowisku PE znajdują się również informacje, które informują moduł ładujący systemu Windows, czy zestaw jest przeznaczony dla określonej architektury. Te dodatkowe informacje zapewniają, że zestawy przeznaczone dla określonej architektury nie są ładowane w innej architekturze.

Kompilatory C#, Visual Basic .NET i C++ Whidbey umożliwiają ustawianie odpowiednich flag w nagłówku PE. Na przykład język C# i TRZECI mają opcję /platform:{anycpu, x86, Itanium, x64} kompilatora.

Uwaga Chociaż technicznie można zmodyfikować flagi w nagłówku PE zestawu po jego skompilowaniu, firma Microsoft nie zaleca tego.

Jeśli chcesz wiedzieć, jak te flagi są ustawione w zestawie zarządzanym, możesz uruchomić narzędzie ILDASM udostępnione w zestawie .NET Framework. Na poniższej ilustracji przedstawiono aplikację "starsza wersja".

Pamiętaj, że deweloper oznaczający zestaw jako Win64 ustalił, że wszystkie zależności aplikacji będą wykonywane w trybie 64-bitowym. Proces 64-bitowy nie może użyć składnika 32-bitowego w procesie (a 32-bitowy proces nie może załadować 64-bitowego składnika w procesie). Należy pamiętać, że możliwość załadowania zestawu do procesu 64-bitowego przez system nie oznacza automatycznego wykonania.

Wiemy więc, że aplikacja składająca się z kodu zarządzanego, który jest bezpiecznym typem 100%, można skopiować (lub wdrożyć go xcopy ) na 64-bitową platformę i mieć go JIT i pomyślnie uruchomić z platformą .NET w trybie 64-bitowym.

Jednak często widzimy sytuacje, które nie są idealne i to przynosi nam główny nacisk na ten dokument, co polega na zwiększeniu świadomości kwestii związanych z migracją.

Możesz mieć aplikację, która nie jest w 100% bezpieczna i nadal może działać pomyślnie w 64-bitowej wersji na platformie .NET. Należy dokładnie przyjrzeć się aplikacji, pamiętając o potencjalnych problemach omówionych w poniższych sekcjach i określeniu, czy można pomyślnie uruchomić aplikację w 64-bitowej wersji.

Migracja i wywołanie platformy

Korzystanie z funkcji wywołania platformy (lub wywołania p/invoke) platformy .NET odnosi się do kodu zarządzanego, który wykonuje wywołania do kodu niezarządzanego lub natywnego. W typowym scenariuszu ten kod natywny to dynamiczna biblioteka linków (DLL), która jest częścią systemu (interfejs API systemu Windows itp.), część aplikacji lub biblioteki innej firmy.

Użycie kodu niezarządzanego nie oznacza jawnie, że migracja do 64-bitowej wersji będzie miała problemy; zamiast tego należy rozważyć wskaźnik, że wymagane jest dodatkowe badanie.

Typy danych w systemie Windows

Każda aplikacja i każdy system operacyjny mają abstrakcyjny model danych. Wiele aplikacji nie ujawnia jawnie tego modelu danych, ale model prowadzi do sposobu pisania kodu aplikacji. W modelu programowania 32-bitowego (znanym jako model ILP32) typy danych liczb całkowitych, długich i wskaźników to 32 bity długości. Większość deweloperów użyła tego modelu bez jego realizacji.

W 64-bitowym systemie Microsoft Windows to założenie parzystości w rozmiarach typów danych jest nieprawidłowe. Tworzenie wszystkich typów danych o długości 64 bitów spowoduje marnowanie miejsca, ponieważ większość aplikacji nie potrzebuje zwiększonego rozmiaru. Jednak aplikacje potrzebują wskaźników do 64-bitowych danych i potrzebują możliwości posiadania 64-bitowych typów danych w wybranych przypadkach. Te zagadnienia doprowadziły zespół systemu Windows do wybrania abstrakcyjnego modelu danych o nazwie LLP64 (lub P64). W modelu danych LLP64 wskaźniki rozszerzają się tylko do 64 bitów; pozostałe podstawowe typy danych (liczba całkowita i długa) pozostają o długości 32 bitów.

Środowisko .NET CLR dla platform 64-bitowych używa tego samego modelu danych abstrakcyjnych LLP64. Na platformie .NET istnieje całkowity typ danych, który nie jest powszechnie znany, przeznaczony do przechowywania informacji o wskaźniku: IntPtr , którego rozmiar jest zależny od platformy (np. 32-bitowej lub 64-bitowej). Rozważmy następujący fragment kodu:

[C#]
public void SizeOfIntPtr() {
Console.WriteLine( "SizeOf IntPtr is: {0}", IntPtr.Size );
}

Po uruchomieniu na 32-bitowej platformie uzyskasz następujące dane wyjściowe w konsoli programu :

SizeOf IntPtr is: 4

Na 64-bitowej platformie uzyskasz następujące dane wyjściowe w konsoli programu :

SizeOf IntPtr is: 8

Uwaga Jeśli chcesz sprawdzić, czy środowisko uruchomieniowe jest uruchomione w środowisku 64-bitowym, możesz użyć parametru IntPtr.Size jako jednego ze sposobów, aby to określić.

Zagadnienia dotyczące migracji

Podczas migrowania aplikacji zarządzanych korzystających z funkcji p/invoke należy wziąć pod uwagę następujące elementy:

  • Dostępność 64-bitowej wersji biblioteki DLL
  • Używanie typów danych

Dostępność

Jedną z pierwszych rzeczy, które należy określić, jest to, czy niezarządzany kod, na który aplikacja ma zależność, jest dostępny dla 64-bitowego.

Jeśli ten kod został opracowany we własnym zakresie, zwiększa się zdolność do sukcesu. Oczywiście nadal trzeba będzie przydzielić zasoby do portu kodu niezarządzanego do 64-bitowego wraz z odpowiednimi zasobami na potrzeby testowania, zapewniania jakości itp. (Ten oficjalny dokument nie tworzy zaleceń dotyczących procesów programowania; zamiast tego próbuje wskazać, że zasoby mogą być konieczne przydzielenie do zadań do kodu portu).

Jeśli ten kod pochodzi od innej firmy, musisz zbadać, czy ta inna firma ma już dostępny kod dla 64-bitowego i czy inna firma będzie chciała udostępnić go.

Wyższy problem z ryzykiem wystąpi, jeśli inna firma nie zapewnia już pomocy technicznej dla tego kodu lub jeśli osoba trzecia nie jest skłonna do wykonania tej pracy. Te przypadki wymagają dodatkowych badań nad dostępnymi bibliotekami, które wykonują podobne funkcje, niezależnie od tego, czy inna firma pozwoli klientowi wykonać sam port itp.

Należy pamiętać, że 64-bitowa wersja kodu zależnego może mieć zmienione sygnatury interfejsu, które mogą oznaczać dodatkową pracę dewelopera i rozwiązać różnice między 32-bitowymi i 64-bitowymi wersjami aplikacji.

Typy danych

Użycie metody p/invoke wymaga, aby kod opracowany na platformie .NET zadeklarował prototyp metody przeznaczonej dla zarządzanego kodu. Biorąc pod uwagę następującą deklarację C:

[C++]
typedef void * HANDLE
HANDLE GetData();

Poniżej przedstawiono przykłady metod prototypowych:

[C#]

[DllImport( "sampleDLL", CallingConvention=CallingConvention.Cdecl )]
      public static extern int DoWork( int x, int y );

[DllImport( "sampleDLL", CallingConvention=CallingConvention.Cdecl )]
      public unsafe static extern int GetData();

Przejrzyjmy te przykłady z myślą o problemach z migracją 64-bitową:

Pierwszy przykład wywołuje metodę DoWork przekazującą dwie (2) 32-bitowe liczby całkowite i oczekujemy, że zostanie zwrócona 32-bitowa liczba całkowita. Mimo że działamy na 64-bitowej platformie, liczba całkowita jest nadal 32-bitowa. W tym konkretnym przykładzie nie ma nic, co powinno utrudnić nasze wysiłki w zakresie migracji.

Drugi przykład wymaga wprowadzenia pewnych zmian w kodzie w celu pomyślnego uruchomienia w wersji 64-bitowej. W tym miejscu wywołujemy metodę GetData i zadeklarowaliśmy, że spodziewamy się zwrócenia liczby całkowitej, ale funkcja rzeczywiście zwraca wskaźnik int. Tutaj leży nasz problem: pamiętaj, że liczby całkowite są 32-bitowe, ale w 64-bitowych wskaźnikach to 8 bajtów. Jak się okazuje, sporo kodu w 32-bitowym świecie zostało napisane przy założeniu, że wskaźnik i liczba całkowita były taką samą długością, 4 bajty. W 64-bitowym świecie nie jest to już prawdziwe.

W tym ostatnim przypadku problem można rozwiązać, zmieniając deklarację metody, aby użyć intPtr zamiast intt.

public unsafe static extern IntPtr GetData();

Wprowadzenie tej zmiany będzie działać zarówno w środowiskach 32-bitowych, jak i 64-bitowych. Pamiętaj, że element IntPtr jest specyficzny dla platformy.

Użycie metody p/invoke w aplikacji zarządzanej nie oznacza, że migracja do platformy 64-bitowej nie będzie możliwa. Nie oznacza to też, że będą problemy. Oznacza to, że należy przejrzeć zależności od kodu niezarządzanego, który ma aplikacja zarządzana, i określić, czy wystąpią jakiekolwiek problemy.

Migracja i współdziałanie modelu COM

Współdziałanie modelu COM to zakładana możliwość platformy .NET. Podobnie jak w poprzedniej dyskusji na temat wywołania platformy, korzystanie z współdziałania modelu COM oznacza, że kod zarządzany wykonuje wywołania do kodu niezarządzanego. Jednak w przeciwieństwie do wywołania platformy współdziałanie modelu COM oznacza również możliwość wywoływania kodu zarządzanego przez kod inny niż zarządzany, tak jakby był to składnik COM.

Po raz kolejny użycie nienarządkowego kodu COM nie oznacza, że migracja do 64-bitowej będzie miała problemy; zamiast tego należy rozważyć wskaźnik, że wymagane jest dodatkowe badanie.

Zagadnienia dotyczące migracji

Ważne jest, aby zrozumieć, że w wersji .NET Framework w wersji 2.0 nie ma obsługi współdziałania między architekturami. Aby być bardziej zwięzłe, nie można używać współdziałania MODELU COM między 32-bitowymi i 64-bitowymi w tym samym procesie. Można jednak korzystać z współdziałania modelu COM między 32-bitowym i 64-bitowym, jeśli masz serwer COM poza procesem. Jeśli nie możesz użyć serwera COM poza procesem, chcesz oznaczyć zarządzany zestaw jako Win32, a nie Win64 lub Agnostic w celu uruchomienia programu w WoW64, aby umożliwić współdziałanie z obiektem COM 32-bitowym.

Poniżej przedstawiono omówienie różnych zagadnień, które należy wziąć pod uwagę w celu korzystania z współdziałania modelu COM, w którym kod zarządzany wykonuje wywołania COM w środowisku 64-bitowym. Są to:

  • Dostępność 64-bitowej wersji biblioteki DLL
  • Używanie typów danych
  • Biblioteki typów

Dostępność

Dyskusja w sekcji p/invoke dotycząca dostępności 64-bitowej wersji kodu zależnego jest również istotna dla tej sekcji.

Typy danych

Dyskusja w sekcji p/invoke dotycząca typów danych 64-bitowej wersji kodu zależnego jest również istotna dla tej sekcji.

Biblioteki typów

W przeciwieństwie do zestawów bibliotek typów nie można oznaczyć jako "neutralne"; muszą być oznaczone jako Win32 lub Win64. Ponadto biblioteka typów musi być zarejestrowana dla każdego środowiska, w którym będzie uruchamiany com. Użyj tlbimp.exe, aby wygenerować zestaw 32-bitowy lub 64-bitowy z biblioteki typów.

Współdziałanie modelu COM w aplikacji zarządzanej nie oznacza, że migracja do platformy 64-bitowej nie będzie możliwa. Nie oznacza to też, że będą problemy. Oznacza to, że należy przejrzeć zależności, które ma aplikacja zarządzana i określić, czy wystąpią jakiekolwiek problemy.

Migracja i niebezpieczny kod

Podstawowy język C# różni się w szczególności od języka C i C++ w pominięciu wskaźników jako typu danych. Zamiast tego język C# udostępnia odwołania i możliwość tworzenia obiektów zarządzanych przez moduł odśmieceń pamięci. W podstawowym języku C# po prostu nie można mieć niezainicjowanej zmiennej, wskaźnika "zwisania" lub wyrażenia, które indeksuje tablicę poza jej granicami. W ten sposób wyeliminowane są całe kategorie błędów, które rutynowo nękają programy C i C++.

Chociaż praktycznie każda konstrukcja typu wskaźnika w języku C lub C++ ma odpowiednik typu referencyjnego w języku C#, istnieją sytuacje, w których dostęp do typów wskaźników staje się koniecznością. Na przykład interfacing z bazowym systemem operacyjnym, uzyskiwanie dostępu do urządzenia mapowanego na pamięć lub implementowanie algorytmu krytycznego dla czasu może nie być możliwe lub praktyczne bez dostępu do wskaźników. Aby rozwiązać ten problem, język C# zapewnia możliwość pisania niebezpiecznego kodu.

W niebezpiecznym kodzie można deklarować i obsługiwać wskaźniki, wykonywać konwersje między wskaźnikami i typami całkowitymi, przyjmować adres zmiennych itd. W pewnym sensie pisanie niebezpiecznego kodu przypomina pisanie kodu C w programie C#.

Niebezpieczny kod jest w rzeczywistości "bezpieczną" funkcją z perspektywy zarówno deweloperów, jak i użytkowników. Niebezpieczny kod musi być wyraźnie oznaczony modyfikatorem niebezpiecznym, aby deweloperzy nie mogli przypadkowo używać niebezpiecznych funkcji.

Zagadnienia dotyczące migracji

Aby omówić potencjalne problemy z niebezpiecznym kodem, zapoznajmy się z poniższym przykładem. Nasz zarządzany kod wykonuje wywołania niezarządzanej biblioteki DLL. W szczególności istnieje metoda o nazwie GetDataBuffer , która zwraca 100 elementów (w tym przykładzie zwracamy stałą liczbę elementów). Każdy z tych elementów składa się z liczby całkowitej i wskaźnika. Poniższy przykładowy kod to fragment kodu zarządzanego przedstawiający niebezpieczną funkcję odpowiedzialną za obsługę tych zwracanych danych.

[C#]

public unsafe int UnsafeFn() {
   IntPtr * inputBuffer = sampleDLL.GetDataBuffer();
   IntPtr * ptr = inputBuffer;
   int   result = 0;

   for ( int idx = 0; idx < 100; idx ++ ) {
      // Add 'int' from DLL to our result
      result = result + ((int) *ptr);

// Increment pointer over int (
      ptr = (IntPtr*)( ( (byte *) ptr ) + sizeof( int ) );

      // Increment pointer over pointer (
      ptr = (IntPtr*)( ( (byte *) ptr ) + sizeof( int ) );
   }
   return result;
}

Uwaga Ten konkretny przykład mógł zostać osiągnięty bez użycia niebezpiecznego kodu. W szczególności istnieją inne techniki, takie jak marshaling, które mogły zostać użyte. Jednak w tym celu używamy niebezpiecznego kodu.

Wartość UnsafeFn przechodzi przez 100 elementów i sumuje dane całkowite. Podczas przechodzenia przez bufor danych kod musi przejść zarówno przez liczbę całkowitą, jak i wskaźnik. W środowisku 32-bitowym ten kod działa prawidłowo. Jednak jak wspomniano wcześniej, wskaźniki są 8 bajtów w środowisku 64-bitowym, a zatem segment kodu (pokazany poniżej) nie będzie działał poprawnie, ponieważ wykorzystuje wspólną technikę programowania, np. traktując wskaźnik jako odpowiednik liczby całkowitej.

// Increment pointer over pointer (
ptr = (IntPtr*)( ( (byte *) ptr ) + sizeof( int ) );

Aby ten kod działał zarówno w środowisku 32-bitowym, jak i 64-bitowym, konieczne byłoby zmianę kodu na następujące.

// Increment pointer over pointer (
ptr = (IntPtr*)( ( (byte *) ptr ) + sizeof( IntPtr ) );

Jak już widzieliśmy, istnieją wystąpienia, w których konieczne jest używanie niebezpiecznego kodu. W większości przypadków jest to wymagane w wyniku zależności kodu zarządzanego od innego interfejsu. Niezależnie od przyczyn, dla których istnieje niebezpieczny kod, należy go przejrzeć w ramach procesu migracji.

Użyty powyżej przykład jest stosunkowo prosty, a poprawka, aby program działał w 64-bitowej wersji, był prosty. Oczywiście istnieje wiele przykładów niebezpiecznego kodu, które są bardziej złożone. Niektóre będą wymagać głębokiego przeglądu i być może wycofywania i ponownego przemyślenia podejścia, z których korzysta zarządzany kod.

Aby powtórzyć to, co zostało już odczytane, użycie niebezpiecznego kodu w aplikacji zarządzanej nie oznacza, że migracja do platformy 64-bitowej nie będzie możliwa. Nie oznacza to też, że będą problemy. Oznacza to, że należy przejrzeć cały niebezpieczny kod, który ma aplikacja zarządzana i określić, czy wystąpią jakiekolwiek problemy.

Migracja i marshaling

Marshaling udostępnia kolekcję metod przydzielania niezarządzanej pamięci, kopiowania niezarządzanych bloków pamięci i konwertowania zarządzanych typów niezarządzanych, a także innych metod używanych podczas interakcji z kodem niezarządzanym.

Marshaling jest manifestowany za pośrednictwem klasy marshal platformy .NET. Metody statyczne lub udostępnione w języku Visual Basic zdefiniowane w klasie Marshal są niezbędne do pracy z danymi niezarządzanymi. Zaawansowani deweloperzy tworzący niestandardowe marszałki, którzy muszą zapewnić most między zarządzanymi i niezarządzanymi modelami programowania zwykle używają większości zdefiniowanych metod.

Zagadnienia dotyczące migracji

Marshaling stanowi niektóre bardziej złożone wyzwania związane z migracją aplikacji do 64-bitowej wersji. Biorąc pod uwagę charakter tego, co deweloper próbuje wykonać za pomocą marshalingu, czyli przesyłania informacji ustrukturyzowanych do, z lub do i z zarządzanego i niezarządzanego kodu, zobaczymy, że dostarczamy informacje, czasami niskiego poziomu, aby pomóc systemowi.

Jeśli chodzi o układ, istnieją dwie konkretne deklaracje, które mogą być dokonywane przez dewelopera; te deklaracje są zwykle wykonywane przy użyciu atrybutów kodowania.

Layoutkind.sequential

Przejrzyjmy definicję zgodnie z informacjami podanymi w pomocy zestawu SDK .NET Framework:

"Elementy członkowskie obiektu są uporządkowane sekwencyjnie, w kolejności, w której są wyświetlane podczas eksportowania do niezarządzanej pamięci. Elementy członkowskie są określone zgodnie z opakowaniem określonym w StructLayoutAttribute.Pack, i mogą być niezgodne."

Mówimy, że układ jest specyficzny dla kolejności, w jakiej jest definiowana. Następnie musimy upewnić się, że zarządzane i niezarządzane deklaracje są podobne. Ale mówimy również, że pakowanie jest również krytycznym składnikiem. W tym momencie nie będziesz zaskoczony, aby dowiedzieć się, że bez wyraźnej interwencji dewelopera istnieje domyślna wartość pakietu. Jak można już się domyślić, wartość pakietu domyślnego nie jest taka sama w systemach 32-bitowych i 64-bitowych.

Instrukcja w definicji dotycząca nieciągliwych elementów członkowskich odnosi się do faktu, że ponieważ istnieją domyślne rozmiary pakietów, dane określone w pamięci mogą nie być w bajtach 0, bajt 1, bajt 2 itp. Zamiast tego pierwszy element członkowski będzie w bajtach 0, ale drugi element członkowski może znajdować się w bajtach 4. System wykonuje to domyślne pakowanie, aby umożliwić maszynie uzyskanie dostępu do członków bez konieczności radzenia sobie z problemami z niezgodnością.

Oto obszar, który musimy zwrócić szczególną uwagę na pakowanie, a jednocześnie spróbować pozwolić systemowi działać w preferowanym trybie.

Poniżej przedstawiono przykład struktury zdefiniowanej w kodzie zarządzanym, a także odpowiednią strukturę zdefiniowaną w kodzie niezarządzanym. Należy zwrócić szczególną uwagę na to, jak w tym przykładzie pokazano ustawienie wartości pakietu w obu środowiskach.

[C#]
[StructLayout(LayoutKind.Sequential, Pack=1)]
public class XYZ {
      public byte arraysize = unchecked((byte)-1);
      [MarshalAs(UnmanagedType.ByValArray, SizeConst=52)]
      public int[] padding = new int[13];
};
[unmanaged c++]
#pragma pack(1)
typedef struct{
      BYTE arraysize;      // = (byte)-1;
      int      padding[13];
} XYZ;

Layoutkind.explicit

Przejrzyjmy definicję zgodnie z opisem w pomocy zestawu .NET FrameworkSDK:

"Dokładna pozycja każdego elementu członkowskiego obiektu w pamięci niezarządzanej jest jawnie kontrolowana. Każdy element członkowski musi użyć atrybutu FieldOffsetAttribute , aby wskazać położenie tego pola w typie.

Mówimy tutaj, że deweloper będzie zapewniać dokładne przesunięcia w celu pomocy w marshalingu informacji. Dlatego ważne jest, aby deweloper poprawnie określić informacje w atrybucie FieldOffset .

Więc gdzie są potencjalne problemy? Należy pamiętać, że przesunięcia pól są definiowane, wiedząc o rozmiarze kontynuowanego rozmiaru elementu członkowskiego danych, należy pamiętać, że nie wszystkie rozmiary typów danych są równe między 32-bitowymi i 64-bitowymi. W szczególności wskaźniki mają długość 4 lub 8 bajtów.

Mamy teraz przypadek, w którym może być konieczne zaktualizowanie zarządzanego kodu źródłowego w celu kierowania określonych środowisk. W poniższym przykładzie przedstawiono strukturę zawierającą wskaźnik. Mimo że wprowadziliśmy wskaźnik intPtr, nadal istnieje różnica podczas przechodzenia do 64-bitowego.

[C#]
[StructLayout(LayoutKind.Explicit)]
    internal struct FooValue {
        [FieldOffset(0)] public int dwType;
        [FieldOffset(4)] public IntPtr pType;
        [FieldOffset(8)] public int typeValue;
    }

W przypadku 64-bitowej musimy dostosować przesunięcie pola dla ostatniego elementu członkowskiego danych w strukturze, ponieważ naprawdę zaczyna się od przesunięcia 12, a nie 8.

[C#]
[StructLayout(LayoutKind.Explicit)]
    internal struct FooValue {
        [FieldOffset(0)] public int dwType;
        [FieldOffset(4)] public IntPtr pType;
        [FieldOffset(12)] public int typeValue;
    }

Użycie marshalingu jest rzeczywistością, gdy wymagana jest złożona współpraca między zarządzanym i niezarządzanym kodem. Korzystanie z tej zaawansowanej możliwości nie jest wskaźnikiem, który można migrować aplikację 32-bitową do środowiska 64-bitowego. Jednak ze względu na złożoność związaną z używaniem marshalingu jest to obszar, w którym wymagana jest staranna uwaga na szczegóły.

Analiza kodu wskaże, czy oddzielne pliki binarne są wymagane dla każdej platformy i czy konieczne będzie również wprowadzenie modyfikacji kodu niezarządzanego w celu rozwiązania problemów, takich jak pakowanie.

Migracja i serializacja

Serializacja jest proces konwersji stan obiektu do formularza, które mogą być utrwalone lub transportowane. Uzupełnieniem serializacji jest deserializacja, która konwertuje strumień na obiekt. Razem te procesy umożliwiają danych można łatwo przechowywane i przekazywane.

.NET Framework oferuje dwie technologie serializacji:

  • Serializacja binarna zachowuje wierność typu, która jest przydatna do zachowania stanu obiektu między różnymi wywołaniami aplikacji. Można na przykład udostępnić obiekt między różnymi aplikacjami, serializując go w Schowku. Można serializować obiektu w strumieniu na dysku w pamięci, za pośrednictwem sieci i tak dalej. Komunikacja zdalna platformy .NET używa serializacji do przekazywania obiektów "według wartości" z jednego komputera lub domeny aplikacji do innej.
  • Serializacji XML serializuje tylko właściwości publiczne i pola i nie zostaną zachowane wierności typu. Jest to przydatne, gdy chcesz podać lub używać danych bez ograniczania aplikacji korzystającej z danych. Ponieważ kod XML jest otwarty standard, jest atrakcyjny wybór udostępnianie danych w sieci Web. Podobnie protokołu SOAP jest otwarty standard, co pozwala na wybór atrakcyjny.

Zagadnienia dotyczące migracji

Kiedy myślimy o serializacji, musimy pamiętać o tym, co staramy się osiągnąć. Jednym z pytań, które należy wziąć pod uwagę podczas migracji do 64-bitowego, jest to, czy zamierzasz udostępniać serializowane informacje między różnymi platformami. Innymi słowy, informacje przechowywane przez 32-bitową aplikację zarządzaną będą odczytywać (lub deserializować) informacje przechowywane przez 32-bitową aplikację zarządzaną.

Twoja odpowiedź pomoże zwiększyć złożoność rozwiązania.

  • Możesz napisać własne procedury serializacji, aby uwzględnić platformy.
  • Możesz ograniczyć udostępnianie informacji, jednocześnie pozwalając każdej platformie na odczytywanie i zapisywanie własnych danych.
  • Możesz chcieć ponownie zapoznać się z serializacji i wprowadzić zmiany, aby uniknąć niektórych problemów.

Więc po tym wszystkim, jakie są zagadnienia dotyczące serializacji?

  • IntPtr ma długość 4 lub 8 bajtów w zależności od platformy. Jeśli serializujesz informacje, piszesz dane specyficzne dla platformy do danych wyjściowych. Oznacza to, że możesz i wystąpią problemy, jeśli spróbujesz udostępnić te informacje.

Jeśli rozważysz naszą dyskusję w poprzedniej sekcji dotyczącej marshalingu i przesunięcia, możesz wymyślić pytanie lub dwa o tym, jak serializacja rozwiązuje informacje o pakowaniu. W przypadku serializacji binarnej platforma .NET wewnętrznie używa poprawnego nieprzygotowanego dostępu do strumienia serializacji przy użyciu odczytów opartych na bajtach i poprawnej obsługi danych.

Jak już widzieliśmy, użycie serializacji nie uniemożliwia migracji do 64-bitowej. Jeśli używasz serializacji XML, musisz przekonwertować typy zarządzane z i na natywne typy zarządzane podczas procesu serializacji, izolowanie cię od różnic między platformami. Korzystanie z serializacji binarnej zapewnia bogatsze rozwiązanie, ale tworzy sytuację, w której należy podejmować decyzje dotyczące sposobu, w jaki różne platformy współdzielą serializowane informacje.

Podsumowanie

Migracja do 64-bitowej wersji jest dostępna, a firma Microsoft pracuje nad przejściem z 32-bitowych aplikacji zarządzanych do 64-bitowej, jak to możliwe.

Jednak nierealistyczne jest założenie, że można po prostu uruchomić 32-bitowy kod w środowisku 64-bitowym i mieć go uruchomić bez przeglądania migrowanych elementów.

Jak wspomniano wcześniej, jeśli masz 100% bezpiecznego kodu zarządzanego, możesz po prostu skopiować go na platformę 64-bitową i uruchomić ją pomyślnie w 64-bitowej clR.

Jednak bardziej niż prawdopodobne, że zarządzana aplikacja będzie związana z dowolnym lub wszystkimi następującymi elementami:

  • Wywoływanie interfejsów API platformy za pośrednictwem metody p/invoke
  • Wywoływanie obiektów COM
  • Korzystanie z niebezpiecznego kodu
  • Używanie marshalingu jako mechanizmu udostępniania informacji
  • Używanie serializacji jako sposobu utrwalania stanu

Niezależnie od tego, które z tych czynności aplikacja wykonuje, będzie ważne, aby wykonać pracę domową i zbadać, co robi kod i jakie zależności masz. Po wykonaniu tej pracy domowej trzeba będzie przyjrzeć się swoim wyborom, aby wykonać dowolne lub wszystkie następujące czynności:

  • Przeprowadź migrację kodu bez zmian.
  • Wprowadź zmiany w kodzie, aby poprawnie obsługiwać wskaźniki 64-bitowe.
  • Współpracuj z innymi dostawcami itp., aby udostępnić 64-bitowe wersje swoich produktów.
  • Wprowadź zmiany w logice w celu obsługi marshalingu i/lub serializacji.

Mogą wystąpić przypadki, w których podejmujesz decyzję, aby nie migrować kodu zarządzanego do 64-bitowego. W takim przypadku możesz oznaczyć zestawy tak, aby moduł ładujący systemu Windows mógł wykonać odpowiednie czynności podczas uruchamiania. Należy pamiętać, że zależności podrzędne mają bezpośredni wpływ na ogólną aplikację.

Fxcop

Należy również pamiętać o narzędziach, które są dostępne, aby ułatwić migrację.

Obecnie firma Microsoft ma narzędzie o nazwie FxCop, które jest narzędziem do analizy kodu, które sprawdza zestawy kodu zarządzanego platformy .NET pod kątem zgodności z wytycznymi firmy Microsoft .NET Framework dotyczącymi projektowania. Używa odbicia, analizowania MSIL i wywoływania analizy grafu w celu sprawdzenia zestawów pod kątem ponad 200 wad w następujących obszarach: konwencje nazewnictwa, projekt biblioteki, lokalizacja, zabezpieczenia i wydajność. FxCop zawiera zarówno graficzny interfejs użytkownika, jak i wersje wiersza polecenia narzędzia, a także zestaw SDK, aby utworzyć własne reguły. Aby uzyskać więcej informacji, zapoznaj się z witryną internetową FxCop . Firma Microsoft jest w trakcie opracowywania dodatkowych reguł FxCop, które będą dostarczać informacje ułatwiające podjęcie działań związanych z migracją.

W środowisku uruchomieniowym znajdują się również funkcje biblioteki zarządzanej, które ułatwiają określenie środowiska, w którym działasz.

  • System.IntPtr.Size — aby określić, czy korzystasz z trybu 32-bitowego, czy 64-bitowego
  • System.Reflection.Module.GetPEKind — aby programowo wykonać zapytanie o .exe lub .dll, aby sprawdzić, czy ma być uruchamiana tylko na określonej platformie lub w obszarze WOW64

Nie ma określonego zestawu procedur, które pozwalają sprostać wszystkim wyzwaniom, które można napotkać. Ten oficjalny dokument ma na celu zwiększenie świadomości na temat tych wyzwań i przedstawienie możliwych alternatyw.