Mac OS 9 Discussion > Development & Programming

Pascal strings in struct for reading a resource

<< < (2/3) > >>

joevt:
The Resorcerer template says the strings are variable length, not fixed length.

joevt:
It appears the filler bytes between strings allow the strings to be treated as C strings.

The comment string has a two byte length and is therefore not a pascal string.

Since the ckid is variable length, I would maybe copy only the non-variable part from the resource to CkidRecord (everthing before projectPath).

C in classic Mac OS may have an option to print pascal strings. Is it "%p" instead of "%s"? I forget. That's not going to work for the comment which as a two-byte length.

Since the strings always have a terminating null character, you can treat them as C strings (unless you think someone might try putting a null character inside the string).

If you need to modify a string, you need to remember to update the length. If you update the length, you need to move all the following strings. If you make a string longer, then you need to move all the strings before making it longer.

tenfifty2:
Ah, thank you joevt, that was really enlightening.  I didn't think to look at it with Resourcerer. 

Yes, the strings can be variable length, that's what I meant when I said the doc says they're Pascal strings, which the Mac OS includes represent as Str255.  So when you say the pad can be used to treat it like a C string, how does that deal with the first part where the string length is coded?  Can we still do this with the struct?

Another option I had was something like this:


--- Code: ---#include <time.h>

typedef unsigned long uLong;
typedef unsigned short uShort;
typedef struct{
uLong checkSum; // checkSum
long LOC; // location identifier
short version; // ckid version number
short readOnly; // checkout state; 0=modifiable
char branch; // if modifiable & byte not 0, then branch was made on checkout
Boolean modifyReadOnly; // Did user execute "ModifyReadOnly"?
uShort history; //  1 if history present, 0 if not
uShort commentLength; // length of current comment if history is present
time_t checkoutDate; // date and time of checkout
time_t modificationDate; // modification date of file
uLong PIDa; // PID.a
uLong PIDb; // PID.b
short userID; // user ID
short fileID; // file ID
short revisionID; // revision ID
char theRest[1536] // We have to parse this out manually due to pstrings
} CKIDRec, *CKIDPtr, **CKIDHandle;

--- End code ---

Then taking CkidRecord.theRest and parsing it out.  Not entirely sure how to do that though... I've taken a few stabs at it and I usually end up crashing the whole machine.  Have a suggestion?

Thank you much!

Daniel:
As it seems you have found out, C doesn't support having variable-length data in the middle of a struct. You have to use pointer arithmetic.

Fortunately, none of the fixed-size data is broken up by variable-length data. So you don't have to have multiple structs (or go with the extreme solution of having a separate pointer variable for every single value you want to extract from that resource). You just need a buffer big enough to hold the max length of everything, plus a bunch of pointer variables (one for the struct at the front, and then one per string).

