The WRAPI Toolkit

A multilanguage toolkit for C libraries

Gregory C. Sarafin

Greg is a consultant and multilanguage developer. He is also the author of the In-Press tool marketed by Dabiwa. He can be contacted on CompuServe at 73747,3112.


Like many software developers, I program in multiple languages. In addition to C/C++, I regularly use Visual Basic, Clipper, FoxPro, and Paradox. After many years of development, my office shelves began to sag under the accumulated weight of third-party support libraries. At last count, I had four communications libraries (one each for Clipper, FoxPro, C, and Visual Basic), multiple printing tools, and a variety of other libraries. After reflecting on the time it takes to learn and remember four or five ways of accomplishing the same thing, I decided to create a language-independent API that generates language-specific libraries. I call it WRAPI, short for "wrapped API."

By inserting a translation layer between the host-language API and the internal functions of a library, WRAPI creates a higher-level access to what has typically been the domain of C and ASM programmers. A C library written with the WRAPI toolkit will find a much wider audience than a C-only library. The number of non-C developers, particularly in the high-level languages, is growing dramatically, and WRAPI provides a way to leverage costly development efforts into this large market.

The host languages WRAPI supports include C, Clipper, FoxPro (DOS and Windows), Visual Basic (DOS and Windows), Pascal, Clarion, Fortran, and many Windows tools via a generic DLL. Certain types of libraries lend themselves to WRAPI. Anything that is currently called an API (such as Microsoft's Mail API or the ODBC API) is a candidate for WRAPI. WRAPI can also be used with universal functions such as mathematics, communications, printing, and multimedia.

WRAPI is proven technology. It's currently the core technology in Dabiwa's In-Press tool, which provides a set of functions for developers who need to produce published-quality output from within host applications. Because In-Press is built from WRAPI, it works with all of the major development languages on the PC platform. With this article, I'm putting WRAPI technology into the public domain, making the source code available electronically through DDJ; see "Availability," page 3.

WRAPI Defined

You usually don't talk in terms of an API when writing a C function library. Such functions are compiled into a LIB and subsequently integrated into an executable whole by a linker. As long as symbol-naming and parameter-passing conventions are uniform among the various components, the executable will indeed execute.

Instead, the term API is generally associated with the high-level languages. FoxPro provides API functions for everything from memory allocation to low-level I/O. Clipper provides API functions for parameter passing. Visual Basic for DOS requires that strings be dereferenced with StringAdress(), arguably an API function.

The term API in the context of WRAPI stands for the interface layer of the host language. In C, that layer is simply the call stack and the C standard library. In reality, there is no API. For the purposes of WRAPI, the term "API" is a fiction of convenience. Throughout the rest of this article, I'll refer to the host API when I wish to reference the interface layer of the target development language.

I use the term "wrap" because WRAPI resolves differences by "wrapping" them in a macro or a data structure. WRAPI provides a mechanism for hiding the various methods of parameter passing that exist among the supported host APIs. It also provides uniform mechanisms for error handling, string manipulation, memory allocation, low-level I/O, and printing. It accomplishes this through a combination of preprocessing tricks and a set of support functions which I'll call the "WRAPI library."

Incidentally, the first wrapping functions I wrote were patterned after the original work of Dabiwa's David Karasek, who used compiler directives (#IFDEF, #ELIF, and so on) and boilerplate code to write functions that conditionally compiled to C, Clipper, FoxPro, and Visual Basic. Dave's work provided a good starting place to further the concepts of multilanguage support. In working with his functions, I realized that most of the compiler directives were required to resolve differences in parameter passing, so that is where I began my attack.

Wrapping Functions

The first task in developing WRAPI was to create the specification for "wrapping" functions--a translation layer that sits between the host API and the underlying "core" function; see Figure 1.

A wrapping function grabs parameters from the host API, type-checks them (for weakly typed languages), calls the core function, traps any error condition, and returns a value to the host API. All serious work is handled by the core function, including the task of domain checking parameters. In the initial iteration of WRAPI, I made the mistake of putting all parameter checking in the wrapping function. It seemed logical at the time, but I subsequently learned that wrapper functions work best when they're free of semantic content.

Traditionally, parameters and return values are placed on the call stack. FoxPro and Clipper don't pass parameters in this manner. Xbase is a weakly typed language, and the mechanisms for passing parameters are structured accordingly. Clipper provides a set of API functions to grab parameters off of the Clipper "Eval" Stack (an internally maintained Clipper stack). FoxPro passes a pointer to a structure which itself points to an array of structures, each of which contains a parameter reference. Both Clipper and FoxPro include a set of functions for returning values through the host API. Listing One (page 89) illustrates the differences among C, Clipper, and FoxPro parameter passing.

Encapsulating the Xbase parameter mechanisms was straightforward. The WRAPI library includes a set of functions for grabbing and type checking Clipper and FoxPro parameters. The trick was reconciling the call-stack method of parameter passing with the explicit parameter grabbing of the Xbase languages. After some fiddling, I came up with the function template in Example 1, which works uniformly well for both.

All uppercase keywords are WRAPI macro definitions. Many, such as TYPE_?, are keyed to a basic data type. WRAPI supports six such data types: C, char; S, string; B, Boolean; I, short int; L, long int; and D, double float.

Thus the macro TYPE_I indicates that the wrapping function returns a short integer, while the macro GRAB_S() grabs a string-type parameter from the API. Listing Two (page 89) is a simple wrapping function written with WRAPI macros. The function has been stripped of the error-handling mechanisms so that you can better understand the basics of parameter wrapping. The corresponding translations to the C, Clipper, and FoxPro APIs are illustrated in Listing Three (page 89).

The form of the wrapping function may seem a little bizarre at first, but it is a bit of preprocessing trickery that greatly increases code readability.

String Handling

After reconciling the different parameter-passing conventions, the next step was to deal with strings. In C, we generally work with zstrings (short for "zero-terminated strings"). Many of the host languages, including Visual Basic for DOS, FoxPro, Clarion, and Pascal, pass buffered strings of one type or another. My first inclination was to convert all strings to zstrings at the wrapping-function level.

This approach would have allowed core functions to work exclusively with zstrings, the advantages of which are obvious. The strategy was simple enough: Allocate a chunk of memory one byte longer than the buffered string, copy the buffered string, and zero terminate it. But some APIs provide precious little available heap, so a function that receives a 48K string would have to burn up another 48K of heap space simply to add a zero terminator.

The alternative was a data structure that could handle the various string types with equal aplomb. This structure, the WSTR, is shown in Figure 2. The PREP_S() and GRAB_S() macros automatically create and load a WSTR structure with the necessary components of the API string. All API strings are resolved to an address stored in the cp component and a string length stored in the uiLen component. Some APIs pass strings via a handle. In such cases, the handle is stored in the ulHnd component.

The WSTR approach adds a level of complexity to writing core functions. Most would agree that it is easier to work with zstrings than a buffered hybrid such as WSTR. Unfortunately, the wrapping macros worked much more efficiently using the WSTR, and that tipped the balance. Listing Four (page 89) shows a simple string-manipulation function. The associated C, Clipper, and FoxPro translations are shown in Listing Five (page 89). These translations are quite involved and are beyond the scope of this article.

Once I decided to wrap strings in a WSTR structure, it became necessary to write a set of manipulation functions. The WRAPI library provides a basic set of WSTR functions; see Table 1.

The C Standard Library

String manipulation highlights one of the bigger drawbacks of WRAPI development. There are effectively no C standard-library functions. Staples of the C programmer such as strcpy(), sprintf(), and strtok() simply don't exist in Clipper, FoxPro, or Visual Basic. However, many APIs provide alternate functions. Consider memcpy(): The FoxPro API includes a function _MemCpy(), and the Clipper standard library includes an undocumented internal _bcopy().

To make WRAPI more C-friendly, I developed a strategy of wrapping C standard functions with a macro of the same name in upper case. Thus, memcpy() became MEMCPY(). The macro translates to the C standard function or to the corresponding API function. Where an API didn't provide the equivalent C standard function, I wrote one. Table 2 lists the C standard functions that I include with WRAPI. You may wish to add to the list. My goal was to minimize dependence on the C standard library, so the list remains quite short.

You may notice the low-level I/O functions deviate slightly from the strategy of wrapping standard functions in uppercase macros. The open() function became F_OPEN_RW(), F_OPEN_RO(), and F_OPEN_WO(). Likewise, lseek() became F_GOBOF(), F_GOEOF(), F_GOTO(), and so on. Low-level I/O flags differed among the supported APIs, and the easiest solution was to eliminate them entirely by creating multiple macros.

Memory Allocation

The next order of business was to provide a uniform method of memory allocation. The malloc() function cannot be used in all APIs. Again, the main culprits are the Xbase dialects. Both allocate memory using an API function that returns a handle. The handle is easily decoded to an address. When it is time to free the memory, it is the handle that must be supplied, not the decoded address. Thus, the handle must be retained somehow.

The WRAPI library includes ALLOC() and FREE() macros. ALLOC() increases the size of each memory allocation by six bytes and returns an address six bytes in from the actual start of allocated memory. The first six bytes are used to store a 4-byte handle (if needed) and a 2-byte length. This allows FREE() to function transparently.

Printing

Under DOS, printing is handled through low-level I/O functions. This allows printing to be directed to either a device or a file. Under Windows, printing to a file is also accomplished through low-level I/O, but printing to a print device requires calls to GDI functions.

In designing WRAPI, I adhere to the DOS printing model. Four constants are used to specify the print devices PRN and LPT1 through LPT3 (no support for COM devices other than a redirected PRN). These constants all have negative values. This allows a single function to be used when selecting a print destination. Either a valid device constant or a file handle (a positive number) can be passed. WRAPI then decodes this information into the appropriate print destination.

Like many things in WRAPI, the print destination requires a wrapping structure. Figure 3 shows the WPRN structure. Under DOS it is sufficient to store the file handle and a Boolean flag indicating that output is directed to a standard print device. Under Windows it is also necessary to store the HDC (handle to a device context) and the state of the spool flag in WIN.INI. (WRAPI automatically turns the spooler off before printing and restores it to its previous state when finished.)

The WRAPI print functions are listed in Table 3. After receiving the desired print destination (in the form of a signed integer) from the host application, iWPrnStart() must be called to decode the destination into a WPRN structure. If the destination is a standard device, iWPrnStart() opens the device under DOS, or creates an HDC under Windows. When printing is completed, iWPrnEnd() must be called to clean up. Failure to call iWPrnEnd() under Windows will result in an orphaned HDC--a very bad idea.

The remaining print functions are used to send data directly to the print device. No effort is made to format output under GDI. All output is sent via the GDI Escape() function as DEVICEDATA. This effectively turns Windows printing into DOS printing. Incidentally, this is also the reason the print spooler must be turned off.

Error Handling

The error handling in WRAPI reflects my belief that the return value of a function should not be the source of error information. WRAPI provides an error-trapping mechanism that delivers the error to the host API in the most appropriate manner. Table 4 lists the different error mechanisms employed by WRAPI.

The biggest compromise in error handling is caused by host APIs that don't have a standard error-trapping mechanism. In such cases, it is necessary for the host application to pass a function reference to the core library. The core library must then make that function reference available to the WRAPI error-trapping mechanism. The error trap simply calls the referenced function. Unfortunately, this destroys an otherwise clean separation between the host, the wrapping, and the core.

This shortcoming of the error system is best illustrated with an example. In-Press provides two functions: IpErrFName(), which takes the name of a FoxPro function designated as the error handler; and IpErrFPtr(), which takes the address of a C function (cast as a long) designated as the error handle. Failure to register an error function with IpErrFName() under FoxPro or IpErrFPtr() under C effectively defeats In-Press error handling. In effect, the application is blind to In-Press errors.

Error handling blurs the line between the core and the wrapping. All core libraries must follow certain error conventions and provide a standard set of error macros to the wrapping functions. These macros, listed in Table 5, act as a call back into the core library. Listing Six (page 90) is an In-Press wrapping function and its corresponding C translation. Note the references to the In-Press error-handling internals.

GET/SET Functions and Nil Values

A WRAPI library design is constrained by several factors. For one thing, the names of wrapping functions are limited to ten characters (as are the names of any support constants). This limitation is imposed by FoxPro, and there is no way around it. More importantly, the data types are limited to the six basic ones listed earlier: character, string, Boolean, short integer, long integer, and double float.

In C, there is a tendency to keep related data in a structure. This works nicely because the structure can be passed by reference. WRAPI precludes that type of interaction between a core library and the host API. How then do you structure a useflibrary if data moves in and out as simple data types?

My solution was to store the data structure internally and provide discrete access to each component with a GET/SET function. For those unfamiliar with the term GET/SET, it is a function that can simultaneously change the state of an internal value while returning its previous state. A GET/SET function can also query the state of an internal value without actually changing it by passing a NIL value.

Although you don't need to use GET/SET functions in the design of a WRAPI library, they are quite useful. WRAPI includes logic for NIL values and a sample GET/SET engine.

Necessary Tools for WRAPI Development

Table 6 lists all of the tools needed to develop and test a WRAPI library. It is no small undertaking. In addition to purchasing and installing the listed products, you will want a mechanism for automating the build process. A set of batch files and a sample makefile are included with the source diskette to help you get started.

Writing WRAPI Functions

Any library requires a bit of planning. WRAPI simply adds to the process. The general scheme for developing a WRAPI library is:

  1. Map out the function list.
  2. Use GET/SET functions where possible. Functions that need to return composite values (such as arrays or structures) can either return them as strings (using the WRAPI stringify() function) or divide the return values among several GET-only functions.
  3. Create function prototypes and constant definitions for the various languages.
  4. Map out which functions will be in which source files.
  5. Write the core error-handling functions first. (They should be patterned after the error-handling functions in the sample library.)
  6. Write the error-wrapping functions and test them in a couple of languages.
  7. If you'll be using GET/SET functions, set up the GET/SET engine.
  8. Write any other core functions that are required for underpinning the library.
  9. Write wrapping functions in related groups. Debug under DOS first--C is easiest. Next debug under Windows--Visual Basic works well. Always test in FoxPro.
  10. When the library is ready to ship, compile using optimization.

Testing and Debugging WRAPI Functions

It can take time to test a WRAPI library. Each change to a library must be tested under the numerous languages. It is not uncommon to spend an entire afternoon checking the results of a function that took only one hour to write. WRAPI certainly doesn't lend itself to an ad hoc development cycle. After you gain familiarity with WRAPI development you learn where to take some shortcuts. (If it works under Clipper and FoxPro for Windows, for example, it probably works under everything else.)

WRAPI development seems to yield robust libraries. The various host APIs expose different types of problems. I found that general debugging was easiest in the DOS environment using C or Clipper as the host language. Once all of the obvious errors were exorcised, it was on to the protected-mode languages, either FoxPro for Windows or Visual Basic for Windows, to find the memory violations. I was able to use the CodeView debuggers quite successfully.

Conclusion

WRAPI is a work in progress and there are shortcomings to the technology that you may want to improve. For instance, parameters must be passed by value, although it should be possible to wrap parameters passed by reference. WRAPI has no mechanism for wrapping interrupts. The general lack of C standard library functions will certainly impede conversion of existing libraries to WRAPI. Also, WRAPI has not been extended to OS/2, NT, or UNIX.

There are several inherent limitations to WRAPI. The ten-character limit on symbol names can make function and support constants a bit terse.

The technology is not quite a "black box." Thus, the WRAPI developer is required to understand the APIs of the supported languages. And WRAPI functions are by their nature limited to "driver" type applications. Proprietary hooks into a particular O/S or a development environment defeat the purpose of WRAPI.

Even with these limitations, WRAPI is a good starting point if you need to support multiple languages. The source files include everything you need. I will maintain and update WRAPI on the CompuServe DDJ Forum. I hope you will join me there as I answer questions and attempt to improve the technology based on the suggestions of other WRAPI developers.

Figure 1: WRAPI sets between the host API and underlying core function.

Figure 2: The WSTR data structure handles all variations of strings.

/* WSTR typedef */
typedef struct  { // WRAPPED-API STRING STRUCTURE
  char   *cp    ; // Actual pointer to the string
  ushort  uiLen ; // Length of string
  ulong   ulHnd ; // Host API handle if applicable
} WSTR          ;

Example 1: The "wrapping" function template.

TYPE_? <Function Name> ( PARMLIST ) {
          DECL_? ( <Return Value> ) ;
          PREP_? ( <Parameter #> , <Parameter Name> ) ; ...
          SET_ERR_NONE ( ) ;
          GRAB_? ( <Parameter #) , <Parameter Name> ) ; ...
          if ( IS_ERR_NONE ( ) )
               <Return Value> = <Core Function> (
<Parameters> ) ;
          SET_ERR_LOC ( "<Function Name>" ) ;
          TRAP_ERR ( ) ;
          SEND_? ( <Return Value> )
          }

Table 1: WSTR functions.

Function                                        Description
-------------------------------------------------------------------------------
short szwcmp     ( char*, WSTR* )               compares zstring to WSTR
short swzcmp     ( WSTR*, char* )               compares WSTR to zstring
short swwcmp     ( WSTR*, WSTR* )               compares WSTR to WSTR
void  szwcpy     ( char*, WSTR*, ushort)        copies WSTR to zstring
void  swzcpy     ( WSTR*, char* )               copies zstring to WSTR
void  swwcpy     ( WSTR*, WSTR*      )          copies WSTR to WSTR

Figure 3: The WPRN structure.

/* WPRN typedef */
typedef struct   { // WRAPPED PRINT DEVICE STRUCTURE
  bool    bStd   ; // a STD print device?
  fhandle fh     ; // handle to a DOS file/device
                 #ifdef _FAMILY_DLL
  HDC     hdc    ; // handle to a device context
  bool    bSpool ; // indicates that spooler was hooked up
                 #endif
} WPRN           ;

Table 2: C standard functions included with WRAPI.

WRAPI function  C standard   Meaning
---------------------------------------------------------
STRCPY          strcpy       Copy zstring
STRCMP          strcmp       Compare zstrings
STRLEN          strlen       Length of zstring
MEMCPY          memcpy       Copy memory block
MEMCMP          memcmp       Compare memory blocks
MEMSET          memset       Fill memory block with char
ATOI            atoi         zstring to short
ATOL            atol         zstring to long
ATOF            atof         zstring to double
ITOA            itoa         Short to zstring
LTOA            ltoa         Long to zstring
UTOA            utoa         Unsigned short to zstring
ULTOA           ultoa        Unsigned long to zstring
FTOA                         Double to zstring
ROUND                        Round double to decimal place
F_OPEN_RW        open        Open device for read/write
F_OPEN_RO        open        Open device for read only
F_OPEN_WO        open        Open device for write only
F_CREATE         create      Create file
F_READ           read        Read from device
F_WRITE          write       write to device
F_GOBOF          lseek       Go to beginning of file
F_GOEOF          lseek       Go to end of file
F_GOTO           lseek       Go to offset in file
F_GOBACK         lseek       Go to negative offset from EOF
F_POS            lseek       Position pointer in file
F_MOVE           lseek       Move pointer through file
F_CLOSE          close       Close device

Table 3: WRAPI print functions.

Function                                             Description
------------------------------------------------------------------------------------
short iWPrnStart      ( WPRN*, short )               initialize print context
short iWPrnEnd        ( WPRN* )                      end print context
bool  bWPrnC          ( WPRN*, char  )               print a char
bool  bWPrnSZ         ( WPRN*, char* )               print a zstring
bool  bWPrnSB         ( WPRN*, char*, ushort )       print a buffer
bool  bWPrnWSP        ( WPRN*, WSTR* )               print a WSTR

Table 4: WRAPI error mechanisms.

Host API        Mechanism
------------------------------------------
C (DOS)         Call to a function pointer
Clarion         Error posted in queue
Clipper S'87    Call to MISC_ERROR()
Clipper 5.x     Error event
Generic DLL     Error message
FoxPro          Call to a named function
Visual Basic    ON ERROR event

Table 5: WRAPI error macros.

Error Macro            Description
----------------------------------------------------------------------------------------
SYSTEM_NAME()          Returns name of WRAPI library (for example, "In-Press")
IS_ERR_NONE()          Returns Boolean True if no error condition
IS_ERR_ERR()           Returns Boolean True if error in error handler
GET_ERR_CODE()         Returns the current error code
GET_ERR_FPTR()         Returns pointer to a C function designated as the error handler
GET_ERR_FNAME()        Returns name of FoxPro function designated as error handler
GET_ERR_HOST()         Returns host API error designator (used for Visual Basic)
GET_ERR_LOC()          Returns current error location (name of wrapping function)
GET_ERR_TEXT(i)        Returns the text associated with error code i
SET_ERR_NONE()         Sets error code for: no error condition
SET_ERR_TYPE()         Sets error code for: invalid data type
SET_ERR_COUNT()        Sets error code for: invalid parameter count
SET_ERR_ALLOC()        Sets error code for: unable to allocate memory
SET_ERR_OUT()          Sets error code for: problem writing to output device
SET_ERR_GDI_CMD()      Sets error code for: problem writing to GDI
SET_ERR_LOC(sz)        Sets the current location (name of wrapping function)
CALL_ERR_VBD()         Invokes Visual Basic for DOS error bridge
CALL_ERR_CLIP4()       Invokes Clipper S'87 error bridge
CALL_ERR_MSG()         Invokes function to post a Windows error message

Table 6: Tools for developing and testing WRAPI libraries.

Tool                        Needed for
------------------------------------------------------------------
Borland C 3.x               Borland C
Microsoft C 5.1             Clipper (MSC 7.0 can be substituted)
Microsoft C 7.0             Microsoft C, generic DLL, Visual Basic
TopSpeed C                  TopSpeed, Clarion
Watcom C 9.x                Watcom C, FoxPro
Clarion                     Testing (order with TopSpeed)
Clipper S'87                Testing (if you can find it)
Clipper 5.2x                Testing
Paradox for Windows         Testing
Visual Basic for DOS        Testing
Visual Basic for Windows    Testing
Microsoft CodeView & NMake  Debugging and making (comes with MSC)

[LISTING ONE]


/* C Parameters passed on call stack */
double SquareIt ( double dValue ) {
  return ( dValue * dValue ) ;
  }

/* Clipper Parameters aquired through API functions */
CLIPPER SquareIt ( void ) {
  double dValue = 0.0 ;
  // if one parameter of type numeric
  if (    _parinfo ( 0 ) == 1
       && _parinfo ( 1 ) == NUMERIC
     )
    {
    // grab parameter from Clipper
    dValue  = _parnd ( 1 ) ;
    }
  // send return value to Clipper
  _retnd ( dValue * dValue ) ;
  return ;
  }
/* FoxPro Parameters passed in a data structure */
void SquareIt ( ParamBlk *pParmBlk ) {
  double dValue = 0.0 ;
  // if one parameter of type numeric
  if (    (*pParamBlk).pCount == 1
       && (*pParamBlk).p[0].val.ev_type == 'N' )
     )
    {
    // grab parameter
    dValue  = (double) (*pParamBlk).p[0].val.ev-real ;
    }
  // send return value to FoxPro
  _RetFloat ( dValue * dValue , 12 , 4 ) ;
  return ;
  }


[LISTING TWO]



/* Setup for wrapping functions */
#ifdef _FAMILY_C  // parameters passed on call stack
  #define SquareIt(PARMLIST)   SquareIt ( double dValue )
#ednif
#ifdef _API_FOX   // FoxPro requires a global data structure
  FoxInfo myFoxInfo[] =
    { { "SQUAREIT" , (FPFI) SquareIt , 1 , "N" } } ;
  FoxTable _FoxTable =
    {   (FoxTable*) 0
      , sizeof(myFoxInfo) / sizeof(FoxInfo)
      , myFoxInfo
    } ;
#endif
/* Prototypes */
TYPE_D  SquareIt ( PARMLIST ) ;
double _SquareIt ( double )   ;
/* Wrapping function (without error handling) */
TYPE_D SquareIt ( PARMLIST ) { // ( dValue )
  // declare return value (and any other local values)
  DECL_D ( dReturn ) ;
  // prepare to accept parameter(s)
  PREP_D ( 1 , dValue ) ;
  // grab parameter(s) from host API
  GRAB_D ( 1 , dValue ) ;
  // call core function
  dReturn = _SquareIt ( dValue ) ;
  // send return value to host API
  SEND_D ( dReturn ) ;
  }
/* Core function */
double _SquareIt ( double dValue ) {
  return ( dValue * dValue ) ;
  }


[LISTING THREE]



/* C translation of wrapping function */
double SquareIt ( double dValue )
  {
  // DECL_D ( dReturn ) ;
  double dReturn = 0.0 ;
  // PREP_D ( 1 , dValue ) ;
  ;
  // GRAB_D ( 1 , dValue ) ;
  ;
  // call to core function
  dReturn = _SquareIt ( dValue ) ;
  // SEND_D ( dReturn ) ;
  return dReturn ;
  }
/* Clipper translation of wrapping function */
void SquareIt ( void )
  {
  // DECL_D ( dReturn ) ;
  double dReturn = 0.0 ;
  // PREP_D ( 1 , dValue ) ;
  double dValue = 0.0 ;
  // GRAB_D ( 1 , dValue ) ;
  dValue = dAPIgrab ( 1 ) ;
  // call to core function
  dReturn = _SquareIt ( dValue ) ;
  // SEND_D ( dReturn ) ;
  _retnd ( dValue ) ;
  return ;
  }
/* FoxPro translation of wrapping function */
void FAR SquareIt ( ParamBlk* gpFoxParm )
  {
  // DECL_D ( dReturn ) ;
  double dReturn = 0.0 ;
  // PREP_D ( 1 , dValue ) ;
  double dValue = 0.0 ;
  // GRAB_D ( 1 , dValue ) ;
  dValue = dAPIgrab ( 1 , gpFoxParm ) ;
  // call to core function
  dReturn = _SquareIt ( dValue ) ;
  // SEND_D ( dReturn ) ;
  _RetFloat ( dReturn , 12 , 4 ) ;
  return ;
  }


[LISTING FOUR]



/* Setup for wrapping functions */
#ifdef _FAMILY_C  // parameters passed on call stack
  #define FlipIt(PARMLIST)   FlipIt ( APISTR wspText )
#ednif
#ifdef _API_FOX   // FoxPro requires a global data structure
  FoxInfo myFoxInfo[] =
    { { "FLIPIT" , (FPFI) FlipIt , 1 , "C" } } ;
  FoxTable _FoxTable =
    {   (FoxTable*) 0
      , sizeof(myFoxInfo) / sizeof(FoxInfo)
      , myFoxInfo
    } ;
#endif
/* Prototypes */
TYPE_S  FlipIt ( PARMLIST ) ;
WSTR*  _FlipIt ( WSTR* )    ;
/* Wrapping function (without error handling) */
TYPE_S FlipIt ( PARMLIST ) { // ( wspText )
  // declare return value (and any other local values)
  DECL_S ( wspReturn ) ;
  // prepare to accept parameter(s)
  PREP_S ( 1 , wspText ) ;
  // grab parameter(s) from host API
  GRAB_S ( 1 , wspText ) ;
  // call core function
  wspReturn = _FlipIt ( wspText ) ;
  // send return value to host API
  SEND_S ( wspReturn ) ;
  }
/* Core function */
WSTR* _FlipIt ( WSTR* wspText )
  {
  // storage for return value
  static char szaReturn [ 256 ] ;
  // WSTR wrapper for return value
  static WSTR wsReturn ;
  // pointers for string flip loop
  char *cpOld , *cpNew ;
  // counter for string flip loop
  ushort u ;
  // point return WSTR at return buffer
  wsReturn.cp = szaReturn ;
  // set return length to incoming length
  wsReturn.uiLen = *wspText.uiLen ;
  // limit return length to size of return buffer
  if ( wsReturn.uiLen > ( sizeof ( szaReturn ) - 1 ) )
    wsReturn.uiLen = ( sizeof ( szaReturn ) - 1 ) ) ;
  // set up loop variables
  u = wsReturn.uiLen ;
  cpNew = szaReturn ;
  cpOld = *wspText.cp + u - 1 ;
  // flip old into new
  while ( u-- )
    *cpNew++ = *cpOld-- ;
  // zero terminate regardless of host API
  *cpNew = '\0' ;
  // return WSTR pointer
  return ( &wsReturn ) ;
  }


[LISTING FIVE]



/* C translation of wrapping function */
char* FlipIt ( char* wspText )
  {
  // DECL_S ( wspReturn ) ;
  WSTR wsReturn ; WSTR *wspReturn = &wsReturn ;
  // PREP_S ( 1 , wspText ) ;
  WSTR wsBuff1 ;
  // GRAB_S ( 1 , wspText ) ;
  if ( wspText == NULL )
    {
    wsBuff1.cp    = (char*)  "\xFF" ; // defined as NIL
    wsBuff1.uiLen = (ushort) 1 ;
    }
  else
    {
    wsBuff1.cp    = (char*) wspText ;
    wsBuff1.uiLen = (ushort) strlen ( wsBuff1.cp ) ;
    }
  wsBuff1.ulHnd = 0L ;
  wspText = &wsBuff1 ;
  // call to core function
  wspReturn = _FlipIt ( wspText ) ;
  // SEND_S ( wspReturn ) ;
  return *wspReturn.cp ;
  }
/* Clipper translation of wrapping function */
void FlipIt ( void )
  {
  // DECL_S ( wspReturn ) ;
  WSTR* wspReturn = (WSTR*) (void*) 0L ;
  // PREP_S ( 1 , wspText ) ;
  WSTR wsBuff1 ; WSTR* wspText = &wsBuff1 ;
  // GRAB_S ( 1 , wspText ) ;
  _bcopy ( wspText , wspAPIgrab ( 1 ) , sizeof ( WSTR ) ) ;
  // call to core function
  wspReturn = _FlipIt ( wspText ) ;
  // SEND_S ( wspReturn ) ;
  _retc ( *wspReturn.cp ) ;
  return ;
  }
/* FoxPro translation of wrapping function */
void FAR FlipIt ( ParamBlk *gpFoxParm )
  {
  // DECL_S ( wspReturn ) ;
  WSTR* wspReturn = (WSTR*) (void*) 0L ;
  // PREP_S ( 1 , wspText ) ;
  WSTR wsBuff1 ; WSTR* wspText = &wsBuff1 ;
  // GRAB_S ( 1 , wspText ) ;
  _MemCpy (   wspText
            , wspAPIgrab ( 1 , gpFoxParm )
            , sizeof ( WSTR )
          ) ;
  // call to core function
  wspReturn = _FlipIt ( wspText ) ;
  // SEND_S ( wspReturn ) ;
  _RetChar (   wspReturn && *wspReturn.cp
             ? *wspReturn.cp
             : ""
           ) ;
  while ( (*gpFoxParm).pCount )
    if ( (*gpFoxParm).p[--(*gpFoxParm).pCount].val.ev_type == 'C' )
      _HUnLock ( (*gpFoxParm).p[(*gpFoxParm).pCount].val.ev_handle ) ;
  return ;
  }


[LISTING SIX]



/* An In-Press wrapping function (with error handling) */
TYPE_I IpBoStyle ( PARMLIST ) { // ( [iStyle] )
  DECL_I ( iReturn ) ;
  PREP_I ( 1 , iStyle ) ;
  SET_ERR_NONE ( ) ;
  GRAB_I ( 1 , iStyle ) ;
  if ( IS_ERR_NONE )
    iReturn = GETSET_I ( HND_BO_STYLE , iStyle ) ;
  SET_ERR_LOC ( "IpBoStyle" ) ;
  TRAP_ERR ( ) ;
  SEND_I ( iReturn ) ;
  }
/* The C translation of an In-Press wrapping function */
short IpBoStyle ( iStyle ) {
  // DECL_I ( iReturn ) ;
  short iReturn ;
  // PREP_I ( 1 , iStyle ) ;
  ;
  // SET_ERR_NONE ( ) ;
  ( _iIpErrCode ( 0 ) ) ;
  // GRAB_I ( 1 , iStyle ) ;
  ;
  // if ( IS_ERR_NONE ) ...
  if ( ( _iIpErrCode ( -32767 ) == 0 ) )
    iReturn =  ( * ( (short*)  _vpIpSet ( 80 + 512 , &iStyle ) ) ) ;
  // SET_ERR_LOC ( "IpBoStyle" ) ;
  ( _wspIpErrLoc ( szaaswsp ( "IpBoStyle" ) ) ) ;
  // TRAP_ERR ( ) ;
  if ( ! ( _iIpErrCode ( -32767 ) == 0 ) )
    vErrTrapC ( ) ;
  // SEND_I ( iReturn ) ;
  return iReturn ;
  }


Copyright © 1994, Dr. Dobb's Journal