                        Using C with Clipper:
                        Avoiding the Pitfalls
                          by Scott McIntosh

This document is an attempt to help the C programmer write Clipper 
callable functions in C.  Hopefully some of the information contained 
here will allow you to steer clear of any compatibility problems.

I have also attempted to explain how to use different C compilers 
with Clipper.  As you will see, you can use compilers other then 
Microsoft C with varying degrees of success.

Memory Allocation concerns

Memory allocation is probably the number one pitfall when writing 
Clipper 5.0 user defined functions in C.  This is due to the fact 
that with the introduction of Clipper 5.0, run-time memory allocation 
is accomplished via the Clipper VMM (Virtual Memory Manager.)1    In 
past versions of Clipper, when allocating memory from within C, the 
use of the Clipper Extend System memory allocation functions was 
recommended.  With the inclusion of VMM in Clipper 5.x memory 
allocation via the Clipper extend system it is an absolute must.  It 
is equally important to make absolutely sure that you free the memory 
you have allocated.

	Allocating memory with the Extend System

        The Extend System functions for memory allocation are _xalloc 
        and _xgrab.  To allocate memory from C make a call to either 
        _xalloc and _xgrab which returns a pointer if  successful.  
        The difference between the two is the way they handle an 
        error:  _xalloc  returns a  Null pointer to the C routine and 
        _xgrab triggers a Clipper error.  Avoid C Library routines 
        that allocate memory.  Many C Library functions automatically 
        allocate  memory using the C library's memory allocation 
        routines.  This automatic memory allocation must  be avoided.  
        Care must be taken when using library routines from the 
        following categories.2

	Memory Allocation routines

        Based on the statement that all memory must be allocated by 
        the Clipper extend system,  is should be no surprise that C 
        memory allocation routines can not be used.   This  includes 
        alloca, calloc, malloc, along with their companion memory 
        freeing functions.

	String manipulation routines

        Most string manipulation functions are passed both the source 
        and target strings.   Because of this, string manipulation 
        functions are generally quite easy to adapt to clipper use.   
        Simply allocate the source and target buffers using an extend 
        function, and pass them to the  string function as normal.

	Stream (buffered) I/O

        By default, Stream I/O routines (i.e.: fopen(), fclose(), 
        fread(), fwrite(), ... ) automatically allocate  memory for a  
        stream buffer.  They should be avoided, and the Low Level 
        File I/O routines (i.e.:  open(), close(),  read(), write(), 
        ... ) should be used instead.  Also, printf() (and scanf() ) 
        are  stream I/O routines and must be avoided.  Fortunately, 
        cprintf() and sprintf() (along with cscanf() and sscanf()) 
        will work.  The following shows how to substitute these 
        functions for printf():

        Instead of: printf( "Hello, world.\n"  )
               use: cprintf( "Hello, world.\r\n" ) 
                    // uses raw mode - \n is CR only
                or: sprintf( stdout, "Hello, world.\n" )       
                    // stdout is defined in stdio.h


Math Library (Floating Point) Routine compatibility

The Summer '87 and Clipper 5 versions of Clipper both use the MSC 
        Alternate Math routines for Floating Point Routines.  These 
routines were chosen because they were faster than the Emulator 
routines in the MSC 5.x products.   This is the reason you must 
compile all C modules with the /FPa (Floating Point alternate) 
switch.  If your code calls any run-time library routines you must 
link in the MSC library LLIBCA.  This library supports the large 
memory model and includes the alternate floating point support 
routines.

