The Pre-IDL 5.5 Keyword API


Versions of IDL prior to IDL 5.5 used a different, but similar, keyword processing API to that found in the current versions. The remainder of this section provides information of interest to programmers maintaining older system routines that were written to that API.

Note: NV5 Geospatial Solutions recommends that all new code be written using the new keyword processing API. The older API continues to be supported for backwards compatibility, and there is no urgent reason to convert code that uses it. However, the effort of converting old code to the new API is minimal, and can be beneficial.

Background


If you have system routines that were written for use with versions of IDL older than IDL 5.5, your code uses an older keyword processing API, described in “Processing Keywords With IDL_KWGetParams()”, that including the following obsolete elements:

  • IDL_KWGetParams()
  • IDL_KW_ARR_DESC
  • IDL_KWCleanup(), IDL_KW_MARK, IDL_KW_CLEAN

This old API served for many years, but it had some unfortunate features that made it hard to use correctly:

  • The rules for when and how to use IDL_KWCleanup() were difficult to remember. The programmer had to decide whether or not to call it based on the types of the keywords being processed. If you didn’t call it when you should, memory would be leaked.
  • In order to ensure correctness, many programmers would resort to always calling IDL_KWCleanup() whether it was is needed or not. This is inefficient, but otherwise fine.
  • The use of IDL_KWCleanup() is based on a worst case assumption that the keywords that require cleaning will actually be invoked by the IDL user. This is often not the case, and is therefore inefficient.
  • Imagine an existing system routine that does not need to use IDL_KWCleanup(), and therefore is coded not to use it. If a new keyword should later be added to that routine, and that new keyword should require the use of IDL_KWCleanup(), it is very likely that the programmer adding this new keyword will fail to recognize that issue. This leads to memory leaking from a formerly correct routine.
  • If a future version of IDL should get a new data type that requires cleaning, that will change the rules for when IDL_KWCleanup() needs to be called. Existing code may need to be examined to fix this situation.
  • The old keyword API is not reentrant, because it requires static variable addresses to be embedded in the keyword list. This has always been a problem for recursively callable routines (e.g. WIDGET_CONTROL, which can cause an IDL procedure callback to execute, which can in turn call WIDGET_CONTROL again). In the past, we have carefully coded these complex routines to avoid problems, but the large amount of code required is difficult to write and verify. The proper solution is a reentrant keyword API that uses offsets to variables within a structure, instead of actual statically scoped variable addresses. This is what the current API provides.

Advantages of the IDL 5.5 API


In contrast, keyword processing, in IDL 5.5 and later is built around the IDL_KWProcessByOffset() function, has the following advantages:

  • The old API remains in place with full functionality. Hence, you are not required to update your old code (There are benefits to such updating, however).
  • A transitional API, build around the IDL_KWProcessByAddr() function, exists to help ease updating your code. See The Transitional API for details. The transitional API is a halfway measure designed to solve the worst problems of the old API while requiring the minimum amount of change.
  • The new API, and the transitional API both eliminate the confusing IDL_KWCleanup() routine and its requirement to KW_MARK before, and KW_CLEAN after keyword processing based on the types of the keywords. Instead, the keyword processing API keeps track of the need to cleanup itself, and handles this efficiently. The user is freed from guesswork about how and when to do the required cleanup.
  • Keyword cleanup will only happen if the keyword module determines that it is necessary as it processes the actual keywords used. This is more efficient, and it can be easily extended within IDL if a new data type is added to the IDL system, without requiring any change to your code.
  • The internal data structures used to maintaining keyword state are now dynamically allocated, and do not have the static limits that the old implementation did.
  • The new API is able to process keywords using a re-entrant keyword description. Results are written to stack based (C auto) variables rather than statically defined variables. This can be used to greatly simplify the implementation of recursive system routines, and has been used extensively for that purpose within IDL.

Differences And Similarities Between APIs


The current IDL keyword processing API was designed to minimize the changes necessary to convert existing older code. The differences and similarities between these APIs are summarized below:

  • The basic IDL_KW_PAR data structure is unchanged between the two. However, in the old API, the specified, and value fields are addresses to statically allocated C variables or IDL_KW_ARR_DESC structures. In the new API, specified is always an offset into a user defined KW_RESULT structure. The value field is an offset into KW_RESULT when it is used to refer to data. It is an address when used to refer to an IDL_KW_ARR_DESC_R structure.
  • The old API uses the IDL_KW_ARR_DESC structure to define IDL_KW_ARRAY read-only arrays. The new API uses the very similar IDL_KW_ARR_DESC_R structure. This is necessary because IDL_KW_ARR_DESC is not reentrant (the number of array elements used is written into the struct), while IDL_KW_ARR_DESC_R causes them to be written into a field in the KW_RESULT variable instead.
  • The new API requires all keyword variables to be contained in a single KW_RESULT structure, while the old API allowed them to be independent variables. This is important to the offset-based scheme used in the new API, as well as having the nice side effect of improving the organization and readability of most code.
  • The old API uses IDL_KWGetParams() to process keywords. The new API uses IDL_KWProcessByOffset().
  • The old API uses IDL_KWCleanup() to free resources. The rules for using it are complicated and lead to latent coding errors. The new API uses the IDL_KW_FREE macro, and has a simple consistent rule for use.

Converting Existing Code To The New API


