Development z Zapomnianej Strony

..:: Paweł Hofman .NET Portal ::..

Nieprawdą jest, że na platformie .NET nie istnieją wskaźniki. W C# w sekcji unsafe, możemy spokojnie mieszać "typowy" kod .NET z operacjami na wskaźnikach. Tu jednak uwaga:

  1. sekcje unsafe muszą być propagowane w górę, a to oznacza, że i metoda, w której są zawarte, musi być również opisana jako niebezpieczna (czasem i cała klasa),
  2. oraz co dużo ważniejsze - podczas kompilacji modułu (assembly) wymagany jest przełącznik "/unsafe", zezwalający jawnie na używanie niebezpiecznych operacji na wskaźnikach.

Co ciekawe - dostępny staje się dobrze znany z C/C++ operator sizeof (a nie, jak wspomniano dotychczas tylko: Marshal.SizeOf() przy P/Invoke).

 

public unsafe void MakeTriple(int *pValue)
{
  *pValue=(*pValue)*3;
}

 

UWAGA!

Wskaźniki są niebezpieczne przede wszystkim z racji istnienia śmieciarza w .NET (zwanego przez niektórych również Garbage Collectorem, w skrócie GC). Otóż ów GC analizuje użycie wszystkich obiektów utworzonych w czasie życia programu .NET-owego i decyduje, które z nich mają być usunięte z pamięci. Nie wdając się zbytnio w szczegóły procesu decyzyjnego, należy zauważyć, iż obiekt, na który wskazywał wskaźnik, może bez żadnego powiadomienia oraz jawnej przyczyny zostać usuniętym, a jego miejsce zupełnie nieużywane. GC może, co gorsze, rozpocząć również proces upakowywania obiektów (usunąć fragmentację pamięci), aby zrobić więcej dla nowych obiektów, a tym samym wskażnik nie będzie już pokazywal poprawnej lokalizacji, bądź wskazywał inny obiekt.

Typowe referencje (do klas lub struktur) nadążają za tą polityką i nie są powodem tego typu błedów. Aby więc zabezpieczyć się, przed owymi niepożądanymi skutkami, używane jest przypinanie obiektów, które nie pozwala GC na żadne manipulacje wskazanym obiektem. Służy do tego słowo kluczowe fixed.

 

public unsafe static void Main()
{
  MyData data = new MyData();
 
fixed ( int *ptr = &data.valueField )
{
   *ptr = *ptr + 10;
}
}



Niestety czasami okazuje się, że platforma .NET nie dostarcza funkcjonalności, które łatwo osiągało się, korzystając po prostu z natywnego Win32 API w C/C++. Przykładem tutaj są chociażby zaawansowane operacje na kontrolkach systemowych, haki w systemie (SetWindowsHookEx), zarządzanie systemowymi ustawieniami (głośność, wyciszenie). Albo wręcz przeciwnie - jesteśmy już w posiadaniu gotowego i działającego komponentu, któremu jedyne, co można zarzucić to fakt, iż został napisany właśnie w C/C++.

Rozwiązania są dwa (proponowane i wspierane przez Microsoft):

  1. Zacząć naukę programowania w Managed C++, który będzie stanowił pomost i korzystał z metod i klas w C++, a wystawiał interfejs dla .NET-owych modułów (assemblies),
  2. skorzystać z mechanizmu Platform Invoke (w skrócie P/Invoke).

 

Za *NIE* używaniem opcji (1) przemawia sama złożoność i komplikacje wprowadzane przez ten mieszany język programowania. Ponadto dochodzi również fakt, iż użyta w nim technika IJW (It-Just-Works) nie działa na urządzeniach mobilnych (np. na żadnym PDA z zainstalowanym Windows Mobile 2003 lub nowszym). Większość kodu odpowiedzialnego za przenoszenie danych (zwykłe kopiowanie z jednego typu struktur na drugi) spada na barki programisty. I zapewne jeszcze dużo, dużo więcej...

 

P/Invoke z kolei gwarantuje bardzo proste przekazywanie całych struktur C# do C/C++ (i w drugą stronę) z użyciem zaimportowanych funkcji (słowo kluczowe extern). Począwszy od .NET 2.0 (oraz CompactFramework 2.0) wszystko to osiągną się programując w C#, eliminując potrzebę posiadania dodatkowego projektu (modułu) pośredniczącego.

Dane są automatycznie kopiowane, bazując na kilku zdefiniowanych odgórnie przez Microsoft:

 

Wokól techniki tej zgromadzona jest też społeczność, która większość, jeśli nie wszystkie wywołania systemowe, zebrała i udostępnia pod adresem: pinvoke.net.