Because of the differences in the design and implementation of the alternate floating point routines between vendors, only Microsoft C supports the use C floating point routines with Clipper.  This causes incompatibilities to arise between the floating point routines intrinsic in Clipper and those that would be brought in by any non-Microsoft library.

	Library routines that access the Math Library

     The library routines that automatically call floating point 
     routines are as follows.

		Math routines

          Obviously the math library routines use the floating point 
          math support routines. Also,   any use of the float or 
          double variable types will cause the inclusion of the 
          floating point routines.  Less obvious are the functions 
          that can accept these variable types as   parameters (such 
          as printf which could be passed a float or a double.) They 
          can also   cause the math routines to be included.

		Graphics Library routines

          Most of the graphics drawing routines use floating point 
          routines to determine the   position of the pixels along a 
          line or arc.  The basic routines for getting or setting the   
          video mode, changing video pages, or changing cursor 
          positions do not use floating   point.

Library Routines in General

The best way to ensure your C code will be compatible with Clipper is 
to avoid the use of the run-time library routines all together.  Many 
times the use of a library routine could be avoided all together, if 
it was thought to be important.  Many of these routines (low level 
file I/O for example) can be easily implemented using DOS or BIOS 
interrupt calls.  The best situation is one where you do not have to 
link in the C run-time library at all.    While this is not always 
possible, it is worth being aware of.

If you are linking with a C library, be sure to list the Clipper 
libraries on the link line first.  This is important because the 
automatic search request that RTLink performs to find the Clipper  
libraries is performed AFTER libraries on the link line are searched.  
This means you may accidently replace some of Clipper's low level 
functions with versions from the C library - which may not be 
compatible.  The linker command line RTLINK FI Foo LIB LLIBCA the 
linker will search LLIBCA.LIB  before CLIPPER.LIB,  but the line 
RTLINK FI Foo LIB CLIPPER, EXTEND, TERMINAL, DBFNTX, LLIBCA  will 
not.

Compatibility With Other C Compilers

As noted above MSC 5.x with the Large Memory Model, Alternate Math 
Library is the most compatible C compiler for use with Clipper Summer 
'87 and 5.0x versions.  This does not mean that other vendor's C 
compilers can not be used with Clipper.  But there are additional 
limitations that must be observed when using these products.  When I 
started this article I wanted to demonstrate that non-MSC products 
could be used with Clipper very effectively.  Prior to working at 
Nantucket, I had always used Turbo C, and was able to accomplish most 
of what I wanted to do (using Summer '87 at that time.)  By the time 
I was done getting all the different products to compile and link - 
resolving all the little problems - I began to really appreciate the 
MSC  compiler.  If asked now, I think it is the only compiler I would 
recommend for use with Clipper.

Table 1 shows the compatibility of some of the frequently asked about 
C compilers, along with the compiler command line switches that 
correspond to those recommended in the extend system documentation.  
The two products below, however, defy this table's simple 
categorization - so they are explained here in detail.

	C++ Extensions

     The way in which C++ implements method and operation overloading 
     is by using a mechanism  referred to as "symbol table mangling".  
     This means that if you have three object classes that all  have 
     a display() method,  C++ changes the symbol for each version of 
     "display",  into something  that tells it which of the three 
     "display" methods it is referring to.

     It is possible to produce compatible C++ functions by defining 
     the function as:

     extern "C" CLIPPER func ( void )

     This is a C++ declaration that tells the compiler not to 
     "mangle" the name.  But since this turns  the function name into 
     a normal "C" symbol,  you can not have function overloading, 
     which limits  their usefulness as C++ functions.

	Microsoft C 6.0

     Microsoft, with the introduction of MSC 6.0, has renamed some of 
     the math and stack checking  functions calls so that the code 
     produced by the compiler is not backwardly compatible with MSC  
     5.x library code.   The solution is to compile with the /Gh 
     switch - which produces 5.x compatible  library calls - and then 
     link with the 5.x LLIBCA library (assuming you have a copy of 
     the MSC 5.x  LLIBCA).   If you do not have access to a 5.x 
     version of LLIBCA, you can contact Microsoft  Technical Support 
     and ask for the "downgraded" (5.x compatible) library.

     Also new in MSC 6.0 is the inclusion of a new compiler 
     optimization that uses "intrinsic" routines.   These are 
     routines that the compiler resolves into in-line assembly code 
     instead of producing a  call to the library function.  Examples 
     of intrinsic functions are strcpy and strcmp.  A list of  
     functions that can be resolved to intrinsic forms is contained 
     in Figure 1.

     There are two ways to cause a function to be resolved to an 
     intrinsic:  by using the compiler  directive #pragma intrinsic( 
     <function>, ... ) or by compiling with the /Oi (optimize with 
     intrinsic  forms) switch .  If your C function uses only 
     intrinsic functions you do not have to link in the C  library - 
     and would not suffer from the library incompatibility described 
     above.

