GRIB API源码分析——grib_get_[TYPE]实现简析

目录

个人觉得grib api最突出的一点就是使用名称以统一的方式读取变量。所有取值函数均以grib_get_开头,后面接数据类型。如

[cpp]
grib_get_long
grib_get_double
grib_get_string
grib_get_double_array
//…
[/cpp]
详情参阅官方文档《Accessing header and data values
我们只需记住要获取的参数名(GRIB API中叫做key)即可,有关key请参阅《Grib API keys》。

函数定义

[cpp]
/**
* Get a double value from a key, if several keys of
* the same name are present, the last one is returned
* @see grib_set_double
*
* @param h : the handle to get the data from
* @param key : the key to be searched
* @param value : the address of a double where the data will be retrieved
* @return 0 if OK, integer value on error
/
int grib_get_double(grib_handle* h, const char* key, double
value);
[/cpp]

使用方法

[cpp]
double latitudeOfFirstGridPointInDegrees;
GRIB_CHECK(grib_get_double(h,”latitudeOfFirstGridPointInDegrees”,
&latitudeOfFirstGridPointInDegrees),0);
[/cpp]
其中GRIB_CHECK是检测返回值的宏,如果内部调用的函数返回值不为0,则直接退出程序。如果不用GRIB_CHECK,则需要手动检测返回值。

类型转换

grib_get系列函数具有通用型,每个变量均可以返回多种类型,grib api内部实现类型转换。
对于某个变量,我们可能不知道该变量实际的数据类型,或者我们需要用特定的类型表示该变量,比如日期dateDate,不知道是整数类型还是字符串类型。针对这种情况,GRIB API内部实现类型转换,尽可能返回我们需要的数据类型。所以对于dataDate,我们使用grib_get_long,grib_get_double, grib_get_string等函数,均可以得到变量值。例如代码如下:
[cpp]
size_t date_str_length=1024;
char *date_str = new char[date_str_length];
GRIB_CHECK(grib_get_string(h,”dataDate”, date_str, &date_str_length) ,0);
double date_double;
GRIB_CHECK(grib_get_double(h,”dataDate”,&date_double),0);
long date_long;
GRIB_CHECK(grib_get_long(h,”dataDate”,&date_long),0);
cout<<“date.str:”<<date_str<<endl; cout<<“date.double:”<<date_double<<endl; cout<<“date.long:”<<date_long<<endl; [/cpp] 运行时输出 [code] date.str:20130916 date.double:2.01309e+07 date.long:20130916 average:62595.9 [/code] 虽然不同函数获取的值类型不同,但得到的内容是一致的。

具体实现

grib api代码中大量使用C语言结构体继承,并用函数指针实现函数重载。不同的.c文件中设置不同的结构体,并实现它们的“内部函数”。
我只粗略看下一些与获取参数相关的函数。

grib_accessor_class

GRIB API使用一个叫做grib_accessor_class的struct访问变量,可以看做一个访问器,提供各种访问方法。下面列出它的具体定义
[cpp]
struct grib_accessor_class
{
grib_accessor_class *super;
const char
name;
size_t size;
int inited;
accessor_init_class_proc init_class;
accessor_init_proc init;
accessor_post_init_proc post_init;
accessor_destroy_proc destroy;
accessor_dump_proc dump;
accessor_value_proc next_offset;
accessor_string_proc string_length;
accessor_value_proc value_count;
accessor_value_proc byte_count;
accessor_value_proc byte_offset;
accessor_get_native_type_proc get_native_type;
accessor_sub_section_proc sub_section;
accessor_pack_missing_proc pack_missing ;
accessor_pack_is_missing_proc is_missing ;
accessor_pack_long_proc pack_long ;
accessor_unpack_long_proc unpack_long ;
accessor_pack_double_proc pack_double;
accessor_unpack_double_proc unpack_double;
accessor_pack_string_proc pack_string;
accessor_unpack_string_proc unpack_string;
accessor_pack_bytes_proc pack_bytes;
accessor_unpack_bytes_proc unpack_bytes;
accessor_pack_expression_proc pack_expression;
accessor_notify_change_proc notify_change;
accessor_update_size_proc update_size;
accessor_preferred_size_proc preferred_size;
accessor_resize_proc resize;
accessor_nearest_proc nearest_smaller_value;
accessor_next_proc next;
accessor_compare_proc compare;
accessor_unpack_double_element_proc unpack_double_element;
accessor_unpack_double_subarray_proc unpack_double_subarray;
accessor_clear_proc clear;
};
[/cpp]
其中以proc结尾的类型都是函数指针。访问器提供各种操作变量的函数,比如设置long变量的函数指针accessor_pack_long_proc定义如下:
[cpp]
typedef int (*accessor_pack_long_proc) (grib_accessor*, const long*, size_t *len);
[/cpp]
super指针是grib_accessor_class指针的指针,是模拟对象继承的关键,可以看做是父类的指针。继承的结构体根据需要重载相关函数,例如获取dataDate变量的访问器_grib_accessor_class_g2data的定义如下
[cpp]
static grib_accessor_class _grib_accessor_class_g2date = {
&grib_accessor_class_long, /* super */
“g2date”, /* name */
sizeof(grib_accessor_g2date), /* size */
0, /* inited */
&init_class, /* init_class */
&init, /* init */
0, /* post_init */
0, /* free mem */
&dump, /* describes himself */
0, /* get length of section */
0, /* get length of string */
0, /* get number of values */
0, /* get number of bytes */
0, /* get offset to bytes */
0, /* get native type */
0, /* get sub_section */
0, /* grib_pack procedures long */
0, /* grib_pack procedures long */
&pack_long, /* grib_pack procedures long */
&unpack_long, /* grib_unpack procedures long */
0, /* grib_pack procedures double */
0, /* grib_unpack procedures double */
0, /* grib_pack procedures string */
0, /* grib_unpack procedures string */
0, /* grib_pack procedures bytes */
0, /* grib_unpack procedures bytes */
0, /* pack_expression */
0, /* notify_change */
0, /* update_size */
0, /* preferred_size */
0, /* resize */
0, /* nearest_smaller_value */
0, /* next accessor */
0, /* compare vs. another accessor */
0, /* unpack only ith value */
0, /* unpack a subarray */
0, /* clear */
};
[/cpp]

