Linking C functions with your Force programs ------------------------------------------------ Overview -------- When writing complex programs or applications, you may find that you need features that Force does not provide in the Force library, yet do not want to give up the benefits that Force provides for execution time and size. This is where third party libraries may come to your rescue. Third party libraries are code libraries of functions and procedures written by a company other than SEC, usually for Force applications. However, because of Force's flexibility, you may use thousands of third party libraries that were written for Microsoft or Borland C compilers. Third party libraries often cover whole suites of topics, or limit themselves to offering all possible features of one specific topic. Common third party libraries provide support for computer graphics, accounting, higher-level mathematics, and other topics. The third party libraries written specifically for Force usually ship with their own Force header files for the functions and procedures in their library, but third party libraries written for C compilers or assemblers do not. You may also already have functions that you have written in C or assembler, but want to use in your Force programs. For whatever reason that you decide to use a third party or C function library with your application, use this section as a guideline for making the library work with you, not against you. Who's Compatible, and Who's not At the time of this writing, only two major C compilers are compatible with the Force compiler. Those are Microsoft C versions 5.0 and 6.0, and Borland's Turbo C versions 2.0 through 4.02. Microsoft's current C/C++ compiler (version 7.0) generates compatible code, but Force is not compatible with its runtime library. Other, less well-known C compilers may or may not be compatible with Force depending on whether or not they generate standard Intel object code .OBJ files. Understanding the basis of the lack of compatibility makes a number of other facts readily sensible. Microsoft incompatibility is not marked, and in many cases MS C is actually compatible in later versions. FORCE is completely compatible with Microsoft C versions up through 6.x (remember that you have to link in the appropriate MICROx.OBJ file if MS runtime library functions are called). In MS C version 7.x, it appears that the actual compiler code and much of the runtime library code is compatible. Incompatibilities center around the use of DBL numbers (floats) in MS code as Microsoft's handling of these numbers departed from strict IEEE conventions as of MS version 7.0; consequently, MS C versions 7.x and 8.x (VC) produce C code that should be compatible with FORCE as long as floats are not used in the C code or in the MS runtime library functions called. This problem is currently under examination. There are thus indications that MS C versions 7.x and 8.x are in fact compatible if the "alternate math" compiler switch and MS C libraries are used, but this has not been tested completely at the time of this writing. In any event, it appears from experiment that MS Quick C version 2.x is also compatible with FORCE, and other C compilers may be as well. Please notify SEC via email at Compuserve address 76660,1024 if you have found other C compilers that are compatible so that we may share this information with other FORCE users. In no event is C++ code compatible with FORCE! As for assemblers, both Borland's Turbo Assembler (all versions) and Microsoft's Macro Assembler (all versions) generate .OBJ files that are compatible with Force. Before we get started, let us just be sure where we stand. Assumed knowledge for the next subsection, Interfacing With C, is that you are learned and comfortable with the C language. We will not attempt to teach any aspects of the language to you, other than for purposes of review. Interfacing with C ÕÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ͸ ³ Force Data Type C Data Type bytes ³ ³ --------------------------------------------- ³ ³ BYTE signed char 1 ³ ³ (U)INT(unsigned) int 2 ³ ³ (U)LONG(unsigned) long 4 ³ ³ DBL double 8 ³ ³ LOGICAL int 2 ³ ³ CHAR char ³ ³ [] variable ³ ³ --------------------------------------------- ³ ³ Table 8-1 - Force/C data types ³ ÔÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ; C and Force share a very similar language and implementation structure, a trait that allows the two to work together beautifully. Force and C use the stack in the same way, use data segments in the same way and implement functions and procedures nearly identically. C's primitive data types match Force's very well (see Table 8-1). Note the variable length in bytes of the data type CHAR. CHAR's C equivalent is an array of characters, which is determined at compile time. The higher-level Force data types, such as a FILE, ALIAS and MEMO have no real C equivalents, since they are in a real sense not actual data types but rather "predefined data structures". Passing Parameters to C Functions C has various methods of passing parameters, just as Force does (please reference MEMORY MANAGEMENT, Passing Parameters, for a full discussion on how Force passes parameters). C has a CONST keyword to signify that no modifications to the parameter are allowed in the function. C also allows passing by value, but does not use a VALUE keyword; it is implied. Finally, C allows passing by reference, which is implemented by passing the address or pointer as the parameter. ÕÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ͸ ³ Parameter passing Will Pass use in Force use in C ³ ³ ----------------------------------------------------------------- ³ ³ by value a copy VALUE keyword implied ³ ³ ³ ³ by reference the address implied address ³ ³ or pointer ³ ³ constant value a copy, none const keyword ³ ³ but will not ³ ³ modify ³ ³ ³ ³ constant reference the address, CONST keyword const keyword ³ ³ but will not plus address ³ ³ modify or pointer ³ ³ ³ ³ ------------------------------------------------------------------ ³ ³ Table 8-2 - Passing parameters in Force and C ³ ÔÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ; Table 8-2 shows how Force and C implement the different parameter passing methods. When passing by value, Force uses the VALUE keyword, otherwise, passing by reference is implied in Force. The opposite is true with C: C passes parameters by value, unless the address of the parameter is passed. The one exception to this rule is for arrays. C always passes the address of an array, where Force will allow you to pass a copy of the array. Because Force CHAR types correspond to a character array in C, you must pass CHAR parameters by reference. Of course, the same applies to other types of arrays. Let's take a look at some Force function prototypes, and try to write corresponding C function prototypes. First, we'll try passing by value. FUNCTION UINT fn PROTOTYPE PARAMETERS VALUE UINT x, VALUE DBL y This function, if written in Force, returns an unsigned integer value, and takes two parameters, an integer x and a double precision value y. Because the parameters are values, the corresponding function prototype in C would be: unsigned int fn( int x, double y ); Seems simple enough. Now, let's try passing by reference. This procedure will take a CHAR string and a long integer as its parameters: PROCEDURE proc PROTOTYPE PARAMETERS CHAR string, ULONG x Recall from MEMORY MANAGEMENT that passing by reference means passing the address of (or passing a pointer to) the parameter. Thus, a corresponding C function prototype would be expecting pointers to the parameters: void proc( char * string, long * x ); If we were to modify the procedure so that the parameters were to be constant (non-modifiable by the procedure), we would add the CONST keyword to our parameters: PROCEDURE proc PROTOTYPE PARAMETERS CONST CHAR string, CONST ULONG x The corresponding C function would have the same changes: void proc( const char * string, const long * x ); Now let's have a look at arrays. Remember that C always expects the address of the (first element of the) array, therefore, you cannot pass a copy of the array to C. This function takes an array of integers and the number of integers in the array as its parameters. It returns some integer value. FUNCTION UINT fn PROTOTYPE PARAMETERS UINT array[12], VALUE UINT length The corresponding C function prototype looks like this: int fn( int array[12], int length ); Finally, let's take a look at one Force/C discrepancy, a function that returns a CHAR data type. The Force library function upper() takes a CHAR parameter and returns its uppercase equivalent. The Force function prototype for upper() is as follows (note the CONST because the original string is not modified). FUNCTION CHAR upper PROTOTYPE PARAMETERS CONST CHAR source_string You would expect the corresponding C prototype to return... Well, what would it return? A CHAR in C and Force is an array of single-byte characters, and you cannot return arrays in either language. In C, you could have the function return a pointer to a new string, but where would this new string come from? Actually, when a function returns a CHAR parameter, Force passes the address of the return destination as an "invisible" first parameter. This means that the corresponding C function must receive a leading CHAR parameter. This leading parameter is the location that the resulting string should be stored. Thus, the corresponding C function prototype for the upper() function is really: void upper( char * destination, const char * source) For this example, the result of upper() will be stored in the destination parameter. If you are planning to call Force functions from your C functions, then this discussion simply applies in reverse. That is to say, the Force function call is made "C style" in the C program even though this may not agree with the usual Force prototype. Thus, if you were to call Force's upper() function from C, you would have code similar to the following: void upper( char * dest, const char * source); void proc( void ) { char string1[] = "Hello, Force!" char string2[50]; upper( string2, string1 ); printf("lowercase: %s\n", string1 ); printf("UPPERcase: %s\n", string2 ); } Preliminary Rules of Thumb Before we get into actually writing and linking C code to your Force applications, there are a few rules that you must always heed when working with C. The application may consist of as much C code as you like, but must start at force_main(), and not C's main() function. If most of the application will be in C, then you may call a main()-like function as your first or only command in force_main(). The main()-like function may then call Force functions as described above. NOTE: Always compile your C code with the large memory model. If you are using functions in the Microsoft C library, or are using any third party library that does, be sure to call setup_micro() as your first command in force_main(). Also be sure to link in the Microsoft start-up code, MICRO5.OBJ or MICRO6.OBJ depending on which version of the compiler you have (these start-up modules are in your \FORCE\LIB directory). If you are using any double precision values in your Microsoft C code, be sure that you link in the Microsoft floating-point startup code, MFP5.OBJ (will work for Microsoft 6.0, also). If you are using functions in the Borland C library, or are using any third party library that does, be sure to call setup_turboc() as your first command in force_main(). Also be sure to link in the Borland start-up code, TURBOC.OBJ. Now let's get on with some examples. Interfacing Force with Your Own C Functions If you are writing your own C functions to be called from Force, you can often write them without calling any functions from the standard C library provided with your C compiler. This is often possible because Force already provides similar functions for most of the functions in the C library. For example, standard C libraries provide several functions for low-level file I/O. Force provides those same functions, as you can see in Table 8-3. ÕÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ͸ ³ Table 8-3. File I/O functions ³ ³ -------------------------------- ³ ³ C function Force function ³ ³ ---------- -------------- ³ ³ open() fb_open() ³ ³ close() fb_close() ³ ³ read() fb_read() ³ ³ write() fb_write() ³ ³ lseek() fb_seek() ³ ³ eof() fb_eof() ³ ³ ³ ÔÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ; To see this in action, here's a function written in C that uses only Force library functions to copy a file from one filename to another. It uses a dynamically allocated memory buffer to read and write bytes from the first file to the second. Note that C does not require us to write function prototypes, but it is considered good programming practice, as it helps the C compiler perform parameter type-checking. /* * copyfile.c -- Example code: Calling Force library * functions from a C source module. */ typedef int LOGICAL; /* C has no LOGICAL data type, ** so we roll our own. */ /* * Here are the function prototypes for the Force library * functions that copy_file() will be using. */ logical fb_open( int *handle, const char *filename, int mode ); void fb_close( int handle ); unsigned int fb_read( int handle, void * buffer, unsigned int nbytes ); unsigned int fb_write(int handle, void * buffer, unsigned int nbytes ); void far * malloc( unsigned int nbytes); void free( void * ptr ); /* * File open modes. */ #define B_READ 1 #define B_CREAD_WRITE 2 #define B_CREATE 2 #define B_WRITE 3 #define B_READ_WRITE 4 #define B_CREAD 5 #define B_CWRITE 6 /* * procedure: copy_file() * * description: copy_file() takes two filenames, and copies * the first into the second. copy_file() returns False * if there is an error (such as no dynamic memory left, * or file does not exist). */ logical copy_file( char * source, char * dest ) { void far * buffer; int in_handle; int out_handle; int bytes_read; /* * Let's open the files, and return False if we can't. */ if (! fb_open( &in_handle, source, B_READ )) return( 0 ); /* return False */ if (! fb_open( &out_handle, dest, B_CWRITE )) { fb_close( in_handle ); return( 0 ); /* return False */ } /* * Now we allocate a 5K buffer and return False if * we cannot get it. */ buffer = malloc( 5120 ); if (! buffer) { fb_close( in_handle ); fb_close( out_handle ); return( 0 ); /* return False */ } do { bytes_read = fb_read( in_handle, buffer, 5120 ); fb_write( out_handle, buffer, bytes_read ); } while( bytes_read == 5120 ); fb_close( in_handle ); fb_close( out_handle ); return( 1 ); /* return True */ } /*-- EOF: copyfile.c ----------------------------------*/ Now let's write a test program in Force that uses copy_file(). Our test program will simply have copy_file() copy the executable code to a different name. We'll get the name of the executable from the Force library function get_exec(). * * test.prg - Tests the copy_file() function * #include io.hdr #include system.hdr FUNCTION LOGICAL copy_file PROTOTYPE PARAMETERS CONST CHAR source,CONST CHAR dest PROCEDURE force_main VARDEF CHAR(128) This_File ENDDEF This_File = get_exec( ) ? "Copying " + This_File + "to output.exe" IF .NOT. copy_file( This_File, "output.exe" ) ? "Cannot copy file!" ELSE ? "Done!" ENDIF ENDPRO *-- EOF: test.prg --------------------------------* To make sure that everything works, save the C source code as COPYFILE.C and the above Force test program as TEST.PRG. Compile TEST.PRG module using the following line: Force test.prg Now compile the COPYFILE.C module. If you're using Microsoft C, version 5.0 or 6.0, use the following line: cl /AL /Gs copyfile.c If you're using Borland's Turbo C or Borland C, compile it with this line: tcc -ml -c copyfile.c Now you should have two more files in your directory, TEST.OBJ and COPYFILE.OBJ and we need to link them together with the Force library. If you're using Microsoft C, use this line: link test copyfile,test,Nul,force ; For Borland: TLINK test copyfile,test,nul,force ; Run the TEST.EXE program, and you'll see the lines: Copying test.exe to output.exe done Interfacing with C libraries Let's get a little more complicated now by using the functions in the C library. We'll modify our C source example to change the date of the destination file to the date of the source file. To do this, we'll have to use the functions in the standard C library to read and write file dates and times. Unfortunately, the names of these functions differ between Borland and Microsoft libraries, but we can get around this problem using the C preprocessor. Here's the new code for COPYFILE.C. /* * copyfile.c -- Example code: Calling Force library * functions from a C source module. */ #include #include typedef int LOGICAL; /* C has no LOGICAL data type, ** so we roll our own. */ /* * Here are the function prototypes for the Force library * functions that copy_file() will be using. */ LOGICAL fb_open( int *handle, const char *filename, int mode ); void fb_close( int handle ); unsigned int fb_read( int handle, void * buffer, unsigned int nbytes ); unsigned int fb_write(int handle, void * buffer, unsigned int nbytes ); void far * malloc( unsigned int nbytes); void free( void * ptr ); /* * File open modes. */ #define B_READ 1 #define B_CREAD_WRITE 2 #define B_CREATE 2 #define B_WRITE 3 #define B_READ_WRITE 4 #define B_CREAD 5 #define B_CWRITE 6 /* * procedure: copy_file() * * description: copy_file() takes two filenames, and copies * the first into the second. copy_file() returns False * if there is an error (such as no dynamic memory left, * or file does not exist). */ logical copy_file( char * source, char * dest ) { void far * buffer; int in_handle; int out_handle; int bytes_read; #ifdef _MSC_VER unsigned file_date, file_time; #else struct ftime file_time; #endif /* * Let's open the files, and return False if we can't. */ if (! fb_open( &in_handle, source, B_READ )) return( 0 ); /* return False */ if (! fb_open( &out_handle, dest, B_CWRITE )) { fb_close( in_handle ); return( 0 ); /* return False */ } /* * Here we get the time and date of the source file. */ # ifdef _MSC_VER _dos_getftime( in_handle, &file_date, &file_time ); # else getftime( in_handle, &file_time ); # endif /* * Now we allocate a 5K buffer and return False if * we cannot get it. */ buffer = malloc( 5120 ); if (! buffer) { fb_close( in_handle ); fb_close( out_handle ); return( 0 ); /* return False */ } do { bytes_read = fb_read( in_handle, buffer, 5120 ); fb_write( out_handle, buffer, bytes_read ); } while( bytes_read == 5120 ); fb_close( in_handle ); /* * Before we close the output file, we change its * date and time to that of the source file. */ # ifdef _MSC_VER _dos_setftime( out_handle, file_date, file_time ); # else setftime( out_handle, &file_time ); # endif fb_close( out_handle ); return( 1 ); /* return True */ } /*-- EOF: copyfile.c ----------------------------------*/ The only change that we need to make to the TEST.PRG module is to call setup_micro() for Microsoft C or setup_turboc() for Borland's C. If you are using the Borland C or Turbo C compiler, you'll need to #define the macro TURBOC before #including MIXED.HDR. * test.prg - Tests the copy_file() function * #include io.hdr #include system.hdr * #define TURBOC // for Turbo C users #include mixed.hdr FUNCTION LOGICAL copy_file PROTOTYPE PARAMETERS CONST CHAR source, CONST CHAR dest PROCEDURE force_main VARDEF CHAR(128) This_File ENDDEF #ifdef TURBOC setup_turboc() #else setup_micro() #endif This_File = get_exec( ) ? "Copying " + This_File + "to output.exe" IF .NOT. copy_file( This_File, "output.exe" ) ? "Cannot copy file!" ELSE ? "Done!" ENDIF ENDPRO *-- EOF: test.prg -------------------------------- Compile the files as you did before. Linking them will be a little more complex. Recall that when you link with a C library, you have to link in not only the object modules for the code that you have written, but you must also link in the Force library, the large memory model of the C library, and the start-up code for the C library. For this example, if you are using Microsoft C version 5.0, use this line: LINK test copyfile c:\force\lib\micro5, test, Nul, FORCE.LIB llibce.lib; If you are using Microsoft C version 6.0 change micro5 to micro6. For Turbo C version 2.0 through 4.0, use this line: TLINK /n test copyfile c:\force\lib\turboc, test, Nul, FORCE.LIB cl.lib ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Linking C Third Party Libs into FORCE ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ FORCE requires the C Calling Convention --------------------------------------- The single incompatibility for FORCE and third party C libraries is that FORCE is unable to use the PASCAL calling convention. A major element of the PASCAL convention involves the absence of any leading underscore on symbol names. Because the FORCE compiler follows the C calling convention and automatically prepends a leading underscore, code that it generates is incompatible with compilers that follow the PASCAL convention. Most prominent C third parties (such as the TurboPower libs) customarily ship with source and it is generally simple simply to remake the third party lib with the C convention specified, rather than PASCAL. Writing "Wrappers" for Third Party C Functions ---------------------------------------------- The C - oriented question heard most often at Tech Support is "what must I do to link in my familiar C third party libraries?". Generally, the answer is "not much", because FORCE and C approach matters very similarly in many areas. Most of the work involves the simple writing of a "wrapper" function to send parameters to and get return values from the C third party function in the proper order and form. As an example of linking in standard and well behaved C third parties, please review the following discussion of the method for writing a simple Force "wrapper" that allows the functions contained in the popular and versatile FUNCKy library to be called easily and simply from Force: Let's use the FUNCky "dtoi" procedure to convert a date string to integer values. Now, Force and FUNCky use the large model, far calls, and the so called "C calling convention". This last means, among other things, that the library routine names all begin with an underscore ("_"). Force prepends the underscore to your symbols so that "dtoi()" becomes "_dtoi() in the generated object file, thus matching the library names. In the FUNCky surface/core library hierarchy this means that the surface library routine names must have the leading underscore. The surface library handles the language interface requirements & then calls the appropriate core library routines. In order to distinguish the surface and core routine names, the core names have an additional leading underscore, ie- in the core library our "dtoi" procedure becomes "__dtoi". What does all this mean to us? The first "secret" is that we must add one underscore to the routine name and Force adds the other. Here's how to do it: *- PROGRAM DTOITEST.PRG *- inspection of the FUNCky procedure description reveals that we need these * parameters in this order. Note that we specify the length of the date char * string and note also the leading underscore applied to "dtoi" procedure _dtoi prototype parameters const char(8) stringDate, int iiYear, int iiYmonth, int iiDay *- OK, now let's test it.. procedure force_main vardef char(8) cDate int iYear,iMonth,iDay enddef cDate = "06/09/94" _dtoi(sDate, iYear, iMonth, iDay) ? iYear ? iMonth ? iDay endpro The second "secret" is the use of the qualifier "const" before the input string name in the prototype. This allows you to pass a literal string as well as a variable name, so you could have written _dtoi("06/09/94",iYear,iMonth,iDay). Now compile & link with the core library: FL dtoitest,,,f:\funcky\lib\funcky2c force You could always add the FUNCky lib directory to your SET LIB=... statement and link with "FL dtoitest" but we prefer to control the link libraries manually. This is most easily done via a makefile, of course. Another challenge in converting Funcky2 to Force seems to be dealing with the differences in parameter (data) type syntax. We just need to pass all values by reference and remember to use the "const" qualifier when we need to allow the passage of a literal value. (There is also the problem that the Clipper Julian date base is apparently different from that of Force - Force internal dates are the number of days since day 1, year 0; we haven't at this point researched the Clipper date base. We therefore feel it best to avoid the use of julian stuff unless it is entirely "internal" to our libraries.) The FUNCKy2C (core library) uses C style parameter passing and is therefore sometimes at odds with Force _function_ parameter passing. For example, the Funcky2 function _catstr is defined as: char * _catstr ( str1, str2, buffer ) this would correspond to the Force PROCEDURE (not function) procedure _catstring prototype params const char cVar1, const char cVar2, char cBuffer This is because, for FUNCTIONS, Force pushes a final "extra" parameter onto the stack after the "visible" function parameters. This extra pointer points to the return value destination. So, in order to turn the Funcky2 "function" into a Force function we must use the _catstr prototype for Force and write a wrapper: procedure _catstr prototype params const char cVar1, const char cVar2, char cBuffer function char catstring params const char cVar1, const char CVar2 vardef char buffer enddef _catstring(cVar1,cVar2,buffer) return buffer endfunc We would stick this module in our "surface" library for Force & then we could just call catstring() whenever we want: ? catstring("1234","ABCdef") or newString:= catstr("Holy"," Moly!") This is a trivial example, of course, because we don't need a function to concatenate strings but serves to illustrate the principle. Watch for things like the __itod() function - note the extra underscore. This little tidbit (the need for the extra underscore) is burried in the docs and is the sort of thing that can lead to much frustration and solvent abuse. The foregoing is a necessarily incomplete discussion of a fascinating area: the close compatibility of FORCE and C code which makes nearly all well-behaved C libraries immediately callable from FORCE with only a little effort!