PRODUCT : All NUMBER : 8517 VERSION : All OS : All DATE : October 25, 1993 PAGE : 1/18 TITLE : Borland Language News, volume 1, number 18 _Borland Languages Support News_ ---------------------------------------------------------------- volume 1, number 18 October 12, 1993 ---------------------------------------------------------------- IMPLEMENTING TEMPLATES THROUGH THE PREPROCESSOR The week's lead article may only possess historical signifi- cance or help those needing to port C++ code to other envi- ronments and compilers without template facilities. It may spark interest or awareness of the preprocessor's capabil- ity, but readers should note that the preprocessor does not perform type-checking as C++ provides by default, and thus is inferior to the features provided by the language speci- fication itself. Because of this reason, usage of the pre- processor should be cognizantly limited to features which cannot be mimicked by the language proper. Serving as the basis of discussion will be the stack class proposed back in the June 14, 1993 issue (volume 1, number 8) of the newsletter and subsequently refined in later install- ments. Ultimately, the class definitions proposed there were similar to -- class node { friend class stack; int value; node *next; node(const int &i) : value(i) { next = 0; } }; class stack { node *root; public: stack() { root = 0; } stack& operator<<(const int&); // "push" stack& operator>>(int&); // "pop" ~stack(); }; The limitation to this design is that only integer values can be pushed and popped from the stack. Since reusability is one of the chief reasons for using templates, the spe- cific type can aliased via indirection. eg. PRODUCT : All NUMBER : 8517 VERSION : All OS : All DATE : October 25, 1993 PAGE : 2/18 TITLE : Borland Language News, volume 1, number 18 typedef int T; class node { friend class stack; T value; node *next; node(const T &t) : value(t) { next = 0; } }; class stack { node *root; public: stack() { root = 0; } stack& operator<<(const T&); // "push" stack& operator>>(T&); // "pop" ~stack(); }; This would allow the class definitions to be placed into a header file which could then be included wherever it is needed without change (again promoting reusability), but a major limitation to this design is that only one type of stack can be used in a module; an integer stack and a float- ing-point stack cannot coexist in the same source file. This restriction can still be overcome, but not via the typedef technique. Indirection must proceed through two levels to achieve something similar to the template language extension already accepted by the ANSI/ISO standardization committee. The first level to be addressed is the generation of unique names; this was the problem suffered by the typedef ap- proach. The simplest manner of creating unique names would be to concatenate the type onto the class's name. This can be accomplished by using the preprocessor's concatenation operator, ##. The standard C++ header file, GENERIC.H uses this operator with several macros. Ultimately, 'name2' is defined as -- #define name2(a,b) a##b Use of 'name2' in code, however, is awkward. To more natur- ally denote parameterization, two more macros (in this case) can be defined to show that the parameterization type is to appear as a parameter of the class name itself. PRODUCT : All NUMBER : 8517 VERSION : All OS : All DATE : October 25, 1993 PAGE : 3/18 TITLE : Borland Language News, volume 1, number 18 #define node(T) name2(T,node) #define stack(T) name2(T,stack) Because of the necessity of using parentheses, the standard- ized use of angle brackets accepted by ANSI for template classes has to be abandoned. Likewise, the need for unique names will force parameterization to appear anywhere the class name is to be found. This also deviates with what can be found in the ANSI rulings. The second level of indirection required to simulate param- eterized types is to actually generate code for each tem- plate type needed. This can be done with the 'declare' ma- cro also defined in GENERIC.H. Simplistically, this is de- fined as -- #define declare(a,b) name2(a,declare)(b) which can be used to concatenate the word 'declare' onto the end of the class name. For integer stacks, code can be gen- erated when the following line of code is encountered: declare(stack,int); The entire class definition including all member function definitions can be wrapped into a single macro definition (and consequently as the header file for the stack class) as -- // STACK.H #if !defined(STACK_H) #define STACK_H #include #include #define node(T) name2(T,node) #define stack(T) name2(T,stack) #define stackdeclare(T) \ class node(T) { \ friend class stack(T); \ T value; \ PRODUCT : All NUMBER : 8517 VERSION : All OS : All DATE : October 25, 1993 PAGE : 4/18 TITLE : Borland Language News, volume 1, number 18 node(T) *next; \ node(T)(const T &t) : value(t) { next = 0; } \ }; \ class stack(T) { \ node(T) *root; \ public: \ stack(T)() { root = 0; } \ stack(T)& operator<<(const T&); \ stack(T)& operator>>(T&); \ int empty() { return root == 0; } \ ~stack(T)(); \ }; \ stack(T)& \ stack(T)::operator<<(const T &t) { \ node(T) *p = new node(T)(t); \ assert(p != 0); \ if (root) { \ p->next = root; \ root = p; \ } \ else \ root = p; \ return *this; \ } \ stack(T)& \ stack(T)::operator>>(T &t) { \ assert(root != 0); \ t = root->value; \ node(T) *p = root->next; \ delete root; \ root = p; \ return *this; \ } \ stack(T)::~stack(T)() { \ while (root) { \ node(T) *p = root->next; \ delete root; \ root = p; \ } \ } #endif PRODUCT : All NUMBER : 8517 VERSION : All OS : All DATE : October 25, 1993 PAGE : 5/18 TITLE : Borland Language News, volume 1, number 18 Use of this parameterized class can easily be integrated in- to code which still resembles ANSI's convention. // MAIN.CPP #include #include "stack.h" declare(stack,int); declare(stack,float); int main() { stack(int) stk; // integer stack const int n = 8; // #stack entries const int w = 5; // field width for (int i = 0; i < n; ++i) stk << i; while (!stk.empty()) { int i; stk >> i; cout.width(w); cout << i; } cout << endl; stack(float) fstk; // floating-point stack for (i = 0; i < n; ++i) fstk << float(i + i * 0.1); while (!fstk.empty()) { float f; fstk >> f; cout.width(w); cout << f; } cout << endl; return 0; } Merging all member function definitions into the same header file with the class definition is not necessarily good coding practice given large classes. This could cause various com- piler limits to be exceeded in the worst case. Since break- ing code into numerous modules is one of the best answers to this type of problem, all member functions within STACK.H will be moved into another file, STACK.CPP. Yet, how can this be done since the class definition is still written in PRODUCT : All NUMBER : 8517 VERSION : All OS : All DATE : October 25, 1993 PAGE : 6/18 TITLE : Borland Language News, volume 1, number 18 terms of a preprocessor macro? The proposed answer here is to do something similar to what was done with the macro 'declare'. Within GENERIC.H is another macro 'implement' simplistically defined as -- #define implement(a,b) name2(a,implement)(b) Breaking the stack class implementation apart also will aid in maintainability. In its new form, STACK.H can be reduced to -- // NEW STACK.H #if !defined(STACK_H) #define STACK_H #include #include #define node(T) name2(T,node) #define stack(T) name2(T,stack) #define stackdeclare(T) \ class node(T) { \ friend class stack(T); \ T value; \ node(T) *next; \ node(T)(const T &t) : value(t) { next = 0; } \ }; \ class stack(T) { \ node(T) *root; \ public: \ stack(T)() { root = 0; } \ stack(T)& operator<<(const T&); \ stack(T)& operator>>(T&); \ int empty() { return root == 0; } \ ~stack(T)(); \ }; #endif The code to be generated for all instances of the stack class can be confined to a .CPP file as -- PRODUCT : All NUMBER : 8517 VERSION : All OS : All DATE : October 25, 1993 PAGE : 7/18 TITLE : Borland Language News, volume 1, number 18 // STACK.CPP #include #include "stack.h" #define stackimplement(T) \ stack(T)& \ stack(T)::operator<<(const T &t) { \ node(T) *p = new node(T)(t); \ assert(p != 0); \ if (root) { \ p->next = root; \ root = p; \ } \ else \ root = p; \ return *this; \ } \ stack(T)& \ stack(T)::operator>>(T &t) { \ assert(root != 0); \ t = root->value; \ node(T) *p = root->next; \ delete root; \ root = p; \ return *this; \ } \ stack(T)::~stack(T)() { \ while (root) { \ node(T) *p = root->next; \ delete root; \ root = p; \ } \ } declare(stack,int); // generates definitions declare(stack,float); implement(stack,int); // generates code implement(stack,float); STACK.CPP and MAIN.CPP given above can be made into a pro- ject to obtain the same behaviour seen before. Breaking code, however, into smaller modules is a more robust solu- tion. PRODUCT : All NUMBER : 8517 VERSION : All OS : All DATE : October 25, 1993 PAGE : 8/18 TITLE : Borland Language News, volume 1, number 18 Use of the preprocessor as a replacement for templates is is not a recommended action. Debugging macros can be diff- icult since all substitutions will be performed prior to compilation. The lack of type-checking as mentioned before can lead to very subtle errors. What this article has at- tempted to do is show that the flexibility inherent to C++ can be used to extend the language in different directions. Judiciously used, this agility can aid the programmer in bridging numerous environments and problems. HOT ISSUES How do I print out the output from my EasyWin application? To print the output from an EasyWin program, the easiest method is to download the file EASYWIN2.ZIP, available on Borland's BBS at (408)431-5096. This file contains a module that allows output to be copied to a file, which can then be sent to a printer. Another method is to invoke "WIN BCW.EXE > OUTPUT.TXT" to start Windows and the compiler. Then any output that would normally appear in your text window gets redirected only to the file OUTPUT.TXT. The disadvantage of this method is that program output does not appear on the display monitor. One final method of capturing program output is to hit Alt-PrintScreen during or after program execution. This captures an image into the Windows clipboard, which can then be pasted into a program like PaintBrush to print, view, etc. See the Microsoft Windows _User's Guide_ for more information about using this technique. DOS What causes a "Null Pointer Assignment" error? Borland places four zero bytes at the bottom of the data segment, followed by the Borland copyright notice. In the small and medium memory models, a null pointer points to DS:0000. Thus, assigning a value to the memory referenced PRODUCT : All NUMBER : 8517 VERSION : All OS : All DATE : October 25, 1993 PAGE : 9/18 TITLE : Borland Language News, volume 1, number 18 by this pointer will overwrite the first zero byte in the data segment. At program termination, the four zeros and the copyright banner are checked. If either has been modi- fied, then the "Null Pointer Assignment" error message is generated. Note that the pointer may not truly be NULL, but may be a wild pointer that references these key areas in the data segment. In the compact, large and huge memory models, far pointers are used for data meaning that a null pointer will reference 0000:0000, or the base of system memory. Using this address will not cause a corruption of the key values at the base of the data segment, but modifying the base of system memory usually causes a system crash. Although it would be pos- sible that a wild pointer would overwrite the key values, it would not indicate a NULL pointer. OS/2 The help views provided with Borland C++ for OS/2 do not automatically link to each other. For this reason, you may encounter hot links in a help window to which you cannot jump. The resolution for this is to run VIEW.EXE and have it load all the help files in one session. Doing this is quite simple: 1. Open the Borland Help Icon View window. 2. Make a copy of any help icon. 3. Bring up the Settings dialog for the new icon. 4. Change the Parameters entry to: bc.inf+guiref20.inf+ipfc20.inf+ipfcexmp.inf+ pmfun.inf+pmgpi.inf+pmhok.inf+pmmsg.inf+pmrel.inf+ pmwin.inf+pmwkp.inf 5. Set the working directory to the Borland C++ BIN di- rectory. For example: C:\BCOS2\BIN 6. Go to the General page and change the Title. When you start this application, it willl be slower loading because it is loading all the help files. However, you can leave it open in the background while you work such that it will always be available. PRODUCT : All NUMBER : 8517 VERSION : All OS : All DATE : October 25, 1993 PAGE : 10/18 TITLE : Borland Language News, volume 1, number 18 WINDOWS How do I use the EasyWin library? If your application is being developed in the Windows IDE, nothing special has to be done. Just be sure that function main() is defined and select Static under Options|Linker| Library, Runtime Library. If the command-line tools are be- ing used, make sure that CWx.LIB as one of the libraries linked into the final executable. EasyWin is not defined in the dynamic-link version of the runtime library (CRTLDLL.LIB or BC30RTL.DLL). The Windows C static runtime libraries already define a WinMain() function. When a Windows program is built, the linker will link in a default WinMain() function. This ver- sion of WinMain() will initialize the EasyWin library before calling main(). This default behaviour makes it very easy for DOS-like programs to be compiled and executed with lit- tle or no change under Windows. When a Windows program is built, the linker warning: "No module definition file specified: using defaults" may be generated. The module definition file is a part of a true Windows program that tells the linker how to mark different segments. For an EasyWin program, this warning can be ig- nored as the linker uses its default values if one is not specified. Even if a true Windows program is being developed where Win- Main() is explicitly in the code, the EasyWin library can still be used. Making a call to _InitEasyWin() somewhere within the code will initialize the EasyWin library and en- able functions like printf(). Note that if the option to warn of duplicate symbols during link has been enabled, the linker will issue a warning that WinMain() has been rede- fined. This can be expected and can be ignored if WinMain() has been placed into code. TURBO VISION How can I send a message to a modeless view to close itself? PRODUCT : All NUMBER : 8517 VERSION : All OS : All DATE : October 25, 1993 PAGE : 11/18 TITLE : Borland Language News, volume 1, number 18 The following program sends a broadcast message to tell a modeless view to close. #include #define Uses_TApplication #define Uses_TDeskTop #define Uses_TMenuBar #define Uses_TSubMenu #include #include #include const unsigned int cmMakeView = 100; const unsigned int cmRemoveView = 101; const unsigned int cmRemoveYourself = 102; class TMyView : public TView { virtual void handleEvent(TEvent&); public: TMyView(TRect r) : TView (r) { // Tell view it can receive broadcast messages eventMask |= evBroadcast; } }; void TMyView::handleEvent(TEvent &event) { if (event.what == evBroadcast) switch (event.message.command) { case cmRemoveYourself: // Tell view to remove itself. After removing view, // don't execute any other code that relies on this // view because it will be nonexistent. TProgram::deskTop->remove (this); break; } // OK to execute this code after removing the view // because it doesn't rely on the TMyView just re- // moved. TView::handleEvent(event); } PRODUCT : All NUMBER : 8517 VERSION : All OS : All DATE : October 25, 1993 PAGE : 12/18 TITLE : Borland Language News, volume 1, number 18 class TMyApplication : public TApplication { static char nextX, // Where to put nextY; // the view next static TMenuBar* initMenuBar(TRect); virtual void handleEvent(TEvent&); public: TMyApplication () : TProgInit(&TApplication::initStatusLine, &TMyApplication::initMenuBar, &TApplication::initDeskTop) { } }; char TMyApplication::nextX = 0; char TMyApplication::nextY = 0; TMenuBar* TMyApplication::initMenuBar(TRect r) { r.b.y = r.a.y + 1; return new TMenuBar(r, *new TSubMenu("~E~xample", kbAltE) + *new TMenuItem("~M~ake Modeless View", cmMakeView, kbAltM, hcNoContext, "Alt-M") + *new TMenuItem ("~R~emove All Views", cmRemoveView, kbAltR, hcNoContext, "Alt-R")); } void TMyApplication::handleEvent(TEvent &event) { if (event.what == evCommand) { switch (event.message.command) { case cmMakeView: // insert new TMyView into deskTop TRect r; r.a.x = nextX++; r.a.y = nextY++; r.b.x = r.a.x + 10; r.b.y = r.a.y + 10; TMyView *p = new TMyView(r); TProgram::deskTop->insert(p); break; case cmRemoveView: // Tell any TMyView objects that are inserted // into the deskTop to remove themselves. PRODUCT : All NUMBER : 8517 VERSION : All OS : All DATE : October 25, 1993 PAGE : 13/18 TITLE : Borland Language News, volume 1, number 18 message(TProgram::deskTop, evBroadcast, cmRemoveYourself, this); nextX = nextY = 0; break; } } TApplication::handleEvent(event); } int main() { TMyApplication MyApp; MyApp.run(); MyApp.shutDown(); return 0; } PARADOX ENGINE 1. Why is my Paradox Engine application under version 3.01 larger than under version 2.0? Applications created with version 3.01 of the Paradox Engine will be approximately 65-70k larger in C/C++ and up to 160k larger in Pascal than applications compiled under version 2.0. The application will be larger because support has been added for a new locking protocol, composite secondary indexes, and for blob (memo) fields. 2. Why is my Engine application having problems when SHARE is loaded? One possibility is that SHARE could be running out of locks or file handles. The command line "SHARE /L:75 /F:4096" will increase the number of files than can be locked to 75 and the memory available for files to 4096 (the defaults are 20 and 2048). PASCAL Similar to the program posted in last week's DOS column dis- playing all printable strings in a file, here is the Pascal PRODUCT : All NUMBER : 8517 VERSION : All OS : All DATE : October 25, 1993 PAGE : 14/18 TITLE : Borland Language News, volume 1, number 18 equivalent. program Strings; const DEFSTR : integer = 5; MAXSTR : integer = 10000; var f : file; c : char; s : pchar; r : pchar; flen : longint; slen : integer; err : integer; function isPrint(c : char) : boolean; const l : char = chr ( 32); u : char = chr (126); begin isPrint := (c >= l) and (c <= u); { TRUE when } end; { printable } begin slen := DEFSTR; if paramCount < 1 then begin writeln ('Usage: ', paramStr(0), ' [string length]'); exit; end; if paramCount = 2 then begin val(paramStr(2), slen, err); if (err <> 0) or (slen <= 0) then begin writeln('=================================='); writeln(paramStr(0), ':'); writeln('Using default string length of ', DEFSTR); writeln('=================================='); PRODUCT : All NUMBER : 8517 VERSION : All OS : All DATE : October 25, 1993 PAGE : 15/18 TITLE : Borland Language News, volume 1, number 18 writeln; writeln; writeln; slen := DEFSTR; end; end; fileMode := 0; assign(f, paramStr(1)); {$I-} reset(f, 1); if ioResult <> 0 then begin writeln('Couldn''''t open ', paramStr(1)); exit; end; flen := fileSize(f); { get filelength } { Allocate space for string. Built-in heapFunc will } { errors. } getMem(r, sizeOf(r^) * (MAXSTR + 1)); s := r; while (flen > 0) do begin dec(flen); blockRead(f, c, 1); if isPrint(c) then begin { Add printable character to string. } if ((s - r) <= (MAXSTR)) then begin s^ := c; inc(s); end else begin { Print full string. } s^ := chr(0); writeln(r); s := r; PRODUCT : All NUMBER : 8517 VERSION : All OS : All DATE : October 25, 1993 PAGE : 16/18 TITLE : Borland Language News, volume 1, number 18 end; end else begin { The current character isn't printable. } { Print current string? } if ((s - r) >= slen) then begin s^ := chr(0); { print current string } writeln(r); end; s := r; end; end; close(f); end. QUIZ Last week's quiz/exercise, asked for a function to be writ- ten which would output the binary representation of a float- ing point value. Though the following is not a single func- tion, it does encapsulate the idea of outputting binary val- ues as a template class. As such, it can also be used for integers, longs, doubles, characters, etc. #include #include template class type { T v; char byte(const int i) { return *((char*) &v + i); } char byte(const int i) const { return *((char*) &v + i); } public: type(const T &t) : v(t) { } PRODUCT : All NUMBER : 8517 VERSION : All OS : All DATE : October 25, 1993 PAGE : 17/18 TITLE : Borland Language News, volume 1, number 18 friend ostream& operator<<(ostream&, const type&); }; template ostream& operator<<(ostream &out, const type &t) { for (int i = sizeof(T) - 1; i > -1; --i) { const int bits = 8; // #bits / byte for (int j = bits - 1; j > -1; --j) out << (t.byte(i) & (1 << j) ? '1' : '0'); out << ' '; } return out; } int main() { const int n = 5; for (int i = 0; i < n; ++i) cout << float(i) << " = " << type(pow(2.0, i)) << endl; for (i = 0; i < n; ++i) cout << double(i) << " = " << type(pow(2.0, i)) << endl; return 0; } For this week's quiz, explain why the following output: a = 3 b = 2 c = 2 is generated by the program below: #include int f(int i) { return ++i; } int g(int &i) { return ++i; } int h(char &c) { return ++c; } int main() { int a, b, c; a = b = c = 0; a += f(g(a)); b += g(f(b)); c += f(h(c)); PRODUCT : All NUMBER : 8517 VERSION : All OS : All DATE : October 25, 1993 PAGE : 18/18 TITLE : Borland Language News, volume 1, number 18 cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "c = " << c << endl; return 0; } NEW TECHNICAL INFORMATION DOCUMENTS AVAILABLE Some of the latest technical information documents available to customers include the following. 1703 Explanation of why using certain time and date functions may cause the date to be incorrect. 1702 Example of nested class and template usage. 1701 How to stream a user defined dialog box in Turbo Vision. 1700 Example which uses the BI_IArrayAsVector BIDS template as a data member within a class. 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.