Confused Development

I develop software and I often get confused in the process. I usually find the answers after a while, but a month later I can't remember them. So from now on, I will write them down here.

Wednesday, April 05, 2006

Dynamically creating a va_list in C ...

... seems to be impossible, from what I can find out on the web.

A va_list is a variable argument list which you can define as an argument to a function (or a method in Objective-C). A prominent example of this is vprintf (see printf and variadic functions on Wikipedia). In Apple's Cocoa library, one example is NSString's initWithFormat:arguments:.

My problem was this: I want to call this method, but I don't know at compile-time which arguments I want to pass. Instead, I create an array with arguments. Now, I thought I would be able to create a va_list from this array and pass it to initWithFormat:arguments:, but, as noted above, this is apparently not possible. So, to make a long story short, I had to conjure up my own mock-up of such a format function. I only need to deal with strings, so basically I scan for occurrences of "%@" (indicates an object in Objective-C) and replace them with the elements from the array. Not very elegant, but it works.

- (NSString *)evaluatePseudoFormat: (NSString *)format
                     withArguments: (NSArray *)array
  NSMutableString *evaluatedString = 
    [NSMutableString stringWithString: format];
  NSRange varRange, scanRange;
  int length = [format length];
  scanRange = NSMakeRange(0, length);
  int index = [array count];
  NSString *replacement;
  while ((varRange = [format rangeOfString: @"%@"
      options: NSBackwardsSearch
        range: scanRange]).length > 0 && index >= 0) {
    replacement = [array objectAtIndex: --index];
    [evaluatedString replaceCharactersInRange: varRange 
                                   withString: replacement];
    length = varRange.location;
    scanRange = NSMakeRange(0, length);
  return evaluatedString;

Don't ask me why I search backwards. I had some complicated reason for that. After changing some things, the reason became obsolete. However, since the code works, I don't see why I should change it now. ;-)


At 7:46 am, Blogger Peter Hosey said...

It is possible to forge a va_list. It's simply a packed array; for example, a char followed by an int would be a total of five bytes long (assuming four-byte int).

So, something like this:

union {
va_list varargs;
void *packedArray;
} myFakeArray;
void *ptr = myFakeArray.packedArray = alloca(5); //See x-man-page://alloca
*(char *)ptr = myChar;
ptr += sizeof(char);
*(int *)ptr = myInt;

vprintf("Character %c; int %i\n", myFakeArray.varargs);

The C standard doesn't actually define what a va_list is, though, so you should probably file this solution under “non-portable fragile black magic”. Thus, while the above is true of Mac OS X today, it may vary on some other OS or some future version of OS X.


Post a Comment

<< Home