in comp.lang.c i read:
Quote:
>I was very interested in the concepts for cross-platform development
>exposed in chapter 22 of C Unleashed. The idea is to define an
>abstraction layer in portable ISO C that will be implemented for every
>platform it needs to be ported to.
i don't own a copy of the book, so i'll just have to guess at what was
presented. even if i don't match it exactly i'm fairly sure i'll have
the gist.
Quote:
>The networking interface designed in the chapter comprises the concept
>of a "connection". On any given platform, a "connection" structure
>holds the relevant non-portable data it needs to carry out its duty.
>E.g. on Windows, we have the following definition:
[some detailed window-y stuff]
Quote:
>Now, wherever we use our portable networking library code, we have to
>have a definition for our "connection", thus, we need to include the
>header where the above definition lays, and thus indirectly include
>the platform-specific header.
typically you use a pointer to an incomplete structure, which allows you to
hide the details from the iso conforming code, so:
portable_networking.h:
/* guards and other, usual, header fluff */
typedef struct pn_netcon PN_NETCON;
PN_NETCON * pn_open(const char *name, const char *mode);
int pn_close(const PN_NETCON *connection);
size_t pn_read(void *buf, size_t size, size_t n, PN_NETCON *connection);
/* you should be thinking of stdio file streams by now */
the pn_netcon structure is incomplete, which makes it `opaque' from the
point of view of the portable code -- there are no members for them to
inspect, or worse to diddle. the portable code must call the functions
defined by the api, receiving and/or passing only a pointer around, as that
is all that you are allowed to do with an incomplete struct. each platform
has it's own implementation, which has a complete definition of the struct,
which contains whatever it needs, and which need not be the same size as
any other (though that is handy sometimes).
this is the c world's notion of an adt.
there is some, slight, danger in this technique, in that the portable code
might complete the structure, whether correct or not, which the compiler
will not complain about, in which case it's quite likely it will attempt to
diddle the content, which would cause problems. but as with FILE * you
document that that is a no-no and leave the consequences undefined.
(surely you can attempt to cope e.g., keep a checksum or cryptographic hash
of the content as a member which is checked before any operation, but that
adds run-time expense, in time and storage, that may not be warranted.)
here's an example implementation, on a platform without networking support:
#include "portable_networking.h"
/* no need to complete the structure, but i will because most
implementations would do so, hence i need at least one member */
struct pn_netcon { int dummy; };
/* open cannot succeed, return a null pointer */
PN_NETCON * pn_open(const char *name, const char *mode)
{ return 0; }
/* ignore close attempts, return a no-error indicator */
int pn_close(const PN_NETCON *connection)
{ return 0; }
/* read cannot succeed, return that nothing was read */
size_t pn_read(void *buf, size_t size, size_t n, PN_NETCON *connection)
{ return 0; }
and here's an example usage (look familiar?):
#include <stdio.h>
#include "portable_networking.h"
int main(void)
{
PN_NETCON *nc = pn_open("...", "r+");
if (0 == nc) {
fputs("error trying to open connection to ...\n", stderr);
abort();
}
else
{
char buf[1000];
size_t nr;
while(0 < (nr = pn_read(buf, 1, sizeof buf, nc)))
{
/* do stuff with nr bytes */
}
/* probably there would be a pn_error and pn_eof so that you could
test them here, just as you would for stdio file streams */
if (0 != pn_close(nc)
{
fputs("error trying to close connection to ...\n", stderr);
abort();
}
}
return 0;
}
--
bringing you boring signatures for 17 years