Difference between revisions of "Cele i zasady tworzenia klas pochodnych"

From MorphOS Library

(The Dispatcher: Translation in progress.)
m (Formatting restored.)
 
(3 intermediate revisions by the same user not shown)
Line 11: Line 11:
 
* Tworzenie gadżetów i obszarów z własnymi procedurami rysowania się oraz obsługiwania zdarzeń zewnętrznych (mysz, klawiatura). W tym celu tworzy się klasy pochodne od klasy ''Area''.
 
* Tworzenie gadżetów i obszarów z własnymi procedurami rysowania się oraz obsługiwania zdarzeń zewnętrznych (mysz, klawiatura). W tym celu tworzy się klasy pochodne od klasy ''Area''.
 
Niezależnie od powodu tworzenia klas pochodnych, robi się to zawsze w ten sam sposób. Programista musi napisać nowe metody lub przeciążyć istniejące, stworzyć funkcję dispatchera, zdefiniować strukturę danych obiektu (bywa, że jest ona pusta) i w końcu stworzyć klasę. Warto zauważyć, że klasy pochodne w MUI robi się tak samo jak [[Rzut oka na BOOPSI|w BOOPSI]]. Jedyną różnicą jest to, że MUI posiada [[Rzut oka na BOOPSI#Jak MUI rozszerza BOOPSI|własne funkcje]] do tworzenia i usuwania klas.
 
Niezależnie od powodu tworzenia klas pochodnych, robi się to zawsze w ten sam sposób. Programista musi napisać nowe metody lub przeciążyć istniejące, stworzyć funkcję dispatchera, zdefiniować strukturę danych obiektu (bywa, że jest ona pusta) i w końcu stworzyć klasę. Warto zauważyć, że klasy pochodne w MUI robi się tak samo jak [[Rzut oka na BOOPSI|w BOOPSI]]. Jedyną różnicą jest to, że MUI posiada [[Rzut oka na BOOPSI#Jak MUI rozszerza BOOPSI|własne funkcje]] do tworzenia i usuwania klas.
 +
  
 
==Dane obiektu==
 
==Dane obiektu==
Line 17: Line 18:
  
 
Z powodu wewnętrznych ograniczeń [[Rzut oka na BOOPSI|BOOPSI]], rozmiar obszaru danych obiektu dla danej klasy nie może przekraczać 64 kB. Duże bufory i tablice powinny być w razie potrzeby alokowane dynamicznie w konstruktorze i zwalniane w destruktorze. Obszar danych obiektu jest zawsze kasowany przez BOOPSI zerami przed przekazaniem obiektowi do użytku. Jeżeli klasa nie potrzebuje danych obiektu, może jako rozmiar bloku danych podać zero funkcji ''MUI_CreateCustomClass()''.
 
Z powodu wewnętrznych ograniczeń [[Rzut oka na BOOPSI|BOOPSI]], rozmiar obszaru danych obiektu dla danej klasy nie może przekraczać 64 kB. Duże bufory i tablice powinny być w razie potrzeby alokowane dynamicznie w konstruktorze i zwalniane w destruktorze. Obszar danych obiektu jest zawsze kasowany przez BOOPSI zerami przed przekazaniem obiektowi do użytku. Jeżeli klasa nie potrzebuje danych obiektu, może jako rozmiar bloku danych podać zero funkcji ''MUI_CreateCustomClass()''.
 +
  
 
==Pisanie metod==
 
==Pisanie metod==
Line 49: Line 51:
  
 
Druga forma tej funkcji pozwala na rekonstrukcję struktury parametrów metody z argumentów funkcji. Jest używana, jeżeli parametry metody są modyfikowane przed wywołaniem tej metody w klasie nadrzędnej. Metoda klasy nadrzędnej może być wywołana w dowolnym miejscu metody klasy podrzędnej, może też nie zostać wywołana wcale. Dla standardowych klas i metod MUI zasady wywoływania metody klasy nadrzędnej są opisane w dokumentacji a także innych rozdziałach tego przewodnika. Dla metod nowo zdefiniowanych przez programistę, kwestia wywoływania metod klas nadrzędnych jest całkowicie pozostawiona do jego uznania.
 
Druga forma tej funkcji pozwala na rekonstrukcję struktury parametrów metody z argumentów funkcji. Jest używana, jeżeli parametry metody są modyfikowane przed wywołaniem tej metody w klasie nadrzędnej. Metoda klasy nadrzędnej może być wywołana w dowolnym miejscu metody klasy podrzędnej, może też nie zostać wywołana wcale. Dla standardowych klas i metod MUI zasady wywoływania metody klasy nadrzędnej są opisane w dokumentacji a także innych rozdziałach tego przewodnika. Dla metod nowo zdefiniowanych przez programistę, kwestia wywoływania metod klas nadrzędnych jest całkowicie pozostawiona do jego uznania.
 +
  
 
==Dispatcher==
 
==Dispatcher==
Line 72: Line 75:
 
Typ ''Msg'' jest typem bazowym dla wszystkich struktur parametrów metod. Definiuje on strukturę zawierającą tylko identyfikator metody typu ''ULONG''. Wszystkie pozostałe parametry metody muszą mieć adresy wyrównane zgodnie z wymaganiami stosu procesora, ponieważ funkcja ''DoMethod()'' buduje strukturę parametrów na stosie. Dlatego każdy parametr metody musi być zdefiniowany albo jako wskaźnik, albo ''IPTR'', jeżeli jest liczbą.
 
Typ ''Msg'' jest typem bazowym dla wszystkich struktur parametrów metod. Definiuje on strukturę zawierającą tylko identyfikator metody typu ''ULONG''. Wszystkie pozostałe parametry metody muszą mieć adresy wyrównane zgodnie z wymaganiami stosu procesora, ponieważ funkcja ''DoMethod()'' buduje strukturę parametrów na stosie. Dlatego każdy parametr metody musi być zdefiniowany albo jako wskaźnik, albo ''IPTR'', jeżeli jest liczbą.
  
After receiving arguments the dispatcher checks the method identifier from the message and jumps to the respective method. It is usually implemented as a ''switch'' statement. If only a few methods are implemented, it may also be an ''if/if else'' cascade. Here is a typical example:
+
Po otrzymaniu argumentów dispatcher sprawdza identyfikator metody i skacze do niej. Najczęściej robi się to w instrukcji ''switch''. W przypadku małej ilości metod można też się posłużyć kaskadowym ''if/else if''. Oto typowy przykład:
  
 
  switch (msg->MethodID)
 
  switch (msg->MethodID)
Line 86: Line 89:
 
  }
 
  }
  
For every method a message pointer is typecast to a message structure of this particular method. Some programmers place the method's code inside the ''switch'' statement directly, especially if methods are short and only a few. In the example above, some methods of ''Area'' class are overridden. The naming scheme used for the method functions is just an example, there are no constraints on this. Although prefixing method function names with a class name has an advantage of avoiding name conflicts between custom classes if method functions are not declared as ''static''.
+
Dla każdej metody wskaźnik na strukturę parametrów jest rzutowany na typ struktury parametrów tej konkretnej metody. Są programiści, którzy umieszczają kod metod bezpośrednio wewnątrz wyrażenia ''switch'', zwłaszcza gdy metod jest mało, a ich kod jest krótki. W powyższym przykładzie przeciążono kilka metod klasy ''Area''. Sposób nazywania funkcji implementujących metody jest wyłącznie przykładowy, nie ma tu żadnych narzuconych konwencji. Jednakże dodawanie nazwy klasy do nazw funkcji ma tę zaletę, że nie występują konflikty nazw między różnymi klasami jeżeli funkcje metod nie są zadeklarowane jako statyczne.
 +
 
  
==Class Creation==
+
==Tworzenie klasy==
  
Having all components done (methods, dispatcher, gate, object data structure) one can create a MUI class.
+
Jeżeli wszystkie niezbędne elementy (metody, dispatcher, bramka danych, struktura danych obiektu) są przygotowane, można stworzyć z nich kompletną klasę MUI.
  
 
  struct MUI_CustomClass *MyClass;
 
  struct MUI_CustomClass *MyClass;
Line 97: Line 101:
 
   (APTR)&MyClassGate);
 
   (APTR)&MyClassGate);
  