grib_accessor

还有一个相关结构体grib_accessor,应该与存储的数据本身有关,包含grib_accessor_class作为访问方法。定义如下:
[cpp]
struct grib_accessor
{
const char *name ; /** < name of the accessor */ const char* name_space; /** < namespace to which the accessor belongs */ grib_action *creator ; /** < action that created the accessor */ long length ; /** < byte length of the accessor */ long offset ; /** < offset of the data in the buffer */ grib_section *parent; /** < section to which the accessor is attached */ grib_accessor *next ; /** < next accessor in list */ grib_accessor *previous; /** < next accessor in list */ grib_accessor_class *cclass; /** < behavior of the accessor */ unsigned long flags; /** < Various flags */ grib_section* sub_section; const char* all_names[MAX_ACCESSOR_NAMES] ; /** < name of the accessor */ const char* all_name_spaces[MAX_ACCESSOR_NAMES]; /** < namespace to which the accessor belongs */ int dirty; grib_accessor *same; /** < accessors with the same name */ long loop; /** < used in lists */ grib_virtual_value* vvalue; /** < virtual value used when transient flag on **/ const char* set; }; [/cpp] grib_accessor_class的“继承”没有改变结构体的类型,但grib_accessor的继承更接近对象继承,将基类本身作为一个成员变量,为基类添加新的成员。如dataDate变量的访问器结构体如下: [cpp] typedef struct grib_accessor_g2date { grib_accessor att; /* Members defined in gen */ /* Members defined in long */ /* Members defined in g2date */ const char* century; const char* year; const char* month; const char* day; } grib_accessor_g2date; [/cpp] 根据分析,这里新增的变量用于保存key的名称。

继承层次

以_grib_accessor_class_g2date为例说明继承结构。首先只关注accessor结构体,如何从变量名称得到accessor以及如何调用访问器的解码函数暂时不在本文关注范围内。_grib_accessor_class_g2date的层次关系如下:

grib_accessor_class_gen
    |-grib_accessor_class_long
       |-grib_accessor_class_g2date

