深入了解 gawk 扩展 API:数据类型、内存管理与功能注册
在使用 gawk 进行扩展开发时,我们会遇到请求类型和实际类型不匹配的情况。此时,访问函数会返回 “false”,并填充实际值的类型,方便扩展程序打印错误信息,例如 “scalar passed where array expected”。虽然可以直接使用函数指针调用 API 函数,但接口不够美观。为了让扩展代码更像常规代码,
gawkapi.h
头文件定义了一些宏,我们可以在代码中使用这些宏。
通用数据类型
扩展 API 定义了一些通用的简单类型和结构,以下是详细介绍:
1.
awk_ext_id_t
:
typedef void *awk_ext_id_t;
当扩展加载时,会从 gawk 接收该类型的值,之后必须将其作为每个 API 函数的第一个参数传回给 gawk。
2.
awk_const
:
#define awk_const …
编译扩展时,该宏会扩展为
const
;编译 gawk 本身时,扩展为空。这使得 API 数据结构中的某些字段在扩展代码中不可写,而 gawk 可以按需使用。
3.
awk_bool_t
:
typedef enum awk_bool {
awk_false = 0,
awk_true
} awk_bool_t;
这是一个简单的布尔类型。
4.
awk_string_t
:
typedef struct awk_string {
char *str; /* data */
size_t len; /* length thereof, in chars */
} awk_string_t;
表示可变字符串。如果值由 gawk 提供,gawk 拥有指向的内存;否则,它会接管指向的内存。这些内存必须通过调用
gawk_malloc()
、
gawk_calloc()
或
gawk_realloc()
函数获得。字符串使用当前多字节编码维护。
5.
awk_valtype_t
:
typedef enum {
AWK_UNDEFINED,
AWK_NUMBER,
AWK_STRING,
AWK_ARRAY,
AWK_SCALAR, /* opaque access to a variable */
AWK_VALUE_COOKIE /* for updating a previously created value */
} awk_valtype_t;
该枚举表示值的类型,用于
awk_value_t
结构体中。
6.
awk_value_t
:
typedef struct awk_value {
awk_valtype_t val_type;
union {
awk_string_t s;
double d;
awk_array_t a;
awk_scalar_t scl;
awk_value_cookie_t vc;
} u;
} awk_value_t;
这是一个 “awk 值”,
val_type
成员表示联合中存储的值的类型,每个成员都是相应的类型。为了更方便地访问
awk_value_t
的字段,还定义了以下宏:
#define str_value u.s
#define num_value u.d
#define array_cookie u.a
#define scalar_cookie u.scl
#define value_cookie u.vc
-
awk_scalar_t和awk_value_cookie_t:
typedef void *awk_scalar_t;
typedef void *awk_value_cookie_t;
awk_scalar_t
表示标量的不透明类型,可通过它直接获取或修改变量的值,避免每次访问都查找变量,提高性能。
awk_value_cookie_t
表示缓存值的不透明类型,如果需要为多个变量使用相同的数值或字符串值,可以创建一次并保留值 cookie,后续设置变量值时传入该 cookie,节省存储和创建时间。
内存分配函数和便捷宏
API 提供了一些内存分配函数和便捷宏,用于分配可传递给 gawk 的内存:
1.
内存分配函数
:
void *gawk_malloc(size_t size);
void *gawk_calloc(size_t nmemb, size_t size);
void *gawk_realloc(void *ptr, size_t size);
void gawk_free(void *ptr);
这些函数会调用相应的标准 C 库函数,确保扩展和 gawk 使用的 C 库版本兼容。
2.
便捷宏
:
-
emalloc
:
#define emalloc(pointer, type, size, message) …
参数说明:
-
pointer
:指向分配存储的指针变量。
-
type
:指针变量的类型,用于为
gawk_malloc()
调用创建类型转换。
-
size
:要分配的总字节数。
-
message
:致命错误消息的前缀,通常是使用该宏的函数名。
示例:
awk_value_t result;
char *message;
const char greet[] = "Don't Panic!";
emalloc(message, char *, sizeof(greet), "myfunc");
strcpy(message, greet);
make_malloced_string(message, strlen(message), & result);
- **`erealloc`**:
#define erealloc(pointer, type, size, message) …
与
emalloc
类似,但调用
gawk_realloc()
而不是
gawk_malloc()
,参数相同。
构造函数
API 提供了一些构造函数和便捷宏,用于创建字符串和数值:
static inline awk_value_t *
make_const_string(const char *string, size_t length,
awk_value_t *result);
static inline awk_value_t *
make_malloced_string(const char *string, size_t length,
awk_value_t *result);
static inline awk_value_t *
make_null_string(awk_value_t *result);
static inline awk_value_t *
make_number(double num, awk_value_t *result);
这些函数分别用于创建常量字符串值、已分配内存的字符串值、空字符串值和数值。
注册函数
以下是用于将扩展的各个部分注册到 gawk 的 API 函数:
1.
注册扩展函数
:
扩展函数由
awk_ext_func_t
结构体描述:
typedef struct awk_ext_func {
const char *name;
awk_value_t *(*function)(int num_actual_args, awk_value_t *result);
size_t num_expected_args;
} awk_ext_func_t;
字段说明:
-
name
:新函数的名称,awk 级代码通过该名称调用函数,函数名必须遵循 awk 标识符规则。
-
function
:指向提供扩展功能的 C 函数的指针,该函数必须将结果填充到
*result
中,可以是数字或字符串,gawk 会接管字符串内存,字符串内存必须来自
gawk_malloc()
、
gawk_calloc()
或
gawk_realloc()
。
num_actual_args
参数表示从调用的 awk 代码传递的实际参数数量,函数必须返回
result
的值。
-
num_expected_args
:函数期望接收的参数数量,每个扩展函数可以决定如何处理参数数量不符的情况,通常可以忽略额外的参数。
注册扩展函数使用
add_ext_func
函数:
awk_bool_t add_ext_func(const char *namespace,
const awk_ext_func_t *func);
该函数成功时返回
true
,否则返回
false
。
namespace
参数目前未使用,应传递空字符串
""
,
func
指针是表示函数的结构体的地址。
2.
注册退出回调函数
:
void awk_atexit(void (*funcp)(void *data, int exit_status),
void *arg0);
退出回调函数是 gawk 退出前调用的函数,可用于执行扩展的清理任务,如关闭数据库连接或释放资源。
funcp
是指向退出前调用的函数的指针,
data
参数将是
arg0
的原始值,
exit_status
是 gawk 打算传递给
exit()
系统调用的退出状态值。
arg0
是指向私有数据的指针,gawk 会保存该数据并传递给
funcp
指向的函数。退出回调函数按后进先出(LIFO)顺序调用。
3.
注册扩展版本字符串
:
void register_ext_version(const char *version);
该函数用于注册表示扩展名称和版本的字符串,gawk 不会复制版本字符串,因此不应更改它。使用
--version
选项调用 gawk 时,会打印所有注册的扩展版本字符串。
自定义输入解析器
默认情况下,gawk 读取文本文件作为输入,使用
RS
值查找记录结尾,然后使用
FS
(或
FIELDWIDTHS
或
FPAT
)将其拆分为字段,并设置
RT
值。如果需要,可以提供自定义输入解析器,其工作是将记录返回给 gawk 记录处理代码,并提供
RT
的值和长度指示。
要提供输入解析器,需要先提供两个函数(
XXX
是扩展的前缀名称):
awk_bool_t XXX_can_take_file(const awk_input_buf_t *iobuf);
awk_bool_t XXX_take_control_of(awk_input_buf_t *iobuf);
-
XXX_can_take_file函数检查iobuf中的信息,决定是否使用输入解析器处理该文件,不改变 gawk 内部状态。 -
XXX_take_control_of函数在 gawk 决定将文件控制权交给输入解析器时调用,需要填充awk_input_buf_t结构体中的某些字段,并确保某些条件为真,成功返回true,否则返回false。
输入解析器的结构如下:
typedef struct awk_input_parser {
const char *name; /* name of parser */
awk_bool_t (*can_take_file)(const awk_input_buf_t *iobuf);
awk_bool_t (*take_control_of)(awk_input_buf_t *iobuf);
awk_const struct awk_input_parser *awk_const next; /* for gawk */
} awk_input_parser_t;
注册输入解析器的步骤如下:
1. 创建一个静态
awk_input_parser_t
变量并适当初始化。
2. 扩展加载时,使用
register_input_parser
函数注册输入解析器:
void register_input_parser(awk_input_parser_t *input_parser);
awk_input_buf_t
结构体如下:
typedef struct awk_input {
const char *name; /* filename */
int fd; /* file descriptor */
#define INVALID_HANDLE (-1)
void *opaque; /* private data for input parsers */
int (*get_record)(char **out, struct awk_input *iobuf,
int *errcode, char **rt_start, size_t *rt_len);
ssize_t (*read_func)();
void (*close_func)(struct awk_input *iobuf);
struct stat sbuf; /* stat buf */
} awk_input_buf_t;
字段可分为两类:供
XXX_can_take_file
函数使用的字段和供
XXX_take_control_of
函数使用的字段。
-
XXX_can_take_file
函数根据文件名称、文件描述符、
struct stat
信息等决定是否使用输入解析器。
-
XXX_take_control_of
函数填充
get_record
或
read_func
字段,并确保
fd
不为
INVALID_HANDLE
,还可以设置
opaque
、
close_func
等字段。
XXX_get_record
函数用于创建输入记录,参数如下:
-
char **out
:指向记录的指针。
-
struct awk_input *iobuf
:文件的
awk_input_buf_t
结构体。
-
int *errcode
:错误码,出错时设置为
<errno.h>
中的适当代码。
-
char **rt_start
和
size_t *rt_len
:
RT
的起始指针和长度。
返回值是
*out
指向的缓冲区长度,或文件结束或出错时返回
EOF
。
也可以提供一个只读取字节的函数,让 gawk 将数据解析为记录,该函数应遵循标准 POSIX
read()
系统调用的行为,填充
read_func
指针。但必须选择一种方法,不能同时提供返回记录和返回原始数据的函数。
gawk 附带一个读取目录的示例扩展,可作为编写自定义输入解析器的参考。编写输入解析器时,应考虑并记录它与 awk 代码的交互方式。
自定义输出包装器
输出包装器与输入解析器相反,允许扩展接管使用
>
或
>>
I/O 重定向运算符打开的文件的输出。
输出包装器的结构如下:
typedef struct awk_output_wrapper {
const char *name; /* name of the wrapper */
awk_bool_t (*can_take_file)(const awk_output_buf_t *outbuf);
awk_bool_t (*take_control_of)(awk_output_buf_t *outbuf);
awk_const struct awk_output_wrapper *awk_const next; /* for gawk */
} awk_output_wrapper_t;
awk_output_buf_t
结构体如下:
typedef struct awk_output_buf {
const char *name; /* name of output file */
const char *mode; /* mode argument to fopen */
FILE *fp; /* stdio file pointer */
awk_bool_t redirected; /* true if a wrapper is active */
void *opaque; /* for use by output wrapper */
size_t (*gawk_fwrite)(const void *buf, size_t size, size_t count,
FILE *fp, void *opaque);
int (*gawk_fflush)(FILE *fp, void *opaque);
int (*gawk_ferror)(FILE *fp, void *opaque);
int (*gawk_fclose)(FILE *fp, void *opaque);
} awk_output_buf_t;
扩展需要定义
XXX_can_take_file
和
XXX_take_control_of
函数,
XXX_can_take_file
函数根据文件名称、模式和其他状态信息决定是否接管文件,
XXX_take_control_of
函数填充
awk_output_buf_t
结构体的其他字段(除
fp
外),并将
redirected
字段设置为
true
。
注册输出包装器使用
register_output_wrapper
函数:
void register_output_wrapper(awk_output_wrapper_t *output_wrapper);
自定义双向处理器
双向处理器结合了输入解析器和输出包装器,用于
|&
运算符的双向 I/O。
双向处理器的结构如下:
typedef struct awk_two_way_processor {
const char *name; /* name of the two-way processor */
awk_bool_t (*can_take_two_way)(const char *name);
awk_bool_t (*take_control_of)(const char *name,
awk_input_buf_t *inbuf,
awk_output_buf_t *outbuf);
awk_const struct awk_two_way_processor *awk_const next; /* for gawk */
} awk_two_way_processor_t;
扩展需要提供
XXX_can_take_two_way
和
XXX_take_control_of
函数,分别决定是否接管双向 I/O 和填充
awk_input_buf_t
和
awk_output_buf_t
结构体。
注册双向处理器使用
register_two_way_processor
函数:
void register_two_way_processor(awk_two_way_processor_t *
two_way_processor);
通过以上介绍,我们详细了解了 gawk 扩展 API 的各个方面,包括通用数据类型、内存管理、构造函数、注册函数以及自定义输入解析器、输出包装器和双向处理器等功能。掌握这些知识,我们可以更好地开发 gawk 扩展,满足各种复杂的需求。
深入了解 gawk 扩展 API:数据类型、内存管理与功能注册
各组件功能总结
为了更清晰地理解 gawk 扩展 API 中各个组件的作用,下面我们通过一个表格来总结:
| 组件类型 | 功能描述 | 关键函数/结构体 |
| — | — | — |
| 通用数据类型 | 定义了扩展开发中常用的数据类型和结构 |
awk_ext_id_t
,
awk_const
,
awk_bool_t
,
awk_string_t
,
awk_valtype_t
,
awk_value_t
,
awk_scalar_t
,
awk_value_cookie_t
|
| 内存分配 | 提供安全的内存分配和释放机制 |
gawk_malloc()
,
gawk_calloc()
,
gawk_realloc()
,
gawk_free()
,
emalloc
,
erealloc
|
| 构造函数 | 用于创建字符串和数值 |
make_const_string()
,
make_malloced_string()
,
make_null_string()
,
make_number()
|
| 注册函数 | 将扩展的功能注册到 gawk 中 |
add_ext_func()
,
awk_atexit()
,
register_ext_version()
|
| 自定义输入解析器 | 接管文件输入处理 |
XXX_can_take_file()
,
XXX_take_control_of()
,
awk_input_parser_t
,
awk_input_buf_t
|
| 自定义输出包装器 | 接管文件输出处理 |
XXX_can_take_file()
,
XXX_take_control_of()
,
awk_output_wrapper_t
,
awk_output_buf_t
|
| 自定义双向处理器 | 处理双向 I/O |
XXX_can_take_two_way()
,
XXX_take_control_of()
,
awk_two_way_processor_t
|
开发流程示例
以下是一个简单的 mermaid 流程图,展示了开发一个 gawk 扩展的基本流程:
graph TD
A[定义通用数据类型和结构] --> B[进行内存分配]
B --> C[创建所需的值(构造函数)]
C --> D[注册扩展功能]
D --> E{是否需要自定义输入/输出/双向处理}
E -- 是 --> F[开发自定义处理逻辑并注册]
E -- 否 --> G[完成扩展开发]
F --> G
注意事项
在使用 gawk 扩展 API 进行开发时,有一些重要的注意事项需要牢记:
1.
内存管理
:
- 确保所有分配的内存都通过
gawk_malloc()
、
gawk_calloc()
或
gawk_realloc()
进行,释放内存时使用
gawk_free()
,避免内存泄漏。
- 使用
emalloc
和
erealloc
宏时,要正确设置参数,特别是错误消息前缀,方便调试。
2.
数据类型匹配
:
- 在使用
awk_value_t
时,要确保
val_type
成员正确反映联合中存储的值的类型,否则可能导致未定义行为。
3.
注册函数
:
- 注册扩展函数时,函数名必须遵循 awk 标识符规则,字符串内存必须来自指定的内存分配函数。
- 退出回调函数按后进先出(LIFO)顺序调用,合理安排注册顺序。
4.
自定义处理
:
- 自定义输入解析器、输出包装器和双向处理器时,要确保
XXX_can_take_*
和
XXX_take_control_of
函数正确处理各种情况,避免影响 gawk 的正常运行。
- 选择提供返回记录或返回原始数据的函数时,只能选择一种方法,不能同时使用。
示例代码综合应用
下面是一个简单的综合示例,展示了如何使用上述介绍的部分功能来开发一个 gawk 扩展:
#include <stdio.h>
#include <string.h>
#include "gawkapi.h"
// 定义扩展函数
awk_value_t *my_ext_function(int num_actual_args, awk_value_t *result) {
// 分配内存
char *message;
emalloc(message, char *, 20, "my_ext_function");
strcpy(message, "Hello from extension!");
// 创建字符串值
make_malloced_string(message, strlen(message), result);
return result;
}
// 注册扩展函数
void my_extension_init(awk_ext_id_t ext_id) {
awk_ext_func_t func;
func.name = "my_ext_function";
func.function = my_ext_function;
func.num_expected_args = 0;
add_ext_func("", &func);
}
// 退出回调函数
void my_exit_callback(void *data, int exit_status) {
printf("Extension is cleaning up...\n");
}
// 注册退出回调函数
void my_extension_register_exit(awk_ext_id_t ext_id) {
awk_atexit(my_exit_callback, NULL);
}
// 注册扩展版本字符串
void my_extension_register_version() {
register_ext_version("My Extension v1.0");
}
在这个示例中,我们定义了一个扩展函数
my_ext_function
,它会返回一个字符串值。在
my_extension_init
函数中注册了这个扩展函数,
my_extension_register_exit
函数注册了退出回调函数,
my_extension_register_version
函数注册了扩展版本字符串。
总结
通过对 gawk 扩展 API 的深入学习,我们了解了通用数据类型、内存管理、构造函数、注册函数以及自定义输入解析器、输出包装器和双向处理器等重要内容。掌握这些知识和技巧,我们可以根据具体需求开发出功能强大的 gawk 扩展,为数据处理和分析提供更多的灵活性和扩展性。在实际开发过程中,要严格遵循 API 的使用规则和注意事项,确保扩展的稳定性和可靠性。同时,参考示例代码和流程图,能够帮助我们更高效地完成扩展开发任务。
超级会员免费看
7

被折叠的 条评论
为什么被折叠?



