The PLIB general Utility Library.

By Sebastian Ude

Introduction

The 'UL' utility library is primarily targeted towards hiding common operating system functions behind a thin layer that makes them portable. Additionally, it provides some helper classes or routines that were usually written to support certain parts of the PLIB library, but at some point we felt that they could be useful for user applications as well.

UL is a part of PLIB.

Contents:

Quick reference:

Classes:

Non-class functions:

Misc. routines

ulSleep


  void ulSleep ( int seconds ) ;
Now comes a typical example of what the UL library does. If you need to 'sleep' for, say, 3 seconds, then under Linux/UNIX, you'd need to call:

  sleep ( 3 ) ;
But under MS-Windows, you have to say:

  Sleep ( 3000 ) ;
In order to avoid writing non-portable code, you can instead call:

  ulSleep ( 3 ) ;
...under either operating system.

ulMilliSecondSleep


  void ulMilliSecondSleep ( int milliseconds ) ;
Same as ulSleep, except that it sleeps (as you may have guessed) a certain number of milliseconds instead of seconds.

File handling

ulFindFile


  void ulFindFile ( char *filenameOutput, const char *path, const char * tfnameInput, const char *sAPOM ) ;
Basically, this utility function adds tfnameInput to the path and puts this into the buffer filenameOutput.

It handles special chars in path:

If there are ";" in path, the path-variable is interpreted as several paths "segments", delimited by ";". The first file found by this function is returned. It looks from left to right. A segment may end in $(...). ulFindFile will then look in in this path and recursively in all the sub-paths.

Some examples:

To load *.MDl-models, it is very nice to set the texture path to "$(APOM);$(APOM)/texture;$(APOM)/../texture". This consists of three segments and tells ulFindFile to look in the path of the model, in a subpath texture and in a path texture "besides" the path of the model. Some *.mdl-models are shipped in a directory which contains a "texture"-directory, a "Model"-directory and others. In this case you find the texture in "$(APOM)/../texture".

Another example: You have all your textures in a directory-structure under /roomplan:

textures --+-- Wallpapers
           |
		   +-- Wood --+-- Oak
           |          |
		   |          +-- pine
           ...
Then you should simply use the following texture path: "/roomplan/$(...)"

ulFileExists


  bool ulFileExists ( const char *fileName ) ;
Returns "true" if a file with the name 'fileName' exists, or "false" if it does not.

ulIsAbsolutePathName


  int ulIsAbsolutePathName ( const char *pathname ) ;
Returns '1' if 'pathname' is an absolute pathname or '0' if it is an relative one.

ulGetCWD


  char * ulGetCWD ( char *result, int maxlength ) ;
Stores the current working directory in 'result', which has enough space for 'maxlength' characters, including the trailing '\0'. On success, 'result' is returned.

ulMakePath


  char * ulMakePath ( char *path, const char *dir, const char *fname ) ;
Concatenates the strings 'dir' and 'fname', puts the system's slash character inbetween and stores stores the result in 'path'. Be sure that the buffer 'path' is large enough to store 'strlen ( dir ) + strlen ( fname ) + 2' (slash and trailing '\0') characters.

Directory handling

struct ulDir

This structure provides a portable way to read directories. To allocate and initialize a new ulDir structure, call:

  ulDir * ulOpenDir ( const char* dirname ) ;
This function returns a pointer to a newly allocated ulDir structure on success or a NULL pointer if the specified directory could not be read.

After you have constructed an ulDir structure, the directory content can be read with subsequent calls to:


  struct ulDirEnt
  {
    char d_name [ UL_NAME_MAX+1 ] ;
    bool d_isdir ;
  } ;
  ulDirEnt * ulReadDir ( ulDir *dir ) ;
This function returns a pointer to a ulDirEnt structure which resides in the corresponding ulDir structure and which has the above form. The "d_isdir" flag indicates if a directory entry is another directory. If the end of the directory has been reached, the function returns NULL.

To free an ulDir object and to close the associated directory stream, please call:


  void ulCloseDir ( ulDir *dir ) ;

Data storage

class ulList


  ulList::ulList ( int init_max = 1 ) ;
This class stores a list of generic (void*) pointers using an automatically-growing array. Since the process of resizing the internal array is rather expensive, it is important that one picks a reasonable default array size (init_max) when constructing an ulList object if performance matters. Remember that a too large size means that some memory is wasted (but memory is cheap nowadays), while a too small one means that expensive array resize operations will be necessary later.

If you have absolutely no clue about how many entities will be stored, you should probably look at the linked list class below, which has it's own disadvantages, though.

Once you have constructed an ulList (hopefully with a good initial size), you can insert an element using one of the following methods:


  void ulList::addEntity ( void *entity ) ;
  void ulList::addBefore ( int n, void *entity ) ;
While the first one simply adds the new entity to the tail of the list, the second one lets you specify an exact position (0 is the first element). Note that due to it's array implementation, inserting an entity at the head or the middle of the ulList requires that the following entities are all shifted one array slot to the right, which can be a rather expensive operation.

These functions automatically check whether there is room for one more element and double the internal array's size if necessary. However, as said previously, resizing the array is a rather expensive process, so it is not recommended that you rely too much on this behavior. Instead, pick a good initial array size.

Note that ulList allows you to have multiple entities with the same data value in the list. Please also note that ulList does not make it's own copy of the data pointed to by 'entity'. It is up to you to take care of that the memory region that 'entity' points to holds something useful as long as the ulList exists. Be especially careful with addresses of variables that have a limited lifetime.

To retrieve an entity, call:


  void * ulList::getEntity ( unsigned int n ) ;
Where 'n' is the position of the entity in the list. If 'n' is not a valid index, NULL is returned. Once you have called this function, you can use subsequent calls to

  void * ulList::getNextEntity ( void ) ;
in order to retrieve the following entities in the list. If there are no more entities, this function will return NULL.

To remove an entity, call one of:


  void ulList::removeEntity ( unsigned int n ) ;
  void ulList::removeEntity ( void *entity ) ;
Where the second function removes the first entity with the specified data value in case there are multiple ones.

To replace the value of an entity, call one of:


  void ulList::replaceEntity ( unsigned int n, void *new_entity ) ;
  void ulList::replaceEntity ( void *old_entity, void *new_entity ) ;
Where the second function will replace the value of the first entity with the specified old data value in case there are multiple ones.

And finally, you can retrieve the number of entities stored in the list (that's not necessarily the internal array's size), remove all entities or retrieve the position of an entity by specifying it's data value:


  void ulList::getNumEntities ( void ) const ;
  void ulList::removeAllEntities () ;
  int  ulList::searchForEntity ( void *entity ) const ;
Where the latter returns a negative value if no entity with the specified value was found in the list, and otherwise the position of the first entity with the specified data value.

class ulLinkedList


  ulLinkedList::ulLinkedList () ;
The ulLinkedList class stores generic (void*) pointers using a linked list of nodes where each node maintains a pointer to the next node.

This technique has some advantages compared to an array implementation of a list like ulList.

There are some disadvantages, though: Decide yourself if a list implemented as an array such as ulList or a linked list implementation like this one fits your needs better.

Once you have constructed a ulLinkedList object, you can insert a node using one of:


  void ulLinkedList::appendNode ( void *data ) ;
  void ulLinkedList::prependNode ( void *data ) ;
  void ulLinkedList::insertNode ( void *data, int pos ) ;
While "appendNode" adds the new node at the tail of the list, "prependNode" will place the new node at the head of the list, and "insertNode" allows you to specify the desired position ('0' is the first node) yourself; 'pos' must be either '0' or a number between '0' and the number of nodes minus one. "prependNode" is equal to calling "insertNode" with pos == 0.

Note that ulLinkedList allows you to have two nodes with the same data in the list. Also note that just as ulList, ulLinkedList does not make it's own copy of the memory pointed to by 'data', so make sure that the corresponding memory location holds something useful as long as the list exists.

To retrieve the number of nodes in the list, call:


  int ulLinkedList::getNumNodes ( void ) const ;

To retrieve the data of the node at a certain position in the list, call:


  void * ulLinkedList::getNodeData ( int pos ) const ;
Where 'pos' must be a number between '0' and the number of nodes minus one, again.

If you need to retrieve the position of a node in the list by specifying it's data value, call:


  int ulLinkedList::getNodePosition ( void *data ) const ;
If there is more than one node whose data value is 'data' in the list, this function will return the position of the first one. If there is no node with the specified data value in the list, this function will return a negative number to indicate failure.

Checking if the return value of "getNodePosition" is non-negative is also the recommended way to determine whether there exists at least one node with a certain data value in a list.

To remove a node from the list, call one of:


  bool ulLinkedList::removeNode ( void *data ) ;
  void * ulLinkedList::removeNode ( int pos ) ;
Where the first function returns 'true' if the node was sucessfully removed or 'false' if it could not find a node whose value is 'data'. In case there is more than one node whose data value is 'data', it will remove the first one. With the second function, 'pos' has to be a number between '0' and the number of nodes minus one. It's return value is the removed node's data value.

To iterate over the list (starting from the head) and to have a custom function being called for each node's data pointer, call:


  typedef bool (*ulIterateFunc)( void *data, void *user_data ) ;
  void * ulLinkedList::forEach ( ulIterateFunc fn, void *user_data = NULL ) const ;
The iteration process will stop if your ulIterateFunc returns 'false', in which case "forEach" returns the data value of the node at which the iteration stopped, or if the tail of the list has been reached, in which case "forEach" returns NULL. The user_data pointer will be passed to your ulIterateFunc as the second argument.

ulLinkedList allows you to maintain a sorted list as an option. To set up a sorted list, simply be sure to insert all nodes using the sorted insertion function as soon as there is at least one node in the list. It's prototype is:


  typedef int (*ulCompareFunc)( const void *data1, const void *data2 ) ;
  int ulList::insertSorted ( void *data, ulCompareFunc comparefn ) ;
Where 'comparefn' is your custom comparison function that takes two data pointers, compares them and returns a memcmp / strcmp-like result: The return value of "insertSorted" is the position of the new node in the list on success, or a negative value if you tried to do a sorted insertion on a non-sorted list, that is a list which contains more than one node of which at least one was not inserted using the sorted insertion function. In the latter case, the new node would not have been inserted to the list.

To determine whether a list is sorted or not, call the following function:


  bool ulLinkedList::isSorted ( void ) const ;
Note that with a linked list, you must not modify the criteria of a node's data that is used for sorting without removing the node from the list and re-inserting it.

Finally, you can empty a list (remove all nodes) by calling:


  typedef bool (*ulIterateFunc)( const void *data ) ;
  void ulList::empty ( ulIterateFunc destroyfn = NULL, void *user_data = NULL ) ;
Where "destroyfn" is an optionally specified function that will be called with each destroyed node's data pointer as the first and with user_data as the second argument. This is for example useful if the list entries are pointers to dynamically allocated objects that have to be freed on destruction of the list. The return value of the specified function is ignored.

ulHashTable

Not yet.

Error handling

ulSetErrorCallback


  typedef void (*ulErrorCallback) ( enum ulSeverity severity, char* msg ) ;
  void ulSetErrorCallback ( ulErrorCallback cb ) ;
PLIB has an internal error handling system that the subsystems use to report debug, warning or error messages. An application can set up an error callback that PLIB will call whenever such a message occurs in addition to printing the message on the user's terminal.

