Arved said:
I might note that I ran across one comment by an Axis 2 C developer
where he said that the model to be followed was
"typedef done inside the header and the struct declaration is in
source...in a case you still want to move the struct to the header (it
is not a much recommended approach in c programming) ... [Ed. How-To
description of procedure follows]"
Maybe I missed something in my years away from C, but those
recommendations were new to me.
His description's not quite right, but incomplete structure
declarations are an important aspect of encapsulation in C.
Ignore any mention of "typedef" for a moment. typedef is a misnomer,
since it doesn't define a type, just an alias for an existing type.
(It's also of questionable utility. Some people think it's useful for
defining complex function-pointer types; I say if you don't understand
C's function-pointer syntax, don't write C code.)
Yeah, the typedef mention I ignored. I would use it myself with structs
for the reason that everyone does, so that you don't have to retype
'struct' all the time. What the Axis 2 C developer meant by that bit is
basically the incomplete structure declaration that you mention below.
Which makes sense in general.
In C, new types are defined using the struct keyword (and sometimes
union, but that's really just a specialized struct). The struct
keyword can do either or both of two things:
- define a structure type
- introduce a type name into the struct-tag namespace
The former is necessary if you want to inspect the contents of an
object of the type, evaluate its size or the size of the type, etc.
But it is not necessary to define certain derivative types, such as
the const-qualified equivalent type, or the associated pointer type.
The latter is what lets you encapsulate. In a header, you provide an
incomplete structure declaration and an API that uses the pointer type
derived from it:
struct foo;
struct foo *CreateFoo();
DoThingToFoo(struct foo *, ...);
PureFunctionOnFoo(const struct foo *, ...);
DeleteFoo(struct foo *);
Consumers of your API have no access to the implementation of struct
foo, so they're insulated from any changes to it. And since struct foo
* is a perfectly good object pointer, they can do whatever they'd do
with any other pointer, except dereference it.
(You can of course wrap "struct foo" in a typedef, if the people who
use your API are too lazy to type the word "struct".)
Which is what the Axis 2 C headers do. Except that they are quite
inconsistent in their use of the typedef; makes you wonder why they
threw it in.
Note the initial "struct foo;" is necessary. Otherwise the use of an
unknown "struct foo" in the declarations of the API would only
introduce the type name in "prototype scope", which ends at the end of
each declaration. Prototype scope is basically useless.
This is a useful and fairly widely used technique - though not nearly
as widely as it should be. Of course, the API has to provide for
whatever its consumers need, since the consumers don't have direct
access to the contents of the structure, can't allocate or copy one, etc.
I'm cool with all this. Good explanation. I learnt C quite a long time
ago and don't remember that incomplete types were being strongly pushed
back then. Nor have I used C much in over 20 years. I must admit, I'm
much more aware of comparable techniques in C++, and simply never made
the backwards leap. Bit too much of not seeing the forest for the trees.
I suspect that when I did write a fair bit of C, that I wrote the client
code in such a way that I usually needed the full declaration anyway -
that is, no particular APIs to wrap the anticipated handling of the
struct. You make a good point about writing the APIs in such a way that
client code can do what it needs to do with only the pointer.
This is where Axis 2 C made this mistake, as I described. Their WSDL
stub generator (and probably their skeleton generator also) produces
code that deferences at least one very important struct pointer. The
relevant header (and accompanying implementation) has no provision for
supplying a suitable function that would obviate the need to dereference.
As I see it you'd have 2 options here - (1) expose the complete struct
in the header, and not change the client, or (2) add a function to carry
out the operation that the client code requires, which means modifying
both the client (the code generator actually if you're going to do this
a lot) and the library. Either way you have to modify the library code,
which is not something you should require of your library users.
AHS