Introduction
This guide explains how to develop plugins for netCDF-C user-defined formats (UDFs). It covers dispatch table implementation, plugin structure, building, testing, and deployment.
Plugin Architecture
A UDF plugin consists of:
- Dispatch table: Structure with function pointers implementing the netCDF API
- Initialization function: Called during plugin loading to register the dispatch table
- Format-specific code: Your implementation of file I/O and data operations
Plugin Lifecycle
- Library initialization (
nc_initialize())
- RC file parsing (if configured)
- Plugin library loading (
dlopen/LoadLibrary)
- Init function location (
dlsym/GetProcAddress)
- Init function execution
- Dispatch table registration via
nc_def_user_format()
- Plugin remains loaded for process lifetime
Dispatch Table Structure
The NC_Dispatch structure is defined in include/netcdf_dispatch.h. It contains function pointers for all netCDF operations.
Required Fields
int model;
int dispatch_version;
int (*create)(...);
int (*open)(...);
struct NC_Dispatch NC_Dispatch
Register a user-defined format.
Minimal Dispatch Table Example
#include "netcdf_dispatch.h"
NC_DISPATCH_VERSION,
NC_RO_create,
my_open,
NC_RO_redef,
NC_RO__enddef,
NC_RO_sync,
my_abort,
my_close,
NC_RO_set_fill,
my_inq_format,
my_inq_format_extended,
NC4_inq,
NC4_inq_type,
};
#define NC_FORMATX_UDF0
User-defined format 0.
Pre-defined Functions
Use these for operations your format doesn't support:
- Read-only:
NC_RO_* - Returns NC_EPERM
- Not NetCDF-4:
NC_NOTNC4_* - Returns NC_ENOTNC4
- Not NetCDF-3:
NC_NOTNC3_* - Returns NC_ENOTNC3
- No-op:
NC_NOOP_* - Returns NC_NOERR
- Default implementations:
NCDEFAULT_* - Generic implementations
- NetCDF-4 inquiry:
NC4_* - Use internal metadata model
Initialization Function
The initialization function is called when your plugin is loaded.
Function Signature
Requirements
- Must be exported (not static)
- Must call
nc_def_user_format() to register dispatch table
- Should return NC_NOERR on success, error code on failure
- Name must match RC file INIT key
Example Implementation
int my_plugin_init(void)
{
int ret;
&my_dispatcher,
"MYFMT");
return ret;
}
EXTERNL int nc_def_user_format(int mode_flag, NC_Dispatch *dispatch_table, char *magic_number)
Add handling of user-defined format.
Main header file for the C API.
#define NC_NETCDF4
Use netCDF-4/HDF5 format.
#define NC_UDF0
User-defined format 0 (bit 6).
#define NC_NOERR
No Error.
Implementing Dispatch Functions
Open Function
int my_open(const char *path, int mode, int basepe, size_t *chunksizehintp,
void *parameters,
const NC_Dispatch *dispatch,
int ncid)
{
}
Close Function
int my_close(int ncid, void *v)
{
}
Format Inquiry Functions
int my_inq_format(int ncid, int *formatp)
{
if (formatp)
}
int my_inq_format_extended(int ncid, int *formatp, int *modep)
{
if (formatp)
if (modep)
}
#define NC_FORMAT_NETCDF4
Format specifier for nc_set_default_format() and returned by nc_inq_format.
Building Plugins
Unix/Linux/macOS
# Makefile for UDF plugin
CC = gcc
CFLAGS = -fPIC -I/usr/local/include
LDFLAGS = -shared -L/usr/local/lib -lnetcdf
myplugin.so: myplugin.c
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $<
install:
cp myplugin.so /usr/local/lib/
Windows
REM Build UDF plugin on Windows
cl /LD /I"C:\netcdf\include" myplugin.c /link /LIBPATH:"C:\netcdf\lib" netcdf.lib
CMake Example
cmake_minimum_required(VERSION 3.10)
project(MyPlugin)
find_package(netCDF REQUIRED)
add_library(myplugin SHARED myplugin.c)
target_link_libraries(myplugin netCDF::netcdf)
target_include_directories(myplugin PRIVATE ${netCDF_INCLUDE_DIRS})
install(TARGETS myplugin LIBRARY DESTINATION lib)
Testing Your Plugin
Unit Testing
#include <assert.h>
extern int my_plugin_init(void);
int main() {
int ret;
ret = my_plugin_init();
assert(disp == &my_dispatcher);
printf("Plugin tests passed\n");
return 0;
}
EXTERNL int nc_inq_user_format(int mode_flag, NC_Dispatch **dispatch_table, char *magic_number)
Query a registered user-defined format.
Integration Testing
int main() {
int ncid, ret;
my_plugin_init();
fprintf(stderr,
"Open failed: %s\n",
nc_strerror(ret));
return 1;
}
int format;
printf("Integration test passed\n");
return 0;
}
EXTERNL int nc_close(int ncid)
Close an open netCDF dataset.
EXTERNL int nc_open(const char *path, int mode, int *ncidp)
Open an existing netCDF file.
EXTERNL int nc_inq_format(int ncid, int *formatp)
Inquire about the binary format of a netCDF file as presented by the API.
EXTERNL const char * nc_strerror(int ncerr)
Given an error number, return an error message.
RC File Testing
Create .ncrc:
NETCDF.UDF0.LIBRARY=/path/to/myplugin.so
NETCDF.UDF0.INIT=my_plugin_init
NETCDF.UDF0.MAGIC=MYFMT
Test automatic loading:
int main() {
int ncid;
nc_open(
"file_with_magic.dat", 0, &ncid);
return 0;
}
Debugging
Enable NetCDF Logging
export NC_LOG_LEVEL=3
./test_program
Check Symbol Exports
# Unix
nm -D libmyplugin.so | grep init
# Windows
dumpbin /EXPORTS myplugin.dll
GDB Debugging
gdb ./test_program
(gdb) break my_plugin_init
(gdb) run
(gdb) backtrace
Common Issues
Plugin not loaded
- Check RC file syntax
- Verify both LIBRARY and INIT are present
- Use absolute path for LIBRARY
Init function not found
- Ensure function is not static
- Check function name matches INIT key
- Verify symbol is exported
ABI version mismatch
- Recompile against current netCDF-C headers
- Check NC_DISPATCH_VERSION value
Best Practices
- Error Handling: Return appropriate NC_E* error codes
- Memory Management: Clean up in close/abort functions
- Thread Safety: Use thread-safe operations if needed
- Logging: Use nclog functions for diagnostic output
- Documentation: Document your format and API
- Testing: Test all code paths thoroughly
- Versioning: Version your plugin and document compatibility
Example Plugin
See examples/C/udf_plugin_example/ for a complete working plugin implementation.
Reference
- Dispatch table definition:
include/netcdf_dispatch.h
- Pre-defined functions:
libdispatch/dreadonly.c, libdispatch/dnotnc*.c
- Example implementations:
libhdf5/hdf5dispatch.c, libsrc/nc3dispatch.c
- Test plugins:
nc_test4/test_plugin_lib.c