Re: API design
- From: Barry Schwarz <schwarzb@xxxxxxxxx>
- Date: Sat, 17 Nov 2007 18:28:34 -0800
On Sat, 17 Nov 2007 10:29:48 GMT, Jef Driesen
<jefdriesen@xxxxxxxxxxxxxxxxxxx> wrote:
I'm writing a library (to communicate with a number of devices over a
serial port) and have some questions about the design. I have now a
header and source file like this:
/* device.h */
typedef struct device device;
int device_open (device **dev, const char *name);
int device_close (device *dev);
int device_read (device *dev, void *data, unsigned int size);
int device_write (device *dev, const void *data, unsigned int size);
/* device.c */
struct device {
/* some data here */
};
int
device_open (device **dev, const char *name)
{
if (dev == NULL)
return -1;
struct device *out = malloc (sizeof (struct device));
if (out == NULL)
return -1;
/* Do some stuff here */
*dev = out;
return 0;
}
As you can see, I made the internal data structure opaque by means of
the typedef and pointers to an incomplete data structure. Now my
questions are:
1. Is there any advantage to change the typedef to
typedef struct device *device;
IMO, no advantage and a major disadvantage. If you make the change,
then code declaring a pointer will look like
device name;
which will inevitably at some point in the future lead some
unsuspecting maintenance programmer to believe he needs to add an &
when he passes name to some function expecting a pointer.
If you still decide to do it, change the typedef name from *device to
something like *device_ptr_t to give this unfortunate heir a hint.
and thus hiding the fact that the "device" type is actually a pointer.
Hide only the details that are worth hiding. As has already been
suggested, FILE is a precedent worth following.
2. I want to add support for a second type of device, which happens to
be very similar to the first one. For instance the read/write functions
are different, but the open/close functions and the contents of the
struct itself are the same. How can I implement this without copying the
code to a new pair of header and source files?
Again, FILE and the standard library show a reasonable approach. For
the things that are common, such as the struct declaration and the
open and close functions, you only need one. For the things that are
specific to a particular device (read and write functions), you need
unique versions. But the prototypes for these unique functions can
(not must) still be in a common header.
On my system, the standard headers include both the public and the
opaque information. You can't really hide the opaque data from the
programmer since he has to be able to read the headers to perform the
compile. It becomes a question of how much he has to wade through to
find the information.
If one program is capable of dealing with two device types, you have
the risk of a common structure associated with the second device being
passed to a function dedicated to the first device. Two typedef's
aliasing the same struct type will not solve this problem. If you
include in the structure a member indicating which of the possible
devices this instance of the structure applies to, your device
dependent routines can check before they do any real work.
I was thinking to move the shared code to a common header and source
file and make the device-specific functions call the common functions.
Putting code in a header is almost always a mistake. Maybe that's not
what you meant.
But what should I do with the typdefs below, because both structs would
be the same in my case. But I would like to hide that from the user of
the library.
/* common.h (for internal use only) */
struct device {
/* some data here */
};
int device_open (device **dev, const char *name);
/* device1.h (public) */
typedef struct device1 device1;
int device1_open (device1 **dev, const char *name);
...
/* device2.h (public) */
typedef struct device2 device2;
int device1_open (device1 **dev, const char *name);
...
A single header: device.h
typedef struct device
{
...
enum type {device_1, device_2, ...};
...
} device;
device* device_open(...);
device* device_close(...);
int device_1_read(...);
...
A source file for each function, such as device_open.c
#include <device.h>
#include <stdlib.h>
device* device_open(...)
{
device *ptr = malloc(...);
if (ptr != NULL)
{
...
}
return ptr;
{
In device dependent functions, such as device_1_read
int device_1_read(device *ptr, ......)
{
if (ptr->type != device_1)
/*error handler*/
...
}
Whenever a device is opened
dev = device_open(...);
if (dev == NULL)
/* error handler */
dev_type = device_1; /*or _2 or whatever */
Remove del for email
.
- References:
- API design
- From: Jef Driesen
- API design
- Prev by Date: Re: Discussing the criticisms
- Next by Date: Re: Which hashing function to use?
- Previous by thread: Re: API design
- Next by thread: earning lot of money in your home......
- Index(es):
Relevant Pages
|