mirror of
https://github.com/Unidata/netcdf-c.git
synced 2024-12-09 08:11:38 +08:00
305 lines
9.7 KiB
Plaintext
305 lines
9.7 KiB
Plaintext
/** \file
|
||
\internal
|
||
|
||
\page nc_dispatch Internal Dispatch Table Architecture
|
||
|
||
This document describes the architecture and details of the netCDF
|
||
internal dispatch mechanism. The idea is that when a user opens or
|
||
creates a netcdf file, a specific dispatch table is
|
||
chosen. Subsequent netcdf API calls are then channeled through that
|
||
dispatch table to the appropriate function for implementing that API
|
||
call.
|
||
|
||
At least the following dispatch tables are supported.
|
||
- netcdf classic files (netcdf-3)
|
||
- netcdf enhanced files (netcdf-4)
|
||
- OPeNDAP to netcdf-3
|
||
- OPeNDAP to netcdf-4
|
||
|
||
The dispatch table represents a distillation of the netcdf API down to
|
||
a minimal set of internal operations. The format of the dispatch table
|
||
is defined in the file libdispatch/ncdispatch.h. Every new dispatch
|
||
table must define this minimal set of operations.
|
||
|
||
\page adding_dispatch Adding a New Dispatch Table
|
||
|
||
In order to make this process concrete, let us assume we plan to add
|
||
an in-memory implementation of netcdf-3.
|
||
|
||
\section dispatch_step1 Step 1.
|
||
|
||
Define a –enable flag and an AM_CONFIGURE flag in configure.ac. We
|
||
will use the flags –enable-netcdfm and USE_NETCDFM respectively.
|
||
|
||
\section dispatch_step2 Step 2
|
||
|
||
Choose some prefix of characters to identify the new dispatch
|
||
system. In effect we are defining a name-space. For our in-memory
|
||
system, we will choose "NCM" and "ncm". NCM is used for non-static
|
||
procedures to be entered into the dispatch table and ncm for all other
|
||
non-static procedures.
|
||
|
||
\section dispatch_step3 Step 3.
|
||
|
||
Modify file libdispatch/ncdispatch.h as follows.
|
||
|
||
Add a index for this implementation:
|
||
|
||
\code
|
||
#define NC_DISPATCH_NCM 5
|
||
\endcode
|
||
|
||
Define an external reference to the in-memory dispatch table.
|
||
|
||
\code
|
||
#ifdef USE_NETCDFM
|
||
extern NC_Dispatch* NCM_dispatch_table;
|
||
#endif
|
||
\endcode
|
||
|
||
\section dispatch_step4 Step 4.
|
||
|
||
Modify file libdispatch/netcdf.c as follows.
|
||
|
||
Add a ptr to the in-memory dispatch table.
|
||
|
||
\code
|
||
#ifdef USE_NETCDFM
|
||
NC_Dispatch* NCM_dispatch_table = NULL;
|
||
#endif
|
||
\endcode
|
||
|
||
Add includes for any necessary header files as needed.
|
||
|
||
\section dispatch_step5 Step 5.
|
||
|
||
Define the functions necessary to fill in the dispatch table. As a
|
||
rule, we assume that a new directory is defined, libsrcm, say. Within
|
||
this directory, we need to define Makefile.am, the source files
|
||
containing the dispatch table and the functions to be placed in the
|
||
dispatch table – call them ncmdispatch.c and ncmdispatch.h. Look at
|
||
libsrc/nc3dispatch.[ch] for an example.
|
||
|
||
As part of the ncmdispatch.c file, you must define the following.
|
||
|
||
\code
|
||
NC_Dispatch NCM_dispatcher = {
|
||
NC_DISPATCH_NCM,
|
||
NCM_create,
|
||
NCM_open,
|
||
...
|
||
};
|
||
|
||
int
|
||
NCM_initialize(void)
|
||
{
|
||
NCM_dispatch_table = &NCM_dispatcher;
|
||
return NC_NOERR;
|
||
}
|
||
\endcode
|
||
|
||
Assuming that the in-memory library does not require any external
|
||
libraries, then the Makefile.am will look something like this.
|
||
|
||
\code
|
||
NCM_SOURCES = ncmdispatch.c ncmdispatch.h ...
|
||
AM_CPPFLAGS += -I$(top_srcdir)/libsrc -I$(top_srcdir)/libdispatch
|
||
libnetcdfm_la_SOURCES = $(NCM_SOURCES)
|
||
noinst_LTLIBRARIES = libnetcdfm.la
|
||
\endcode
|
||
|
||
\section dispatch_step6 Step 6.
|
||
|
||
Provide for the inclusion of this library in the final libnetcdf
|
||
library. This is accomplished by modifying liblib/Makefile.am by
|
||
adding something like the following.
|
||
|
||
\code
|
||
if USE_NETCDFM
|
||
libnetcdf_la_LIBADD += $(top_builddir)/libsrcm/libnetcdfm.la
|
||
endif
|
||
\endcode
|
||
|
||
\section dispatch_step7 Step 7.
|
||
|
||
Modify the NC_intialize function in liblib/stub.c by adding
|
||
appropriate references to the NCM dispatch function.
|
||
|
||
\code
|
||
#ifdef USE_NETCDFM
|
||
extern int NCM_initialize(void);
|
||
#endif
|
||
...
|
||
int NC_initialize(void)
|
||
{
|
||
...
|
||
#ifdef USE_DAP
|
||
if((stat = NCM_initialize())) return stat;
|
||
#endif
|
||
...
|
||
}
|
||
\endcode
|
||
|
||
\section dispatch_step8 Step 8.
|
||
|
||
Add a directory of tests; ncm_test, say. The file ncm_test/Makefile.am
|
||
will look something like this.
|
||
|
||
\code
|
||
# These files are created by the tests.
|
||
CLEANFILES = ...
|
||
# These are the tests which are always run.
|
||
TESTPROGRAMS = test1 test2 ...
|
||
test1_SOURCES = test1.c ...
|
||
...
|
||
# Set up the tests.
|
||
check_PROGRAMS = $(TESTPROGRAMS)
|
||
TESTS = $(TESTPROGRAMS)
|
||
# Any extra files required by the tests
|
||
EXTRA_DIST = ...
|
||
\endcode
|
||
|
||
\section dispatch_step9 Step 9.
|
||
|
||
Provide for libnetcdfm to be constructed by adding the following to
|
||
the top-level Makefile.am.
|
||
|
||
\code
|
||
if USE_NETCDFM
|
||
NCM=libsrcm
|
||
NCMTESTDIR=ncm_test
|
||
endif
|
||
...
|
||
SUBDIRS = ... $(DISPATCHDIR) $(NCM) ... $(NCMTESTDIR)
|
||
\endcode
|
||
|
||
\section choosing_dispatch_table Choosing a Dispatch Table
|
||
|
||
The dispatch table is chosen in the NC_create and the NC_open
|
||
procedures in libdispatch/netcdf.c. The decision is currently based on
|
||
the following pieces of information.
|
||
|
||
The file path – this can be used to detect, for example, a DAP url
|
||
versus a normal file system file.
|
||
|
||
The mode argument – this can be used to detect, for example, what kind
|
||
of file to create: netcdf-3, netcdf-4, 64-bit netcdf-3, etc. For
|
||
nc_open and when the file path references a real file, the contents of
|
||
the file can also be used to determine the dispatch table. Although
|
||
currently not used, this code could be modified to also use other
|
||
pieces of information such as environment variables.
|
||
|
||
In addition to the above, there is one additional mechanism to force
|
||
the use of a specific dispatch table. The procedure
|
||
"NC_set_dispatch_override()" can be invoked to specify a dispatch
|
||
table.
|
||
|
||
When adding a new dispatcher, it is necessary to modify NC_create and
|
||
NC_open in libdispatch/netcdf.c to detect when it is appropriate to
|
||
use the NCM dispatcher. Some possibilities are as follows.
|
||
- Add a new mode flag: say NC_NETCDFM.
|
||
- Use an environment variable.
|
||
- Define a special file path format that indicates the need to use a
|
||
special dispatch table.
|
||
|
||
\section special_dispatch Special Dispatch Table Signatures.
|
||
|
||
Several of the entries in the dispatch table are significantly
|
||
different than those of the external API.
|
||
|
||
\section create_open_dispatch Create/Open
|
||
|
||
The create table entry and the open table entry have the following
|
||
signatures respectively.
|
||
|
||
\code
|
||
int (*create)(const char *path, int cmode,
|
||
size_t initialsz, int basepe, size_t *chunksizehintp,
|
||
int useparallel, MPI_Comm comm, MPI_Info info,
|
||
struct NC_Dispatch*, struct NC** ncp);
|
||
\endcode
|
||
|
||
\code
|
||
int (*open)(const char *path, int mode,
|
||
int basepe, size_t *chunksizehintp,
|
||
int use_parallel, MPI_Comm comm, MPI_Info info,
|
||
NC_Dispatch*, NC** ncp);
|
||
\endcode
|
||
|
||
The key difference is that these are the union of all the possible
|
||
create/open signatures from the netcdf.h API. Note especially the last
|
||
two parameters. The dispatch table is included in case the create
|
||
function (e.g. NCM_create) needs to invoke other dispatch
|
||
functions. The very last parameter is a pointer to a pointer to an NC
|
||
instance. It is expected that the create function will allocate and
|
||
fill in an instance of an "NC" object and return a pointer to it in
|
||
the ncp parameter.
|
||
|
||
\page dispatch_notes Dispatch Programming Notes
|
||
|
||
As with the existing code, and when MPI is not being used, the comm
|
||
and info parameters should be passed in as 0. This is taken care of in
|
||
the nc_open() and nc_create() API procedures in libdispatch/netcdf.c.
|
||
|
||
In fact, the object returned in the ncp parameter does not actually
|
||
have to be an instance of struct NC. It only needs to "look like it
|
||
for the first few fields. This is, in effect, a fake version of
|
||
subclassing. Let us suppose that the NCM_create function uses a struct
|
||
NCM object. The initial part of the definition of NCM must match the
|
||
fields at the beginning of struct NC between the comments BEGIN_COMMON
|
||
and END_COMMON. So, we would have the following.
|
||
|
||
\code
|
||
typedef struct NCM {
|
||
/*BEGIN COMMON*/
|
||
int ext_ncid; /* uid «« 16 */
|
||
int int_ncid; /* unspecified other id */
|
||
struct NC_Dispatch* dispatch;
|
||
#ifdef USE_DAP
|
||
struct NCDRNO* drno;
|
||
#endif
|
||
/*END COMMON*/
|
||
...
|
||
} NCM;
|
||
\endcode
|
||
|
||
This allows the pointer to the NCM object to be cast as an instance of
|
||
NC* and its pointer returned in the ncp file. Eventually, this will be
|
||
replaced with a separate structure containing the common fields.
|
||
|
||
\page put_vara_dispatch Accessing Data with put_vara() and get_vara()
|
||
|
||
\code
|
||
int (*put_vara)(int ncid, int varid, const size_t *start, const size_t *count,
|
||
const void *value, nc_type memtype);
|
||
\endcode
|
||
|
||
\code
|
||
int (*get_vara)(int ncid, int varid, const size_t *start, const size_t *count,
|
||
void *value, nc_type memtype);
|
||
\endcode
|
||
|
||
Most of the parameters are similar to the netcdf API parameters. The
|
||
last parameter, however, is the type of the data in
|
||
memory. Additionally, instead of using an "int islong" parameter, the
|
||
memtype will be either ::NC_INT or ::NC_INT64, depending on the value
|
||
of sizeof(long). This means that even netcdf-3 code must be prepared
|
||
to encounter the ::NC_INT64 type.
|
||
|
||
\page put_attr_dispatch Accessing Attributes with put_attr() and get_attr()
|
||
|
||
\code
|
||
int (*get_att)(int ncid, int varid, const char *name,
|
||
void *value, nc_type memtype);
|
||
\endcode
|
||
|
||
\code
|
||
int (*put_att)(int ncid, int varid, const char *name, nc_type datatype, size_t len,
|
||
const void *value, nc_type memtype);
|
||
\endcode
|
||
|
||
Again, the key difference is the memtype parameter. As with
|
||
put/get_vara, it used ::NC_INT64 to encode the long case.
|
||
|
||
*/
|