If my guess is right and the strings in the resource have both length bytes and null terminators (making them both C and Pascal strings), then this code will work. Though it might need stylistic tweaking (the typedefs don't have Ptr & Handle varients, for instance).


--- Code: ---
typedef struct {
uLong checkSum; // checkSum
long LOC; // location identifier
short version; // ckid version number
short readOnly; // checkout state; 0=modifiable
char branch; // if modifiable & byte not 0, then branch was made on checkout
Boolean modifyReadOnly; // Did user execute "ModifyReadOnly"?
uShort history; //  1 if history present, 0 if not
uShort commentLength; // length of current comment if history is present
time_t checkoutDate; // date and time of checkout
time_t modificationDate; // modification date of file
uLong PIDa; // PID.a
uLong PIDb; // PID.b
short userID; // user ID
short fileID; // file ID
short revisionID; // revision ID
} FixedSizeData;

typedef struct {
FixedSizedData  * fixedSizeData;
Str255 * projectPath;
Str255 * userName;
Str255 * revisionNum;
Str255 * fileName;
Str255 * taskDesc;
Str255 * comment;
} CKIDRecPStr;

typedef struct {
FixedSizedData  * fixedSizeData;
char * projectPath;
char * userName;
char * revisionNum;
char * fileName;
char * taskDesc;
char * comment;
} CKIDRecCStr;

void parseCKIDPStr(CKIDRecPstr * ckidRec, void * ckidBuf) {
  unsigned char * buf = (char *) ckidBuf;
  ckidRec->fixedSizeData = (FixedSizedData *) buf; buf +=sizeof FixedSizedData;
  ckidRec->projectPath = (Str255 *) buf; buf += 1 + buf[0] + 1;//deal with the len byte + string + null term sandwich
  ckidRec->userName = (Str255 *) buf; buf += 1 + buf[0] + 1;
  ckidRec->revisionNum = (Str255 *) buf; buf += 1 + buf[0] + 1;
  ckidRec->fileName = (Str255 *) buf; buf += 1 + buf[0] + 1;
  ckidRec->taskDesc = (Str255 *) buf; buf += 1 + buf[0] + 1;
  ckidRec->comment = (Str255 *) buf; buf += 1 + buf[0] + 1;
}

void parseCKIDCStr(CKIDRecCstr * ckidRec, void * ckidBuf) {
  unsigned char * buf = (char *) ckidBuf;
  ckidRec->fixedSizeData = (FixedSizedData *) buf; buf +=sizeof FixedSizedData;
  ckidRec->projectPath = (char *) buf+1; buf += 1 + buf[0] + 1;//deal with the len byte + string + null term sandwich
  ckidRec->userName = (char *) buf+1; buf += 1 + buf[0] + 1;
  ckidRec->revisionNum = (char *) buf+1; buf += 1 + buf[0] + 1;
  ckidRec->fileName = (char *) buf+1; buf += 1 + buf[0] + 1;
  ckidRec->taskDesc = (char *) buf+1; buf += 1 + buf[0] + 1;
  ckidRec->comment = (char *) buf+1; buf += 1 + buf[0] + 1;
}

--- End code ---

joevt:
A Str255 is a fixed size field of 256 bytes. The string inside the Str255 is variable length but a Str255 is not variable length.

TheRest could be up to 64K since the length for the comment is two bytes. I think in C you would define it like this:

--- Code: ---char theRest[];

--- End code ---

or this (if the compiler doesn't like flexible arrays):

--- Code: ---char theRest[0];

--- End code ---

or this (if the compiler doesn't like zero sized arrays):

--- Code: ---char theRest[1];

--- End code ---

With an array size of 1, You would have to use sizeof(CKIDRec) - sizeof(CKIDRec.theRest) to calculate the size of the non-string data.
Or use offsetof(CKIDRec, theRest) to calculate the size of the non-string data.
If the compiler doesn't have offsetof, then #define it. https://en.wikipedia.org/wiki/Offsetof

If the data is in a locked resource, then you don't need to copy it to a local or global variable.

--- Code: ---HLock(ResHandle);
CKIDPtr ckid = *(CKIDHandle)ResHandle;
char* p = &ckid->theRest;
char* projectPath = p; p += *p + 2;
char* userName    = p; p += *p + 2;
char* revisionNum = p; p += *p + 2;
char* fileName    = p; p += *p + 2;
char* taskDesc   = p; p += *p + 2;
char* comment   = p; p += *(unsigned short *)p + 3;
long size = p - (char*)ckid;
printf("size:%ld handlesize:%ld\n", size, GetHandleSize(ResHandle));
printf("username:\"%s\"\n", username+1);
printf("revisionNum:\"%s\"\n", revisionNum+1);
printf("fileName:\"%s\"\n", fileName+1);
printf("taskDesc:\"%s\"\n", taskDesc+1);
printf("comment:\"%s\"\n", comment+2);

--- End code ---

A char pointer is defined for each string. Since the strings have one or two size bytes, you need to add 1 or 2 when passing it to functions that expect a C string.

p needs to be changed to unsigned char * if char is not unsigned.

You have to recalculate the pointers if you unlock the handle. You need to unlock the handle if you want to increase the size.

Navigation

[0] Message Index

[#] Next page

[*] Previous page

Go to full version