D
Dave Vandervies
I'm writing an abstraction layer over an OS's ability to do non-blocking
reads and writes on various things. The basic idea is that the caller
will be able to let the I/O happen in the background, and will poll
for completion using something along the lines of *nix's select() or
Win32's WaitForMultipleObjects, and when the OS signals that a read has
completed or a write will be accepted the abstraction layer will be called
to actually do the I/O call and call any code that's interested in it.
The read and write handling have some basic asymmetry, so the abstraction
layer's data and the interface to the code that requests reads and writes
will be different depending on which direction the data is going, but
the basic poll-and-call-handler pattern in the main loop will be the
same either way.
What I'd like to do is something like this:
--------
/*in overlapped.h*/
struct overlapped_cookie
{
sync_type need_attention;
struct overlapped_data *internal;
};
--------
/*in overlapped-read.c*/
struct overlapped_data
{
/*Internal data for read bookkeeping*/
};
struct overlapped_cookie *setup_read(/*args*/)
{
struct overlapped_cookie *ret=malloc(sizeof *ret);
if(!ret)
return NULL;
ret->internal=malloc(sizeof ret->internal);
if(!ret->internal)
{
free(ret);
return NULL;
}
/*Continue with initialization*/
}
--------
/*in overlapped-write.c*/
struct overlapped_data
{
/*Internal data for write bookkeeping*/
};
struct overlapped_cookie *setup_write(/*args*/)
{
struct overlapped_cookie *ret=malloc(sizeof *ret);
if(!ret)
return NULL;
ret->internal=malloc(sizeof ret->internal);
if(!ret->internal)
{
free(ret);
return NULL;
}
/*Continue with initialization*/
}
--------
The idea is that the main loop can do something like:
--------
/*These arrays are populated by other code to keep an up-to-date list of
reads and writes (possibly to be extended to other events) that are
being waited for
*/
sync_type sync[N];
int (*handler[N])(struct overlapped_cookie *);
struct overlapped_cookie *all_overlapped[N];
/*main loop, minus error handling:*/
while(!are_we_done_yet())
{
int which_one=do_wait(sync,num_filled);
handler[which_one](all_overlapped[which_one]);
}
--------
I have two questions about this:
(1) Is it legal and well-defined for external code to use a pointer to
the same incomplete struct type for what is actually different types
in different translation units, as long as no read struct ever gets
passed to the write functions and no write struct ever gets passed to
the read functions?
(I'm fairly sure, but not certain, that the struct pointer representation
requirements make this valid.)
(2) Is this considered acceptable, maintainable, and not aesthetically
unpleasant by other C programmers? Or, at least, no worse than the
alternative of forcing the main poll loop to handle reads and writes
separately?
(This one I'm not so sure about.)
(2b) Is there a better way to do this?
dave
reads and writes on various things. The basic idea is that the caller
will be able to let the I/O happen in the background, and will poll
for completion using something along the lines of *nix's select() or
Win32's WaitForMultipleObjects, and when the OS signals that a read has
completed or a write will be accepted the abstraction layer will be called
to actually do the I/O call and call any code that's interested in it.
The read and write handling have some basic asymmetry, so the abstraction
layer's data and the interface to the code that requests reads and writes
will be different depending on which direction the data is going, but
the basic poll-and-call-handler pattern in the main loop will be the
same either way.
What I'd like to do is something like this:
--------
/*in overlapped.h*/
struct overlapped_cookie
{
sync_type need_attention;
struct overlapped_data *internal;
};
--------
/*in overlapped-read.c*/
struct overlapped_data
{
/*Internal data for read bookkeeping*/
};
struct overlapped_cookie *setup_read(/*args*/)
{
struct overlapped_cookie *ret=malloc(sizeof *ret);
if(!ret)
return NULL;
ret->internal=malloc(sizeof ret->internal);
if(!ret->internal)
{
free(ret);
return NULL;
}
/*Continue with initialization*/
}
--------
/*in overlapped-write.c*/
struct overlapped_data
{
/*Internal data for write bookkeeping*/
};
struct overlapped_cookie *setup_write(/*args*/)
{
struct overlapped_cookie *ret=malloc(sizeof *ret);
if(!ret)
return NULL;
ret->internal=malloc(sizeof ret->internal);
if(!ret->internal)
{
free(ret);
return NULL;
}
/*Continue with initialization*/
}
--------
The idea is that the main loop can do something like:
--------
/*These arrays are populated by other code to keep an up-to-date list of
reads and writes (possibly to be extended to other events) that are
being waited for
*/
sync_type sync[N];
int (*handler[N])(struct overlapped_cookie *);
struct overlapped_cookie *all_overlapped[N];
/*main loop, minus error handling:*/
while(!are_we_done_yet())
{
int which_one=do_wait(sync,num_filled);
handler[which_one](all_overlapped[which_one]);
}
--------
I have two questions about this:
(1) Is it legal and well-defined for external code to use a pointer to
the same incomplete struct type for what is actually different types
in different translation units, as long as no read struct ever gets
passed to the write functions and no write struct ever gets passed to
the read functions?
(I'm fairly sure, but not certain, that the struct pointer representation
requirements make this valid.)
(2) Is this considered acceptable, maintainable, and not aesthetically
unpleasant by other C programmers? Or, at least, no worse than the
alternative of forcing the main poll loop to handle reads and writes
separately?
(This one I'm not so sure about.)
(2b) Is there a better way to do this?
dave