Now I have a more clear idea how to continue the work, let’s document the next steps.
First of all I want to remember that the first test of building the application listed about 30 missing functions, all used only in the first source file but I’m sure others are used in other source files. The missing functions may be part of the Funcky library or an application level library of which I only have the .lib file. A friend decompiled a couple of executables so that I could recover some of these missing sources. A blinker generated file I found in the archive lists all the object files linked in one exectuable and the functions it contains so that I could create the source code files using the same name.
I used the online Norton Guide of the Funcky libs to detect which functions were used from the library, and from the first list of missing functions I had the following list:
CLS: clear entire screen ROLOC: reverse attribute PRINT: print a string BORDER: set border color (CGA/EGA/VGA) CLOCK24, UNCLOCK24: display/turn off a 24 hour military format clock ONDO: execute expressionin the list FLRESET: reset default font/mode CENTER: add leading spaces to center in width AMAXSTRLEN: get longest string length in array CURDRIVE: get the logged drive letter (d:) ALEN: length of array without undefined elements CHDIR: change directory
Looking at the samples in the NG and their use in the code I created the following code:
#include "set.ch" // Unused, no more valid, functions // CLOCK24, UNCLOCK24: display/turn off a 24 hour military format clock FUNCTION clock24() RETURN .T. FUNCTION unclock24() RETURN .T. // BORDER: set border color (CGA/EGA/VGA) FUNCTION border( n ) RETURN .T. // FLRESET: reset default font/mode FUNCTION FLReset() RETURN .T. // STUB functions (need changes) // CURDRIVE: get the logged drive letter (d:) // PROBLEM: returns a fixed drive letter FUNCTION curdrive() RETURN "f:" // CHDIR: change directory FUNCTION chdir( cPath ) hb_cwd( cPath ) RETURN .T. // CENTER: add leading spaces to center in <nn> width // PROBLEM: samples in NG says that there should be a trim() FUNCTION center( c, n ) RETURN PadC( c, n ) // ROLOC: reverse attribute // PROBLEM: Returns a fixed value FUNCTION roloc( x ) RETURN "w" // PRINT: print a string // PROBLEM: nColore not used FUNCTION print( y, x, cTesto, nColore, nRepl ) IF ! HB_ISNUMERIC( nRepl ) nRepl := 1 ENDIF @ y, x SAY Replicate( cTesto, nRepl ) RETURN .T. // CLS: clear entire screen // PROBLEM: cChar not used, not all cases covered FUNCTION cls( cColor, cChar ) @ 0, 0 CLEAR TO MaxRow(), MaxCol() RETURN .T. // Completed functions, may need changes // ONDO: execute expression <n> in the list FUNCTION ondo( nValue, ... ) LOCAL b, c IF ! HB_ISNUMERIC( nValue ) ? "ERRORE ONDO valore non numerico" QUIT ENDIF IF nValue > Len( hb_AParams() ) - 1 ? "ERRORE ONDO, passato valore", nValue, "ma solo ", Len( hb_AParams ) -1, "valori" QUIT ENDIF c := hb_AParams()[ nValue + 1 ] b := hb_macroBlock( c ) Eval( b ) RETURN .F. // AMAXSTRLEN: get longest string length in array FUNCTION aMaxStrLen( a ) LOCAL n LOCAL i IF Len( a ) == 0 RETURN 0 ENDIF n := Len( a[ 1 ] ) FOR i := Len( a ) TO 2 STEP -1 IF HB_ISCHAR( a[ i ] ) .AND. Len( a[ i ] ) > n n := Len( a[ i ] ) ENDIF NEXT RETURN n // ALEN: length of array without undefined elements FUNCTION aLen( a ) LOCAL i LOCAL n := 0 FOR i := 1 TO Len( a ) IF ValType( a[ i ] ) != "U" n++ ENDIF NEXT RETURN n
Then I created funcky.hbp file
# I want a library -hblib # I want to call it "funcky" -ofuncky # src\funcky </pre> And built it <pre> hbmk2 funcky.hbp </pre> The library was successfully created. I then updated exe01.hbp file adding the following lines: <pre> -L. -lfuncky </pre> to link the library just created. I could have used a .hbc file. I will, in the future. Now the build process reports only the functions that should be in the missing application-level library. I create a .hbp file for this new library (I called it applib) adding all the decompiled functions that I'm aware of, using the blinker generated file as a guide. 1 # I want a library -hblib # I want to call it "applib" -oapplib # source files src\decomp01 src\decomp02 .. src\decomp13
There are still missing functions reported by the linker, and all used in the first source file only. Looking at the names and how they are used in the code, they are almost all related to screen handling, to create fancy effects, like imploding or exploding windows... Just one is another clone of padr function.
So I create all empty stub functions, except for the clone function, and finally added a function I usually use for debugging:
#pragma BEGINDUMP #include <windows.h> HB_FUNC( OUTPUTDEBUGSTRING ) { OutputDebugString( (LPCSTR) hb_parcx( 1 ) ) ; } #pragma ENDDUMP
Now the last step, trying to build the program and finally get the new missing functions from the next source files.... ready ?
hbmk2 exe01.hbp
Nice sensation when you get the errors you were looking for !!!
The errors had the same pattern: if there is only one missing function the linker processes the following file, otherwise it stops. So I have an error for file02.prg and file03.prg but 3 for file04.prg so I have to solve them before getting new missing functions...
Now I have to loop: compile and get the list of the missing functions. If the function is from Funcky lib, check its docs and its usage, then create a clone.
If not from Funcky lib, check in the decompiled files. At the moment I had just 2 minor executables decompiled; the main one, with almost all functions, fails to decompile. I know this will be a problem in the future... I know...
The job was quite repetitive, but in the process I found something interesting...
I had to correct this code snippet that failed to compile:
Chr( K_CTRL_HOME ) + Chr( K_DOWN ) // fails to compile in Harbour
there was obviously a missing KEYBOARD()... I don't have Clipper 87 installed so I can't check if it also failed to compile... but anyway, it never worked as intended...
I found several Funcky functions used to set/delete the volume label, or check if a drive is ready (A:) or if the passed string points to a valid dir. I created stubs to always return .T. These functions are used just in a couple of source files to perform backups, move data between clients/year or create specially formatted data to upload/send for tax filings or other law related matters.
Then there was a subtly named function, used in this way
CASE ISCHAR( LastKey() ) .OR. LastKey() = K_ENTER
I quickly, and wrongly, converted ISCHAR to HB_ISCHAR. Wrong, wrong, wrong !
Since Lastkey() always returns a numeric value, HB_ISCHAR will be always .F. Instead, the real job of the function is to return .T. if the pressed key is a "char", a letter of the alphabet. I will check better in the following lines to understand if the Lastkey() value is used for some other job, in the meantime I created this:
FUNCTION ischar( nValue ) IF nValue >= 32 .and. nValue <= 128 RETURN .T. ENDIF RETURN .F.
Another function I found is Funcky ReadScreen(): it returns the string at passed screen coordinates. It's used only once and I plan to replace it, when I will look at that code. At the moment I just exit the application.
FUNCTION ReadScreen() ? "Function ReadScreen not implemented" QUIT
Then there is one last function that I hope I can replace easily. This function is not listed in the blinker generated files. Looking at the few places where it is used, it seems to be a sort of "change directory": it is used in the sourcse that handle index creation and file copying. I will have to understand it better. In the meantime I will create a stub function that prints a debug line and returns .T.
To have my program compile and link I had to add a couple of functions from the decompiled executables, to write 6 application functions and some more Funcky stubs (see code below).
Of the 6 rewritten functions, one is related to licensing (that was already disabled in the code so the code could also be removed); another is a clone of pad, some are screen related, all created as stubs.
The first executable is now ready to be tested. The environment variable set to the root data directory, program launched....
Now I'm were I was a couple of days ago, but with clean, well formatted code, the process documented in this blog and some ideas on how to continue on the drawing board...
The program runs, the menu appears on screen... now the next step is to let the software find its data files...
During the compile/link/error/add_functions loop I added the following functions to funcky.prg
// get status of Clipper's various set commands // PROBLEM: incomplete FUNCTION Status( nIndex ) DO CASE CASE nIndex == 6 RETURN( _SET_CURSOR ) END CASE clear screen ? "Richiesto status valore ", nIndex QUIT RETURN .F. // csrput() - place cursor at specified row/column // PROBLEM: is it correct ? FUNCTION csrput( y, x ) @ y,x SAY "" RETURN .T. // palette() - access 64 colors on EGA and VGA adaptors FUNCTION PALETTE() RETURN 1 FUNCTION SWAP( p1, p2 ) LOCAL t := p1 p1 := p2 p2 := t RETURN .T.
Hi Francesco,
really an interesting and well done blog. A clear and comprehensive path to obtain an effective port.
About your csrput() ported version I think that:
1. the SAY “” is unnecessary and you can omit it at all;
2. the @ y,x is always pre-processed to SetPos( y, x );
3. the original FuncKy function returns NIL.
So, I suggest this code:
In addition, here is my ancient version of aMaxStrLen(), just to show that programming isn’t an exact science:
Waiting for the next post.
Thanks a lot.
Maurizio
Thanks Maurizio.
Yes, programming isn’t an exact science. And infact aMaxStrLen can be written in different ways… it would be nice to list some of them and analyze which is the best for speed and/or readability…
I will comment on my version of the code in a dedicated post.