|
Volume Number: | 7 | |
Issue Number: | 2 | |
Column Tag: | Jörg's Folder |
Related Info: Quickdraw
C++ Methods In FORTRAN
By Jörg Langowski, Editorial Board
“C++ Methods in FORTRAN”
This article had to come. Next thing you’ll see, you may think, is calling Eiffel from Forth. But seriously, implementing cross-language calling very often helps you to understand how one particular language really works and how to get most out of it. Also, the Fortran run time system has some advantages, like well-designed console I/O that may help in debugging, file I/O, an easily accessible output window, and therefore adding it may be very helpful in some applications. So here we go, and write a C++ method in Language Systems Fortran.
C++ methods are really independent subroutines, and the association between a method and its class is done through the header file - on the source code side - and through the modified method name on the object code side. When you define two different classes with methods of the same name, as in
// 1 class aaa { public: void doit(); } class bbbb { public: void doit(); }
the methods are usually defined in a place different from where they are actually used. In fact, most often they may be pre-compiled in a library. The linker can distinguish between the two different doit methods, because their symbolic name on the linker level is modified by the class name. The two methods’ names in this case would be: doit__3aaaFv and doit__4bbbbFv.
C++ Function Name Encoding
To remind you of the C++ linker naming conventions: the first part of the linker name is the method name (doit), followed by two underscores. Then follows the class name, preceded by the number of characters in that name. Appended to that are some symbols describing the parameter list. In our case, for methods taking no parameters, it is simply “Fv”. F stands for a function, v for a void parameter list. I am not describing all the modifiers that are used in creating a linker name out of a C++ method name; the full encoding scheme is described in the AT&T C++ release notes, chapter 6, page 22.
Usually, you never see the encoded names; only when e.g. a method is defined in a class header file, then referenced by some code using objects from that class, but never actually implemented, the linker might complain about a missing function. There, you will probably have seen those funny names already, and used the MPW unmangle tool to make sense out of them.
Here, we want to go the other direction. When we implement a C++ method in some non-object language, we must write a routine that has the encoded method’s name. The easiest way to find out about the linker name of a C++ method is not to construct it yourself (you never get it right in the first place), but just to define the method in the class header, use it in the code, and never actually implement it. The linker will tell you in its error message what function it is looking for.
That way I found out that the name for the method pascal void FtnCall() in the class TMacTutorDocument is FTNCALL__17TMACTUTORDOCUMENT. The characters “Fv” are not appended here, and the name is written in all capitals, because of the “pascal” keyword. This also means that all external Pascal-type methods of the same name in the same class will have the same linker name, regardless of the argument list.
Language Systems Fortran, of course, uses Pascal calling conventions, so a method written in Fortran will have to be defined in C++ as pascal. We want to pass some parameters, so lets define an argument list:
{2} pascal void FtnCall(short *menuitem, long k, float *r);
The method will later be called from a menu handler, just for the fun of it, and we pass the number of the menu item selected, and some numbers. Note one restriction here: LS Fortran symbolic names are limited to 31 characters, so don’t use too complicated class and method names if you don’t want to run out of space.
The Sample Program
We use the sample program that I showed you in V6#1: Apple’s C++ mini-application skeleton. You should now go back to your library and get that MacTutor issue, or reorder it, because we obviously cannot reprint its full source here. Listing 2 only shows those parts that have been changed.
In the constructor and destructor methods of the application class we have added calls to InitFortran() and ExitFortran() for initializing the Fortran run time system at the start of the program and leaving it properly at the end. During the program, then, the run time system is at our disposal. The Fortran method is defined in the class TMacTutorDocument. It is used in the TMacTutorApp class, in its DoMenuCommand method:
// 3 x = 4.567; fMacTutorCurDoc->FtnCall(&menuItem,3456,&x);
This way we have some calls by reference (standard Fortran calling convention), and one call by value.
The Fortran routine (listing 1) receives the three parameters in the same order. Furthermore, on every method call a handle to the method’s current object (this) is pushed on the stack after all the method’s parameters. The Fortran code has to take that extra parameter into account. Thus, the first line of the Fortran routine will be
C 4 subroutine FTNCALL__17TMACTUTORDOCUMENT (menuitem,%val(k),r,%val(this)) .
menuitem and r are passed by reference, k is received by value (Language Systems Fortran has an option for receiving parameters by value in the subroutine definition). this is also received by value. Through this, we can access the instance variables of the method’s current object. We also define the list of instance variables, which becomes rather complicated as we have to dereference handles in the (Languages Systems) Fortran way. The structure definitions at the beginning of the Fortran routine show you how to do that.
this points to the beginning of the object’s instance variables, starting with those of the topmost ancestor class and descending through the class hierarchy. In our case, we have a window pointer fDocWindow and the pointer to the virtual methods table vptr from the TDocument class, then the instance variables of TMacTutorDocument, fItemSelected and fDisplayString.
The main body of the routine displays the passed parameters and some information about the instance variables in the Fortran output window.
That’s all! You’re now able to add Fortran code to C++ programs as you like, and have it behave like real C++ methods.
Listing 1: C++ method written in Fortran CC++ test method written in Language Systems Fortran CFor editing, some of the lines had to be split. I have not Ccreated proper Fortran continuation lines, because a. C I’m lazy and b. I think the text can be read better that Cway -- jl -- subroutine FTNCALL__17TMACTUTORDOCUMENT (menuitem,%val(k),r,%val(this)) Cpascal void FtnCall__17TMacTutorDocument (short *menuitem, long k, float *r, struct TMacTutorDocument **); include “Quickdraw.f” structure /MTDocVars/ record /WindowPtr/ fDocWindow integer*4 vptr integer*2 fItemSelected record /StringPtr/ fDisplayString end structure structure /MTDocPtr/ pointer /MTDocVars/ p end structure structure /MTDocHdl/ pointer /MTDocPtr/ h end structure record /MTDocHdl/ this integer*2 menuitem integer top,left,bottom,right call MoveOutWindow (20,260,490,340) write (*,*) “Arguments to FtnCall:”,menuitem,k,r write (*,*) “fItemSelected = “,this.h^.p^.fItemSelected write (*,*) “fDisplayString = “,this.h^.p^.fDisplayString.sptr^ top = this.h^.p^.fdocwindow.wp^.portrect.top bottom = this.h^.p^.fdocwindow.wp^.portrect.bottom left = this.h^.p^.fdocwindow.wp^.portrect.left right = this.h^.p^.fdocwindow.wp^.portrect.right write (*,*) “doc window = “,top,left,bottom,right return end
Listing 2: Changes to the .cp and .h files from the V6#1 example File MacTutorApp.make: OBJECTS = TApplication.cp.o TDocument.cp.o MacTutorApp.cp.o MacTutorDoc.cp.o MacTutorGrow.cp.o FtnCall.f.o HEADERS = MacTutorApp.h MacTutorDoc.h MacTutorGrow.h TApplication.cp.o ƒ TApplication.cp TApplication.h CPlus TApplication.cp TDocument.cp.o ƒ TDocument.cp TDocument.h CPlus TDocument.cp MacTutorApp.cp.o ƒ MacTutorApp.make {HEADERS} MacTutorApp.cp CPlus MacTutorApp.cp MacTutorDoc.cp.o ƒ MacTutorApp.make {HEADERS} MacTutorDoc.cp CPlus MacTutorDoc.cp MacTutorGrow.cp.o ƒ MacTutorApp.make {HEADERS} MacTutorGrow.cp CPlus MacTutorGrow.cp FtnCall.f.o ƒ MacTutorApp.make FtnCall.f Fortran -mc68020 -mc68881 -opt=3 -bkg=0 FtnCall.f MacTutorApp ƒƒ MacTutorApp.make {OBJECTS} Link -w -t APPL -c JLMT “{CLibraries}”CRuntime.o {OBJECTS} “{Libraries}”Interface.o “{CLibraries}”StdCLib.o “{CLibraries}”CSANELib.o “{CLibraries}”Math.o “{CLibraries}”CInterface.o “{CLibraries}”CPlusLib.o #”{CLibraries}”Complex.o “{FLibraries}FORTRANLib.o” -o MacTutorApp MacTutorApp ƒƒ MacTutorApp.make MacTutorApp.r Rez MacTutorApp.r -append -o MacTutorApp File MacTutorApp.h: class TMacTutorApp : public TApplication { public: TMacTutorApp(void); // Our constructor ~TMacTutorApp(void); // need a destructor to call EXITFORTRAN private: // routines from TApplication we are overriding long HeapNeeded(void); unsigned long SleepVal(void); void AdjustMenus(void); void DoMenuCommand (short menuID, short menuItem); // routines for our own devious purposes void DoNew(void); void Terminate(void); }; File MacTutorDoc.h: class TMacTutorDocument : public TDocument { protected: short fItemSelected; // string corresponding to menu item selected StringPtr fDisplayString; void DrawWindow(void); public: TMacTutorDocument(short resID, StringPtr s); ~TMacTutorDocument(void); // routine to access private variables void SetDisplayString (StringPtr s) {fDisplayString = s;} short GetItemSelected(void) {return fItemSelected;} void SetItemSelected(short item) {fItemSelected = item;} // methods from TDocument we override void DoUpdate(void); // Fortran calling; pascal void FtnCall(short *menuitem, long k, float *r); }; File MacTutorApp.cp: pascal void initFortran(); pascal void exitFortran(); // Methods for our application class TMacTutorApp::TMacTutorApp(void) { Handle menuBar; initFortran(); // initialize Fortran runtime system // read menus into menu bar menuBar = GetNewMBar(rMenuBar); // install menus SetMenuBar(menuBar); DisposHandle(menuBar); // add DA names to Apple menu AddResMenu(GetMHandle(mApple), ‘DRVR’); DrawMenuBar(); // create empty mouse region fMouseRgn = NewRgn(); // create a single empty document DoNew(); } TMacTutorApp::~TMacTutorApp(void) { exitFortran(); // exit Fortran runtime system } void TMacTutorApp::DoMenuCommand (short menuID, short menuItem) { short itemHit; Str255 daName; short daRefNum; float x;// for testing the Fortran call WindowPtrwindow; TMacTutorDocument* fMacTutorCurDoc = (TMacTutorDocument*) fCurDoc; window = FrontWindow(); switch ( menuID ) { case mApple: switch ( menuItem ) { case iAbout: // About box itemHit = Alert(rAboutAlert, nil); break; default: // DAs etc. GetItem(GetMHandle(mApple), menuItem, daName); daRefNum = OpenDeskAcc(daName); break; } break; case mFile: switch ( menuItem ) { case iNew: DoNew(); break; case iClose: if (fMacTutorCurDoc != nil) { fDocList->RemoveDoc(fMacTutorCurDoc); delete fMacTutorCurDoc; } else CloseDeskAcc(((WindowPeek) fWhichWindow)->windowKind); break; case iQuit: Terminate(); break; } break; case mEdit: // SystemEdit for DA editing & MultiFinder if ( !SystemEdit(menuItem-1) ) { switch ( menuItem ) { case iCut: break; case iCopy: break; case iPaste: break; case iClear: break; } } break; case myMenu: if (fMacTutorCurDoc != nil) { switch ( menuItem ) { case item1: fMacTutorCurDoc-> SetDisplayString(“\pC++”); break; case item2: fMacTutorCurDoc-> SetDisplayString(“\pSample”); break; case item3: fMacTutorCurDoc-> SetDisplayString(“\pApplication”); break; case item5: fMacTutorCurDoc->SetDisplayString(“\pHave Fun”); x = 4.567; fMacTutorCurDoc->FtnCall(&menuItem,3456,&x); break; } fMacTutorCurDoc->SetItemSelected(menuItem); InvalRect(&(window->portRect)); fMacTutorCurDoc->DoUpdate(); } break; } HiliteMenu(0); } // DoMenuCommand
- SPREAD THE WORD:
- Slashdot
- Digg
- Del.icio.us
- Newsvine