C Language Debugger Support with Clipper

	Clipper Debugger

     Neither C source code nor C variables are visible from within 
     the Clipper debugger.  This is  because the C compiler does not 
     include Clipper debugging information.

	CodeView and Turbo debugger

     Both CodeView and Turbo debugger can be used to debug user 
     compiled C functions within a  Clipper application.  Clipper 
     source code and Clipper run-time library code will be displayed 
     as  machine code without symbols.  This is because the Clipper 
     compiler does not produce  CodeView (or Turbo Debugger) 
     information.  The C Code, however,  will display as C source  
     code with all the proper symbols information included.  This 
     means you can set a break point on  your C function name and run 
     to that break point, and you can inspect all the C function's  
     variables by name.

     The C  code must be compiled with the debugger information 
     switch, and the application must be  linked with the proper 
     linker option for that product:

     MSC:                                    Turbo:
	Compile with the /Zi  option			Compile with the -v option
	and replace /Oalt with /Od			and remove -G -O -Z
     Link with LINK adding the /CO option    Link with TLINK adding 
                                             the /v option

Linking Issues

A number of problems can arise when linking C libraries with Clipper.  
It is easy to link the C library ahead of the Clipper libraries, 
effectively replacing the Clipper functions with their C 
counterparts.  The most common mistake with RTLink is believing that 
the automatic search request generated by Clipper will cause Clipper 
to be linked in before any libraries included on the link line.   
This is not the case.  The following link line, for example, will 
cause LLIBCA to be linked in ahead of Clipper's libraries.

RTLINK FI Foo, Cstuff LIB LLIBCA

This will produce duplicate symbol warnings that should not be 
ignored.  The reason for this is that the libraries included in the 
link line are searched before the Clipper generated automatic search 
request.  This can be fixed be either of the following methods.  
First, you can link using a PLL such as BASE50.PLL because the PLL is 
always resolved first - before libraries and even OBJs.  Secondly, 
you can simply include the Clipper libraries on the link line ahead 
of any C libraries, like the following example.

RTLINK FI Foo, Cstuff LIB CLIPPER, LLIBCA /NOE

This link line correctly links the Clipper run-time support routines 
instead of the C library versions.  The /NOE switch prevents an 
extended search of the C library.  Without this you will get a 
duplicate symbol warning on the symbol __cflcvt_tab when Clipper and 
LLIBCA both try to bring in this same function.

Calling Clipper functions from C

One of the more common calls we receive in Technical Support is the 
question about calling a Clipper UDF from C.   It is commonly thought 
that if C can be called from Clipper, then Clipper  functions and C 
functions must be operate identically.  This is not the case.    
Anyone familiar with the way C calls a function knows that C pushes 
strongly-typed arguments on the processor stack and then does a jump 
to the function that is being called.  Clipper does not use the 
processor stack for the passing of arguments to functions.  Clipper 
functions operate within an "activation context" that must be 
established before the Clipper function is called.  This allows 
Clipper to support loosely-typed arguments, variable number of 
arguments, as well as functions such as PROCNAME().   This means that 
even if you succeeded in calling a Clipper function, you would be 
unable to pass it parameters, and it would be operating "out of 
context."

The extend system also assumes that it has complete control over 
virtual memory while you are executing a C or assembler function.  If 
you call a Clipper function, this assumption is no longer valid and 
may cause the system to crash.  Certain Clipper operations are also 
inherently problematic:  what happens if the Clipper function BREAKs 
(or calls the error handler and it BREAKs)?  There is no equivalent C 
ability.

