PRODUCT : Borland C++ NUMBER : 8508 VERSION : All OS : All DATE : October 25, 1993 PAGE : 1/17 TITLE : C++ Language News -- Volume 1 Number 9 _Borland Language Support News_ ---------------------------------------------------------------- volume 1, number 9 July 5, 1993 ---------------------------------------------------------------- MIGRATING TO AN OBJECT-ORIENTED MINDSET II In the previous installment to this series, a stack class was developed using some of the tenants of object-oriented programming. In continuing along this vein, the same stack class will be modified to take objects of any type. Due to the length of discussion here for templates, iteration will be covered in the next issue. Converting the stack class presented in the last issue of the newsletter to a template class is quite straightforward; all references to integers within the class will need to be changed to the template class's template argument (place- holder). The common problem with template use seen in Tech- nical Support revolves around using template classes across multiple files. Breaking template code apart where the mem- ber functions are implemented in one file and template in- stances are found in another file creates a dilemma for the compiler. Remember, the compiler treats each compilation as independent to every other compile. This indicates that when the compiler comes to the module implementing the tem- plate member functions, it will have no idea as to what type of code to generate with respect to the template arguments. If all template code is included into the module where the template class is being used, the compiler will have enough information, but when broken apart, it will not. This is the reason for the #pragma options and typedef's seen in the examples in the documentation (pp. 152-155 of the Borland C++ 3.1 _Programmer's Guide_ or pp. 497-499 of the Turbo C++ 3.0 _User's Guide_). The following code shows how the template syntax will appear in a simple implementation of the template stack class: #include template class node { PRODUCT : Borland C++ NUMBER : 8508 VERSION : All OS : All DATE : October 25, 1993 PAGE : 2/17 TITLE : C++ Language News -- Volume 1 Number 9 friend class stack; T value; node *next; node(const T &t) : value(t) { next = 0; } }; template class stack { node *root; public: stack() { root = 0; } int operator<<(const T&); // push analog int operator>>(T&); // pop analog ~stack(); }; template int stack::operator<<(const T &t) { node *p = new node(t);// allocate new node if (!p) return 1; if (root) { // add to existing stack p->next = root; root = p; } else root = p; // first iteration return 0; } template int stack::operator>>(T &t) { if (!root) // invalid operation return 1; t = root->value; node *p = root->next; delete root; root = p; return 0; } template PRODUCT : Borland C++ NUMBER : 8508 VERSION : All OS : All DATE : October 25, 1993 PAGE : 3/17 TITLE : C++ Language News -- Volume 1 Number 9 stack::~stack() { while (root) { T t; // ensures destroying t *this >> t; } } main() { const int n = 4; stack istk; stack dstk; for (int i = 0; i < n; ++i) {// fill both stacks istk << i; dstk << i + 0.1 + i * 0.1; } for (i = 0; i < n; ++i) { // output integer stack int j; istk >> j; cout << " " << j; } cout << endl; for (i = 0; i < n; ++i) { // output double stack double d; dstk >> d; cout << " " << d; } cout << endl; return 0; } This is OK for the fundamental types, but subtle problems can occur when using user-defined classes as the template argument. In particular, the manner in which the construc- tor for the node class initializes 'value' with the incom- ing parameter. This then implies that a copy-constructor needs to be specified. Within the overloaded '>>' operator member function (analogous to popping from the stack), as- signment back to the incoming parameter is done which would require that the assignment operator ('=') be defined with- in the class. Likewise, within the destructor for the stack class, the default constructor (requiring no argu- ments) of the template argument is required. These types of subtleties are frequently part of any class design. PRODUCT : Borland C++ NUMBER : 8508 VERSION : All OS : All DATE : October 25, 1993 PAGE : 4/17 TITLE : C++ Language News -- Volume 1 Number 9 As an example, here is a class definition (with minimal member functions) which will work with the above stack template class: class foo { int v; public: foo() { } foo(const int i) { v = i; } foo(const foo &f) { v = f.v; } foo& operator=(const foo &f) { v = f.v; return *this; } friend ostream& operator<<(ostream &out, const foo &f) { return out << "foo(" << f.v << ")"; } }; main() { const int n = 4; stack stk; for (int i = 0; i < n; ++i) stk << foo(i); for (i = 0; i < n; ++i) { foo f; stk >> f; cout << " " << f; } cout << endl; return 0; } HOT ISSUES In the scenario where a virtual base class is also abstract due to an pure virtual function, Borland C++ 3.1 will com- plain that it cannot create an instance of the abstract class 'Z'. class D { PRODUCT : Borland C++ NUMBER : 8508 VERSION : All OS : All DATE : October 25, 1993 PAGE : 5/17 TITLE : C++ Language News -- Volume 1 Number 9 virtual void f() = 0; }; class X : virtual public D { }; class Y : virtual public D { virtual void f() { } }; class Z : public X, public Y { }; main() { Z z; return 0; } This can be corrected by altering the class topology to the following functionally equivalent hierarchy -- class D { virtual void f() = 0; }; class X : virtual public D { }; class Y : virtual public D, public X { virtual void f() { } }; class Z : public Y { }; main() { Z z; return 0; } DOS 1. How can a function with a variable number of arguments send these same parameters to another function taking a variable number of arguments? Looking at STDARG.H, it can be seen that va_list is a type- def for a void far pointer and is used to point to the first argument placed on the stack. It would seem intuitive that the following would be allowed -- void g(...); void f(int i, ...) { va_list a; PRODUCT : Borland C++ NUMBER : 8508 VERSION : All OS : All DATE : October 25, 1993 PAGE : 6/17 TITLE : C++ Language News -- Volume 1 Number 9 va_start(a, i); g(...); // ... } void g(int i, ...) { va_list a; va_start(a, i); // ... } however, the stack frame that g() uses is completely sep- arate from the frame that f() is using; 'a' in both func- tions point to different stack locations. The question then becomes "How can then I make them point to the same location?" The answer to this question is to pass the stack frame pointer of the first function to the second. The following example illustrates this idea. NOTE: the first argument sent to function f() is not passed on to function g() -- #include #include #include void f(const char*, ...); void g(va_list, ...); main() { const char *a[] = { "bashful", "doc", "dopey", "grumpy", "happy", "sleepy", "sneezy" }; f(a[0], a[1], a[2], a[3], a[4], a[5], a[6], 0); return 0; } void f(const char *p, ...) { va_list a; va_start(a, p); g(a); cout << "f(): ( " << p; char *q; while ((q = va_arg(a, char*)) != 0) cout << " " << q; cout << " )" << endl; va_end(a); } PRODUCT : Borland C++ NUMBER : 8508 VERSION : All OS : All DATE : October 25, 1993 PAGE : 7/17 TITLE : C++ Language News -- Volume 1 Number 9 void g(va_list a, ...) { cout << "g(): ("; char *q; while ((q = va_arg(a, char*)) != 0) cout << " " << q; cout << " )" << endl; va_end(a); } OS/2 The following files RTC.CPP, RTC.DEF, and IO16.ASM demon- strate how to use Borland C++ for OS/2 to create an IOPL segment. An IOPL segment will allow your program to di- rectly access hardware using the IN and OUT assembly lang- uage instructions. Under OS/2 2.X user programs run at privilege level 3 which does not allow the use of the IN or OUT assembly language instructions. These in instruc- tions are only allowed at privilege level 2, which is the privilege level of the IOPL segment. Borland C++ does not allow functions that reside in IOPL segments to have any parameters passed on the stack. This limitation requires the IOPL function to use the CPU reg- isters for both passed parameters and return values. The IOPL segment is required to be a 16 bit segment which adds yet another layer of complexity to the situation. Since the IOPL segment resides in a 16 bit segment, special thunk- ing code must be created by the compiler to call from the 32 bit flat model segment to the 16 bit IOPL segment. The 32 bit to 16 bit thunking code makes extensive use of almost all the CPU registers. EDX is the only register that is not used used by the 32->16 bit thunking code. The reduced complexity of the return code from the 16 bit IOPL segment allows the use of ECX for a return values. The code that follows will work provided these registers are used to pass and return values to the IOPL 16 Bit functions. *** USE ONLY THE DOCUMENTED REGISTERS TO PASS AND *** *** RETURN VALUES FROM THE LISTED IOPL FUNCTIONS *** PRODUCT : Borland C++ NUMBER : 8508 VERSION : All OS : All DATE : October 25, 1993 PAGE : 8/17 TITLE : C++ Language News -- Volume 1 Number 9 -------------------- START OF RTC.CPP ------------------------ // // Delcare the following prototypes for the 16 bit functions // that have IOPL. // extern "C" { void __pascal __far16 WPORTW(void); void __pascal __far16 WPORTB(void); void __pascal __far16 RPORTW(void); void __pascal __far16 RPORTB(void); } #include #include // Structure to hold the time read from the CMOS. struct Time { unsigned Seconds; unsigned Minutes; unsigned Hours; }; void getTime(Time &t) { // Get the seconds count from the Real Time Clock. _EDX = 0x00000070; WPORTB(); _EDX = 0x00000071; RPORTB(); t.Seconds = _CL; // Get the minutes from the RTC. _EDX = 0x00020070; WPORTB(); _EDX = 0x00000071; RPORTB(); t.Minutes = _CL; // Get the hours from the RTC. _EDX = 0x00040070; WPORTB(); PRODUCT : Borland C++ NUMBER : 8508 VERSION : All OS : All DATE : October 25, 1993 PAGE : 9/17 TITLE : C++ Language News -- Volume 1 Number 9 _EDX = 0x00000071; RPORTB(); t.Hours = _CL; } // Display the current time void DisplayTime(Time &t) { cout<< hex << setfill('0') << setw(2) << t.Hours << ":" << hex << setfill('0') << setw(2) << t.Minutes << ":" << hex << setfill('0') << setw(2) << t.Seconds << endl; } void main(void) { cout << "TIME IS: "; Time time; getTime(time); DisplayTime(time); cout << endl; } ----------------------- END OF RTC.CPP -------------------- ---------------------- START OF RTC.DEF ------------------- NAME IOPLTEST STACKSIZE 2000 SEGMENTS IOPLCODE IOPL ---------------------- END OF RTC.DEF --------------------- ----------------------- START OF IO16.ASM ----------------- ; ************************************************************* ; * Assembly Language module that declares a 16 bit segment. * ; * It is required that a 16 bit segment is used to create an * ; * IOPL segment. * PRODUCT : Borland C++ NUMBER : 8508 VERSION : All OS : All DATE : October 25, 1993 PAGE : 10/17 TITLE : C++ Language News -- Volume 1 Number 9 ; ************************************************************* P386 ; Procedure in the IOPL segment must be pascal to ; clean up their stack. MODEL LARGE, PASCAL IOPLCODE SEGMENT WORD PUBLIC USE16 'CODE' ASSUME CS:IOPLCODE PUBLIC WPORTW ; Write a word to a port PUBLIC WPORTB ; Write a byte to a port PUBLIC RPORTW ; Read a word from a port PUBLIC RPORTB ; Read a byte from a port ; ************************************************************** ; * Write port functions WPORTW and WPORTB are called with EDX * ; * holding both the port address and the value to write. * ; * EDX low word contains the address of the port, and the EDX * ; * high word contains the value to write. * ; ************************************************************** WPORTW PROC FAR push edx xor eax, eax ; clear eax shld eax, edx, 16 ; get the value to write out dx,ax pop edx ret ENDP WPORTB PROC FAR push edx xor eax, eax ; clear the eax shld eax, edx, 16 ; get the value to write out dx,al pop edx ret ENDP PRODUCT : Borland C++ NUMBER : 8508 VERSION : All OS : All DATE : October 25, 1993 PAGE : 11/17 TITLE : C++ Language News -- Volume 1 Number 9 ;************************************************************** ;* The Read port functions should contain the port address to * ;* read from the DX register. The CX register will contain * ;* the value read from the port in either CL or CX for byte * ;* or word port reads. * ;************************************************************** RPORTW PROC FAR in ax,dx mov ecx, eax ret ENDP RPORTB PROC FAR in al,dx mov ecx, eax ret ENDP IOPLCODE ENDS END ----------------------- END OF IO16.ASM -------------------- WINDOWS 1. The IMPLIB.EXE utility coming from Microsoft is similar to the one available from Borland, but the output of both pro- grams differ in how case-sensitivity is handled. The Micro- soft version uppercases output which could lead to undefined symbols when linking with Borland. 2. How can the main window in my OWL application startup maxi- mized? Though this question was briefly touched in volume 1, number 6, the following fragment (from ObjectWindow's STEP1.CPP tutorial) shows a slightly different method to the same end. PRODUCT : Borland C++ NUMBER : 8508 VERSION : All OS : All DATE : October 25, 1993 PAGE : 12/17 TITLE : C++ Language News -- Volume 1 Number 9 void TMyApp::InitMainWindow() { MainWindow = new TWindow(NULL, Name); nCmdShow=SW_SHOWMAXIMIZED; } TURBO VISION 1. Some precautions need to be taken when using a memory checking utility (such as Bounds Checker or MemCheck) on a Turbo Vision application. In a Turbo Vision app- lication, the main function typically looks like -- main() { TMyApp mine; mine.run(); return 0; } Since the destructor of a Turbo Vision object does not call the member function shutdown(), a memory leak will exist unless function destroy() is called explicitly as in: main() { TmyApp mine; mine.run(); mine.shutdown(); return 0; } or -- main() { TMyApp *pmine; pmine = new TMyApp; pmine->run(); destroy(pmine); return 0; } PARADOX ENGINE PRODUCT : Borland C++ NUMBER : 8508 VERSION : All OS : All DATE : October 25, 1993 PAGE : 13/17 TITLE : C++ Language News -- Volume 1 Number 9 1. Here is a description of the various files used and/or created by the Paradox Engine in performing its tasks: .DB: Paradox tables storing information. .MB: contains all data from BLOB or memo fields. Each table will only have one .MB file which stores all memo field information. .PX: contains primary index information. .Xnn: .Ynn: both contain secondary index infor- mation. Numbers 00-FF represent non- composite secondary indexes (the num- ber represented the indexed field). Numbers G0-VF represent composite secondary indexes. .LCK: used by version 2.0 of the Paradox En- gine to handle all locks on the given table. Normally, these files will not be seen because they should exist only when the Engine is instantiated. PARADOX.NET: used by version 2.0 of the Paradox En- gine for directory access. This file will be created if one does not already exist. PARADOX.LCK: Directory lock used by the Paradox En- gine version 3.0 to preclude use by En- gine 2.0 applications & Paradox versions lower than 4.0 (ie. anything using 3.5 locking). This file will also not be normally seen because it will be deleted before the Engine terminates. PDOXUSRS.LCK: used by version 3.0 of the Paradox En- gine to handle locks. This file or any other .LCK files will not normally be seen due to deletion by the Engine's cleanup. See pp. 29-34 of the Paradox Engine _User's Guide_ for more informa- tion on _Locking & Lock Files_. PDOXUSRS.NET: used in Paradox 4.0-style locking for user ID, use counts, and coordinate locking. This file will be created if one does not exist. PRODUCT : Borland C++ NUMBER : 8508 VERSION : All OS : All DATE : October 25, 1993 PAGE : 14/17 TITLE : C++ Language News -- Volume 1 Number 9 PASCAL 1. What code can I incorporate into my own application which will reboot my computer? The following code illustrates one method of rebooting: program reboot; var a : integer absolute $0040:$0072; begin a := $1234; inline($9A/$F0/$FF/$00/$F0); end. 2. How can I change/blank out the cursor? The following unit alters the cursor's display by calling the BIOS's interrupt 10: Unit Cursor; { allows getting/setting the shape of the cursor } Interface Uses DOS; Type CursorType = record { storage of the cursor } Stop, Start : Byte; end; Procedure GetCursorShape(Var CursorShape: CursorType); Procedure SetCursorShape(CursorShape: CursorType); Implementation Procedure GetCursorShape(Var CursorShape: CursorType); Var PRODUCT : Borland C++ NUMBER : 8508 VERSION : All OS : All DATE : October 25, 1993 PAGE : 15/17 TITLE : C++ Language News -- Volume 1 Number 9 R : registers; Begin R.AH := 3; R.BH := 0; { Page number (Turbo uses page 0 } Intr($10, R); CursorShape := CursorType(R.CX); end; Procedure SetCursorShape(CursorShape: CursorType); Var R : registers; Begin R.AH := 1; R.CX := Word(CursorShape); Intr($10, R); end; end. **************** Sample Program **************** Program TestCursor; Uses CRT, { Must be included for cursor to work } Cursor; Var Cur,Save : CursorType; Begin getCursorShape(Save); { save cursor } Writeln('Default Cursor'); readln; Cur.Start := 20; Cur.Stop := 20; SetCursorShape(Cur); { change shape (OFF) } Writeln('No Cursor'); Readln; Cur.Start := 0; Cur.Stop := 20; SetCursorShape(Cur); { change shape (Block) } Writeln('Block Cursor'); Readln; PRODUCT : Borland C++ NUMBER : 8508 VERSION : All OS : All DATE : October 25, 1993 PAGE : 16/17 TITLE : C++ Language News -- Volume 1 Number 9 SetCursorShape(Save); { restore cursor } Writeln('Default Cursor'); readln end. QUIZ Last week's quiz centered about designing a class 'foo' which adheres to the following two conditions: 1. An instance of class 'foo' can be created by anyone and anywhere within a program. 2. Any attempt to derive a class from class 'foo' will re- sult in a compile time error when the derived class is instantiated. Here is one possible solution: class V { protected: V() { } }; class B : private virtual V { }; class D : public B { }; main() { B b; // anyone can create a B D d; // cannot call V::V() return 0; } For this week's quiz, what happens when the following pro- gram is run? // FILE a.cpp | // FILE b.cpp class A { | class A { public: | public: virtual f(); | virtual g(); virtual g(); | virtual f(); }; | }; A::g() { } | A::f() { } main() { | PRODUCT : Borland C++ NUMBER : 8508 VERSION : All OS : All DATE : October 25, 1993 PAGE : 17/17 TITLE : C++ Language News -- Volume 1 Number 9 A *p = new A; | p->f(); | delete p; | } | Note the suspicious difference in class declarations across the two files. NEW TECHNICAL INFORMATION DOCUMENTS AVAILABLE Some of the latest technical information documents available to customers include the following. 1399 How to use Borland C++ for OS/2 under the IBM Workframe/2 environment. 1397 Use of constreams to implement multiple windows. 1396 Example of saving & restoring an entire 640x480 screen image by using BGI graph- ics functions. 1033 Example of a derived TListViewer Turbo Vision object supporting fully oper- ational horizontal and vertical scroll bars & mouse double-clicks. These documents can be found on either LIB-2 of BCPPDOS or BCPPWIN on CompuServe, Borland's DLBBS at (408)431-5096, TechFAX at (800)822-4269, or OAS at (408)431-5250. DISCLAIMER: You have the right to use this technical information subject to the terms of the No-Nonsense License Statement that you received with the Borland product to which this information pertains.