The first argument is a library base if the created class is public. Writing MUI public classes will be covered later. Let's say for now, that public classes are implemented as shared libraries, so such a public class has a library base. For private classes this argument should always be ''NULL''.  
+
Pierwszym argumentem ''MUI_CreateCustomClass()'' jest baza biblioteki, o ile tworzona klasa będzie publiczna. Pisanie klas publicznych będzie omówione w jednym z dalszych artykułów. Na razie powiedzmy jedynie, że klasy publiczne są implementowane jako biblioteki współdzielone, więc każda taka klasa ma bazę biblioteki. Dla klas prywatnych ten argument powinien być zawsze ''NULL''-em.
  
The next two arguments are used alternatively and specify the superclass. The superclass may be either private (referenced by pointer) or public (referenced by name). Public classes are usually subclassed, so the pointer is set to ''NULL'' as in the example. More complex projects may use multilevel subclassing and subclass their own private classes. In this case, a pointer to a private class is passed as the first argument and the second one is ''NULL''.
+
Dwa kolejne argumenty określają klasę nadrzędną tworzonej klasy. Są one używane zamiennie, jeżeli klasa nadrzędna jest prywatna, to odwołujemy się do niej przez wskaźnik (argument 3), jeżeli publiczna – to przez nazwę (argument 2). W każdym przypadku drugi, nieużywany argument tej pary powinien mieć wartość ''NULL''. Przykład powyżej zakłada tworzenie klasy pochodnej od klasy publicznej, jest to przypadek najczęstszy. Większe programy mogą mieć bardziej rozbudowane hierarchie klas i wtedy zdarza się dziedziczenie po klasie prywatnej.
  