数据解码函数使用继承关系。首先在当前类中寻找解码函数,如果不存在使用父类的解码函数,如果都没有,则返回错误。
下面以grib_get_long为例说明层次关系。
[cpp]
int grib_get_long(grib_handle* h, const char* name, long* val)
{
grib_accessor* act = NULL;
size_t l = 1;
int ret=0;
act = grib_find_accessor(h, name);
ret = act ? grib_unpack_long(act, val, &l) : GRIB_NOT_FOUND;
return ret;
}
[/cpp]
grib_get_long中首先查找accessor,通过调用accessor中grib_accessor_class设置的grib_unpack_long函数获取long型val。
grib_unpack_long函数则利用accessor的继承关系获取val。
[cpp]
int grib_unpack_long(grib_accessor* a,long* v, size_t *len )
{
grib_accessor_class *c = a->cclass;
while©
{
if(c->unpack_long)
{
return c->unpack_long(a,v,len);
}
c = c->super ? *(c->super) : NULL;
}
Assert(0);
return 0;
}
[/cpp]
grib_accessor中保存访问器方法类grib_accessor_class的指针。我们使用grib_accessor获取值,grib_accessor使用grib_accessor_class中提供的函数获取值。
上文提到grib_accessor_class类通过super变量实现继承关系。如果当前grib_accessor_class中的unpack_long指针有效,则使用当前grib_accessor_class中的unpack_long指向的解码函数,否则继续使用grib_accessor_class的“父类”。无论在何时调用unpack_long,三个参数值都是相同的。所以所有层级的unpack_long函数均有相同的grib_accessor*参数,也就可以从grib_accessor中得到完整的grib_accessor_class继承关系,类型转换正是使用这种继承关系。

grib_get_long获取dataDate

先看使用grib_get_long获取dataDate变量的情况,_grib_accessor_class_g2date中设置了函数指针unpack_long,调用uppack_long函数
[cpp]
static int unpack_long(grib_accessor* a, long* val, size_t *len)
{
int ret=0;
grib_accessor_g2date* self = (grib_accessor_g2date*)a;
long year = 0;
long month = 0;
long day = 0;
if ((ret=grib_get_long_internal(a->parent->h, self->day,&day))!=GRIB_SUCCESS) return ret;
if ((ret=grib_get_long_internal(a->parent->h, self->month,&month))!=GRIB_SUCCESS) return ret;
if ((ret=grib_get_long_internal(a->parent->h, self->year,&year))!=GRIB_SUCCESS) return ret;
if(*len < 1) return GRIB_WRONG_ARRAY_SIZE; val[0] = year * 10000 + month * 100 + day; return GRIB_SUCCESS; } [/cpp] 则直接调用该uppack_long函数,得到变量值。从实现中可以看到,dataDate由year、month、day三个变量计算得到。这与section1.def中dataDate的定义是一致的 [code] meta dataDate g2date(year,month,day) : dump; [/code]

grib_get_string获取dataDate

可以得到string类型的dataDate。grib_accessor_class_g2date没有设置unpack_string指针,在_grib_accessor_class_g2date的父类grib_accessor_class_long中查找。grib_accessor_class_long中设置了unpack_string函数:
[cpp]
static int unpack_string(grib_accessor*a , char* v, size_t *len){
long val = 0;
size_t l = 1;
char repres[1024];
grib_unpack_long (a , &val, &l);
if ((val == GRIB_MISSING_LONG) && ((a->flags & GRIB_ACCESSOR_FLAG_CAN_BE_MISSING) != 0) )
sprintf(repres,”MISSING”);
else
sprintf(repres,”%ld”, val);
l = strlen(repres)+1;
if(l >*len ){
grib_context_log(a->parent->h->context, GRIB_LOG_ERROR, “grib_accessor_long : unpack_string : Buffer too small for %s “, a->name );
*len = l;
return GRIB_BUFFER_TOO_SMALL;
}
grib_context_log(a->parent->h->context,GRIB_LOG_DEBUG, “grib_accessor_long: Casting long %s to string “, a->name);
*len = l;
strcpy(v,repres);
return GRIB_SUCCESS;
}
[/cpp]
该函数使用grib_unpack_long获取long型变量值,再用sprintf将long型转换为字符串类型。这就是grib api内部的类型转换。

更多关于类型转换的实现

