Extending the Application Procedural Interface


The Application Procedural Interface (API) is a layer of functions that expose the underlying functionality of the ACIS modeler. The API guarantees a stable interface, regardless of modifications to low level ACIS data structures or functions, and bundles model management and error handling features. When an error or interrupt occurs in an API routine, ACIS automatically rolls the model back to the state when that API routine was called. This ensures that the model is not left in a corrupted state.

The API follows the general guidelines of the CAMI Application Interface Specification (AIS), but is broadened to handle the wider range of entities that are represented in ACIS models.

Developers can create their own API functions to extend the interface to ACIS. This section describes how to create API functions that conform to ACIS standards.

Creating a New API Function

Each API function should be placed in a single file. The filename should reflect the routine name if possible. An API function name begins with the prefix api_. This prefix should be followed by a two or three character sentinel that identifies the owner of the API.

This section describes how to create an API. It uses api_make_cuboid as an example and describes the major features used in APIs.

API Function Characteristics

An API function performs an ACIS modeling or management task. APIs:

Procedure

The following steps describe how to create API functions that conform to ACIS standards.

Step 1. Using the API Source .cxx File

  1. Copy an existing API file and rename the file according to its new function.
  2. Name the API function api_<sentinel>_<function> where <sentinel> is the two or three letter code unique to the component or application (refer to the Sentinels section).
  3. Create a comment header block that describes the API in the standardized format.
  4. Include standard API .hxx header files and your API specific header files.
  5. Declare the API function as returning an outcome.
  6. Declare all output arguments to be passed by reference, not by value.
  7. Check arguments to the function using standard argument checking functions. Refer to the Argument Error Checking section.
    if (api_checking_on){check_body(arg)...
  8. Bracket the body of the API function with macros API_BEGIN and API_END, API_SYS_BEGIN and API_SYS_END, or API_NOP_BEGIN and API_NOP_END.
  9. Perform the specific functions for which this API was created.
  10. Return a result.

Step 2. Using the API .hxx Header File

  1. Edit the API header file that exists in the source directory in which the .cxx file was created or, if one does not already exist, create a header file in this directory.  
    The header file is named <dir_name>/<hdr_name>.hxx, where <dir_name> is the name of the source code directory in which the API source code .cxx file is located and <hdr_name> is the name of that directorys API header file.  
  2. Place the prototype for the API function into the header file.

For support of DLLs, the declaration of the API (prototype) must be prefaced by its appropriate DECL_<MODULE> macro, where <MODULE> is an abbreviation for the module (component). Refer to the section Object Libraries for more information.

Example API Function

Example. Header File for api_make_cuboid shows the function prototype (from the header file) while Example. Source File for api_make_cuboid shows most of the source file for the API function api_make_cuboid. The prototype is in the header file <install_dir>/cstr/constrct/kernapi/api/cstrapi.hxx:

C++ Example

DECL_CSTR outcome api_make_cuboid(
double,            // size in x coordinate direction
double,            // size in y coordinate direction
double,            // size in z coordinate direction
BODY *&            // body constructed
);

Example. Header File for api_make_cuboid

The following example contains most of the source file, <install_dir>/cstr/constrct/kernapi/api/mkcuboid.cxx:

C++ Example

#include acis.hxx
#include dcl_kern.h
#include cstrapi.hxx
#include api.hxx
// {... Documentation code template not shown ...}
// Include files:
#include <stdio.h>
#include logical.h
#include check.hxx
#include api.err
#include primtive.hxx
#include body.hxx
#include module.hxx
#define MODULE() api
MODULE_REF(KERN);
// ************************************************************
outcome api_make_cuboid(
    double     width,
    double     depth,
    double     height,
    BODY*&     body )
{
    DEBUG_LEVEL(DEBUG_CALLS)
       fprintf(debug_file_ptr, calling api_make_cuboid\n);
    API_BEGIN
       if (api_checking_on)
       {
           check_pos_length(width, width);
           check_pos_length(depth, depth);
           check_non_neg_length(height, height);
       }
       body = make_parallelepiped( width, depth, height );
       result = outcome( body == NULL ? API_FAILED : 0 );
    API_END
    DEBUG_LEVEL(DEBUG_FLOW)
       fprintf(debug_file_ptr, leaving api_make_cuboid: %s\n,
       find_err_ident(result.error_number()));
    return result;
}

Example. Source File for api_make_cuboid

Include Files

The following example shows some include definitions. These are typical of the header files that are not specific to the API that are required for most API functions.

The acis.hxx file must always be included, must always be the first ACIS include file, and must be before any application include files.

C++ Example

#include <stdio.h>
#include acis.hxx
#include logical.h
#include dcl_kern.h
#include api.hxx
#include check.hxx
#include api.err
#include module.hxx

Example. Header File Declarations

Function Return Type and Error Handling

Most API routines return a record of type outcome. For example:

outcome api_make_cuboid(...)

An outcome holds an error number of type err_mess_type, which is an integer. If the outcome is successful, the error number is 0. A calling function may examine the outcome using the following functions:

ok
Returns TRUE if the outcome was successful, (the error number is 0).
error_number
Returns the error number.
find_err_mess
Returns the text for the error message.

The following example illustrates an API function call.

C++ Example

outcome result = api_make_cuboid(width, depth, height, body);
if(! result.ok())
{
    err_mess_type err_no = result.error_number();
    printf(Error in make_cuboid %d : %s\n,err_no, find_err_mess(err_no));
}

Example. Example API Call

Every API routine includes an error trap to catch error exits from within the modeler.

The API routine may detect some warning conditions that do not cause the operation to exit. The warning messages are not returned with the outcome. Rather, the application must ask the error system for a list of the warnings caused.

The following example shows how to print warning messages.

C++ Example

err_mess_type* warnings;
int nwarn = get_warnings(warnings);
for (int i = 0; i < nwarn; ++i) {
      printf(Warning %d : %s\n,
      warnings[i],
      find_err_mess(warnings[i]));
}

Example. Example Warning Message Handling

If logging for roll back is in operation, an error in an API routine causes the model to be rolled back to its state prior to the call. If logging is off, the modeler halts.

Function Arguments

An API may have arguments that are input or returned. No argument is used for both purposes. Arguments given should precede arguments returned in an argument list. Output arguments are passed by pointer reference, not by value. For example, api_make_cuboid takes three doubles for width, depth, and height. The fourth argument is a pointer to the body that is returned.

Making APIs C-Callable

A C++ function is not necessarily made C callable by prototyping the C++ function with the extern C keywords. While the extern C keyword prevents the name of the C++ from being mangled, there are other issues to be considered.

C does not use classes. Therefore, it cannot pass classes by value. If a C++ API function requires that a class be passed by value as its input, it is not C callable, regardless of whether it is prototyped with the extern C keyword. This leads to the following rule: API functions must not pass classes by value.

C does have void pointers. A C void pointer points to the address of a class just as it points to a structure. Therefore, a C++ API function would be C callable if its class arguments were passed by address ( BODY*) or by reference (BODY&). Either method allows the API function to be called from C.

ENTITY Class Arguments

Model entities are referred to by pointers. For example, if a routine requires a body as an input argument, it passes an argument of type BODY*. Similarly, if a body is to be returned, it is returned via a variable of type BODY*&. After an error, output argument values are undefined.

ENTITY_LIST Class Arguments

When a group of similar arguments must be returned and the number of arguments is not known in advance, they are returned as a list of entities using class ENTITY_LIST. For instance, when the routine api_cover_sheet is used to find every simple loop of external edges of a sheet body, the faces made are put into an ENTITY_LIST, which is returned.

The ENTITY_LIST implementation uses hashing, so look up is fast if the lists are not long. It is also efficient for repeated lookups of the same ENTITY.

ENTITY_LIST provides constructor (which creates an empty list) and destructor functions to add an entity to the list, look up an entity by pointer value, to remove an entity from the list, and to count the number of entities in the list. It also provides an overloaded [ ] operator for access by position, and two methods, init and next, for faster sequencing through the array.

The preferred way of accessing the items in the list is through ENTITY_LIST::init, which rewinds the list, and ENTITY_LIST::next, which returns the next undeleted item, as is shown in Example 6.

C++ Example

ENTITY_LIST my_face_list;
api_cover_sheet(sheet_body, new_surface, my_face_list);
ENTITY* my_list_item my_face_list.init();
while ((my_list_item=my_face_list.next()) != NULL){
    if is_FACE(my_list_item){
.   
.   
.    
.    
}
}

Example. ENTITY_LIST Using init and next

The ENTITY_LIST class is a variable length associative array of ENTITY pointers. When using the subscript operator, a cast is required to coerce the ENTITY pointer into a pointer of the correct type. Refer to Example 7. For best performance in loops that step through this list by index value, have the loops increment rather than decrement the index counter. Internal operations for methods like operator[] and remove store the index counter from the previous operation, allowing faster access to the next indexed item when indexing up.

C++ Example

ENTITY_LIST my_face_list;
api_cover_sheet(sheet_body, new_surface, my_face_list);
int number_of_faces = my_face_list.count();
for (int i = 0; i < number_of_faces; i++)
{
   FACE* face = (FACE*)my_face_list[i];
    .
    .
    .
    .
}

Example. ENTITY_LIST Using Cast and the Operator

Begin and End Macros

The body of every API is enclosed within a pair of begin and end macros. The pair of macros used depends on the type of API. These macros set up error handlers and alter the behavior of the bulletin board. The text for macros is in the files <install_dir>/kern/kernel/kernapi/api/api.hxx and <install_dir>/kern/kernel/kernutil/errorsys/errorsys.hxx.

The possible pairs of macros are described in the following sections.

API_BEGIN and API_END Macros

The purposes of an API_BEGIN and API_END block are to:

The API_BEGIN macro declares a variable result of class outcome. When performing modeling operations within an API_BEGIN and API_END block, the result variable should be used to contain the success or failure of those operations. Take for example the following fictional customer function, which returns true or false if the following set of operations succeeds/fails:

bool do_something(BODY *& retbody)
{
   bool ret;
   BODY *prism = NULL;
   BODY *cone = NULL;
   API_BEGIN
   result = api_make_prism(..., prism);
   if(result.ok())
              result = api_make_frustum(..., cone);
   // prism is tool, cone is blank.  The result of
   // this call is the blank (cone); the  tool (prism)
   // is deleted.
   if(result.ok())
       result = api_intersect(prism, cone);
   // If a NULL body is returned, then  there was no
   // overlap - we consider this failure.  Force a rollback
   // by setting result to a failing  value.
   if (cone == NULL)
       result = outcome(API_FAILED);
   API_END
   // Setup our return values.
   ret = result.ok();
   retbody = cone;
   return ret;
}

There are some significant points to note about this simple example:

Let us take this example one step further by considering how this routine is called:

void main(void)
{
  API_BEGIN
           BODY *bod;
  bool r = do_something(bod);
  result = (r == true) : outcome(0) ? outcome(API_FAILED);
 // If the result is not ok, no cleanup is  needed; all
  // results will be rolled back when we pass through API_END.
  if (result.ok())
       api_del_entity(bod);
  API_END
}

This illustrates that nesting of API_BEGIN/ API_END blocks is perfectly acceptable. However, there are important behavioral differences:

API_NOP_BEGIN and API_NOP_END Macros

The macros API_NOP_BEGIN and API_NOP_END begin and end every ACIS API that does not change the model. They provide a way to use the bulletin mechanism to make changes to an ENTITY, then throw away the changes when done. This is useful when you want to evaluate the model without changing it. Changes are allowed to happen as part of the operation, but they are then undone by API_NOP_END.

API_NOP_BEGIN creates a stacked bulletin board. The stacked bulletin board is logically nested inside an already existing bulletin board, if one exists. Nesting of the associated macros must be strictly maintained.

Because API_NOP_BEGIN and API_NOP_END make use of the bulletin facilities, the effect is lost if bulletin board logging is turned off. Therefore, bulletin board logging is temporarily turned on in API_NOP_BEGIN and reset in API_NOP_END.

API_NOP_BEGIN creates a new bulletin board but does not roll back any failed previous bulletin boards. This ensures that the bulletin board is in the same state after the NOP macros as it was before. API_NOP_END rolls over the bulletin board opened by API_NOP_BEGIN. The bulletin pointers of the entities in the previous bulletin are restored to their original state, leaving the bulletin board in the state it was before the API_NOP_BEGIN macro.

API_TRIAL_BEGIN and API_TRIAL_END Macros

Macros API_TRIAL_BEGIN and API_TRIAL_END can be used to bracket code that may or may not have results you want to keep. Like API_NOP_BEGIN, API_TRIAL_BEGIN creates a stacked bulletin board. In API_TRIAL_END, the outcome result is checked. If the result is good, the model edits are retained and the stacked bulletin board is merged with the main bulletin board. If the outcome is bad, the results are rolled away as if API_NOP_BEGIN and API_NOP_END had been used. These trial macros only work if logging is turned on. Therefore, bulletin board logging is temporarily turned on in API_TRIAL_BEGIN and reset in API_TRIAL_END.

API_TRIAL_BEGIN creates a stacked bulletin board. The stacked bulletin board is logically nested inside an already existing bulletin board, if one exists. Nesting of the associated macros must be strictly maintained.

The main difference between these trial macros and the normal API_BEGIN/ API_END macros is in the timing and in what gets rolled back. The trial macros create a stacked bulletin board so the rolling section is exactly what is between the two macros, regardless of how they may be nested inside other sets of macros. In the trial case, the roll back occurs in API_TRIAL_END, whereas in the normal case, the roll back is delayed until the next bulletin board is opened. In the normal case, the roll back is also dependent on the error eventually getting propagated to the outermost API_END. The trial case always rolls back on failure without having to propagate to the outermost level.

API_SYS_BEGIN and API_SYS_END Macros

Use API_SYS_BEGIN and API_SYS_END for API system routines. System routines manipulate bulletin boards and roll back, and therefore do not return bulletin boards, or change the model.

Argument Error Checking

Argument checking is conditional in ACIS. The api_checking_on function controls whether the validity of the API arguments is checked before continuing with the API operation. In the file api.hxx, a line of code defines api_checking_on:

#define api_checking_on (api_check_on())

If api_checking_on is TRUE, the checking functions determine if the arguments are valid. API checking is disabled. The function api_checking_on is TRUE when the option is TRUE. The checking functions in this example check only for greater than 0.

Conditionally check arguments:

if (api_checking_on)
  {
      check_pos_length(width, width);
      check_pos_length(depth, depth);
      check_pos_length(height, height);
  }

Argument Checking Functions

The API error checking functions that can be called are as follows:

check_array_exists
Error if NULL pointer to array
check_array_length
Error if zero length
check_body
Error if NULL pointer
check_coedge
Error if NULL pointer
check_delta
Error if NULL pointer
check_edge
Error if NULL pointer
check_entity
Error if NULL pointer
check_face
Error if NULL pointer
check_graph
Error if NULL pointer
check_plane
Error if NULL pointer
check_sheet
Error if NULL pointer
check_wire
Error if NULL pointer
check_non_neg_length
Error if length less than SPAresabs
check_non_zero_length
Error if absolute length less than SPAresabs
check_pos_length
Error if length less than SPAresabs
check_3sides
Check # of sides > 3
check_wire_body
Checks wire pointer not NULL and shell pointer NULL

Checking Arguments

The questions that must be evaluated regarding argument checking are:

Argument checking slows down the performance of the routine; however, when the check is omitted, an important debugging tool is lost. The developer must weigh these factors when writing APIs.

Error Handling

In the following example, API make_parallelepiped is called to create a cuboid, and the result of the API is determined (using a conditional statement):

body = make_parallelepiped(width, depth, height);
result = outcome(body == NULL ? API_FAILED : 0);

If the operation was successful, the result is set to 0; otherwise, the result is set to a generic API failed condition. If some other internal error occurred, the result is set by the error handling code in the API_BEGIN macro. API_END closes the roll back and error handling that API_BEGIN established. Refer to Error Handling and Messaging, for more information about error handling.

Debugging Macro

The following macro will print calling followed by the name of the API being called, if the user sets the debug level to print DEBUG_CALLS statements.

DEBUG_LEVEL(DEBUG_CALLS)
   fprintf(debug_file_ptr, calling api_make_cuboid\n);

The preprocessor expands this macro call to:

if (api_module_header.debug_level >= 10)
   fprintf(debug_file_ptr, calling api_make_cuboid\n);

The second call to the macro sets up debugging to print an additional leaving message, if the user sets the debug level to print DEBUG_FLOW statements.

DEBUG_LEVEL(DEBUG_FLOW)
   fprintf(debug_file_ptr, leaving api_make_cuboid: %s\n,
   find_err_ident(result.error_number()));

The preprocessor expands this macro call to:

if (api_module_header.debug_level >= 20)
   fprintf(debug_file_ptr, leaving api_make_cuboid: %s\n,
   find_err_ident(result.error_number()));

Refer to the section Debugging for more information.

[Top]