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

From MorphOS Library

(Wprowadzenie: Translation in progress.)
m (Formatting restored.)
 
(8 intermediate revisions by the same user not shown)
Line 8: Line 8:
 
Tworzenie klas pochodnych to jedna z podstawowych technik programowania obiektowego. W MUI najczęściej używamy klas pochodnych do następujących celów:
 
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''.
 
* 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''.
* Customizing classes by writing methods intentionally left unimplemented (or having some default implementations) in standard  MUI classes. The most common example is the ''List'' class, but also the ''Numeric'' one and others.
+
* Dostosowywanie klas standardowych do potrzeb programu przez przeciążanie specjalnie do tego przeznaczonych metod. Typowym przypadkiem są klasy pochodne od ''List'', ''Numericbutton'' czy ''Popstring''.
* Writing custom drawn gadgets or areas. The ''Area'' class is subclassed in this case.
+
* 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''.
Regardless of the reason for subclassing, it is always done in the same way. A programmer must write new methods or override existing methods, create a dispatcher function, define an instance data structure (an empty one in some cases), then create the class. It is worth noting, subclassing MUI classes is done the same as subclassing [[Short_BOOPSI_Overview|BOOPSI ones]]. The only difference is that MUI provides its [[Short_BOOPSI_Overview#MUI_Extensions_to_BOOPSI|own functions]] for class creation and disposition.
+
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.
  
==Object Data==
 
  
Object data are stored in a memory area automatically allocated for every object created. The object data area is used for storing attribute values and for internal variables and buffers. This area is usually defined as a structure. Size of the area is passed to the ''MUI_CreateCustomClass()'' function. In a class hierarchy, every class may add its own contribution to the object data area. Unlike in C++, a class has no direct access to anything except its own data area. It can't access data defined in the superclass (In C++ it is possible that a field is declared as ''protected'' or ''public''). Object data defined in any of the superclasses may only be accessed using methods or attributes provided by these superclasses.
+
==Dane obiektu==
  
Because of internal [[Short BOOPSI Overview|BOOPSI]] design, the size of the data instance area is limited to 64 kB. Large buffers should be allocated dynamically in OM_NEW() and freed in OM_DISPOSE(). The area data is always cleared to all zeros at object creation. If a class does not need any object instance data it can pass 0 as the area size to ''MUI_CreateCustomClass()''.
+
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ń [[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()''.
  
==Writing Methods==
 
A MUI method is just a plain C function, but with a partially fixed prototype.
 
  
IPTR ''MethodName''(Class *cl, Object *obj, ''MessageType'' *msg);
+
==Pisanie metod==
  
The method return value may be either integer or pointer to anything. That is why it uses the ''IPTR'' type which means "integer big enough to hold a pointer". In the current MorphOS it is just a 32-bit integer (the same as ''LONG''). If a method has no meaningful value to return, it can just return 0. Two first, fixed arguments are: pointer to the class and pointer to the object. The last one is a method message. When a method is being overridden, the type of message is determined by the superclass. For a new method, message type is defined by the programmer. Some methods may have empty messages (containing only a method identifier), in this case the third argument may be omitted.
+
Metoda MUI jest zwykłą funkcją w C, jednakże zestaw argumentów i ich typów jest częściowo stały.
  
Most methods need access to the object instance data. To get a pointer to the data area, one uses the ''INST_DATA'' macro, defined in ''<intuition/classes.h>''. An example below shows the macro usage:
+
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
 
  struct ObjData
 
  {
 
  {
   LONG SomeVal;
+
   LONG JakasWartosc;
 
   /* ... */
 
   /* ... */
 
  };
 
  };
 
   
 
   
  IPTR SomeMethod(Class *cl, Object *obj)
+
  IPTR JakasMetoda(Class *cl, Object *obj)
 
  {
 
  {
 
   struct ObjData *d = (struct ObjData*)'''INST_DATA(cl, obj)''';
 
   struct ObjData *d = (struct ObjData*)'''INST_DATA(cl, obj)''';
 
   
 
   
   d->SomeVal = 14;
+
   d->JakasWartosc = 14;
 
   /* ... */
 
   /* ... */
 
   return 0;
 
   return 0;
 
  }
 
  }
  
If a method is an overridden method from a superclass, it may want to perform the superclass method. There are no implicit super method calls in MUI. The superclass method must always be called explicitly with the ''DoSuperMethodA()'' call:
+
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 = DoSuperMethodA(cl, obj, msg);
  result = DoSuperMethod(cl, obj, ''MethodID'', ...);
+
  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.
  
The second form rebuilds the method message from variable arguments, and is used when the message is modified before calling the superclass method. The super method may be called in any place of the method, or may not be called at all. For MUI standard classes and methods, rules of calling super methods are described in the documentation and will be discussed later in this tutorial. For custom methods the question of calling a super method is up to the application programmer.
 
  
 +
==Dispatcher==
  
==The Dispatcher==
+
Funkcja dispatchera klasy pełni rolę tablicy skoków do metod. Gdy jakakolwiek metoda zostanie wywołana na obiekcie (za pomocą ''DoMethod()''), [[Rzut oka na BOOPSI|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()''.
A dispatcher function is a kind of jump table for methods. When any method is called on an object (with ''DoMethod()''), BOOPSI finds the dispatcher of the object's class and calls it. The dispatcher checks a method identifier, which is always the first field of any method message. Based on the identifier, a method is called. If a method is unknown to the class, the dispatcher should pass it to the superclass with the ''DoSuperMethod()'' call.
 
  
The dispatcher is a kind of [[Hooks|hook]] function. It makes its calling convention independent of programming language. A disadvantage of this is that the dispatcher's arguments are passed in virtual M68k processor registers. This inconvenience allows support for legacy M68k software and also allows for native PowerPC classes to be used by M68k applications and old M68k classes to be used by native applications. Being a hook, a dispatcher needs an ''EmulLibEntry'' structure to be created and filled first. The structure is defined in  ''<emul/emulinterface.h>'' and acts as a data gate between PowerPC native code and the M68k emulator.
+
Dispatcher jest szczególnym przypadkiem [[Hooks|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};
 
  const struct EmulLibEntry ClassGate = {TRAP_LIB, 0, (void(*)(void))ClassDispatcher};
  
Then the dispatcher is defined as follows:
+
Sam dispatcher zdefiniowany jest następująco:
  
 
  IPTR ClassDispatcher(void)
 
  IPTR ClassDispatcher(void)
Line 68: Line 71:
 
   /* ... */
 
   /* ... */
  
Arguments of the dispatcher are the same as arguments of a method. They are passed in virtual M680x0 processor address registers A0, A1 and A2 instead of being just arguments. The dispatcher's data gate is passed as an argument to ''MUI_CreateCustomClass()''. The data gate is used even when a native application calls a native dispatcher. It introduces some overhead, but it's negligible. Many programmers prefer to hide these details behind a set of preprocessor macros, such macros have not been used here however, for better understanding.
+
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.
  
The ''Msg'' type is a root type for all method messages. It defines a structure containing only the method identifier field (defined as ULONG). All following parameters have to keep the CPU stack aligned, as ''DoMethod()'' builds the message on the stack. It requires that every parameter is defined either as an ''IPTR'' or as a pointer.
+
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 98: 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 &ndash; 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 &ndash; 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.