Aros/Developer/Docs/Libraries/Intuition/BOOPSI
BOOPSI(Intuition 的基本面向物件程式設計系統)是 AmigaOS 的面向物件程式設計系統。它使用面向物件的子系統擴充套件了 AmigaOS 視窗環境 (Intuition),允許物件類的層次結構,其中每個類定義一個單獨的 GUI 小部件或介面事件。
BOOPSI 使開發人員更容易建立自己的小部件系統並建立標準化的圖形使用者介面。Magic User Interface (MUI) 和 ReAction 是基於 BOOPSI 構建的完整小部件工具包的示例。這兩個工具包已成為 Amiga 軟體程式設計師生成和維護圖形使用者介面的流行選擇。
面向物件的設計帶來了諸如物件與其他物件的直接耦合之類的優勢。例如,程式設計師可以連結一個數值輸入欄位和一個滑塊控制元件,如果使用者調整滑塊控制元件,則輸入欄位中的數值會自動更改。
BOOPSI 是隨 AmigaOS (TM) 2.0 正式引入的,並在後續版本中得到進一步擴充套件。在稍加了解 BOOPSI 的情況下,資料型別會變得更容易。
oop.library 是另一個 AROS 面向物件軟體基礎,用於構建 HIDD。(圖形 HIDD、PCI HIDD、滑鼠 HIDD 等)。過去,Staf(監督 ABI 更改)表示,他可能會在那裡改變一些方面(他不希望為當前的 ABIv1 工作凍結它),因此最好從他那裡獲得有關這些擔憂的意見,如果將任何編譯器/框架支援與任何東西統一起來是否有意義。
Intuition 的基本面向物件程式設計系統 (BOOPSI) 是一個簡單的 OS 支援模型,用於為您的程式生成系統範圍的和語言無關的類。BOOPSI 本身是透過 intuition.library 提供的,儘管它可以用於 GUI 以外的事情並沒有什麼理由。該系統透過提供一種方法來使面向物件風格的類可用,然後提供一組函式來建立物件並在這些物件上執行操作來工作。
建立小工具物件,並呼叫 AddGadget()。設定位置和大小資料,以便將其放置在需要的位置。計算視窗調整大小時的新位置和大小等。Intuition 確實有一個名為 GM_LAYOUT 的新小工具方法,用於告知 Boopsi 小工具其視窗的尺寸已更改。GM_LAYOUT 方法使用 gpLayout 結構。因此,您可以新增、刪除和修改已新增到小工具物件的 BOOPSI 物件。透過使用 LAYOUT_AddChild、LAYOUT_AddImage、LAYOUT_RemoveChild 和 LAYOUT_ModifyChild 標籤。
Why Zune is recommended as the default gui option for AROS.
BOOPSI 中的類非常易於建立、維護和使用。它們只是一個充當類每個“方法”的排程器的函式。本質上,一個關於請求方法的“訊息”是使用 DoMethod() 函式傳送給物件的。DoMethod() 函式將檢視該物件並確定它所屬的類,然後將訊息傳遞到該類的“方法”排程器。訊息本身是一個簡單的結構,形式如下
typedef struct {
ULONG MethodID;
/* Method specific information follows */
} *Msg;
例如,OM_SET 訊息如下所示
struct opSet {
ULONG MethodID;
struct TagItem *ops_AttrList;
struct GadgetInfo *ops_GInfo;
};
排程器實際上只是一個單一函式,它使用指向其類結構、呼叫該方法的物件和傳送給該物件的訊息的指標來呼叫。然後,排程器檢視訊息內部以檢視在物件上呼叫了哪個方法,然後採取適當的行動。以下是排程器的一個示例
ULONG dispatcher( Class *cl, Object *o, Msg msg )
{
ULONG retval = NULL;
/* figure out what method was called */
switch( msg->MethodID )
{
/* these are just some of the standard methods defined by rootclass */
case OM_NEW:
break;
case OM_SET:
break;
case OM_GET:
break;
/* other methods that the class can handle are placed here... */
/* the method called isn't understood by this class, call our parent
class with the message */
default:
DoSuperMethodA( cl, o, msg );
break;
}
return( retval );
}
BOOPSI 中的物件只是一個分配的記憶體塊,它被物件所屬的類及其所有祖先擁有的部分分割。每個類都知道其資料在哪裡,方法是使用其類結構中的一個欄位,該欄位由系統設定,因此為了訪問其資料,該類會在該欄位中查詢,然後將其值新增到物件指標中。幸運的是,提供了一個宏來為您執行此功能。
struct localObjData {
ULONG value1;
ULONG value2;
};
void dispatcher( Class *cl, Object *o, Msg msg )
{
struct localObjData *lod;
ULONG retval;
switch( msg->MethodID )
{
case OM_NEW:
break;
...
case SM_SOMEMETHOD:
lod = INST_DATA(cl, o); /* get the pointer to our instance data */
lod->value2 = lod->value1; /* do something with it */
lod->value1 = SOME_NUMBER;
break;
...
default:
DoSuperMethodA(cl, o, msg);
break;
}
return( retval );
}
!!!!!!!!!!!!!!!警告!!!!!!!!!!!!!!! 如果您打算製作類,請務必確保您的例項資料指標 lod 已設定,否則您將遇到很多麻煩。我無法告訴你我忘記設定它多少次,最終花費了很長時間才弄清楚問題出在那裡。
類由系統跟蹤,因此要向系統新增新類,您必須首先使用 MakeClass() 函式建立一個系統函式可以理解和使用的類結構。MakeClass() 函式只接受一些資訊,例如類的名稱、父類的識別符號和例項大小。排程器不會傳遞到函式中,因為它是稍後新增到類結構中的。然後,如果您想使該類公開可用,則必須使用 AddClass() 函式。
if( cl = MakeClass( "someclassnamehere",
"parentclassnamehere",
0, /* this is used to point to a private Class structure */
sizeof( struct localObjData ), /* the size of the class */
0 ) )
{
cl->cl_Dispatcher.h_Entry = dispatcher;
AddClass( cl );
}
當不再需要該類時,它將透過 RemoveClass() 和 FreeClass() 函式刪除。所有這些工作都在一個簡單框架類中完成,該類作為共享庫實現,當載入時會自動將自己新增到系統中。
BOOPSI 中的物件只是分配的記憶體塊,由物件所屬的類控制。要建立新物件,請在 intuition.library 中使用 NewObject() 呼叫,並傳遞物件要成為其例項的類的識別符號以及任何初始屬性設定。
struct Gadget *gad; gad = NewObject( NULL, "buttongclass", GA_Left, 0, GA_Top, 0, GA_Width, 10, GA_Height, 10, ... TAG_DONE );
使用 NewObject() 建立物件後,您可以透過 DoMethod() 呼叫其其中一個方法在它上執行許多函式。對於一些系統支援的方法,例如 OM_SET 和 OM_GET,會為您提供函式,因此您不需要使用 DoMethod()。
ULONG data; SetAttrs( someobject, GA_Left, 10, GA_Top, 10, TAG_DONE ); SetGadgetAttrs( somegadget, window, requester, GA_Left, 10, ..., TAG_DONE ); /* get some attribute from some object and put it in the data ULONG */ GetAttr( &data, someobject, SA_SomeAttribute ); DoMethod( someobject, SM_SOMEMETHOD, ... );
SetGadgetAttrs() 是為小工具類的任何子類提供的,以便可以建立這些類所需的某些特殊資訊。雖然在設定小工具的屬性時並不總是需要它,但任何可能改變小工具圖形的屬性都應該使用 SetGadgetAttrs() 設定。
DoMethod() 函式實際上只是普通連結庫的一部分,通常操作起來很簡單,儘管它可能會導致問題。基於堆疊的 DoMethod() 被設計為,您傳遞給它的每個引數都將被解釋為 ULONG,如果它不是 ULONG,則會將其轉換為 ULONG。因此,如果您需要傳遞更小的資料,請確保將其適當地打包,或者直接使用標籤陣列 DoMethod()。例如,影像類使用的 IM_DRAW 方法接受一個 X 和 Y 引數,它們都是字
struct impDraw
{
ULONG MethodID;
struct RastPort *imp_RPort;
struct
{
WORD X;
WORD Y;
} imp_Offset;
ULONG imp_State;
struct DrawInfo *imp_DrInfo;
struct
{
WORD Width;
WORD Height;
} imp_Dimensions;
};
/* BAD BAD BAD BAD BAD BAD */
DoMethod( imageobject, IM_DRAW, rp, x, y, state, dri );
/* end BAD */
/* GOOD GOOD GOOD */
struct impDraw msg;
msg.MethodID = IM_DRAW;
msg.imp_RPort = rp;
...
DoMethodA( imageobject, &msg );
/* also GOOD */
struct Offset {
WORD X;
WORD Y;
};
/* ... */
struct Offset off;
off.X = x;
off.Y = y;
DoMethod( imageobject, IM_DRAW, rp, off, state, dri );
^^^
/* this is the key since it will put the two words on the stack
as if it were just a ULONG */
/* end GOOD */
請注意,儘管 imp_Draw 結構大於我們在堆疊上傳遞的結構,但這並不重要,因為只有 IM_DRAWFRAME 方法會關注額外的欄位,因此不需要進行額外的工作。
BOOPSI 中的物件具有一個特殊屬性,即每個建立的物件在負偏移處都包含一個結構。該結構由根類放置到位,幷包含一個指向物件類的指標和一個 MinNode 結構,該結構可用於將物件儲存在列表中。
為了使用每個物件中的 MinNode 結構,根類提供了 OM_ADDTAIL、OM_REMOVE、OM_ADDMEMBER 和 OM_REMMEMBER 方法。OM_ADDTAIL 和 OM_REMOVE 方法由根類實現以執行其預期功能,因此您可以在程式或類中使用它們。但是,OM_*MEMBER 方法沒有由根類實現,而是為了為子類提供實現模型。成員函式的一個示例可能是將專案新增到您分配的某個列表檢視物件中。
DoMethod( listview, OM_ADDMEMBER, sometextitem );
由於物件具有根類資料位於負偏移處的屬性,因此可以輕鬆地建立基類並公開其結構,因此無需使用 SetAttrs() 和 GetAttr() 函式。這在 gadgetclass 基類中得到了有效利用,因此作為小部件的 BOOPSI 物件可以輕鬆地用作 Intuition 中的舊式小部件。
struct Gadget *gad; gad = NewObject( NULL, "strgclass", GA_Left, 0, ... TAG_DONE ); AddGadget( win, gad, -1 ); RefreshGadgets( gad, win, 0 ); ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ /* Dont forget this!!! Its the number one problem ppl have */
作為 icclass 或 gadgetclass 例項的物件也能夠透過其屬性相互通訊。目標物件由 ICA_TARGET 屬性設定,然後每當類的某個屬性發生改變時,它都會向目標物件傳送 OM_UPDATE 訊息,然後目標物件可以以某種方式對其進行操作。目標不一定是物件,因為您可以將 ICA_TARGET 值設定為 ICTARGET_IDCMP,以便從物件傳送的任何 OM_UPDATE 訊息都將透過 IDCMP 埠傳遞給應用程式。不幸的是,當目標是物件時,可能會混淆屬性識別符號應該意味著什麼。為了解決這個問題,使用了一個名為 ICA_MAP 的特殊屬性,將屬性 ID 對映到另一個屬性,目標物件能夠理解該屬性。例如,propgclass 使用 PGA_Top 屬性作為其當前值,而 strgclass 使用 STRINGA_LongVal 屬性作為其當前值。這兩個屬性具有不同的識別符號,因此如果它們彼此作為目標,它們將無法理解屬性應該意味著什麼。
/* example of communication between a prop gadget, string gadget, and an app */
struct TagItem prop2intmap[] =
{
{PGA_Top, STRINGA_LongVal},
{TAG_DONE,}
};
struct TagItem int2propmap[] =
{
{STRINGA_LongVal, PGA_Top},
{TAG_DONE,}
};
#define PROPID 1
#define STRID 2
void main()
{
/* Open libs and window */
prop = NewObject( NULL, "propgclass",
GA_Left, 0,
...
GA_ID, PROPID,
ICA_MAP, prop2intmap,
PGA_Top, 0,
...
TAG_DONE );
if( prop && (integer = NewObject( NULL, "strgclass",
GA_ID, STRID,
...
ICA_TARGET, prop,
ICA_MAP, int2propmap,
GA_Previous, prop,
STRINGA_LongVal, 0,
...
TAG_DONE )) )
{
SetAttrs( prop, ICA_TARGET, integer, TAG_DONE );
AddGList( window, prop, -1, -1, 0 );
RefreshGList( prop, window, 0, -1 );
...
RemoveGList( window, prop, -1 );
DisposeObject( integer );
}
DisposeObject( prop );
}
以下是用 IDCMP 埠進行通訊的相同示例。
struct TagItem prop2intmap[] =
{
{PGA_Top, STRINGA_LongVal},
{TAG_DONE,}
};
struct TagItem int2propmap[] =
{
{STRINGA_LongVal, PGA_Top},
{TAG_DONE,}
};
#define PROPID 1
#define STRID 2
void main()
{
/* Open libs and window */
model = NewObject( NULL, "modelclass",
ICA_TARGET, ICTARGET_IDCMP,
ICA_MAP, int2propmap
TAG_DONE );
prop = NewObject( NULL, "propgclass",
GA_Left, 0,
...
GA_ID, PROPID,
ICA_TARGET, model
PGA_Top, 0,
...
TAG_DONE );
if( int2prop = NewObject( NULL, "icclass",
ICA_TARGET, prop,
ICA_MAP, int2propmap,
TAG_DONE ) )
{
DoMethod( model, OM_ADDMEMBER, int2prop );
if( model && prop && int2prop && (integer = NewObject( NULL, "strgclass",
GA_ID, STRID,
...
ICA_TARGET, model,
GA_Previous, prop,
STRINGA_LongVal, 0,
...
TAG_DONE )) )
{
if( prop2int = NewObject( NULL, "icclass",
ICA_TARGET, integer,
ICA_MAP, prop2intmap,
TAG_DONE ) )
{
DoMethod( model, OM_ADDMEMBER, prop2int );
AddGList( window, prop, -1, -1, 0 );
RefreshGList( prop, window, 0, -1 );
...
RemoveGList( window, prop, -1 );
}
DisposeObject( integer );
}
}
DisposeObject( prop );
/* we only have to delete the model and not the icclasses since it
will dispose of its internal list when we dispose of it */
DisposeObject( model );
}
最後,當您完全完成一個物件時,需要透過 DisposeObject() 函式將其釋放。它使用起來很簡單,但您需要注意不要釋放兩次同一個物件。這可能發生的唯一時間是,如果您將物件傳遞給另一個物件,並且該物件已經為您釋放了它。另一種可能性是,如果您在視窗中添加了自己的系統小部件(關閉、深度等),那麼一旦您關閉了視窗,系統將自動為您釋放視窗中的任何系統小部件。您還應該記住,DisposeObject() 函式足夠智慧,知道不要嘗試釋放空指標。此屬性允許您建立許多相互獨立的物件,而無需將它們封裝在 if 語句中。
/* use */
gad = NewObject(...
gad2 = NewObject(...
if( gad && gad2 )
{
}
DisposeObject( gad );
DisposeObject( gad2 );
/* instead of */
if( gad = NewObject(...) )
{
if( gad2 = NewObject(...) )
{
DisposeObject( gad2 );
}
DisposeObject( gad );
}
/* if you can :) */
請注意,如果您這樣做,則必須注意不要在任何其他物件中使用物件指標。例如,GA_Previous 屬性接受指向小部件的指標,然後修改其 NextGadget 欄位以指向正在建立的物件。如果傳遞到該屬性的小部件不存在,則會破壞記憶體。
建立自己的類時,建議您使用 Skeleton 類,然後從那裡繼續,因為它已經為您完成了許多繁重的工作,因此您可以直接編寫類的核心程式碼。
最好的做法是帶您逐步瞭解按鈕類的程式碼... 編寫過程中發現的任何錯誤都留給讀者作為練習 :)
Header File #ifndef BBBUTTONCLASS_H #define BBBUTTONCLASS_H #define BGA_Dummy (TAG_USER + 0x60000) #define BGA_Push (BGA_Dummy + 1) #define BGA_Image (BGA_Dummy + 2) //Convenient macro for creating buttons #define ButtonObject NewObject( NULL, "bbbuttongadget" #endif Casting Macros I like to use casting macros a lot so here are some: #define GA(o) ((struct Gadget *)o) #define IA(o) ((struct Image *)o) #define IM(o) ((struct Image *)o) #define SET(o) ((struct opSet *)o) #define GET(o) ((struct opGet *)o) #define GPR(o) ((struct gpRender *)o) #define GPI(o) ((struct gpInput *)o) #define GPL(o) ((struct gpLayout *)o)
它們只是提供了一種簡單快捷的方式,可以將 BOOPSI 樣式的訊息從 Msg 轉換為該訊息的適當結構
幾乎每個類都需要一些與之關聯的標誌來編碼選項或狀態。
#define BB_TEXT 0 //is the label text? #define BB_PUSH 1 //should the button hold its state when (de)selected #define BF_TEXT (1L << BB_TEXT) #define BF_PUSH (1L << BB_PUSH)
與其將所有程式碼都打包到排程器中,不如將其分解成單獨的函式,這是一個好主意。
//always good for debugging, since you can't use printf() from a library or even a class i think...
extern int kprintf( const char *str, ... );
//the dispatcher itself
ULONG
ASM dispatchClass( REG(a0) Class * cl, REG(a2) Object * o, REG(a1) Msg msg );
//Used to set our attributes
ULONG
ASM setClassAttrs( REG(a0) Class * cl, REG(a2) Object * o, REG(a1) struct opSet * msg );
//Used to get some attribute
ULONG
ASM getClassAttr( REG(a0) Class *cl, REG(a2) Object *o, REG(a1) struct opGet * msg);
//a custom TextLength() that doesn't need a RastPort
LONG myTextLength( struct TextFont *font, char *str, int len );
//The ubiquitous Notify() function found in all my gadget classes
//It takes care of sending the OM_UPDATE messages to the target object
void ASM Notify( REG(a0) Class *cl, REG(a2) Object *o, REG(a1) struct opSet *msg, REG(d1) ULONG flags, REG(d2) sel, REG(a3) struct GadgetInfo *ginfo );
Globals
Class *cl = 0; // our class structure pointer
struct Library *ClasservBase; //pointer to the classerv.library base, see the my classes page
struct DrawInfo defdri; //a default drawinfo in case one isn't passed in
//we use it to find a height and width of buttons with text labels so we really need it
WORD defpens[NUMDRIPENS]; //default pen array for the drawinfo
//in case we're disabled we should ghost
USHORT ghostdata[] =
{
0x2222, 0x8888
};
//our instance data, try to make it small
struct localObjData {
struct DrawInfo *dri;
BYTE flags;
};
//ahhh, easy open and closing of libs
struct OpenLibTemplate olt[] =
{
{"intuition.library",36,&IntuitionBase},
{"graphics.library",0,&GfxBase},
{"utility.library",0,&UtilityBase},
{"classerv.library",0,&ClasservBase},
{0}
};
//same for classes although we don't need any here
struct OpenClassTemplate oct[] =
{
{0}
};
我們需要為小部件和超類選擇一個名稱。就命名約定而言,目前還沒有統一的標準。一旦 classerv.library 充分發揮其潛力,就應該有一個標準。
#define MYCLASSID "bbbuttongadget"
#define SUPERCLASSID "gadgetclass"
/* OpenLibrary/CloseLibrary
I do all of my classes so that they are in libraries, you don't have to though, you can make private classes and public classes in your own code. I just like being able to have them shareable and easily replaced. */
int ASM SAVEDS __UserLibInit( REG(a6) struct MyLibrary *libbase )
{
//Open libraries and classes
if( OpenLibraries( olt ) )
{
if( OpenClasses( oct ) )
{
//setup our default drawinfo
defpens[BACKGROUNDPEN] = 0;
defpens[SHADOWPEN] = 1;
defpens[SHINEPEN] = 2;
defpens[TEXTPEN] = 1;
defdri.dri_Pens = defpens;
defdri.dri_NumPens = NUMDRIPENS;
defdri.dri_Font = GfxBase->DefaultFont;
//setup our class
if( cl = MakeClass( MYCLASSID,
SUPERCLASSID, NULL,
sizeof(struct localObjData), 0))
{
/* Fill in the callback hook */
cl->cl_Dispatcher.h_Entry = (ULONG (*) ())dispatchClass;
/* Keep track of the libbase here since we'll need it later */
cl->cl_UserData = (ULONG)libbase;
/* Make the class public */
AddClass( cl );
return( FALSE );
}
/* something is hosed, close the classes and libs */
}
CloseClasses( oct );
}
CloseLibraries( olt );
return( TRUE );
}
void ASM SAVEDS __UserLibCleanup( REG(a6) struct MyLibrary *libbase )
{
if( cl )
{
/* Remove and free our class structure */
RemoveClass( cl );
FreeClass(cl);
}
/* Close libs and classes, see how easy it is :) */
CloseClasses( oct );
CloseLibraries( olt );
}
排程器... 糟糕,困難的部分來了... 請注意沒有 __saveds,它在這裡不能使用...
ULONG ASM dispatchClass( REG(a0) Class *cl, REG(a2) Object *o, REG(a1) Msg msg)
{
ULONG retval = FALSE;
Object *newobj;
struct localObjData *lod;
/* C can be a pain when coding shared libraries. Just because this function is in a shared library doesn't mean that a6 will be set to our library base so we must first put our lib base, which we saved in cl_UserData, into a6 before we can get a4 and access to all our global data (library bases, etc...). */
putreg( REG_A6, cl->cl_UserData );
geta4();
switch (msg->MethodID)
{
case OM_NEW: /* First, pass up to superclass */
#ifdef DEBUG
kprintf( "class.class/OM_NEW:\n" );
#endif
if(newobj = (Object *)DSM(cl, o, msg))
{
首先,您使用 DoSuperMethodA()(簡稱 DSM())將訊息傳遞給上層,這最終會命中根類,建立我們的物件,然後在我們父類將其例項資料初始化為預設值後,訊息會傳回給我們。然後,在下面,我們獲得例項資料指標並設定任何預設值。始終確保設定例項指標,並且絕不要從 OM_NEW 方法中複製和貼上例項宏,因為它使用 lod = INST_DATA( cl, newobj ),而所有其他方法都應該使用 lod = INST_DATA(cl, o),請注意應該是 o 而不是 newobj。注意:您應該始終將訊息傳遞給父類,將來有些人可能希望實現錯誤處理,以便在 taglists 中傳遞的任何物件在 OM_NEW 失敗時都會被釋放。
/* Initial local instance data */
lod = INST_DATA( cl, newobj );
lod->dri = &defdri;
//Use set function to interpret the tags passed in
setClassAttrs( cl, newobj, (struct opSet *)msg );
retval = (ULONG)newobj;
}
break;
case OM_SET:
#ifdef DEBUG
kprintf( "class.class/OM_SET:\n" );
#endif
retval = DSM( cl, o, msg );
retval += setClassAttrs( cl, o, SET(msg) );
break;
case OM_GET:
#ifdef DEBUG
kprintf( "class.class/OM_GET:\n" );
#endif
retval = getClassAttr( cl, o, GET(msg) );
break;
GM_RENDER 出現了,每當 Intuition 請求時,它就會執行小部件的所有繪製操作。由於這是第一個與小部件相關的函式,因此我們現在將討論 GadgetInfo 結構。
struct GadgetInfo
{
struct Screen *gi_Screen;
struct Window *gi_Window;
struct Requester *gi_Requester;
struct RastPort *gi_RastPort;
struct Layer *gi_Layer;
struct IBox gi_Domain;
struct {
UBYTE DetailPen;
UBYTE BlockPen;
} gi_Pens;
struct DrawInfo *gi_DrInfo;
ULONG gi_Reserved[4];
};
此結構為您提供了有關小部件在系統中的位置以及任何繪製所需資訊的豐富資訊。它在所有小部件方法訊息和根類定義的 OM_SET 訊息中都可用。但請注意,所有小部件方法的指標都位於 MethodID 之後,但 OM_SET 訊息的指標位於不同的位置,這之前曾給我帶來過問題。
case GM_RENDER:
{
struct RastPort *rp = GPR(msg)->gpr_RPort;
struct DrawInfo *dri;
//Support for the GREL_ flags, these should be made into macros someday.
WORD left = (GA(o)->Flags & GFLG_RELRIGHT) ? GPR(msg)->gpr_GInfo->gi_Domain.Width + GA(o)->LeftEdge - 1 : GA(o)->LeftEdge;
WORD top = (GA(o)->Flags & GFLG_RELBOTTOM) ? GPR(msg)->gpr_GInfo->gi_Domain.Height + GA(o)->TopEdge - 1 : GA(o)->TopEdge;
WORD width = (GA(o)->Flags & GFLG_RELWIDTH) ? GPR(msg)->gpr_GInfo->gi_Domain.Width + GA(o)->Width : GA(o)->Width;
WORD height = (GA(o)->Flags & GFLG_RELHEIGHT) ? GPR(msg)->gpr_GInfo->gi_Domain.Height + GA(o)->Height : GA(o)->Height;
lod = INST_DATA( cl, o );
dri = lod->dri; //cache a pointer to the drawinfo
//here we check if there is a border image (most likely a frameiclass object) which we should draw else we just setup a background color
if( GA(o)->GadgetRender )
{
DrawImageState( rp, GA(o)->GadgetRender, left, top, (GA(o)->Flags & GFLG_SELECTED) ? IDS_SELECTED : IDS_NORMAL, GPR(msg)->gpr_GInfo->gi_DrInfo );
SetDrMd( rp, JAM1 );
}
else
{
SetAPen( rp, (GA(o)->Flags & GFLG_SELECTED) ? dri->dri_Pens[FILLPEN] : dri->dri_Pens[BACKGROUNDPEN] );
RectFill( rp, left, top, left + width - 1, top + height - 1 );
}
if( GA(o)->GadgetText )
{
WORD lwidth, lheight;
WORD xoffset, yoffset;
//check if we're drawing some text or an image, and figure out the width and height of which one
if( (lod->flags & BF_TEXT) )
{
lwidth = myTextLength( dri->dri_Font, (char *)GA(o)->GadgetText, strlen( (char *)GA(o)->GadgetText ) );
lheight = dri->dri_Font->tf_YSize;
}
else
{
lwidth = IA(GA(o)->GadgetText)->Width;
lheight = IA(GA(o)->GadgetText)->Height;
}
//center the text or image inside the gadget
xoffset = left + ((width - lwidth) / 2);
yoffset = top + ((height - lheight) / 2);
//draw whatever it is
if( (lod->flags & BF_TEXT) )
{
SetFont( rp, dri->dri_Font );
SetAPen( rp, (GA(o)->Flags & GFLG_SELECTED) ? dri->dri_Pens[HIGHLIGHTTEXTPEN] : dri->dri_Pens[TEXTPEN] );
Move( rp, xoffset, yoffset + dri->dri_Font->tf_Baseline );
Text( rp, (STRPTR)GA(o)->GadgetText, strlen( (char *)GA(o)->GadgetText ) );
}
else
{
DrawImageState( rp, GA(o)->GadgetRender, xoffset, yoffset, (GA(o)->Flags & GFLG_SELECTED) ? IDS_SELECTED : IDS_NORMAL, GPR(msg)->gpr_GInfo->gi_DrInfo );
}
}
//check if we're disabled and draw a ghosting pattern if we are
if( GA(o)->Flags & GFLG_DISABLED )
{
SetAPen( rp, GPI(msg)->gpi_GInfo->gi_DrInfo->dri_Pens[SHADOWPEN] );
SetAfPt( rp, ghostdata, 1 );
RectFill( rp, left, top, left + width - 1, top + height - 1 );
}
}
break;
/* GM_LAYOUT is an OS 3.x only function but if it is here it won't hurt. Here we use it to automatically resize the framing image, although you can use it for a number of other things. Remember though that you shouldn't do any drawing here since Intuition will call GM_RENDER later, this is just setup so you can do recomputing. */
case GM_LAYOUT:
{
WORD width = (GA(o)->Flags & GFLG_RELWIDTH) ? GPR(msg)->gpr_GInfo->gi_Domain.Width + GA(o)->Width : GA(o)->Width;
WORD height = (GA(o)->Flags & GFLG_RELHEIGHT) ? GPR(msg)->gpr_GInfo->gi_Domain.Height + GA(o)->Height : GA(o)->Height;
lod = INST_DATA( cl, o );
if( GA(o)->GadgetRender )
{
SetAttrs( GA(o)->GadgetRender,
IA_Width, width,
IA_Height, height,
TAG_DONE );
}
}
break;
/* GM_HITTEST is here so that you can tell Intuition whether the gadget was hit or not and then respond accordingly. Here we just respond that we were in fact hit and we should be activated since it is a square gadget. Although this method can be used so that it check something else to see if it was hit or not (ie. call IM_HITTEST on a BOOPSI image object) */
case GM_HITTEST:
retval = GMR_GADGETHIT;
break;
/* GM_GOACTIVE is here so that you can set things up and do any long time operations before the gadget starts to receive all of the input.device messages. This function has a special return value which tells intuition what it should do with the input event which caused us to go active. They are GMR_NOREUSE which tells Intuition to just kill the event, GMR_REUSE which means it should use the event again (ie. if there was a right mouse button event we would want the menu to show up instead of just trashing it, this can only happen in GM_HANDLEINPUT though), GMR_NEXTACTIVE which is just like pressing tab when a string gadget is selected, GMR_PREVACTIVE which is just like pressing shift tab when a string gadget is selected, and GMR_MEACTIVE which tells Intuition that we are active and want to stay that way. Note that below should've checked to see if an input event activated us or if it was ActivateGadget(), this should be fixed. */
case GM_GOACTIVE:
{
struct RastPort *rp;
lod = INST_DATA( cl, o );
//check if we're a push in button and act appropriately
if( lod->flags & BF_PUSH )
GA(o)->Flags ^= GFLG_SELECTED;
else
GA(o)->Flags |= GFLG_SELECTED;
//Always, always, always use ObtainGIRPort to get the RastPort,
//unless of course its a GM_RENDER msg
if( rp = ObtainGIRPort( GPI(msg)->gpi_GInfo ) )
{
DoMethod( o, GM_RENDER, GPI(msg)->gpi_GInfo, rp, GREDRAW_REDRAW );
ReleaseGIRPort( rp );
}
//If its a push in button there is no need to go into GM_HANDLEINPUT
//so we just return GMR_NOREUSE.
if( lod->flags & BF_PUSH )
retval = GMR_NOREUSE;
else
retval = GMR_MEACTIVE;
}
break;
/* GM_HANDLEINPUT is where the gadget does all the processing of the input events. Here its pretty simple since just need to catch timer events and send messages on those and check to make sure the mouse is actually over the gadget */
case GM_HANDLEINPUT:
{
struct RastPort *rp;
WORD x = GPI(msg)->gpi_Mouse.X; //these are relative to the upper left corner of the gadget
WORD y = GPI(msg)->gpi_Mouse.Y;
struct InputEvent *ie = GPI(msg)->gpi_IEvent;
BOOL sel = FALSE;
WORD left = (GA(o)->Flags & GFLG_RELRIGHT) ? GPR(msg)->gpr_GInfo->gi_Domain.Width + GA(o)->LeftEdge - 1 : GA(o)->LeftEdge;
WORD top = (GA(o)->Flags & GFLG_RELBOTTOM) ? GPR(msg)->gpr_GInfo->gi_Domain.Height + GA(o)->TopEdge - 1 : GA(o)->TopEdge;
WORD width = (GA(o)->Flags & GFLG_RELWIDTH) ? GPR(msg)->gpr_GInfo->gi_Domain.Width + GA(o)->Width : GA(o)->Width;
WORD height = (GA(o)->Flags & GFLG_RELHEIGHT) ? GPR(msg)->gpr_GInfo->gi_Domain.Height + GA(o)->Height : GA(o)->Height;
lod = INST_DATA( cl, o );
//check to see if the mouse is over the gadget
if( (x >= 0) && (x < (width)) &&
(y >= 0) && (y < (height)) )
{
sel = TRUE;
}
//on timer events we should send messages to our ICA_TARGET
//Notice they are OPUF_INTERIM since the user isn't done with us yet
if( ie->ie_Class == IECLASS_TIMER )
{
Notify( cl, o, msg, OPUF_INTERIM, sel, GPI(msg)->gpi_GInfo );
}
//this takes care of mouse events
if( ie->ie_Class == IECLASS_RAWMOUSE )
{
switch( ie->ie_Code )
{
case SELECTUP:
//user let up the select button we need to deactivate
retval = GMR_NOREUSE;
//if we are selected we need to tell intuition to send a IDCMP_RELVERIFY
if( GA(o)->Flags & GFLG_SELECTED )
retval |= GMR_VERIFY;
//send a final notify, (ie. no OPUF_INTERIM flag)
Notify( cl, o, msg, 0, sel, GPI(msg)->gpi_GInfo );
//Set the code field of the IntuiMessage to our GadgetID
(*GPI(msg)->gpi_Termination) = GA(o)->GadgetID;
break;
case MENUDOWN:
//this is where GMR_REUSE come into play mainly
retval = GMR_REUSE;
break;
default:
//check if we need to change the graphics of the gadget if
//the mouse is/isn't over the gadget
if( (!sel && (GA(o)->Flags & GFLG_SELECTED)) ||
(sel && !(GA(o)->Flags & GFLG_SELECTED)) )
{
GA(o)->Flags ^= GFLG_SELECTED;
if( rp = ObtainGIRPort( GPI(msg)->gpi_GInfo ) )
{
DoMethod( o, GM_RENDER, GPI(msg)->gpi_GInfo, rp, GREDRAW_REDRAW );
ReleaseGIRPort( rp );
}
}
break;
}
}
}
break;
/* GM_GOINACTIVE is where we cleanup after GM_GOACTIVE and GM_HANDLEINPUT. Do the final render here since the gadget could've been inactivated by a select up or a mouse down, so we would've had to write this code for both GM_HANDLEINPUT. */
case GM_GOINACTIVE:
{
struct RastPort *rp;
lod = INST_DATA( cl, o );
if( !(lod->flags & BF_PUSH) )
{
GA(o)->Flags &= ~GFLG_SELECTED;
if( rp = ObtainGIRPort( GPI(msg)->gpi_GInfo ) )
{
DoMethod( o, GM_RENDER, GPI(msg)->gpi_GInfo, rp, GREDRAW_REDRAW );
ReleaseGIRPort( rp );
}
}
}
break;
/* Finally, if we don't know what the method is we just pass it up to the parent classes */
default:
retval = DSM(cl, o, msg);
break;
}
return(retval);
}
SetAttrs()... 這裡沒有更好的選擇。Skeleton 類中提供的 SetClassAttr() 函式旨在簡化處理所有愚蠢標籤的過程。同樣請注意,函式中沒有 __saveds,儘管我認為它應該可以用,因為它始終被排程器呼叫,但誰知道呢。
ULONG
ASM setClassAttrs( REG(a0) Class * cl, REG(a2) Object * o, REG(a1) struct opSet * msg )
{
struct localObjData *lod = INST_DATA(cl, o);
struct TagItem *tags = msg->ops_AttrList;
struct TagItem *tstate;
struct TagItem *tag;
ULONG tidata;
BOOL change = FALSE;
BOOL sizechange = FALSE;
putreg( REG_A6, cl->cl_UserData );
geta4();
/* process rest */
tstate = tags;
while (tag = NextTagItem(&tstate))
{
tidata = tag->ti_Data;
switch (tag->ti_Tag)
{
//This is a special attribute to tell make the gadget just
//show the image instead of trying to try the frame
case BGA_Image:
GA(o)->GadgetRender = tidata;
GA(o)->Width = IM(tidata)->Width;
GA(o)->Height = IM(tidata)->Height;
break;
//A text label is wanted, set flags and set change flag
case GA_Text:
lod->flags |= BF_TEXT;
change = TRUE;
break;
//An image for the label is wanted, clear flag and set change flag
case GA_LabelImage:
lod->flags &= ~BF_TEXT;
change = TRUE;
break;
case GA_DrawInfo:
lod->dri = (struct DrawInfo *)tidata;
break;
//programmatic size change, do a redraw
case GA_Width:
case GA_Height:
sizechange = TRUE;
break;
//flag thingies, should prolly use one of the utility.library functions.
case BGA_Push:
if( tidata )
lod->flags |= BF_PUSH;
else
lod->flags &= ~BF_PUSH;
break;
//dunno what it is
default:
break;
}
}
//Check for size change, change the frame's rectangle, should prolly do a GM_RENDER here, oops
if( sizechange )
{
if( GA(o)->GadgetRender )
{
SetAttrs( GA(o)->GadgetRender,
IA_Width, GA(o)->Width,
IA_Height, GA(o)->Height,
TAG_DONE );
}
}
//Theres some new text gotta adjust the size. There should be some flags here
//so that the programmer can stop the resize from occurring
if( change && GA(o)->GadgetText )
{
struct IBox cont, frame;
WORD width, height;
//check for text/image label and find the size
if( (lod->flags & BF_TEXT) )
{
struct DrawInfo *dri = lod->dri;
width = myTextLength( dri->dri_Font, (char *)GA(o)->GadgetText, strlen( (char *)GA(o)->GadgetText ) );
height = dri->dri_Font->tf_YSize;
}
else
{
width = IA(GA(o)->GadgetText)->Width;
height = IA(GA(o)->GadgetText)->Height;
}
//try and do an IM_FRAMEBOX to figure out how big the frame wants to be for this size graphic
cont.Width = width;
cont.Height = height;
if( GA(o)->GadgetRender )
{
frame.Left = 0;
frame.Top = 0;
frame.Width = width;
frame.Height = height;
DoMethod( GA(o)->GadgetRender, IM_FRAMEBOX, &frame, &cont, lod->dri, 0 );
}
//GREL_ flags should be check above
if( !(GA(o)->Flags & GFLG_RELWIDTH) )
GA(o)->Width = cont.Width;
if( !(GA(o)->Flags & GFLG_RELHEIGHT) )
GA(o)->Height = cont.Height;
//Adjust the frame size, dunno if we wanna redraw here or not since the
//programmer might want to manually do it so a redraw would be ugly
if( GA(o)->GadgetRender )
{
SetAttrs( GA(o)->GadgetRender,
IA_Width, cont.Width,
IA_Height, cont.Height,
TAG_DONE );
}
}
return (1L);
}
GetClassAtr... 只是純粹的脂肪。目前這對於我們來說毫無用處,不過沒有理由刪除它。
ULONG
ASM getClassAttr( REG(a0) Class *cl, REG(a2) Object *o, REG(a1) struct opGet * msg )
{
struct localObjData *lod = INST_DATA(cl, o);
putreg( REG_A6, cl->cl_UserData );
geta4();
switch (msg->opg_AttrID)
{
default:
return ((ULONG) DSM(cl, o, (Msg)msg));
}
return (1L);
}
myTextLength... 糟糕... 這可能很糟糕,但我討厭不得不建立一個 RastPort 只是為了獲取文字長度。
LONG myTextLength( struct TextFont *font, char *str, int len )
{
int lpc;
LONG width = 0;
int currch;
if( font->tf_Flags & FPF_PROPORTIONAL )
{
for( lpc = 0; lpc < len; lpc++ )
{
currch = str[lpc] - font->tf_LoChar;
width += ((WORD *)font->tf_CharSpace)[currch] + ((WORD *)font->tf_CharKern[currch]);
}
}
else
{
width = font->tf_XSize * len;
}
return( width );
}
通知... 很容易做但概念上很難 OM_NOTIFY/OM_UPDATE 連線是我永遠無法理解的東西。無論如何,我們需要向我們的目標傳送一個 OM_UPDATE,並將我們的 GA_ID 作為屬性。該屬性的 ti_Data 反映了滑鼠是否在按鈕上。
void ASM Notify( REG(a0) Class *cl, REG(a2) Object *o, REG(a1) struct opSet *msg, REG(d1) ULONG flags, REG(d2) sel, REG(a3) struct GadgetInfo *ginfo )
{
struct TagItem tt[2];
struct localObjData *lod = INST_DATA(cl, o);
putreg( REG_A6, cl->cl_UserData );
geta4();
tt[0].ti_Tag = GA_ID;
tt[0].ti_Data = sel ? GA(o)->GadgetID : -GA(o)->GadgetID;
tt[1].ti_Tag = TAG_DONE;
DoSuperMethod( cl, o, OM_NOTIFY, tt, ginfo, flags );
}
//Set up proportional gadget and arrows using standard Intuition. /* Obtain Images for the up and down arrows (use BOOPSI sysiclass)). */ upImage = NewObject(NULL, SYSICLASS, SYSIA_Which, UPIMAGE, SYSIA_DrawInfo, di, SYSIA_Size, SYSISIZE_MEDRES, TAG_END); downImage = NewObject(NULL, SYSICLASS, SYSIA_Which, DOWNIMAGE, SYSIA_DrawInfo, di, SYSIA_Size, SYSISIZE_MEDRES, TAG_END); ... /* Up and down gadget definitions */ upArrow.NextGadget = &vPropGad; upArrow.LeftEdge = (-sim.win[WIN_SRC]->BorderRight + 1); upArrow.TopEdge = (-upImage->Height - downImage->Height - sizeGHeight); upArrow.Width = upImage->Width; upArrow.Height = upImage->Height; upArrow.Flags = GFLG_RELBOTTOM | GFLG_RELRIGHT | GFLG_EXTENDED | GFLG_GADGIMAGE; upArrow.Activation = GACT_IMMEDIATE | GACT_RIGHTBORDER | GACT_RELVERIFY | GACT_BOOLEXTEND; upArrow.GadgetType = GTYP_BOOLGADGET; upArrow.GadgetRender = upImage; upArrow.SelectRender = NULL; upArrow.GadgetText = NULL; upArrow.MutualExclude = 0; //Obsolete anyway. upArrow.SpecialInfo = NULL; upArrow.GadgetID = GAD_SRC_UP; upArrow.UserData = NULL; upArrow.MoreFlags = 0; /* Bounds variables not initialised because GMORE_BOUNDS is not set */ downArrow.NextGadget = &upArrow; downArrow.LeftEdge = (-sim.win[WIN_SRC]->BorderRight + 1); downArrow.TopEdge = (-downImage->Height - sizeGHeight); downArrow.Width = upImage->Width; downArrow.Height = upImage->Height; downArrow.Flags = GFLG_RELBOTTOM | GFLG_RELRIGHT | GFLG_EXTENDED | GFLG_GADGIMAGE; downArrow.Activation = GACT_IMMEDIATE | GACT_RIGHTBORDER |GACT_RELVERIFY | GACT_BOOLEXTEND; downArrow.GadgetType = GTYP_BOOLGADGET; downArrow.GadgetRender = downImage; downArrow.SelectRender = NULL; downArrow.GadgetText = NULL; downArrow.MutualExclude = 0; //Obsolete anyway. downArrow.SpecialInfo = NULL; downArrow.GadgetID = GAD_SRC_DOWN; downArrow.UserData = NULL; downArrow.MoreFlags = 0; /* Bounds variables not initialised because GMORE_BOUNDS is not set */ (void)AddGList(sim.win[WIN_SRC], (struct Gadget *)&downArrow, 0, -1, NULL); RefreshGadgets((struct Gadget *)&downArrow, sim.win[WIN_SRC], NULL);
struct Image *NewImageObject (ULONG which)
* Creates a sysiclass object. */
{
return ((struct Image *)NewObject (NULL, SYSICLASS,
SYSIA_DrawInfo, DrawInfo,
SYSIA_Which, which,
SYSIA_Size, Scr->Flags & SCREENHIRES ? SYSISIZE_MEDRES : SYSISIZE_LOWRES, TAG_DONE));
/* NB: SYSISIZE_HIRES not yet supported. */
建立物件相對容易,只需要找出你想建立物件的類名,或者傳入私有類的類指標。
struct Gadget *gad;
Class *privclass;
/* for public classes */
gad = NewObject( NULL, "strgclass", ... );
/* for private classes */
gad = NewObject( privclass, 0, ... );
/* defines are a handy shortcut instead of writing them out all the time */
#define ButtonObject NewObject( NULL, "buttongclass"
There are a few gotchas though. Some attributes can't be specified or can only be given during creation so you need to be careful about what you pass in. For example, the frbuttonclass won't use the GA_Width/GA_Height attributes during creation so you need to use a separate SetAttrs() call in order to adjust it properly.
struct Image *frame;
struct Gadget *gad;
frame = NewObject( NULL, "frameiclass", TAG_DONE );
if( frame )
{
gad = NewObject( NULL, "frbuttonclass",
GA_Left, 10,
GA_Top, 10,
GA_Width, 200, /* these won't do anything */
GA_Height, 30,
GA_Image, frame,
GA_Text, "Hello World",
TAG_DONE );
if( gad )
{
/* you have to use a SetAttrs() in order for it to work */
SetAttrs( gad, GA_Width, 200, GA_Height, 30, TAG_DONE );
AddGadget( win, gad, -1 );
RefreshGadgets( gad, win, 0 );
}
}
建立多個物件也可能很麻煩,因為你應該檢查每次建立以確保它成功,然後才能使用它。對每次建立都這樣做非常繁瑣,所以最好一次建立幾個,然後在一個 "if" 語句中檢查它們。
/* use */
gad = NewObject( ... );
gad2 = NewObject( ... );
if( gad && gad2 )
{
}
/* DisposeObject() is smart enough to not dispose of a NULL so we can do this safely */
DisposeObject( gad );
DisposeObject( gad2 );
/* instead of */
if( gad = NewObject( ... ) )
{
if( gad2 = NewObject( ... ) )
{
/* ... */
DisposeObject( gad2 );
}
DisposeObject( gad );
}
BOOPSI 小工具就像普通的小工具一樣,它們透過 IDCMP 埠使用相同的訊息進行通訊。它們還有一個額外的訊息類,稱為 IDCMP_IDMCPUPDATE,它允許小工具向任務傳送屬性列表。要設定它,你需要將小工具的 ICA_TARGET 屬性設定為 ICTARGET_IDMCP,並且可以選擇將一個屬性對映到 ICSPECIAL_CODE,這樣該屬性的值就會被插入到 IntuiMessage 的 Code 欄位中。小工具還會將包含小工具 ID 的 GA_ID 屬性放入標籤列表中,這樣你就可以知道哪個小工具傳送了訊息。
struct TagItem prop2idcmp[] = {
{PGA_Top, ICSPECIAL_CODE}, /* we are mapping the PGA_Top to the
IntuiMessages Code field */
{TAG_DONE}
};
void main()
{
struct Window *win;
if( win = OpenWindowTags( NULL,
...,
/* use IDCMP_IDCMPUPDATE to get taglists from gadgets */
WA_IDCMP, IDCMP_IDCMPUPDATE|(otherflags),
...,
TAG_DONE ) )
{
struct Gadget *prop;
prop = NewObject( NULL, "propgclass",
...,
GA_ID, 1, /* set the GadgetID so we can distinguish it from the others */
ICA_MAP, prop2idcmp, /* pass in the map taglist */
ICA_TARGET, ICTARGET_IDCMP, /* tell the gadget to send
an IDCMP_IDCMPUPDATE message
when a notify attribute changes */
TAG_DONE );
if( prop )
{
ULONG waitsigs, portsig;
BOOL done = FALSE;
struct IntuiMessage *imsg;
ULONG gadgetid;
AddGadget( win, prop, -1 );
RefreshGadgets( prop, win, 0 );
portsig = 1L << win->UserPort->mp_SigBit;
while( !done )
{
waitsigs = Wait( portsig | SIGBREAKF_CTRL_C );
if( waitsigs & portsig )
{
while( imsg = GetMsg( win->UserPort ) )
{
switch( imsg->Class )
{
case IDCMP_IDCMPUPDATE:
/* the taglist will have a GA_ID with it's gadget ID
We use GetTagData() and set the default to 0 so
we know if something went wrong and we got a message
without a GA_ID */
gadgetid = GetTagData( GA_ID, 0, imsg->IAddress );
switch( gadgetid )
{
case 1:
/* its the prop gad, do something */
printf( "prop top %ld\n", imsg->Code );
break;
default:
break;
}
break;
}
ReplyMsg( imsg );
}
}
if( waitsigs & SIGBREAKF_CTRL_C )
done = TRUE;
}
RemoveGadget( win, prop );
}
DisposeObject( prop );
CloseWindow( win );
}
}
由 Tim Stack (stack@cs.utah.edu) 維護 最後更新於 1997 年 12 月 27 日 09:51
BOOPSI 和 ObjC 似乎都使用類似的物件模型。ObjC 允許直接訪問瞬態變數,而 BOOPSI 似乎需要一個宏才能做到這一點。但是,即使 ObjC 允許直接訪問瞬態變數,這也很少使用,而是使用訪問器(只有在類內部才直接訪問例項變數,幾乎從不使用 @public 例項變數,因為這會破壞鴨子型別)。
分派似乎也很相似,兩者都有一個分派表,用於每次呼叫。但是,有一個顯著的差異:選擇器(這是 ObjC 中分派表中的一個條目,它指定要傳送給物件的訊息)在 BOOPSI 中似乎是每個類的連續整數,而 ObjC 中的整數(它們可以是連續的,但不必是,通常它們不是)是全域性唯一的。ObjC 中的選擇器對於每個類都是全域性的,如果兩個類具有相同名稱的方法,它們共享同一個選擇器。選擇器(例如,你透過 @selector 獲取它們)是一個結構體,包含方法的名稱和(如果編譯器知道)型別編碼,然後執行時註冊該方法並將結構體的內容替換為指向分派表中條目的指標。
因此,這意味著 BOOPSI 中的分派表很小,因為它們只包含該類實現的方法,而在 ObjC 中,它們很大,因為它們需要為存在的每個選擇器保留一個插槽。ObjC 中的解決方案是使用稀疏陣列(在我的執行時中是 3 級,GNU 使用 4 級,這使得它在沒有真正收益的情況下變得明顯更慢(2 倍!),因為 2^24 應該足夠了)。
因此,為了將它們統一起來,有兩個問題
- 我們如何統一選擇器?我們是否應該更改 BOOPSI 以使用稀疏陣列?否則,如果 BOOPSI 物件應該能夠接受 ObjC 訊息,BOOPSI 會遇到麻煩。
- 我們可以更改 BOOPSI 物件以在索引 0 處指向 ObjC 類嗎?這將允許執行時呼叫它們。
如果你真的對橋接兩者感興趣,我相信可以做到。但這可能需要對 BOOPSI 進行更改。另請參見 Apple 做的 CoreFoundation/Foundation 橋接。
NewObject NewObjectA DisposeObject
SetAttrs SetGadgetAttrs GetAttr
MakeClass AddClass RemoveClass FreeClass
AddGadget RefreshGadgets FreeGadget
ObtainGIRPort() ReleaseGIRPort()
DoMathod DoMethodA DoSuperMethod DoSuperMethodA
CoerceMethodA CoerceMethod
SetSuperAttrs
ULONG DoGadgetMethodA(struct Gadget *object, struct Window *win, struct Requester *req, Msg msg);