Re: struct versioning



Pres wrote:
Noticed that several windows API's take a size parameter.
i.e. one of the members of the struct is dwSize which is supposed
to be initialized by client code, by taking the sizeof of the struct.

I assume this is to enable versioning of structures

/* first version of struct */
typedef struct { int dwSize; type1 t1; } A;

/* 2nd version of struct in new version of the library */

typedef struct { int dwSize; type1 t1; } A_v1; // Old struct renamed to A_v1
typedef struct { int dwSize; type1 t1; type 2;} A;

VMS used/uses this technique extensively in the "control
blocks" that programs use to communicate with various kinds
of system services (maybe that's where Windows got the idea:
VW, MN, ST). And once upon a time, it blew up badly.

The VAX' assembly language had a macro facility that made
it easy to create and populate these control blocks: You'd
write the WIDGET macro-opcode and provide operands for the
fields you wanted to specify: ORG=SEQ,RECFM=VFS,BUFSIZ=4096
or whatever, and the macro expansion would fill all the other
fields with suitable defaults. Among these other fields there
was very often a length indicator for the whole block. As the
subsystem gained new capabilities that required new fields in
the control block, they'd be added at the end so the blocks
in already-existing executables would still have the correct
format for what was now a prefix of the expanded block, and
the system services would use the length indicator to discover
which form they were dealing with.

This scheme of initializing the control blocks worked fine
in assembly language, but did not mesh well with C. It was
easy enough to declare a struct with the necessary elements,
but how to get all the fields initialized? You could write an
initializer

struct WIDGET widget = { ... };

.... but then the source code would be sensitive to the exact
order of all the fields, and would initialize all un-mentioned
elements to zero rather than to a "suitable default." A clumsy,
error-prone, and unhappy state of affairs. (C99's designated
initializers were not available then, but even they would not
have really solved the problem.)

To remedy this, the VMS libraries for C provided read-only
pre-initialized copies of the various control blocks, and you
built your own blocks at run-time rather than at compile time:

extern const struct WIDGET widgetTemplate; /* in a .h */
struct WIDGET widget;
widget = widgetTemplate;
widget.org = WIDGET_SEQ;
widget.recfm = WIDGET_VFS;
widget.bufsiz = 4096;

.... and you then had a properly-initialized struct WIDGET,
ready to use with the system services. Among the fields copied
from widgetTemplate was, of course, the length indicator.

And that's what broke when VMS added the ability to read
ISO CD-ROM disks. The WIDGET struct needed a couple extra
fields, so DEC appended them and relied on the length indicator
to tell whether they were present. And the new VMS upgrade
included new shared libraries that had a pre-initialized copy
of the expanded widgetTemplate, including its longer length.
And all of a sudden, pre-existing C executables compiled with
the old, shorter struct WIDGET started breaking: They had all
devoted 40 bytes (say) to each struct WIDGET, and then they'd
filled them in from a new-form widgetTemplate whose length field
proclaimed "I'm 48 bytes long!", and then the system service
happily clobbered whatever was adjacent to the program's struct.

(This was one of those relatively rare occasions when I
found an interactive debugger essential to diagnosing the
problem.)

On reflection, I came to the opinion that this sort of
technique, while useful in other languages, is not a good fit
for C programs. C's initializers, even now, are not robust
enough to do a reliable job of building the struct variants
correctly and conveniently (and you can bet that if proper
initialization is inconvenient, it will often be incorrect).
Looked at another way, the many-versioned struct is really a
form of type-punning, with all the concomitant dangers thereof.

If you're using somebody else's pre-established interface,
there's not much you can do about their questionable choice of
method. But if you're writing your own and if you need to
maintain "binary compatibility" with existing code, I think it's
much better to handle versioning by adding a brand-new type and
a bunch of brand-new function names:

struct WIDGETv1 { ... };
int WaggleWidgetV1(WIDGETv1 *);
void WrestleWidgetV1(WIDGETv1 *);

struct WIDGETv2 { ... };
int WaggleWidgetV2(WIDGETv2 *);
void WrestleWidgetV2(WIDGETV2 *);
long WeakenWidgetV2(WIDGETv2 *, double);

It's not pretty (although some preprocessor lipstick could be
smeared on), but I think it's the best C can offer short of
"recompile the world."

--
Eric Sosman
esosman@xxxxxxxxxxxxxxxxxxxx
.



Relevant Pages

  • Re: [RFC 04/10] Temporary struct vfs_lookup in file_permission
    ... Used to check for read/write/execute permissions on an already opened ... int file_permission(struct file *file, int mask) ... I didn't use initializers because they initialize the entire data structure. ... In case of struct vfs_lookup, unless the LOOKUP_OPEN flag is set, then the ...
    (Linux-Kernel)
  • Re: [PATCH] Detect mmconfig on nVidia MCP55
    ... more reviewable - and more pleasant to ... Same goes for static structure initializers. ... static const struct file_operations perf_fops = { ... The typos are of the ...
    (Linux-Kernel)
  • Re: missing braces around initializer
    ... to warn about missing braces or struct members in initializers. ... it's annoying that gcc warns about this. ...
    (comp.lang.c)
  • Re: [patch] add private data to notifier_block
    ... notifier_block struct to add a void *, ... initializers -- their bare priority initialization might now shift to ... putting the void * after the int. ...
    (Linux-Kernel)
  • I need an embeddable menu widget
    ... I'm a maintainer of Ruby/Tk. ... I want a kind of a menu widget which is embeddable into a container ... struct dummy_TkMenu *menuPtr; ... Tcl_Interp *interp; ...
    (comp.lang.tcl)