Programming
Techniques

 

The following article is excerpted from the Metagraphics C/C++ Programming Guidelines manual. A full copy of this manual can be downloaded from the Metagraphics web site at: http://www.metagraphics.com/pubs/MetagraphicsCodingGuide.pdf (213Kb, Adobe Acrobat PDF file)

 
 

4.8)  Enhancing struct Compatibility

Even with the best design planning, it's often necessary to add new variables to existing data structures to support new features or capabilities in your program. Data structure extensibility becomes an issue, however, unless all the components for creating and accessing the data are kept exactly in sync. Struct "versioning" problems may occur in many common situations:

  1. The data structure is written as part of a file that is stored on disk and read back later for processing (potentially by a new version of the program).

  2. The structure is used to store information that is part of a data transfer that is passed electronically for processing by another machine (potentially using a newer or different version of the program).

  3. Portions of the the application are compiled and linked at different times (such as DLL's and COM modules).

Whenever components of an application, utility or shared library are built as separate parts, the risk for data structure version incompatibility arises. This is becoming even more prevalent today as as information is passed through networks or over the Internet for processing by a remote server. The following guidelines are designed to enhance structures to promote extensibility and upward compatibility.

Include a structSize Member In Structures
Often with functions that are built into dynamically linked libraries (DLL's) it may be necessary later to update a function which, in turn, may require adding additional items to a structure that is passed by pointer reference. Version problems will occur if an existing application passes a pointer of an old structure type to a function in a new DLL with an updated structure type.

To simplify updates and to facilitate DLL compatibility between versions, unless you are absolutely certain that a structure will never change, include an "INT32 structSize" variable as the first data member of all structures. By examining the structSize variable, updated functions can determine specifically which version of a structure is being passed, and then act accordingly in a compatible manner. The _InitStruct() macro is a convenient method to initialize a structure to zero and then automatically set the structSize member (see the Metagraphics C/C++ Programming Guidelines manual for more information on _InitStruct()).

The application code would be something like this:

typedef struct _fooStruct
{
    INT32   structSize;  /* size of this structure */
    /* other structure members defined here */
} fooStruct;

    _InitStruct( &myStruct );  /* zeros the structure and sets structSize */
    /* set other structure members */

    foo( &myStruct );   /* call a function passing a pointer to the struct */

The called function code can now distinguish between different structure versions:

typedef struct _FOOSTRUCT_REV1  /* old version of FOOSTRUCT */
{
    INT32   structSize;  /* size of this structure */
    /* other structure members defined here */
} FOOSTRUCT_REV1;

typedef struct _FOOSTRUCT   /* current version of FOOSTRUCT */
{
    INT32   structSize;  /* size of this structure */
    /* original structure members defined here */
    /* new structure members defined here */
} FOOSTRUCT;

void foo( FOOSTRUCT *fooStruct )
{
    if ( fooStruct->structSize == sizeof(FOOSTRUCT_REV1) )
    {
        /* cast fooStruct to FOOSTRUCT_REV1 and process accordingly */
    }
    else if ( fooStruct->structSize == sizeof(FOOSTRUCT) )
    {
        /* process fooStruct with current FOOSTRUCT definition */
    }
    else
    {   /* We can also double check we are getting passed a valid parameter. */
        /* Something is bad if FOOSTRUCT doesn't match any size we expect!   */
        _ASSERT( FALSE );  /* debug trap */
    }
} /* foo() */

A structSize variable automates the older technique of having a manually updated version number at the beginning of a data structure.

Include a structSize Parameter For Functions Returning Structure Data
With a structSize variable as the first element in a structure, when a function is called passing the structure as input the called function can now easily identify a specific structure version (see above).

Returning data in a structure passed by pointer reference can also create potential versioning problems. If only a pointer to an empty structure is passed, the called function can only assume its size and version. If the caller's structure version is out of date and too small, data overwrite errors can easily occur.

To enhance compatibility for structures passed by pointer for returning data, include a structSize parameter along with the structure pointer parameter passed to the function. The structSize parameter allows the called function to identify specifically which version of a structure is being passed, and then act accordingly in a compatible manner.

FOO         myFoo;       /* FOO object instance handle */
FOOINFO     myFooInfo;   /* FOOINFO data structure     */
MRESULT     result;      /* function return code       */

/* create a FOO instance (foo instance handle is returned) */
result = Foo_Create( &myFoo );
_ASSERT( SUCCEEDED(result) );

/* get FOOINFO struct data */
result = Foo_GetFooInfo( myFoo, sizeof(FOOINFO), &myFooInfo );
_ASSERT( SUCCEEDED(result) );

By explicitly passing the size of the structure, the called function can now distinguish between different structure versions:

/* Return FOOINFO structure data */
MRESULT Foo_GetFooInfo(
    FOO       foo,           /* input,  foo handle             */
    int       fooInfoSize,   /* input,  fooInfo struct size    */
    FOOINFO  *fooInfo );     /* output, fooInfo information    */
{
    if ( fooInfoSize == sizeof(FOOINFO_REV1) )
    {
        /* cast fooInfo to old FOOINFO_REV1 and process accordingly */
    }
    else if ( fooInfoSize == sizeof(FOOINFO) )
    {
        /* process fooInfo with current FOOINFO definition */
    }
    else
    {   /* We can also double check we are getting passed a valid parameter. */
        /* Something is bad if fooInfoSize doesn't match any size we expect! */
        _ASSERT( FALSE );  /* debug trap */
    }
} /* Foo_GetFooInfo() */
 

The preceding was excerpted from Metagraphics C/C++ Programming Guidelines manual, developed internally as part of our on-going efforts to improve the quality of our software products. Implementing a written corporate programming standards document is one of the first steps in the Capability Maturity Model for Software process (CMM-SW). For additional information on CMM-SW visit the Carnegie Mellon University, Software Engineering Institute web site at: http://www.sei.cmu.edu/cmm/cmm.html


Link To Metagraphics Home Page
http://www.metagraphics.com

© Copyright 1999 - Metagraphics Software Corporation. All rights reserved.