home *** CD-ROM | disk | FTP | other *** search
- How OWL does dynamic dispatch virtual table's (DDVT's).
-
- The short version:
- We have extended the language so that you can associate an unsigned
- int with a virutal function. An example of the syntax is:
-
- class A {
- virtual void foo() = [ 12 ];
- };
-
- When the compiler encounters this syntax, it will generate the virtual
- table for class A in a new way. It will associate the number 12 with
- the address of the function A::foo(void). This is all the compiler does.
-
- Within OWL, there are a few WndProc's which have been exported.
- When they get a windows message, windows tells it what window this
- message is for through the hWnd parameter. From this OWL figures out
- which of the users object's this message is for. It then scans the
- virtual table's of this class, then its base classes looking for a
- function which has been associated with that message. It then
- calls that function directly, passing it a RTMessage ( referenence to
- a TMessage structure), which describes the message.
-
- So most of the work for dynamic dispatching is done by OWL.
-
- The long version:
- When you go
- class A {
- public:
- virtual void foo() = [ 12 ];
- };
- where 12 integer (2 byte expression), and the function foo is virtual,
- foo is now called a dynamically dispatched virtual function.
-
- To understand the format of the virtual function table, lets take
- a more complicated example.
-
- class A {
- virtual void dfoo() = [2];
- virtual void nfoo();
- virtual void dfoo2() = [3];
- };
-
- Assuming default settings for virtual tables, (ie smart and not far)
- The compiler will generate a virtual function table called @@A@
- withing a virtual segment @A@ within the _DATA segment. (Virtual
- segments are discuessed in the Turbo Assembler manuals).
- Here is a commented version of what it will look like:
-
- _DATA segment word public 'DATA'
- @A@ segment virtual
- @@A@ label byte // name of VTable for class A
- dd @A@dfoo$qv // list of all dispatched functions, they are
- dd @A@dfoo2$qv // always far addresses, despite memory model.
- // the dispatched functions are listed in the
- // order they are declared.
- db 2 // following the dispatched functions are the unsigned ints
- db 0 // they're dispatched to. These int's are listed in the
- db 3 // order of the dispatched functions also.
- db 0
- // after the dispatch numbers comes a
- db 2 // count of how manyu dispatched functions
- db 0 // there are.
-
- db 0 // Following the count is the address of the normal
- db 0 // part of A's first base class'es virtual table. In this
- // case it is null since A has no base class.
-
- dw @A@nfoo$qv
- // Finally comes the addresses of the regular virtual functions.
- // This part looks like a normal VTable.
- @A@ ends
- _DATA ends
-
- The attribute of being virtually dispatched is inheritted. In the
- following,
- class B : public A {
- dfoo();
- };
- dfoo is virtually dispatched, since it was in A. B's Vtable would like,
-
- @@B@ label byte
- dd @B@dfoo$qv // B's dispatched functions,
- db 2 // dispatch numbers
- db 0
- db 1 // count of how many dispatched functions it has.
- db 0
- dw @@A@+16 // address of where A's Vtable ends. This will be
- // the start of where the regular VTable is.
- dw @A@nfoo$qv
- dw @A@nfoo2$qv
-
- The pointer to the base class VTable is how Owl finds out what messages
- instances of class B have inherited Dynamic Dispatch functions for.
-
- You have to be careful about mixing multiple inheritence with dynamic
- dispatching. The VDDT for a given class only stores a pointer to the
- immediate base classes virtual table. So given,
-
- class C : A, B { ... }
-
- OWL will only be able to use dispatched functions from A and C, not B.
- All other aspects of multiple inheritence will function the same.
-
- How Owl dispatches the message:
- When TWindow objects are created, there addresses are stored in an
- Association whose key is hwnd's (window handles). So when StdWndProc
- gets a message, (StdWndProc is the standard window procedure for
- OWL objects, it processes most of the messages an OWL app receives), it
- uses the hwnd parameter to look up the address, or this pointer of the
- TWindow object that this message is for.
- Before it scans the DDVT's for a function to call, it does a little
- checking on the message number to find functions dispatches to child
- ID's or menu commands. For example, if the message is WM_COMMAND,
- it adds wParam ( which identifes the actual command selected ) to
- CM_FIRST and then searches for that number in the DDVT's.
-
- Searching happens in this way, given the address the TWindow object, OWL
- assumes the first thing at that address will be the pointer to the VTable
- pointer for that object. Normally, when the compiler lays out the
- physical memory for a class, all the data for the class comes first,
- then the Vtable pointer. However, devired classes must have the Vtable
- pointer in the same location. So given,
-
- class A { in memory a
- int a, b; an A object b
- virutal foo(); would look like: vptr
- };
-
- class B : A { in memory a
- int c; a B object b
- virtual foo2(); would look like: vptr
- }; c
-
- The reason why OWL can assume a vtable pointer at the start of an
- object is because all OWL objects derive first from Object as their
- base class. Object is an abstract base class with no data (save a
- static member, which is not stored in instances of the class) but
- several virtual functions. Thus the location of the Vtable pointer
- for an Object and all its derived is at the start of the class
- instance. Given the this pointer for an instance of a class, one
- one could get the vtable pointer by these expressions:
-
- #define offsetofVTptr( x ) sizeof( x ) - sizeof( void * )
- char *VTBPtr = *( char **)( (char *)this + offsetofVTptr( classname ));
-
- This assumes the vtable pointer is of the default size for the
- memory model, and that classname is first class in a single
- inheritence hierarchy having virtual functions, and that what
- this points to is dervived from that class.
-
- Actually, the VTable pointer does not point at the start of
- the VTable, but the start of the normal part of the VTable.
- In our example above, this would be @@A@+16. After Owl has the address
- of the normal part of the vtable, it passes this, and the message its
- searching for, to the assembly function Ddispatch to return the
- address of the function a message is dispatched to. The way Ddispatch
- scans, is that it reads backwards from the vtable address given,
- skiping the address for the base class vtable for now, and reads
- the count of the number of dispatched functions, and scans backwards
- that many words in the VTable looking to see if this object has a
- function dispacted to that message. If so it returns the address
- of that function, if not it repeats this for the base class. If
- there is no function dispatched to this message, it returns null.
- OWL then proceeds directly to doing default message processing for
- the message. Once Owl has an address for a dispatches function, it
- calls it directly after pushing the this pointer and an RTMessage on
- the stack. (so no matter what you declare a dispatched function to
- take, in OWL, it allows gets an RTMessage ).
-
- You cannot call a dispatched function directly, the virtual function
- mechanism is not supported for them. The code needed to support the
- virtual function mechanism given the vtable format would be very
- complicated. Since the vtable pointer points immediatly past
- the dispatched functions, the compiler cannot index off it like it
- normally does. Also the addresses of dispatched functions from the base
- which are not overidden in the derived class are not stored in the
- derived class'es vtable ).
-
- There is a case where the compiler will generate a call to a dispatched
- function, here is an example of that:
-
- class A { public: virtual foo() = [ 2 ]; };
- A aa;
- main() { aa.foo() }
-
- Here, the function is defined in class A, and is not being called through
- a reference, so the compiler knows exactly which object is
- invoking it. In this case the compiler calls foo like it would a non
- virtual function, by pushing the address of aa on the stack and calling
- foo directly.
-
-