UEFI News and Commentary

Sunday, September 08, 2013

Creating New Object Types With SysLib

SysLib adds object-oriented container features to C, using standard structures, a library and some macros. This article talks about the basics of creating a new object type with the library, using the SysList object type as an example.

The SysList type is declared with the macro DECL_SYS_OBJ_TYPE, as follows:

DECL_SYS_OBJ_TYPE (SysList, SYS_LIST_SIGNATURE, &SysObjType, SysListInit, SysListEmpty, NULL, SysListIsValid, NULL);

SysList - The first argument (SysList) is the name of the object structure.  Here is the structure definition:

typedef struct _SYS_LIST {
  CONST SYS_OBJ_TYPE *Obj;

  SYS_LIST_ENTRY     Header;
} SYS_LIST;

typedef SYS_LIST SysList;

Notice that the first member of the structure is a pointer to SYS_OBJ_TYPE. This is a requirement for all of the objects in SysLib. Also, by my own convention, I actually name the object structure SYS_LIST and then use a typedef (typedef SYS_LIST SysList) to get the name SysList.

SYS_LIST_SIGNATURE - The second argument is a 32-bit unsigned integer that identifies that object type. This is not absolutely required, but it helps in debugging if you make the bytes of the number ASCII characters. Some of the default debugging functions will output this signature. The EDK2 macro SIGNATURE_32 merges 4 ASCII characters into a 32-bit unsigned integer and is handy for exactly this purpose.

#define SYS_LIST_SIGNATURE SIGNATURE_32 ('S','Y','S','L') 

&SysObjType - The third argument is the pointer to the parent object type. All object types should have a parent object type, even if it is the root type (SysObjType). Parent object types can provide default implementations of all 5 of the default member functions for SysLib objects, so that if you don't provide one here, the member function from the parent object type will be used.

SysListInit - The fourth argument is the name of the object initialization function. This is the function that will be called to initialize memory that has already been allocated to a valid initial state. It is possible that this function will fail, and if so, it will return NULL. Otherwise it will return the same pointer that was passed as an input parameter. You will notice that I declare this function as STATIC (so that it can only be called through the normal SysInit() macro) and that I don't call the parent object type SysObjInit, because it isn't really necessary. If this call is successful, then IsValid() sould return TRUE.

STATIC
SYS_LIST *
SysListInit (OUT SYS_LIST *p)
{
  p->Header.Prev = p->Header.Next = &p->Header; 
  p->Header.Value = 0;
  return p;
}

SysListEmpty - The fifth argument is the name of the object empty function. This is the function that will be called to free any memory used by the object, but will not free the object itself. This helps with memory leaks, and forms the basis for operations such as SysDelete() and SysTerm().

STATIC
SYS_LIST *
SysListEmpty (IN OUT SYS_LIST *p)
{
  if (!SysIsValid (p)) {
    SYSINFO ("unable to empty List container.");
    return NULL;
  }
  
  while (!SysListIsEmpty (p)) {    
    SysListRemoveHead (p);
  }
  
  return p;                 
}

This function first checks to verify that it was passed a valid object pointer, and then removes nodes from the list until the list is empty.

NULL - The sixth argument is the name of the object copy function. A NULL in this field indicates that the parent object type's copy function should be used. In this case, the default copy operator uses SysObjCopyError() will generate a run-time error if someone tries to copy a SysList, since the behavior is currently undefined. If you wanted a byte-by-byte copy, you can use the SysObjCopyDefault() also. Or you can create your own.

SysListIsValid - The seventh argument is the name of the validity checking function. A NULL in this field indicates that the parent object type's is-valid function should be used. In this case, the SysListIsValid() function does further tests on the member pointers:

STATIC
BOOLEAN
SysListIsValid (IN CONST SYS_LIST *p)
{
  if (p->Header.Prev == NULL || p->Header.Next == NULL) {
    return FALSE;
  }

  return TRUE;
}

NULL - The eighth argument is the name of the debug dump function. A NULL in this field indicates that the parent object type's dump function should be used. In this case, the default dump function uses SysObjDump() will dump the object type information, like the signature and size and all the bytes of the object.

Using this basic template, you can create a quite sophisticated series of derived objects. For example, SysLib contains both SysListO (for a list of objects) and SysListStrA (for a list of null-terminated ASCII strings), which are derived from SysList. These take advantage of the key member functions (like Empty() and Init()) to handle creation of specialized lists.

Since all of these are designed to run under UEFI, they make more powerful applications possible. Currently SysLib showcases this in both the IfrAsm and Setup applications. More coming soon.

No comments: