Modifying Resources

Although in many cases updating resources can be very simple and straightforward, it can also be fraught with many pitfalls for the unwary programmer. Aside from simply creating or modifying a resource and writing that information to a resource file, an application has to be able to reliably traverse any resource in order to access and modify the information in the resource. This section talks about traversal issues as well as creating and modifying resources and resource files.

The following functions are used to access resources. See "Resource Management Functions" in the MicroStation MDL Function Reference Manual for more information.

Function Used to
mdlResource_openFile open a resource file.
mdlResource_add add a new resource to an open file.
mdlResource_load load a resource.
mdlResource_write update a resource.
mdlResource_free free a resource.
mdlResource_closeFile close a resource file.

These functions will also be discussed later in this section.

Finding your way around a resource

Although pointer variables can't be used in resources, variable array structures can provide the same functionality in a different form. To make this clearer, consider what a C programmer does when a structure is used to keep track of data needed by a program. If the program requires a buffer of unknown size, a pointer variable is stored in the structure and when the memory is allocated for this variable, the address is stored in the pointer field of the structure. But since resources are statically sized storage areas, it must have a way of providing for this circumstance in structure definitions.

A variable sized array is assigned a length at compile time by rcomp. As rcomp compiles the resource definition, it counts the number of elements in the array. When the resource is fully compiled and all the elements have been counted, rcomp writes out the element count followed by the elements to the resource on disk. Later, at execution time, the application will load the resource using the mdlResource_load function call. Since the application knows that it is dealing with a variable sized array, it will refer to the unsigned long element count in front of the array to determine the array size.

This means that the actual structure of resources in memory containing variable sized arrays differs from what is defined in a header file describing that resource structure. This is why the structure definitions for the application and the resource manager are different - so that each knows the structure up to the point at which it is statically defined.

A simple example of this is shown below. The structure definition of the resource shows a number of fields followed by an array of additional structures.

typedef struct mysubstruct
{
long subLong1;
char subChar1;
} MySubStruct;
typedef struct myresource
{
int int1;
int int2;
char char1[4];
long long1;
#if defined (resource)
MySubStruct subStructs[];
#else
long subStructLen;
MySubStruct subStructs[1];
#endif
} MyResource;

Up to the subStructs field of the resource, the handling of this resource in memory is straightforward; the resource and MDL structures are identical. The problems occur when trying to reference the information in the subStructs field. Here is where most programmers make mistakes - different platforms and compilers have differing alignment restrictions for structures and for basic types.

The first attempt at traversing this structure will typically look something like the following code:

int i;
MyResource *rscP;
...
for (i=0; i < rscP->subStructLen; i++)
myFunc(rscP->subStructs[i].subChar1, rscP->subStructs[i].subLong1);
...

The problem with the above code sample is that it is not portable. There are no guarantees that the compiler will generate resource structures that meet requirements for long and general structure alignment restrictions. The portable way to traverse this array is to use the mdlDialog_fixResourceAddress function:

void *mdlDialog_fixResourceAddress // <= pointer to loc. of member
(
byte *resP, // => base of the structure next pointer is in
byte *memberP, // => addr of the member needing alignment fixing
int sizeMember // => alignment (usually sizeof(long))
);

The function is called with the address of the known point in the structure(typically the base or current traverse point) as the first parameter, the best guess at the address the application is trying to get to as the second parameter, and sizeof(long) as the third parameter.Using this function, we can rewrite the above code sample.

int i;
MyResource *rscP;
MySubStruct *subP;
...
// Address first array entry
subP = &rscP->subStructs[0];
for (i = 0; i < rscP->subStructLen; i++)
{
myFunc(subP->subChar1, subP->subLong1);
// Get address of next array entry
(byte *) subP + sizeof(MySubStruct), sizeof(long));
}
...

The above example was very simple compared to the way applications must traverse complicated resources such as Pulldown Menu Items or a Dialog Box Resource.Knowing what to look for simplifies the processing of resources immensely.

Once you know your way around a resource, it is straightforward creating and modifying resources.

The Resource Manager Functions

The Resource Manager provides many functions for updating resource files. These functions can be divided into three main areas:

To delete an existing resource from a resource file, an application simply calls the mdlResource_delete function, and indicates the resource file and resource to delete. If no applications have the resource loaded, the resource is deleted from the file, otherwise the request is rejected and the application must try again later.

There are two ways to add new resources to a resource file. One is to allow the Resource Manager to add the resource using the mdlResource_add function. The other is for the application to add the resource to the file itself using the mdlResource_directAdd function. Both of these functions will reject any attempt to add a resource that matches an existing resource in the file (i.e., the resource ID and class match that of a resource already in the resource file). Also, these are the only Resource Manager functions that deal explicitly with resources that do not require the resource pointer (memory area) to be allocated by the Resource Manager.

The mdlResource_add function accepts a pointer to the resource to be added, the resource class, resource ID, resource size and resource alias as input. The mdlResource_directAdd function does not require the resource and resource size as input, but does require a pointer to a RscDirectAccess structure for returning information about where to add the resource to the file. In addition, after an application has finished writing a resource to a file in conjunction with the mdlResource_directAdd function, it must call mdlResource_directAddComplete to notify the Resource Manager that the resource has been added to the file. Examples of the use of these functions are shown below:

...
// Add Text resource to the resource file
mdlResource_add(rscHandle, RTYPE_Text, rscId, textRscP, rscSize,
NULL);
// Now use regular stream file I/O to write our own user pref rsc
mdlResource_directAdd(rscHandle, RTYPE_MyPrefs, rscId1, NULL, &direct);
fseek(direct.fileP, direct.filePos, SEEK_SET);
fwrite(prefsRscP, prefsSize, 1, direct.fileP);
fflush(direct.fileP);

It is important to note that the information in the RscDirectAccess structure remains accurate only up to the point that control is given back to MicroStation. This is especially true for the file pointer field. Any time the application needs to perform direct file I/O on a resource file, it must use the proper mdlResource_direct... function to load the structure with the most up-to-date and correct information.

Updating resources is done using two functions. mdlResource_write performs the actual writing of an existing resource back to the resource file it came from. The resource pointer passed as input to this function must be the same as one originally received from the Resource Manager via mdlResource_load. This means that the size of the resource can not have changed without notifying the Resource Manager and an application cannot copy resources internally and pass the copies to the Resource Manager for processing. To resize a resource, the application needs to use the mdlResource_resize function. This function causes the Resource Manager to either allocate a new memory area to contain the resource if it is being enlarged or truncate the original buffer to reflect a smaller resource. It is the responsibility of the application to ensure that the data is properly entered into the new resource area returned before writing the resource and in the case of down-sizing a resource, saving any resource information which could be truncated.

int localPrefsSize;
MyUserPrefs localPrefs;
MyUserPrefs *currentPrefsP; // loaded from resource file
...
// Truncate preferences by removing the array at end
localPrefsSize = (byte *) &currentPrefsP->endArrayLen - (byte
*) currentPrefsP;
memcpy(&localPrefs, currentPrefsP, localPrefsSize);
localPrefs.endArrayLen = 0;
localPrefsSize += sizeof(long);
// Resize preferences rsrc and if successful, write rsrc back out
currentPrefsP = mdlResource_resize(currentPrefsP, localPrefsSize);
if (currentPrefsP)
{
memcpy(currentPrefsP, &localPrefs, localPrefsSize);
mdlResource_write(currentPrefsP);
}

Copyright © 2017 Bentley Systems, Incorporated. All rights reserved.