The fourth argument defines the size of the object data area in bytes. In most cases object data area is defined as a structure, so using the ''sizeof()'' operator is the obvious way of determining the size. If the class does not need any per-object data, zero may be passed here.
+
Czwarty argument określa rozmiar danych obiektu w bajtach dla tworzonej klasy. Ponieważ z reguły dane obiektu są zdefiniowane jako struktura, wystarczy podać jej rozmiar, korzystając z operatora ''sizeof()''. Jeżeli klasa nie potrzebuje w ogóle żadnych danych obiektu, można podać tu 0.
  
The last argument is an address of the data gate (''EmulLibEntry'' structure). Programmers experienced on M68k programming may notice that there is a difference – in M68k code only the dispatcher function address is used here. As mentioned above, seamless M68k code support requires that program execution passes through the data gate when going from system code to the dispatcher. That is why the data gate address is placed as this argument, then the data gate contains a real dispatcher address.
+
Ostatni argument to adres struktury ''EmulLibEntry'', czyli bramki danych. Programiści z doświadczeniem w AmigaOS na procesorach m68k na pewno zauważą tu różnicę, w tamtym systemie podawało się tu po prostu adres funkcji dispatchera. Jak wspomniano wyżej, bezproblemowa współpraca kodu dla PowerPC i m68k wymaga, aby program zawsze przechodził przez bramkę danych przechodząc z kodu systemu do dispatchera. Dlatego argumentem funkcji ''MUI_CreateCustomClass()'' jest adres bramki, a dopiero w niej znajduje się adres funkcji dispatchera.
  
  
==Class Disposition==
+
==Usuwanie klasy==
  
