Jakiś czas temu zacząłem pisać silnik plus edytor do niego. Postanowiłem, że silnik będzie napisany całkowicie w kodzie native C++, natomiast edytor powstanie w C#.NET. Pierwszym problemem jaki napotkałem było zgrabne połączenie kodu native z managed. VS2005 mimo, że posiada pewne usprawnienia w stosunku do VS2003 to jednak łączenie tych dwóch platform, nadal jest dość uciążliwe. Niestety Orcas czyli VS2008 również nie zmienia wiele w tej kwestii. Musiałem więc opracować własny sposób na to aby połączyć te 2 światy. Ponieważ tego sposobu używam już od pewnego czasu i sprawdza się w moim przypadku, postanowiłem się nim podzielić.

Pierwszym założeniem było stworzenie silnika data-driven. Postanowiłem zamknąć silnik w pliku .DLL aby z góry wymusić pewne rozwiązania związane z interfacem oraz zarządzaniem pamięcią. Wszystkie klasy silnika zostały wyeksportowane na zewnątrz, tak aby był łatwy do nich dostęp z poziomu managed. Zakładam, że przy tworzeniu tzw. FINAL BUILD czyli tworzeniu pliku .DLL na potrzeby rozprowadzanej gry, całkowite eksportowanie będzie wyłączone i na zewnątrz .DLL będą widoczne tylko podstawowe funkcje potrzebne do uruchomienia silnika i gry.

Drugą decyzją, nawet ważniejszą i najbardziej ryzykowną, było stworzenie własnego projektu-wrappera który będzie udostępniał klasy silnika, platformie .NET. Dość długo myślałem czy nie spróbować użyć jakiegoś automatu (jak np. Swig) ale po pierwszych testach stwierdziłem, że jednak pisanie własnego wrappera wbrew pozorom nie będzie aż tak czasochłonne, a pozwoli na łatwe użycie bardziej zaawansowanych właściwości środowiska .NET (properties, attributes, itd). Dodatkowo dla ułatwienia napisałem prosty program który dodaje nową klasę do silnika, tworząc jednocześnie podstawowy wrapper dla niej.

Tak więc, w chwili obecnej mam 3 projekty:

  1. Engine.DLL
  2. Engine.NET.DLL
  3. Editor.NET.EXE

Tworzenie nowej klasy silnika, wygląda teraz następująco (przykład umieściłem w plikach, gdyż trochę zajmuje)

  1. stworzenie klasy native (projekt Engine.DLL) Native.cpp Native.h
  2. stworzenie klasy wrapper (projekt Engine.NET.DLL) Managed.h

Z pewnością dodatkowego wyjaśnienia wymaga “magic number”. Jest to ID klasy które wykorzystuje w moim systemie zarządzania klasami native. Takie proste Reflection dla kodu native. ID jest również pomocne przy identyfikowaniu wskaźników na obiekty native przez kod managed. I tutaj doszedłem do kolejnego problemu przy łączeniu kodu native i managed.

Obiekty klas native, zazwyczaj są tworzone przez kod silnika. Problem powstaje jeśli chcemy móc operować na obiekcie który zostaje zwrócony przez kod native. Np. jeśli nasz kod managed wygląda tak:

Engine.Block1.Foo obiekt = Engine.Block1.Foo.GetChild();

to wywołamy metode GetChild() z kodu native, która zwróci nam wskaźnik native. Taki wskaźnik trzeba rozpoznać i zwrócić odpowiedni obiekt wrappera .NET. Temu posłuży ID klasy.

Jak widać w tych 3 plikach, ID jest zdefiniowane w 2 miejscach. Jako ID w klasie native, oraz ID jako atrybut w klasie managed. Atrybut klasy managed określa, dla której klasy native jest to wrapper. I teraz jeśli otrzymamy z kodu native wskaźnik na obiekt klasy native, to za pomocą odpowiedniej funkcji możemy stworzyć obiekt klasy managed:

Engine::BaseObject^ GetChild()
{
BaseObject* native_object = m_NativePtr->GetChild();
return Engine::BaseObject::FromNativeToManaged( native_object );
}

Statyczna metoda FromNativeToManaged() w kodzie managed, posiada listę klas wrapperów managed i za pomocą ID odnajduje odpowiedniego wrappera i tworzy jego obiekt.

Wrapper powinien posiadać 2 konstruktory. Jeden, w którym otrzyma obiekt native stworzony w silniku, którym ma zarządzać, oraz drugi konstruktor bez parametrowy w który umożliwia z kodu managed, utworzenie obiektu native.

Jeszcze jedną dość istotną informacją związaną klasami native, była decyzja o tym, aby klasy native posiadały tylko 1 konstruktor który jest bez parametrowy i który jest w dodatku protected. Tworzenie obiektu odbywa się tylko poprzez wywołanie statycznej metody CreateObject() w danej klasie (np. Foo::CreateObject() ). Ma to na celu wymuszenie tworzenia obiektów tylko w obszarze pamięci silnika.

Komunikacja między kodem native a managed
0 votes, 0.00 avg. rating (0% score)