Important Design Considerations

Now that we have the technical pitfalls out the way, I would like to 
cover a couple design issues that I think will help in writing C UDFs 
for use with Clipper.

The goal is to isolate the Clipper Extend System interface and Memory 
allocation routines in the C function.  This allows you to design the 
C function so that it can be compiled as a Clipper UDF or as a stand 
alone C program.  This will greatly improve the flexibility of the 
function and aids the debugging process as well.  We will use a  
#define to create a manifest constant for use as a flag that will be 
tested with #ifdef and #ifundef compiler directives, to optionally 
compile different parts of the code.

For example:

#define IS_CLIPPER

#ifdef IS_CLIPPER

   <Some Clipper related code>

#else

   <C code that emulates the Clipper code above>

#endif

Isolate the C function from the Clipper Extend System

The first step is to create a pure C function that does not have any 
Clipper Extend system code in it.   This function should be created 
and prototyped as a normal C function.  Next we create a small 
function that contains all the Extend System interface code.  This is 
the function we actually call from Clipper.  It is this function that 
contains all of the Extend System code to retrieve parameters from 
Clipper, and to return a value back to Clipper.   Since the pure C 
function actually performs the work, we gain the ability to call it 
from other C functions as well as from Clipper.

This means we can optionally compile a Clipper Extend System 
interface function or a C Main function, and either one will work 
properly.  The ability to compile the function as a stand alone 
program is a great help when testing and debugging.  The goal should 
be to create a fully functional, stand alone program, that works 
correctly before trying to interface it to Clipper.

Make the memory allocation generic

But what about memory allocation?  Remember, we MUST allocate memory 
from Clipper's Extend System function if we are being used as a 
Clipper UDF.  But we can't call _xalloc() if we are compiled as a 
stand alone program.  The way I handle this is to #define memory 
management pseudo-functions that will conditionally preprocess to 
either the Extend System functions, or to the C library function, 
depending on whether IS_CLIPPER has been defined.

Example 4 is a template example of using this technique.

Figure 1 - MSC 6.0 Intrinsic Function List

The following functions can be compiled into the intrinsic form:

abs
_disable
_enable
fabs
inp
inpw
_lrotl

_lrotr
labs
memcmp
memcpy
memset
outp
outpw

_rotl
_rotr
strcat
strcmp
strcpy
strlen
strset


Table 1 - C Compiler Compatibility Table

 ͻ
 C Compiler         Compatible     Suggested Compiler  Large Memory   
                    Floating Point Line Switches       Model Library  
 ͹
 Microsoft C 5.x    Yes            /c /AL /FPa /Gs     LLIBCA.LIB     
                                   /Oalt /Zl                          
                                                                      
 Microsoft C 6.0    Yes(1)         /c /AL /FPa /Gs     LLIBCA.LIB(3)  
                                   /Oalt /Zl2                         
                                                                      
 Microsoft QuickC   Yes            /c /AL /FPa /Gs     LLIBCA         
 version 1.x                       /Zl /Ox                            
                                                                      
 Microsoft QuickC   No             /c /AL /Gs /Zl /Ox  LLIBCE         
 version 2.x                                                          
                                                                      
 Borland C and      No             -c -ml -f- -N- -G   CL.LIB         
 Turbo C                           -O -Z                              
 ͼ

Example 1

This program demonstrates how to receive parameters passed from a 
Clipper application, from within C, using the Clipper Extend system.


/***
*   Test.prg
*
*   By: Scott McIntosh, Nantucket Corp.
*
*   Program to test the TestC.c program.
*
*   Compile: CLIPPER test /n /a /w
*
*      Link: RTLINK FI test, testc LIB clipper, llibca /NOE
*/

