PRODUCT : Borland C++ NUMBER : 1026 VERSION : 3.1 OS : ANY DATE : October 19, 1993 PAGE : 1/5 TITLE : An Overview of Borland's Dynamic Dispatching. Borland has extended the C++ language so that one can associate an unsigned int with a virtual 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 basically all the compiler does. Within OWL, there are a few WndProc's which have been exported. One of the Window Procedures receives messages for various Windows. The hWnd parameter identifies the target Window for which the message is destined. OWL then determines which Object's should receive the message. Once the Object has been identified, OWL scans the virtual table's of the Object ( i.e. the Class ) searching for a virtual function with the Message value as it's associated Integer. If none if found in the Object's virtual table, the base classes vtable are searched. Once/if a function is identified, the function is called directly with a Reference to a TMessage passed as argument. To illustrate the mechanism of DDVT, a non-OWL program using DDVT follows; It is a DOS program which dispatches to its command line arguments. The example was created using v3.x of the Borland C++ compiler. To create the program use the following: bcc test.cpp To try out the application, use the following: test -a -b -c -d -e -x other #include PRODUCT : Borland C++ NUMBER : 1026 VERSION : 3.x OS : ANY DATE : October 19, 1993 PAGE : 2/5 TITLE : An Overview of Borland's Dynamic Dispatching. class Dummy { virtual f(); }; #if sizeof( Dummy ) == 4 // Far vtables ? #define FARVTBL // Yes, set define. #endif class Configure { virtual void doa() = [ 'a' ]; virtual void dob() = [ 'b' ]; virtual void doc() = [ 'c' ]; virtual void dod() = [ 'd' ]; virtual void Default(); void Dispatch( unsigned msg ); public: Configure( int argc, char **argv ); }; void Configure::doa() { cout << "Received -a" << endl; } void Configure::dob() { cout << "Received -b" << endl; } void Configure::doc() { cout << "Received -c" << endl; } void Configure::dod() { cout << "Received -d" << endl; } void Configure::Default() { cout << "Non-DDVTed switch" << endl; } Configure::Configure( int argc, char **argv ) { argc--; argv++; while ( argc-- ) { if ( **argv == '-' ) // looking for -x Dispatch( *(*argv + 1) ); // send dispatch else cout << "Not a switch: " << *argv << endl; PRODUCT : Borland C++ NUMBER : 1026 VERSION : 3.x OS : ANY DATE : October 19, 1993 PAGE : 3/5 TITLE : An Overview of Borland's Dynamic Dispatching. argv++; } } typedef void ( Configure::*ConFP )(); typedef void ( *FP)(); /* ---------------------------------------------------------- *\ | _DDVTdispathNULL is a function in the RTL. There are two | | versions,one for far vtables, one for near. They both return| | pointers to pointers to the address of the DDVT function, or | | NULL if one is not found.. | \* ---------------------------------------------------------- */ #ifdef FARVTBL #define GetVptr(thisPtr) (*(void far **)(thisPtr)) FP far * _DDVTdispatchNULL(void far *, int); #else #define GetVptr( thisPtr) (*(void near **)(thisPtr)) FP far * _DDVTdispatchNULL(void near *, int); #endif /* ----------------------------------------------------------- *\ | NOTE: We are assuming the vtable pointer is at the very top | | of the class. See the 'Borland Open Architecture' Manual | | regarding the scenarios where this assumption is not valid. | \* ----------------------------------------------------------- */ void Configure::Dispatch( unsigned int msg ) { ConFP theDDVTfoo; // final location for DDVT foo union { FP fp; // since the RTL returns a basic ConFP memberFP; // function pointer address, we } temp; // use a union to transform to // member function pointer. FP fp = *_DDVTdispatchNULL( GetVptr( this ) , msg ); if ( fp ) PRODUCT : Borland C++ NUMBER : 1026 VERSION : 3.x OS : ANY DATE : October 19, 1993 PAGE : 4/5 TITLE : An Overview of Borland's Dynamic Dispatching. { temp.fp = fp; theDDVTfoo = temp.memberFP; } else theDDVTfoo = &Configure::Default; (this->*theDDVTfoo)(); } main( int argc , char **argv ) { Configure myConfig( argc, argv ); return 0; } ADDITIONAL NOTES: ================= To ensure that the compiler puts the vtable pointer at the begining of your class, derive everything from some dummy class which has no data members but DOES have a virtual function. The above code assumes the vtable pointer is at the begining of the class. In version 3.x of the Borland C++ compier the vptr is the first data member of the class (by default - although this behaviour can be modified via Options ). Therefore if a class is derived from a Base class with no virtual functions, the vtable pointer for the derived classes will *NOT* be at offset 0. When using DDVT with multiple inheritance, be careful! The layout of DDVTs enables one to dispatch to DDVT functions from the first class inherited only; For example: class A; class B; class C : A, B {}; class D; class E : D, C {}; PRODUCT : Borland C++ NUMBER : 1026 VERSION : 3.x OS : ANY DATE : October 19, 1993 PAGE : 5/5 TITLE : An Overview of Borland's Dynamic Dispatching. class C can dispatch to DDVT's from C and A, but not B. class E can dispatch to DDVT's from D and itself, but not C. In version 2.x of the Borland C++ Compiler, the Dispatch function returned the actual address of the DDVT function. Version 3.x, however, returns a pointer to the function. The DDVT table format is the similar though. For more information regarding the DDVTs and their layouts ( etc. etc. ) see the "Borland Open Architecture" HandBook.