A MUI class is disposed with a call to ''MUI_DeleteCustomClass()''.
+
Klasę MUI usuwa się za pomocą funkcji ''MUI_DeleteCustomClass()''.
  
 
  if (MyClass) MUI_DeleteCustomClass(MyClass);
 
  if (MyClass) MUI_DeleteCustomClass(MyClass);
  
Some conditions must be fulfilled before calling this function.
+
Przed usunięciem klasy muszą być spełnione dwa warunki:
* Call it only if the class was successfully created. Calling it with a ''NULL'' class pointer is deadly (hence the checking for ''NULL'' in the example).
+
* Klasa powinna być usuwana tylko w przypadku, kiedy została stworzona. Wywołanie ''MUI_DeleteCustomClass()'' ze wskaźnikiem zerowym może skutkować zawieszeniem programu (stąd sprawdzenie wskaźnika w przykładzie).
* Do not delete a class if it has any remaining subclasses or objects. The best practice is to create all classes before creating the application GUI and to dispose them after the final ''MUI_DisposeObject()'' of the main ''Application'' object. Classes should be deleted in the reversed order of creation. ''MUI_DeleteCustomClass()'' returns a boolean value. It is FALSE when a class cannot be deleted because of potentially orphaned subclasses or objects.
+
* Nie należy usuwać klasy, jeżeli istnieją w systemie jej klasy pochodne bądź obiekty. Najlepszą praktyką jest tworzenie klas przed stworzeniem interfejsu graficznego programu, a usuwanie ich po końcowym ''MUI_DisposeObject()'' usuwającym obiekt aplikacji. Funkcja ''MUI_DeleteCustomClass()'' sprawdza istnienie obiektów i klas podrzędnych i zwraca wartość logiczną ''FALSE'' jeżeli takowe istnieją i klasa nie może być usunięta.

Latest revision as of 12:23, 24 January 2011

Grzegorz Kraszewski


Ten artykuł w innych językach: angielski

Wprowadzenie

Tworzenie klas pochodnych to jedna z podstawowych technik programowania obiektowego. W MUI najczęściej używamy klas pochodnych do następujących celów:

  • Umieszczenie całej funkcjonalności programu w zestawie metod, które mogą być potem wykonywane w notyfikajach. Do tego celu najczęściej służy klasa pochodna klasy Application.
  • Dostosowywanie klas standardowych do potrzeb programu przez przeciążanie specjalnie do tego przeznaczonych metod. Typowym przypadkiem są klasy pochodne od List, Numericbutton czy Popstring.
  • Tworzenie gadżetów i obszarów z własnymi procedurami rysowania się oraz obsługiwania zdarzeń zewnętrznych (mysz, klawiatura). W tym celu tworzy się klasy pochodne od klasy Area.

Niezależnie od powodu tworzenia klas pochodnych, robi się to zawsze w ten sam sposób. Programista musi napisać nowe metody lub przeciążyć istniejące, stworzyć funkcję dispatchera, zdefiniować strukturę danych obiektu (bywa, że jest ona pusta) i w końcu stworzyć klasę. Warto zauważyć, że klasy pochodne w MUI robi się tak samo jak w BOOPSI. Jedyną różnicą jest to, że MUI posiada własne funkcje do tworzenia i usuwania klas.


Dane obiektu

Dane każdego obiektu są przechowywane w obszarze automatycznie przydzielanym przez system. Obszaru tego używa się do przechowywania aktualnych wartości atrubutów, oraz do zmiennych i buforów wewnętrznych, repezentujących stan obiektu. Jest on zwykle definiowany jako struktura. Rozmiar danych obiektu jest podawany jako argument funkcji MUI_CreateCustomClass(). W hierarchii klas, każda klasa może dodać swoją część do obszaru danych obiektu. Jednakże w przeciwieństwie do C++, każda klasa ma dostęp jedynie do swojej części danych. Kod klasy nie może sięgnąć do danych klasy nadrzędnej (W C++ jest to możliwe, jeżeli pole danych ma deklarację protected, albo public). Dane klas nadrzędnych są dostępne wyłącznie poprzez atrybuty i metody zdefiniowane w tych klasach.

