38、深入了解 gawk 扩展 API:数据类型、内存管理与功能注册

深入了解 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
  1. 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 的使用规则和注意事项,确保扩展的稳定性和可靠性。同时,参考示例代码和流程图,能够帮助我们更高效地完成扩展开发任务。

通过短时倒谱(Cepstrogram)计算进行时-倒频分析研究(Matlab代码实现)内容概要:本文主要介绍了一项关于短时倒谱(Cepstrogram)计算在时-倒频分析中的研究,并提供了相应的Matlab代码实现。通过短时倒谱分析方法,能够有效提取信号在时间倒频率域的特征,适用于语音、机械振动、生物医学等领域的信号处理故障诊断。文中阐述了倒谱分析的基本原理、短时倒谱的计算流程及其在实际工程中的应用价值,展示了如何利用Matlab进行时-倒频图的可视化分析,帮助研究人员深入理解非平稳信号的周期性成分谐波结构。; 适合人群:具备一定信号处理基础,熟悉Matlab编程,从事电子信息、机械工程、生物医学或通信等相关领域科研工作的研究生、工程师及科研人员。; 使用场景及目标:①掌握倒谱分析短时倒谱的基本理论及其傅里叶变换的关系;②学习如何用Matlab实现Cepstrogram并应用于实际信号的周期性特征提取故障诊断;③为语音识别、机械设备状态监测、振动信号分析等研究提供技术支持方法参考; 阅读建议:建议读者结合提供的Matlab代码进行实践操作,先理解倒谱的基本概念再逐步实现短时倒谱分析,注意参数设置如窗长、重叠率等对结果的影响,同时可将该方法其他时频分析方法(如STFT、小波变换)进行对比,以提升对信号特征的理解能力。
先看效果: https://pan.quark.cn/s/aceef06006d4 OJBetter OJBetter 是一个 Tampermonkey 脚本项目,旨在提升你在各个在线评测系统(Online Judge, OJ)网站的使用体验。 通过添加多项实用功能,改善网站界面和用户交互,使你的编程竞赛之旅更加高效、便捷。 ----- 简体中文 ----- 安装 主要功能 安装脚本,你可以获得: 黑暗模式支持:为网站添加黑暗模式,夜晚刷题不伤眼。 网站本地化:将网站的主要文本替换成你选择的语言。 题目翻译:一键翻译题目为目标语言,同时确保不破坏 LaTeX 公式。 Clist Rating 分数:显示题目的 Clist Rating 分数数据。 快捷跳转:一键跳转到该题在洛谷、VJudge 的对应页面。 代码编辑器:在题目页下方集成 Monaco 代码编辑器,支持自动保存、快捷提交、在线测试运行等功能。 一些其他小功能…… [!NOTE] 点击 网页右上角 的 按钮,即可打开设置面板, 绝大部分功能均提供了帮助文本,鼠标悬浮在 ”? 图标“ 上即可查看。 使用文档 了解更多详细信息和使用指南,请访问 Wiki 页面。 如何贡献 如果你有任何想法或功能请求,欢迎通过 Pull Requests 或 Issues 我们分享。 改善翻译质量 项目的非中文版本主要通过机器翻译(Deepl & Google)完成,托管在 Crowdin 上。 如果你愿意帮助改进翻译,使其更准确、自然,请访问 Crowdin 项目页面 贡献你的力量。 支持其他OJ? 由于作者精力有限,并不会维护太多的类似脚本, 如果你有兴趣将此脚本适配到其他在线评测系统,非常欢迎,你只需要遵守 GP...
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值