PROCEDURE Main()

   // define a set of misc. data to check the C function with.
   // The array in aVar1 contains a nested array. The nested array
   // can not be accessed through the extend system!
   //
   LOCAL aVar1 := { "Hello, world", 12, DATE(), .F., { "One", "Two" } }
   LOCAL nVar1 := 1234
   LOCAL cVar1 := "This string is bigger than 30 characters!"
   LOCAL dVar1 := DATE()
   LOCAL lVar1 := .T.
   LOCAL xVar

   // and create a new set of data to pass by reference
   //
   LOCAL aVar2 := { "First Element", "Second Element", "Third Element" }
   LOCAL nVar2 := -1234
   LOCAL cVar2 := "A shorter string"
   LOCAL dVar2 := CTOD( "9/15/91" )
   LOCAL lVar2 := .F.

   CLS

   ExtendTest( aVar1,  nVar1,  cVar1,  dVar1,  lVar1, xVar,   ;
               @aVar2, @nVar2, @cVar2, @dVar2, @lVar2, )

   SETPOS( MAXROW(), 0 )

QUIT


/***
*   TestC.c
*
*   By: Scott McIntosh, Nantucket Corp.
*
*   Demonstrates how to use the Clipper extend system to receive parameters
*   from Clipper.  This program simply displays the types and values of the
*   parameters that are passed to it.
*
*   Display is written to stdout and is DOS re-directable.
*
*   Compile: cl /AL /c /FPa /Gs /Oalt /Zl testc.c
*/

#include "extend.h"

CLIPPER ExtendTest( void )
{
   int parNum;                     // current parameter number
   int arrIndex;                   // current array index number

     // process each parameter in turn
   for( parNum = 1; parNum <= PCOUNT; parNum++ )
   {
        // make sure the array index counter is zeroed
      arrIndex = 0;

        // display the current parameter number
      cprintf(  "\r\nParameter %2i    ", parNum );

        // show the type of the current parameter
      ShowType( _parinfo( parNum ), parNum, arrIndex );

      if( ISARRAY( parNum ) )
      {

           // parameter is an array - process each element in turn
         for( arrIndex = 1; arrIndex <= ALENGTH( parNum ); arrIndex++ )
         {
              // indent and display the current array element number
            cprintf(  "       Element %2i    ", arrIndex );

              // display the current array element type
            ShowType( _parinfa( parNum, arrIndex ), parNum, arrIndex );
         }
      }
   }

   _ret();

}




/***
*   ShowType( int parameterType, int parameterNumber, int arrayElement )
*
*   Processes parameters and the individual elements of array parameters.
*
*   Displays the numeric value of the data type of the parameter/element,
*   a text description of the type (i.e.: "Character" ), and the value of
*   the parameter/element.
*
*   The text description will be preceded with a "@" to indicate a
*   parameters that was passed by reference.
*
*   The following substitutions are made for the actual value of the
*   parameter/element:
*       Arrays     display  "{...}"
*       Logicals   display  0 or 1  ( 0 = .F., 1 = .T. )
*       Characters display  First 30 characters of the string
*       Undefines  display  "???"
*/

void ShowType( int type, int param, int index )
{

     // show the correct text description and value for the current item
   switch ( type )
   {
      case UNDEF:
         cprintf( "Undefined    Value: ???\r\n" );
         break;

      case CHARACTER:
         cprintf( "Character    Value: %.30s\r\n", _parc( param, index ) );
         break;

      case NUMERIC:
         cprintf( "Numeric      Value: %i\r\n", _parnl( param, index ) );
         break;

      case LOGICAL:
         cprintf( "Logical      Value: %i\r\n", _parl( param, index ) );
         break;

      case DATE:
         cprintf( "Date         Value: %s\r\n", _pards( param, index ) );
         break;

      case ARRAY:
         cprintf( "Array        Value: {...}\r\n" );
         break;

      case (MPTR + UNDEF):
         cprintf( "@Undefined   Value: ???\r\n" );
         break;

      case (MPTR + CHARACTER):
         cprintf( "@Character   Value: %.30s\r\n", _parc( param, index ) );
         break;

      case (MPTR + NUMERIC):
         cprintf( "@Numeric     Value: %i\r\n", _parni( param, index ) );
         break;

      case (MPTR + LOGICAL):
         cprintf( "@Logical     Value: %i\r\n", _parl( param, index ) );
         break;

      case (MPTR + DATE):
         cprintf( "@Date        Value: %s\r\n", _pards( param, index ) );
         break;

      case (MPTR + ARRAY):
         cprintf( "@Array       Value: {...}\r\n" );
         break;
   }

   return;

}