Z powodu wewnętrznych ograniczeń BOOPSI, rozmiar obszaru danych obiektu dla danej klasy nie może przekraczać 64 kB. Duże bufory i tablice powinny być w razie potrzeby alokowane dynamicznie w konstruktorze i zwalniane w destruktorze. Obszar danych obiektu jest zawsze kasowany przez BOOPSI zerami przed przekazaniem obiektowi do użytku. Jeżeli klasa nie potrzebuje danych obiektu, może jako rozmiar bloku danych podać zero funkcji MUI_CreateCustomClass().


Pisanie metod

Metoda MUI jest zwykłą funkcją w C, jednakże zestaw argumentów i ich typów jest częściowo stały.

IPTR NazwaMetody(Class *cl, Object *obj, TypStruktury *msg);

Rezultat metody może być albo liczbą całkowitą, albo wskaźnikiem na cokolwiek. Dlatego rezultat ten jest typu IPTR, który jest zdefiniowany jako "liczba całkowita wystarczająco duża, aby zmieścił się w niej wskaźnik". W aktualnej wersji MorphOS-a jest to po prostu liczba 32-bitowa (tak samo jak LONG). Jeżeli metoda nie ma nic sensownego do zwrócenia jako wynik, najlepiej po prostu zwrócić zero. Dwa pierwsze, stałe argumenty to: wskaźnik na klasę obiektu, oraz wskaźnik na sam obiekt. Ostatni argument to struktura parametrów metody. Jeżeli metoda jest przeciążeniem metody klasy nadrzędnej, to typ struktury parametrów określony jest przez klasę nadrzędną. Dla nowych metod typ tej struktury jest określany przez programistę. Niektóre metody mogą posiadać puste struktury parametrów (zawierające wyłącznie identyfikator metody). W tym przypadku trzeci argument może być pominięty.

Większość metod potrzebuje dostępu do danych obiektu. Do otrzymania wskaźnika na te dane używa się makra INST_DATA, zdefiniowanego w pliku <intuition/classes.h>. Oto przykład użycia tego makra:

struct ObjData
{
  LONG JakasWartosc;
  /* ... */
};

IPTR JakasMetoda(Class *cl, Object *obj)
{
  struct ObjData *d = (struct ObjData*)INST_DATA(cl, obj);

  d->JakasWartosc = 14;
  /* ... */
  return 0;
}

Jeżeli metoda jest dziedziczona z klasy nadrzędnej, może być w kodzie metody potrzebne wywołanie tej metody na klasie nadrzędnej. Takie wywołanie nigdy nie jest wykonywane niejawnie. Metoda klasy nadrzędnej musi być zawsze wywołana funkcją DoSuperMethodA():

result = DoSuperMethodA(cl, obj, msg);
result = DoSuperMethod(cl, obj, identyfikator, ...);

Druga forma tej funkcji pozwala na rekonstrukcję struktury parametrów metody z argumentów funkcji. Jest używana, jeżeli parametry metody są modyfikowane przed wywołaniem tej metody w klasie nadrzędnej. Metoda klasy nadrzędnej może być wywołana w dowolnym miejscu metody klasy podrzędnej, może też nie zostać wywołana wcale. Dla standardowych klas i metod MUI zasady wywoływania metody klasy nadrzędnej są opisane w dokumentacji a także innych rozdziałach tego przewodnika. Dla metod nowo zdefiniowanych przez programistę, kwestia wywoływania metod klas nadrzędnych jest całkowicie pozostawiona do jego uznania.


Dispatcher

Funkcja dispatchera klasy pełni rolę tablicy skoków do metod. Gdy jakakolwiek metoda zostanie wywołana na obiekcie (za pomocą DoMethod()), BOOPSI odszukuje dispatchera klasy obiektu i wywołuje go. Dispatcher sprawdza identyfikator metody (identyfikator jest zawsze pierwszym polem struktury parametrów metody) i wywołuje odpowiadającą mu metodę. Jeżeli metoda jest nieznana w danej klasie, dispatcher powinien przekazać ją klasie nadrzędnej poprzez funkcję DoSuperMethod().

