Mark W. Schumann lives in Cleveland's trendy Brooklyn Centre neighborhood and works for STR, Inc., of Brecksville, Ohio, to help develop the In$torePlus and In$toreLink retail software lines. He's been programming in C and Clipper long enough to appreciate both. You can reach Mark on the Internet at mark@whizbang.wariat.org or by voice at 216-546-9510 x220.
Introduction
Clipper is a high-level applications programming language for MS-DOS. Originally a pure dBASE compiler, Clipper has since gained lexically scoped variables, multi-dimensional arrays, replaceable database drivers, and predefined object classes, to become a general application development tool. Importantly for C programmers, Clipper has a well-documented interface for C/C++ and assembly-language subroutines.This article shows how to build C and C++ routines that provide new capabilities or greater performance to Clipper applications using the documented Extend System interface. This article also gives examples and a walkthrough, and finally, explains how to use third-party C libraries which have not been designed for Clipper programs.
Why Clipper and C/C++?
Clipper's high-level features can accelerate DOS-based applications development. Clipper includes a DOS character-mode user interface, as well as a database interface with drop-in replaceable database drivers. The core language feature set includes built-in string and date types, and a run-time expression evaluator. Clipper also includes a preprocessor that is more complex and useful than C's.On the other hand, it's hard to beat well-written and optimized C or C++ code on a PC. C and C++ deliver excellent run-time speed, small code size, and direct access when needed to devices, interrupts, and memory.
By combining Clipper and C/C++, you can design and implement a high-level application quickly, without giving up the power and efficiency of C/C++ when you need it.
Compilation
Clipper normally calls C functions through its Extend API, which provides mechanisms for passing function arguments and return values. Since all Clipper releases in widespread use were developed with Microsoft C version 5.1, other compilers may present compatibility problems. If you own later versions of MSC, use the/Gh compiler switch to invoke alternate floating-point libraries. If you own a Borland compiler, you must avoid floating-point declarations and operations in your C code, since they invoke run-time library functions that are incompatible with Microsoft's. When acquiring third-party C or C++ libraries to use with Clipper, you should always request compatibility with Microsoft C 5.1, again because Clipper was also developed with MSC5.1.Most important, compile in the large model, since Clipper deals only with far code pointers and far data pointers. If you include any inline assembly code, be sure to save and restore registers BP, SP, SI, DI, and DS.
Declaring C Functions for Clipper
Clipper variables are not typed, and sometimes not even scoped, at compile-time, so the run-time engine (really a p-code interpreter) must maintain a symbol table and pseudo-stack for evaluations. Clipper's lack of compile-time typing prevents you from passing arguments to a C function directly, since C compilers for the PC must have argument type information to set up the machine stack and don't recognize run-time type information. Likewise, Clipper does not use standard C methods to return function values.Therefore, you should declare any C function to be linked with Clipper as void, and you should declare it with void arguments, as in the following:
void far foo (void);To access function arguments and to create return values, use the Extend API functions described later in this article. The macros in CLIPDEFS.H let you conveniently use CLIPPER foo (void) which preprocesses into the same.
Passing Parameter to C functions
You can transfer parameters and other information from the Clipper stack into C variables with a series of functions whose names begin with _par._parinfo (0) returns the number of arguments passed by Clipper; _parinfo (x) returns an int indicating the data type of the xth argument in the list; you can bitwise-OR its return value with macros such as CHARACTER and NUMERIC (in EXTEND.API) to extract type information. For the most bulletproof code, you should check an argument's data type with _parinfo before attempting to retrieve its value.
The other _par functions, shown in Table 1, return data values. Each _par function takes an argument indicating the position in the call's argument list. When passing an array, an optional second argument indicates which specific array element is required. Be careful: elements of a Clipper array do not have to be the same type.
Returning Values from C Functions
A set of functions whose names begin with _ret parallel the _par functions but return values to the Clipper application. These are shown in Table 2.To return a string, you would normally use _retclen because Clipper strings normally are not null-terminated and therefore could contain nulls.
Returning Values in Reference Parameters
Similarly, you can call Extend API to store values in reference parameters. In Clipper, these are arguments that are passed to a function with an @ (at-sign) prefix or with the obsolete DO <func> WITH <args> syntax.The _stor functions (Table 3) work exactly like their corresponding _ret cousins but take an additional first argument indicating the position in the Clipper function's argument list to be affected. Note that a _stor function call has no effect if it targets a Clipper parameter that was passed by value.
Calling "Unmangled" C++ Functions
So far I have described how to write vanilla C code to interface with Clipper's Extend API. What to do if you want to link in C++ code? The answer is familiar to anyone who has written C-to-C++ interfaces before: prepend the extern declaration modifier to all C++ function declarations to be called directly by C or Clipper code, and prepend extern "C" on the Extend API declarations if they are called from C++.Prepending extern "C" is the standard way to make all C++ compilers turn off "name mangling," the feature which allows function name overloading. Of course, the cost of this approach is that you can't overload function names (or class members) in code that touches C or Clipper code. In practice, this inability to overload is not much of a loss because C and Clipper don't directly support overloading, and in any case, your C++ functions probably will not be elaborate enough to need a complicated interface.
An Example
CLIP.C (Listing 1) contains a set of useful low-level functions not already found in the Clipper run-time library.sum takes any number of numeric arguments even zero and returns their sum as integers. In line 43 I put the number of Clipper function arguments into pcount. Lines 47 through 49 step through the list of arguments, retrieving each argument with _parni, and accumulating their values in sum. Line 52 calls _retni to hand the result as a Clipper numeric value to the caller.
Note that the results of this function are undefined if the passed parameters are not actually numeric. For example, if the Clipper programmer tries to execute SUM ("ABC", "DEF") the results are guaranteed to be meaningless (although in my testing a zero was returned consistently). Also note that Clipper provides no compile-time or link-time type checking, so a call such as this one could go unnoticed until it returned bogus results to the running program.
The correct way to implement this function, and in fact all the functions in Listing 1, is to call the _parinfo function to determine each argument's Clipper type before evaluating it. This type checking is especially important for production-quality code to be used by others. I left out such type checking to avoid obscuring the main logic of these functions.
reverse simply reverses a character string. This exercise is not as trivial as it sounds, since Clipper strings, even those of a single byte, are always dynamically allocated; string arithmetic is therefore relatively expensive. If your Clipper application calls for intensive string manipulation you might want to use C functions like reverse to take the load off of Clipper's memory management subsystem.
reverse first stores the length of the string in len; you can't use C's strlen because you cannot assume that Clipper strings are null-terminated. The functions _xgrab and _xfree from the Fixed Memory (FM.API) work like the Standard C library's malloc and free, to allocate and discard a buffer pointed to by newstring. Next, a straightforward loop copies bytes from the old string to the new. I don't have to append a null terminator since the _retclen call on line 82 takes an explicit length argument.
peek and poke, like the BASIC commands of the same name, enable the Clipper programmer to read and write bytes at specific memory locations. peek simply obtains two integer numeric arguments as a segment and offset, coerces them into a far pointer to an unsigned char, then returns the dereferenced value as a Clipper numeric. poke takes a third numeric argument, which is the value to be stored in the given segment/offset address.
lrc is a very fast routine to calculate a simple checksum on a Clipper character variable. lrc is useful for communications or data compression applications that require parity checks. Like reverse, lrc takes a Clipper string argument and stores both its location and its length with parc and parclen. A while loop XORs each byte in the string into the variable. Both *s and sum are declared as unsigned char to prevent any complications resulting from the presence of a sign bit.
Note that lrc returns the checksum value with _retni. Since Clipper does not have an explicit "unsigned" data type, I count on the fact that the range of values an int can represent exceeds the range of an unsigned char. I assume that the code will never be run on a platform for which sizeof (int) == sizeof (unsigned char). This is a safe bet since Clipper applications only run in an Intel 80x86 environment. However, the cast can conceivably produce portability problems in extreme cases.
Using Third-party C/C++ Libraries
In some cases you may want to link C++ class libraries into a Clipper application. Since Clipper can't call mangled functions by name, you must prevent mangling by making all direct calls through wrapper functions with the extern "C" declaration modifier. If you have the library's source code, of course, you may be able to edit a class header to achieve the same result.You should call only small utility functions from libraries which were developed specifically for C applications. Anything that attempts direct screen writes, standard console I/O, database manipulation, or memory management is likely to conflict with Clipper's standard library code. This limitation is not as severe as it seems. Third party developers have already written an enormous array of utility and special-purpose libraries specifically for Clipper programmers. These utilities and libraries generally use the Extend interface for maximum compatibility with future Clipper releases.
Summary
By combining C and C++ with Clipper, you can design and implement a high-level application quickly and still have low-level control when you need it. I've shown how to call C code from Clipper using well-documented interfaces, and how to adapt existing libraries.I have provided just an overview of what you can do by combining the power and efficiency of C/C++ with a high-level applications development language.
Listing 2: Test Program for CLIP.C
Listing 3: MAKEFILE for CLIP.C and test program
Sidebar: "The Current State of Clipper"