/* EOF: Example 1 */


Example 2

This program demonstrates how to determine the C numeric data type 
for Clipper numeric parameters for use within C.  This version does 
all the data comparisons in the file for illustrative purposes.  
Example 3 uses extend.h style pseudo-functions defined in Number.h to 
hide the details of these comparisons.  This allows a much cleaner 
source file.


/***
*   Testnum.prg
*
*   By: Scott McIntosh, Nantucket Corp.
*
*   Clipper program to test the C function Number()
*/

#include "set.ch"


PROCEDURE Main

   LOCAL nVar1 := 123.456        // a float
   LOCAL nVar2 := 123456         // a long
   LOCAL nVar3 := 12345          // an int

   SET( _SET_DECIMALS, 5 )
   SCROLL()

   // the C function Number() displays the type, we then re-display
   // the value returned from Number() value to confirm that Number()
   // converted it to a C variable and back correctly.
   //
   ? nVar1, Number( nVar1 )      // should be a float, value = 123.456
   ? nVar2, Number( nVar2 )      // should be a long,  value = 123456
   ? nVar3, Number( nVar3 )      // should be an int,  value = 12345

QUIT


/***
*   Number.c
*
*   By: Scott McIntosh, Nantucket Corp.
*
*   Demonstrates how to determine the type of a numeric parameter
*   received from Clipper.  Normally, once we stored the number
*   into the proper data type we would do something useful with
*   it.
*
*   This program just displays the parameters numeric type, and
*   then returns the value back to Clipper to show that we haven't
*   truncated or corrupted its value.
*
*   Compile: cl /AL /c /FPa /Gs /Oalt /Zl number.c
*      Link: RTLINK FI numtest, number LIB clipper, llibca /NOE
*/

#include "extend.h"
#include <limits.h>

CLIPPER Number( void )
{
   double dNum;
   long   lNum;
   int    iNum;

   dNum = _parnd(1);
   lNum = _parnl(1);

    // by subtracting the long value of the parameter from the double
    // value of the same parameter, we can determine if it is a floating
    // point number (i.e. if we have any decimal places)
    //
   if( ( dNum - (double) lNum ) != 0 )
   {
      cprintf( "\r\n\n  Double " );
      _retnd( dNum );
   }
   else
   {
      if( (lNum >= INT_MIN) && (lNum <= INT_MAX) )
      {
         // we're small enough to be an int, so we could safely
         // use iNum and be sure its value is correct
         iNum = _parni( 1 );

         cprintf( "\r\n\n  Integer " );
         _retni( iNum );
      }
      else
      {
         // we're too big to be an int, so we stay as a long
         cprintf( "\r\n\n  Long " );
         _retnl( lNum );
      }
   }
   return;
}


/* EOF: Example 2 */

Example 3

This program is the same as Example 2 except is uses pseudo-functions 
defined in Number.h to simplify the C source code.


/***
*   Number.c
*
*   By: Scott McIntosh, Nantucket Corp.
*
*   This function is identical to Number.c in Example2 except that it
*   uses the pseudo-functions ISFLOAT(), ISLONG(), and ISINT() which are
*   defined in Number.h
*
*   Compile: cl /AL /c /FPa /Gs /Oalt /Zl number.c
*      Link: RTLINK FI numtest, number LIB clipper, llibca /NOE
*/