grib_accessor_class_long中没有unpack_long函数,它的父类grib_accessor_class_gen中的unpack_long函数调用accessor方法类的unpack_double函数或unpack_string函数,再将结果转为long类型,如果都没有实现,则返回错误GRIB_NOT_IMPLEMENTED。
[cpp]
static int unpack_long (grib_accessor* a, long* v, size_t *len){
if(a->cclass->unpack_double && a->cclass->unpack_double != &unpack_double)
{
double val = 0.0;
size_t l = 1;
grib_unpack_double (a , &val, &l);
*v = (long)val;
grib_context_log(a->parent->h->context,GRIB_LOG_DEBUG, ” Casting double %s to long”, a->name);
return GRIB_SUCCESS;
}
if(a->cclass->unpack_string && a->cclass->unpack_string != &unpack_string)
{
char val[1024];
size_t l = sizeof(val);
char *last = NULL;
grib_unpack_string (a , val, &l);
*v = strtol(val,&last,10);
if(*last == 0)
{
grib_context_log(a->parent->h->context,GRIB_LOG_DEBUG, ” Casting string %s to long”, a->name);
return GRIB_SUCCESS;
}
}
return GRIB_NOT_IMPLEMENTED;
}
[/cpp]
这就意味着每种accessor中实际解码的函数一般都在accessor_class继承层次中的最顶层。例如_grib_accessor_class_g2date中的unpack_long函数,低层次一般用来实现类型转换。
下面看下一个简单类型的取值:year。

grib_get_long获取year

year变量在codetable中为unsigned类型,使用访问器方法结构为_grib_accessor_class_unsigned,unpack_long函数如下:
[cpp]
static int unpack_long(grib_accessor* a, long* val, size_t *len)
{
grib_accessor_unsigned* self = (grib_accessor_unsigned*)a;
unsigned long rlen = grib_value_count(a);
unsigned long i = 0;
unsigned long missing = 0;
long pos = a->offset*8;
if(*len < rlen) { grib_context_log(a->parent->h->context, GRIB_LOG_ERROR, ” wrong size (%ld) for %s it contains %d values “,*len, a->name , rlen);
*len = 0;
return GRIB_ARRAY_TOO_SMALL;
}
if (a->flags & GRIB_ACCESSOR_FLAG_TRANSIENT) {
*val=a->vvalue->lval;
*len=1;
return GRIB_SUCCESS;
}
if(a->flags & GRIB_ACCESSOR_FLAG_CAN_BE_MISSING)
{
Assert(self->nbytes <= 4); missing = ones[self->nbytes];
}
for(i=0; i< rlen;i++){ val[i] = (long)grib_decode_unsigned_long(a->parent->h->buffer->data , &pos, self->nbytes*8);
if(missing)
if(val[i] == missing)
val[i] = GRIB_MISSING_LONG;
}
*len = rlen;
return GRIB_SUCCESS;
}
[/cpp]
不管前面类似于cached机制的代码,真正解码数据的函数为grib_decode_unsigned_long。grib_decode_unsigned_long有两个版本,分别定义在grib_bits_any_endian.c和grib_bits_big_endian.c文件中。看下grib_bits_any_endian.c中的函数:
[cpp]
unsigned long grib_decode_unsigned_long(const unsigned char* p, long *bitp, long nbits)
{
int i;
long ret = 0;
long o = *bitp/8;
int l = nbits/8;
if (nbits==0) return 0;
if(nbits > max_nbits)
{
int bits = nbits;
int mod = bits % max_nbits;
if(mod != 0)
{
int e=grib_decode_unsigned_long(p,bitp,mod);
Assert( e == 0);
bits -= mod;
}
while(bits > max_nbits)
{
int e=grib_decode_unsigned_long(p,bitp,max_nbits);
Assert( e == 0);
bits -= max_nbits;
}
return grib_decode_unsigned_long(p,bitp,bits);
}
if((nbits%8 > 0)||(*bitp%8 > 0)) {
for(i=0; i< nbits;i++){ ret <<= 1; if(grib_get_bit( p, *bitp)) ret += 1; *bitp += 1; } return ret; } ret <<= 8; ret |= p[o++] ; for ( i=1; i<l; i++ ) { ret <<= 8; ret |= p[o++] ; } *bitp += nbits; return ret; } [/cpp] 这是从文件指针中取出数值的函数。 以上是grib_accessor获取变量值的简要分析。