ulSeverety indicates the importance of an message and is currently defined the following way:


  enum ulSeverity
  {
    UL_DEBUG,
    UL_WARNING,
    UL_FATAL
  } ;
Where

ulGetErrorCallback


  typedef void (*ulErrorCallback) ( enum ulSeverity severity, char* msg ) ;
  ulErrorCallback ulGetErrorCallback ( void ) ;
Returns the current error callback (if any, else NULL is returned).

ulGetError


  char * ulGetError ( void ) ;
Returns a pointer to the error buffer, that is, the last error message or an empty string if there were not any error messages or if the error buffer has just been cleared.

ulClearError


  void ulClearError ( void ) ;
Clears the error buffer.

Misc. classes

class ulDynamicLibrary


  ulDynamicLibrary::ulDynamicLibrary ( const char *libname ) ;
This class provides a portable way to load a dynamic library and to retrieve the memory address of a specific function afterwards. When constructing an ulDynamicLibrary object, you have to specify the name of the dynamic library you want to operate on without the platform-specific file extension for dynamic libraries.

Afterwards, you can retrieve the memory address where a function / symbol of the library has been loaded by calling:


  void * ulDynamicLibrary::getFuncAddress ( const char *funcname ) :
This function returns NULL if the specified symbol was not found.

ulClock


  ulClock::ulClock () ;
No further documentation yet.

ulPropertySet

Not yet.

String handling

ulStrEqual


  int ulStrEqual ( const char *s1, const char *s2 ) ;
This function provides a portable way to compare two strings while ignoring the case of the characters. We need it since half of the machines on the planet provide strcasecmp and the other half stricmp for this purpose.

In contrast to the libc string comparison routines, this routine returns '1' if the strings are equal and '0' if they are not.

ulStrNEqual


  int ulStrNEqual ( const char *s1, const char *s2, int len ) ;
Same as ulStrEqual, except that it only compares the first 'len' characters of the strings.

Endian handling


  bool ulIsLittleEndian ( void ) ;
  bool ulIsBigEndian ( void ) ;

  unsigned short ulEndianLittle16 ( unsigned short x ) ;
  unsigned int ulEndianLittle32 ( unsigned int x ) ;
  float ulEndianLittleFloat ( float x ) ;

  unsigned short ulEndianBig16 ( unsigned short x ) ;
  unsigned int ulEndianBig32 ( unsigned int x ) ;
  float ulEndianBigFloat ( float x ) ;

  void ulEndianLittleArray16 ( unsigned short *x, int length ) ;
  void ulEndianLittleArray32 ( unsigned int *x, int length ) ;
  void ulEndianLittleArrayFloat ( float *x, int length ) ;

  void ulEndianBigArray16 ( unsigned short *x, int length ) ;
  void ulEndianBigArray32 ( unsigned int *x, int length ) ;
  void ulEndianBigArrayFloat ( float *x, int length ) ;

  unsigned short ulEndianReadLittle16 ( FILE *f ) ;
  unsigned int ulEndianReadLittle32 ( FILE *f ) ;
  float ulEndianReadLittleFloat ( FILE *f ) ;

  unsigned short ulEndianReadBig16 ( FILE *f ) ;
  unsigned int ulEndianReadBig32 ( FILE *f ) ;
  float ulEndianReadBigFloat ( FILE *f ) ;

  size_t ulEndianWriteLittle16 ( FILE *f, unsigned short x ) ;
  size_t ulEndianWriteLittle32 ( FILE *f, unsigned int x ) ;
  size_t ulEndianWriteLittleFloat ( FILE *f, float x ) ;

  size_t ulEndianWriteBig16 ( FILE *f, unsigned short x ) ;
  size_t ulEndianWriteBig32 ( FILE *f, unsigned int x ) ;
  size_t ulEndianWriteBigFloat ( FILE *f, float x ) ;
No further documentation yet.
Valid HTML 4.0!
Sebastian Ude <ude@handshake.de>