#include "extend.h"
#include "number.h"

CLIPPER NUMBER( void )
{
   double dNum;
   long   lNum;
   int    iNum;

   dNum = _parnd(1);
   lNum = _parnl(1);
   iNum = _parni(1);

   if( ISFLOAT(1) )
   {
      cprintf( "\r\n\n  Double " );
      _retnd( dNum );
   }

   if( ISINT(1) )
   {
      cprintf( "\r\n\n  Integer " );
      _retni( iNum );
   }

   if( ISLONG(1) )
   {
      cprintf( "\r\n\n  Long " );
      _retnl( lNum );
   }
   return;
}


/* EOF: Example 3 */

/***
*   Declares extend.h style pseudo-functions (like ISCHAR() or ISNUM() )
*   to check the C data type for a number.
*/

#include <limits.h>


/* macro to determine if a number is within the range of an integer */
#define INTRANGE(n) ( ( n >= INT_MIN ) && ( n <= INT_MAX ) )

/* macros to test for each C numeric data type */
#define ISFLOAT(n)  ( (_parnd(n) - (double) _parnl(n) ) != 0.0 )
#define ISINT(n)    ( !ISFLOAT(n) && INTRANGE( _parnl(n) ) )
#define ISLONG(n)   ( !ISFLOAT(n) && !ISINT(n) )


/* EOF: Number.h */

Example 4

/***
*   Template.c
*
*   By: Scott McIntosh, Nantucket Corp.
*
*   This function contains an example of how to write C code that can be
*   interface to C or to Clipper.
*
*   Note: this is only an example with pseudo-code.  It will not actually
*   compile.
*/

#include <whatever.h>

#define IS_CLIPPER  // comment out this directive for stand-alone EXE

#ifdef IS_CLIPPER

#include "extend.h"
#define GRAB_MEM(bytes) _xalloc(bytes)
#define FREE_MEM(ptr)   _xfree(ptr)

#else

#include <malloc.h>
#define GRAB_MEM(bytes) malloc(bytes)
#define FREE_MEM(ptr)   free(ptr)

#endif

char * SomeFunc( int parm1, double parm2 ) // note: standard C function

   char * ret;

    // generic memory allocation usage example
   if ((ret = GRAB_MEM(100)) == NULL)
      ; // sorry - no memory

   else
      ; // presumably we would do something with all the parameters and
        // store something in the return variable

   return (ret);

}


#ifdef IS_CLIPPER

CLIPPER ClipFunc(void)
{
   int arg1;
   double arg2;

   arg1 = _parni(1);
   arg2 = _parnd(2);

   // normally we would do some parameter checking here

   _retc( SomeFunc( arg1, arg2 ) );

}

#else


#include <stdio.h>
#include <stdlib.h>

void main( int argc, char * argv[] )
{

   int arg1;
   double arg2;

   arg1 = atoi( argv[1] );   // get the command line parameters
   arg2 = atof( argv[2] );

   printf( "The result is %s", SomeFunc( arg1, arg2 ) );


}


/* EOF: Example 4 */


Notes:

1    A detailed discussion of Clipper's VMM can be found in Support 
     Bulletin No. 3 as well as Release Note #6 in the Change Summary 
     of the Norton Guides

2    This information is availiable in the C Runtime Library 
     Documentation.  The information here was obtained from the MSC 
     version 5.1 Run-time Library Reference manual, Chapter 4 
     "Run-time Routines by Catagory".

1    With MSC 6.0  "downgraded" LLIBCA library or MSC 5.x  LLIBCA 
     used in conjunction with the /Gh compiler switch.

2    /Gh is used in conjuction with the MSC 5.x LLIBCA library.

3    Use either the MSC 5.x compatible "downgraded" LLIBCA  or the 
     MSC 5.x LLIBCA  (if compiled with the  /Gh compiler switch).  
     Using Do NOT attempt to use the LLIBCA that was shipped with 
     MSC 6.0.