Dispatcher jest szczególnym przypadkiem hooka. Pozwala to na wywoływanie go z dowolnego języka programowania, ponieważ sposób przekazania argumentów jest uniezależniony od C/C++. Wadą tego rozwiązania jest przekazywanie argumentów hooka poprzez wirtualne rejestry emulowanego procesora m68k. Z drugiej strony pozwala to na wykorzystanie natywnych klas skompilowanych dla PowerPC przez stare programy m68k, podobnie nowe natywne oprogramowanie może skorzystać ze starszych klas MUI pisanych jeszcze dla m68k. Jako że dispatcher jest hookiem, wymaga zdefiniowania odpowiadającej mu struktury EmulLibEntry zdefiniowanej w pliku nagłówkowym <emul/emulinterface.h>. Struktura ta funkcjonuje jako "bramka" dla agrumentów przekazywanych między natywnym kodem PowerPC, a emulatorem m68k.

const struct EmulLibEntry ClassGate = {TRAP_LIB, 0, (void(*)(void))ClassDispatcher};

Sam dispatcher zdefiniowany jest następująco:

IPTR ClassDispatcher(void)
{
  Class *cl = (Class*)REG_A0;
  Object *obj = (Object*)REG_A2;
  Msg msg = (Msg)REG_A1;

  /* ... */

Argumenty dispatchera są takie same jak argumenty metody, ale są przekazywane przez wirtualne rejestry procesora m68k, zamiast być po prostu argumentami funkcji. Jako wskaźnik na dispatcher funkcja MUI_CreateCustomClass() dostaje adres bramki danych (EmulLibEntry). Bramka danych jest używana nawet wtedy, kiedy natywna aplikacja PowerPC wywołuje również natywną klasę MUI. Wprowadzane przez bramkę danych opóźnienie jest jednak pomijalne. Wielu programistów używa prostych makr do "ukrycia" w kodzie powyższych detali, nie zostały one tu jednak użyte dla lepszego zrozumienia zagadnienia.

Typ Msg jest typem bazowym dla wszystkich struktur parametrów metod. Definiuje on strukturę zawierającą tylko identyfikator metody typu ULONG. Wszystkie pozostałe parametry metody muszą mieć adresy wyrównane zgodnie z wymaganiami stosu procesora, ponieważ funkcja DoMethod() buduje strukturę parametrów na stosie. Dlatego każdy parametr metody musi być zdefiniowany albo jako wskaźnik, albo IPTR, jeżeli jest liczbą.

Po otrzymaniu argumentów dispatcher sprawdza identyfikator metody i skacze do niej. Najczęściej robi się to w instrukcji switch. W przypadku małej ilości metod można też się posłużyć kaskadowym if/else if. Oto typowy przykład:

switch (msg->MethodID)
{
  case OM_NEW:            return MyClassNew(cl, obj, (struct opSet*)msg);
  case OM_DISPOSE:        return MyClassDispose(cl, obj, msg);
  case OM_SET:            return MyClassSet(cl, obj, (struct opSet*)msg);
  case OM_GET:            return MyClassGet(cl, obj, (struct opGet*)msg);
  case MUIM_Draw:         return MyClassDraw(cl, obj, (struct MUIP_Draw*)msg);
  case MUIM_AskMinMax:    return MyClassAskMinMax(cl, obj, (struct MUIP_AskMinMax*)msg);
  /* ... */
  default:                return DoSuperMethodA(cl, obj, msg);
}

Dla każdej metody wskaźnik na strukturę parametrów jest rzutowany na typ struktury parametrów tej konkretnej metody. Są programiści, którzy umieszczają kod metod bezpośrednio wewnątrz wyrażenia switch, zwłaszcza gdy metod jest mało, a ich kod jest krótki. W powyższym przykładzie przeciążono kilka metod klasy Area. Sposób nazywania funkcji implementujących metody jest wyłącznie przykładowy, nie ma tu żadnych narzuconych konwencji. Jednakże dodawanie nazwy klasy do nazw funkcji ma tę zaletę, że nie występują konflikty nazw między różnymi klasami jeżeli funkcje metod nie są zadeklarowane jako statyczne.


Tworzenie klasy

Jeżeli wszystkie niezbędne elementy (metody, dispatcher, bramka danych, struktura danych obiektu) są przygotowane, można stworzyć z nich kompletną klasę MUI.

struct MUI_CustomClass *MyClass;

MyClass = MUI_CreateCustomClass(NULL, MUIC_Area, NULL, sizeof(struct MyClassData),
 (APTR)&MyClassGate);

Pierwszym argumentem MUI_CreateCustomClass() jest baza biblioteki, o ile tworzona klasa będzie publiczna. Pisanie klas publicznych będzie omówione w jednym z dalszych artykułów. Na razie powiedzmy jedynie, że klasy publiczne są implementowane jako biblioteki współdzielone, więc każda taka klasa ma bazę biblioteki. Dla klas prywatnych ten argument powinien być zawsze NULL-em.

Dwa kolejne argumenty określają klasę nadrzędną tworzonej klasy. Są one używane zamiennie, jeżeli klasa nadrzędna jest prywatna, to odwołujemy się do niej przez wskaźnik (argument 3), jeżeli publiczna – to przez nazwę (argument 2). W każdym przypadku drugi, nieużywany argument tej pary powinien mieć wartość NULL. Przykład powyżej zakłada tworzenie klasy pochodnej od klasy publicznej, jest to przypadek najczęstszy. Większe programy mogą mieć bardziej rozbudowane hierarchie klas i wtedy zdarza się dziedziczenie po klasie prywatnej.

Czwarty argument określa rozmiar danych obiektu w bajtach dla tworzonej klasy. Ponieważ z reguły dane obiektu są zdefiniowane jako struktura, wystarczy podać jej rozmiar, korzystając z operatora sizeof(). Jeżeli klasa nie potrzebuje w ogóle żadnych danych obiektu, można podać tu 0.

Ostatni argument to adres struktury EmulLibEntry, czyli bramki danych. Programiści z doświadczeniem w AmigaOS na procesorach m68k na pewno zauważą tu różnicę, w tamtym systemie podawało się tu po prostu adres funkcji dispatchera. Jak wspomniano wyżej, bezproblemowa współpraca kodu dla PowerPC i m68k wymaga, aby program zawsze przechodził przez bramkę danych przechodząc z kodu systemu do dispatchera. Dlatego argumentem funkcji MUI_CreateCustomClass() jest adres bramki, a dopiero w niej znajduje się adres funkcji dispatchera.


Usuwanie klasy

Klasę MUI usuwa się za pomocą funkcji MUI_DeleteCustomClass().

if (MyClass) MUI_DeleteCustomClass(MyClass);

Przed usunięciem klasy muszą być spełnione dwa warunki:

  • Klasa powinna być usuwana tylko w przypadku, kiedy została stworzona. Wywołanie MUI_DeleteCustomClass() ze wskaźnikiem zerowym może skutkować zawieszeniem programu (stąd sprawdzenie wskaźnika w przykładzie).
  • Nie należy usuwać klasy, jeżeli istnieją w systemie jej klasy pochodne bądź obiekty. Najlepszą praktyką jest tworzenie klas przed stworzeniem interfejsu graficznego programu, a usuwanie ich po końcowym MUI_DisposeObject() usuwającym obiekt aplikacji. Funkcja MUI_DeleteCustomClass() sprawdza istnienie obiektów i klas podrzędnych i zwraca wartość logiczną FALSE jeżeli takowe istnieją i klasa nie może być usunięta.