To convert code that uses the old API to the new version:

  • Define a typedef for a struct named KW_RESULT, containing the keyword variables. Make the first field be the predefined IDL_KW_RESULT_FIRST_FIELD.
  • Modify your keyword definition list so that the specified and value fields of each IDL_KW_PAR struct contain offsets instead of addresses in all cases except when the value field references an IDL_KW_ARR_DESC struct. To do this, use the IDL_KW_OFFSETOF() macro.
  • Any reference to an IDL_KW_ARR_DESC structure for an IDL_KW_ARRAY keyword must be converted to an IDL_KW_ARR_DESC_R struct.
  • Replace the call to IDL_KWGetParams() with a call to IDL_KWProcessByOffset().
  • Remove any IDL_KWCleanup(IDL_KW_MARK) calls.
  • Replace any IDL_KWCleanup(IDL_KW_CLEAN) calls with the IDL_KW_FREE macro. Check to ensure that all exit paths from your function other than via IDL_Message() include a call to this macro.

The Transitional API


We recommend that you convert your code to the reentrant keyword API based around the IDL_KWProcessByOffset() and IDL_KWFree() functions. This is almost always a straightforward operation, and the resulting code has all of the advantages discussed in “Advantages Of The IDL 5.5 API”. However, there is another alternative that may be useful is some situations. A third keyword API, built around the IDL_KWProcessByAddr() function exists that provides the benefits of eliminating the confusing IDL_KWCleanup() function, while not requiring the use of static non-reentrant separate variables to change.

The transitional API is a halfway measure designed to solve the worst problems of the old API while requiring the minimum amount of change to your code:

int IDL_KWProcessByAddr(int argc, IDL_VPTR *argv, char *argk,       
   IDL_KW_PAR *kw_list, IDL_VPTR *plain_args, 
   int mask, int *free_required)
 
void IDL_KWFree(void)

where:

argc, argv, argk, plain_args, mask

These arguments are the same as those required by IDL_KWProcessByOffset().

kw_list

An array of IDL_KW_PAR structures, in the absolute address form required by the old IDL_KWGetParams() keyword API (the specified and value fields use address to static C variables).

free_required

The address of an integer to be filled in by IDL_KWProcessByAddr(). If set to TRUE, the caller must call IDL_KWFree() prior to exit from the routine.

Example: Converting From The Old Keyword API


To illustrate the use of the old keyword API, the transitional API, and the new reentrant API, this section provides an extremely simple example, written three times, once with each API.

Another useful comparison is to compare the example Keyword Examples on with its original version written with the old API which can be found in “Keyword Examples”.

Old API

IDL_VPTR IDL_someroutine(int argc, IDL_VPTR *argv, char *argk)
{
  static IDL_VPTR count_var; 
  static IDL_LONG debug; 
  static IDL_STRING name;
  static IDL_KW_PAR kw_pars[] = {
  { "COUNT", 0,1,IDL_KW_OUT|IDL_KW_ZERO,0,IDL_CHARA(count_var)},
  { "DEBUG", IDL_TYP_LONG, 1, IDL_KW_ZERO, 0,IDL_CHARA(debug) },
  { "NAME", IDL_TYP_STRING, 1, IDL_KW_ZERO, 0,IDL_CHARA(name) },
   
  { NULL }
  };
   
  IDL_VPTR result;
  IDL_KWCleanup(IDL_KW_MARK);
  argc = IDL_KWGetParams(argc,argv,argk,kw_pars,(IDL_VPTR *)0,1);
   
  /* Your code goes here. Keyword values are available in the
  * static variables.*/
   
  /* Cleanup keywords before leaving */ 
  IDL_KWCleanup(IDL_KW_CLEAN); 
  return(result);
}

Transitional API

The transitional API provides the benefits of simplified and straightforward cleanup, but does not require you to alter your IDL_KW_PAR array or gather the keyword variables into a common structure. The resulting code is very similar to the old API.

IDL_VPTR IDL_someroutine(int argc, IDL_VPTR *argv, char *argk)
{
static IDL_VPTR count_var; 
static IDL_LONG debug; 
static IDL_STRING name;
static IDL_KW_PAR kw_pars[] = {
{"COUNT", 0, 1, IDL_KW_OUT|IDL_KW_ZERO,
0,IDL_KW_ADDROF(count_var) },
{ "DEBUG", IDL_TYP_LONG,1,IDL_KW_ZERO,0,IDL_KW_ADDROF(debug)},
{ "NAME", IDL_TYP_STRING,1,IDL_KW_ZERO,0,IDL_KW_ADDROF(name)},
{ NULL }
};
 
int kw_free; 
IDL_VPTR result;
 
argc = IDL_KWProcessByAddr(argc, argv, argk, kw_pars, 
   (IDL_VPTR *) 0, 1, &kw_free);
 
/* Your code goes here. Keyword values are available in the
* static variables.*/
 
/* Cleanup keywords before leaving */
if (kw_free) IDL_KWFree();
 
return(result);
}

 

New Reentrant API

IDL_VPTR IDL_someroutine(int argc, IDL_VPTR *argv, char *argk)
{
typedef struct {
IDL_KW_RESULT_FIRST_FIELD; /* Must be first entry in struct */ IDL_VPTR count_var;
IDL_LONG debug;
IDL_STRING name;
} KW_RESULT;
static IDL_KW_PAR kw_pars[] = {
{ "COUNT", 0, 1, IDL_KW_OUT | IDL_KW_ZERO,
0, IDL_KW_OFFSETOF(count_var) },
{ "DEBUG", IDL_TYP_LONG, 1, IDL_KW_ZERO,
0, IDL_KW_OFFSETOF(debug) },
{ "NAME", IDL_TYP_STRING, 1, IDL_KW_ZERO,
0, IDL_KW_OFFSETOF(name) },
{ NULL }
};
 
KW_RESULT kw; 
IDL_VPTR result;
 
argc = IDL_KWProcessByOffset(argc, argv, argk, kw_pars, (IDL_VPTR *) 0, 1, &kw);
 
/* Your code goes here. Keyword values are available in the
* kw struct.*/
 
/* Cleanup keywords before leaving if necessary */ 
IDL_KW_FREE;
 
return(result);
}