As I said in previous posts, before delving more into the code base I decided to do a simple test: try to compile and link the first program run by the batch file, just to see what to expect.
Porting the .lnk file was quite easy. The original file was
BLINKER INCREMENTAL OFF FI EXE01,FUNCKY,RESIDENT SEA FUNBLINK BEGINAREA FILE file01 FILE file02 ... FILE file40 ALLOCATE OVERCL ALLOCATE APPLIB ALLOCATE FUNCKY ALLOCATE EXTEND ENDAREA LIB CLIPPER
The converted file is (with the first 3 lines not strictly necessary)
-gui -gtwvt -inc -oEXE01 src/file01 src/file02 ... src/file40
I was sure I was headed to hit a wall… but I was deliberately looking for a failure!
The results were (from the log I saved):
hbmk2: Suggerimento: aggiungere l’opzione ‘hbxpp.hbc’ per la funzione(i) mancante: CurDrive()
hbmk2: Suggerimento: aggiungere l’opzione ‘hbct.hbc’ per la funzione(i) mancante: Center()
hbmk2: Suggerimento: aggiungere l’opzione ‘hbblink.hbc’ per la funzione(i) mancante: BliOvlClr()
And then other 30 missing functions.
Some that I found in Funcky docs: CLS(), ROLOC(), PRINT(), BORDER(), UNCLOCK24(), ONDO(), FLRESET(), CLOCK24(), ALEN(), CHDIR(), AMAXSTRLEN().
And some – I won’t name them – that were in that missing application lib I already told about in a previous post. Among the 700+ files present in the directory there were a couple generated by the linker that list the object files and where they come from so I could confirm I had no sources for them.
A couple of the function’s names referred to the screen: setting attributes, showing messages on screen, setting fonts (we are talking about EGA/VGA times….).
Others were file related functions, to open database files. Others were utility functions and a couple were clones of padr/padl/padc functions that were not present in Clipper 87.
Using the Funcky docs found online I was able to quickly rewrite the most important ones, with very basic functionality, discarding the ones like the clock (on-screen clock was good to have on text-mode programs, not in GUI programs…).
Looking at the code I was able to quickly create stub functions for the others: it has been a very interesting process, trying to understand by the function call which was their use, parameters and the expected return values.
During this process I had to look at several source code files and noticed that there were calls to functions that were expected to be in the missing library and that I had still not rewritten! More, the only missing functions were from file01.prg… possible that the other 39 files used no missing functions?
This surprised me a lot. So I swapped the order of the files in the .hbp file, and put file03.prg as the first one. Now the build process reported different missing functions – but always related to file03. I could not explain exactly why it was happening and had several hypothesis, none fully verified. For one of them I thought to have an answer: browsing the source code I saw that Funcky ondo() command was used.
Ondo has this syntax:
Depending on the value of the n variable, it calls the function listed in the n+1 parameter, using the macro operator. This means that you must have it linked to work at runtime. My idea was that Blinker was linking them while mingw linker wasn’t… and some other strange ideas…
But this was not the problem. It’s really a lot easier: if there is only one missing function to link in the file currently being linked, the linker continues and reports errors from other object files. As soon it reports more than 1 error in a linked file, the other errors from other object files are not reported.
It’s impossible (unless there is a linker switch to use that I’m not aware of) to have a full reports of all the missing functions in all the files.
Doing the tests and looking at the blinker generated reports I found, I could list more than 20 functions I had no source code. I’m not sure the software house has these sources or can provide them to me. So I decided to do something I didn’t want to do: use a decompiler. I don’t own one but a trusted friend of mine does, so I sent one of the executables and I got back well formatted, easy to read, source files for the application. Using the blinker generated file I could recreate the source files reproducing the same structure as the original. For a moment I also wanted to use the decompiler to generate more readable code for ALL the source code but I fear that it may introduce problems – and I will lose all the comments, remarked code, and so on.
Among the decompiled functions there are the routines used to open data tables, create index, create temporary tables, lookup values. Some of them could apparently be easily rewritten, but some have some not easily understandable features or side effects. Original (well… decompiled) code is better.
Ok, so finally the program run, the main menu appeared on the screen but any option I selected would stop the program. I made sure the modules were correctly linked and now the program stopped after a few more program lines.
In the second post I talked about the structure of the data files: one root directory pointed by a environment variable and one subdirectory for each client/year and the data files that can be in both root and subdirectory (the latter has precedence). The main menu, when you choose an action and a workspace is not selected, lists all the available subdirs and then uses chdir() to set the working directory. Then, depending on the selection, executes the menu action or sets the errorlevel and exit the program letting the calling batch file to execute the proper executable.
The program could not locate the data files… so it arrived the time to start to look at the code.
The code of this part is not very complex but it gave me an idea of the difficulties I may need to solve in the future. Let’s have a look at the first few lines of this function, keeping in mind this is Clipper 87 code!
drive=CURDRIVE()+'\' dir=SUBSTR(CURDIR(),4) dir=SUBSTR(gete('ROOTDIR'),4) n=AT('\',dir) IF n>0 drive=drive+LEFT(dir,n) dir=SUBSTR(direct,n+1) ENDIF ADIR(drive+'*.*',a6,.T.,.T.,.T.,a7) nDir=ALEN(a7)
The first thing to note is that there is a really strange code: drive and dir variables are set, then dir variable is overwritten, but not drive. I know that ROOTDIR environment variable points to the root directory of the application and from the code I see that is quite mandatory: without that env variable aDir() reads the root directory of the current drive not the root directory of the application. It can be a valid setup but really not a good idea to have all the subdirectoirs in the drive root dir… unless it is a drive dedicated to the data.
My idea is to change this code to have the env variable mandatory and exit the program if not set. In this way, any data setup you want, you may have it. Now the code is just 2 lines (check of ROOTDIR presence is done at startup):
drive := gete( "ROOTDIR" ) ADIR(drive+'*.*',a6,.T.,.T.,.T.,a7)
I don’t know yet if this change is good or not since it needs to be validated with other issues I will introduce now.
The code after this snippet loops on the a6/a7 arrays and when it finds a directory it does:
IF CHDIR( drive + a6[nIndex] ) IF FILE( 'file1.DBF') .AND. FILE( 'file2.DBF') my_netuse( 'file1' ) company = FILE1->companyname CLOSE ALL // not shown: add the data to the array to be used // later for aChoice ENDIF ENDIF
From this code I learn that file1 and file2 must reside in the subdirectory and that chdir() is used to set it. But with my previous browsing in the code I remembered to have seen something of interest…
Infact in one of the initialization functions, this code is executed:
_temp=GETE('ROOTDIR') _path='.;'+_temp SET PATH TO &_path
This code tells Clipper to try to open the data files in the current directory but if it doesn’t find them, to try in the data root directory.
Looking at the docs, the file() function respects this directive, and checks if the file is present in both directories…
my_netuse function opens the data file and can’t handle paths, infact use its parameter to name the alias:
use &par alias &par
I hope you start to see the problem I’m facing: the code expects to be able to set a working directory AND to be able to keep this setting between executables (something I was not able to achieve using hb_cwd calls) and completely rely on Clipper to handle where the needed file actually is.
So, related to this particular aspect of the application, I’m starting to think about the following points:
– include all the code into one executable (instead of the 15/16 currently necessary)
– have a public variable to hold the current working directory, just for reference, if necessary
– change the program to use the (properly validated) directory in the env variable as the main data directory, with one subdirectory for each client/year; hb_cwd should work in one executable, so that no other changes in file handling code would be necessary.
– use Harbour, 2015 era, directory functions that must also work in linux
Since we have just one executable we won’t need to keep the settings between executables. I just hope that somewhere in the code there isn’t something stopping this idea…