c实现模块开发的一个技巧

简介: c实现模块开发的一个技巧

在实现线程池的功能时,遇到一个c语言模块开发的技巧,愚笨的自己耗费了好大的精力去理解相关的代码逻辑,这里做了相关的整理:

1:对这个思路的理解:

  C语言是面向过程开发的,但是可以用一定的技巧,参考C++面向对象的技巧,有效的组织代码架构。

  基于此思路,实现下面一套适用于c语言的模块开发的技巧:

1:定义基础的结构,确定实现包裹函数。

2:定义业务结构体,一个void*的指针和需要的数据结构。

3:实例化一个变量,根据基础的结构,包含了业务结构体数据结构大小和实际的执行函数。

4:调用包裹函数,传入实例化的对象去初始化,获取到业务结构体==》真正的执行函数地址+数据结构

5:调用其他包裹函数,传入实例化的对象可以实现调用。

**重点:**业务实际的数据结构和 底层包裹函数实例化后的结构的关系,由包裹函数操作

2:对实现思路的解释:

1:思考原理:

程序=算法+数据结构,在实现业务功能时,我们一般关注的有两个点:数据结构和执行函数。

 ==》可以得出,我们如果获取到程序执行需要的相关数据结构和执行函数,则可以控制整个流程。

 ==》为了控制每个业务流程相关的数据结构和执行函数,即,每个程序(实例)需要关注的自身的结构定义 ,如下文的Base_Struct 和 Base_Struct_expand实例的定义

 ==》数据结构一般需要我们手动的初始化,一般需要申请内存,需要关注结构类型和大小。

    相关的执行函数,我们可以通过指针指向的方式实现获取。

==》流程中的控制体: 保存了函数指针和相关初始化过的数据结构的一个对象。 即参考源码中的TEST_MODULE,void* _的定义指向真正的函数实例对象moudle_t

  这是同一类的数据结构和函数,实现同一种业务功能的封装(类比C++中的类)。

2:实现

1:入参 : 数据结构的大小和实际执行的函数的实例化 ==》结构定义参考Base_Struct和Base_Struct_expand

2:返回值/传入控制体:返回指向执行函数指针地址和数据结构内容的结构 ==》参考 TEST_MODULE的定义

3:实际函数的执行: 函数的触发不再依赖传统的调用,而是需要专门的包裹函数去调用执行

  ==》参考这里的New, Delete,Show

  ==》注意New函数中的逻辑,申请内存和把结构体的首地址实际指向入参参数保存的函数指针位置。

4:流程控制:New的时候构造保存了函数指针和需要数据结构的结构指针(返回值)。

其他函数的执行传入New函数返回的结构指针。

3:扩展使用:

1:这样使用的好处:

专门的函数实现了同一类型的同一种功能函数的调用,这样:

  调用者不必关注实际的实例的函数,

  实现者不用关心如何调用。

实现了调用者和和实现者之间的解耦,是一种可扩展的模块开发技巧。

2:扩展使用:

1:nginx模块开发

2:基于c语言实现的单例模式,工厂模式等一系列设计模式的封装。


3:相关测试源代码:

base.h:

#ifndef _BASE_H_
#define _BASE_H_
#include <stdarg.h>
//统一的函数执行体 
//size 保存了子结构地址和需要的数据结构
typedef struct 
{
  size_t size;
  void* (*ctor)(void*_self, va_list *params);
  void* (*dtor)(void*_self);
}Base_Struct;
void* New(const void* _class, ...);
void Delete(void* _class);
typedef struct 
{
  size_t size;
  void* (*ctor)(void*_self, va_list *params);
  void* (*dtor)(void*_self);
  void* (*show)(void*_self);
}Base_Struct_expand;
void Show(void* _class);
#endif

base.c

#include <stdlib.h>
#include <assert.h>
#include "base.h"
void* New(const void* _class, ...)
{
  const Base_Struct *class = _class;
  void * p = calloc(1, class->size); 
  assert(p); 
  //注意  这里是重点  保存了实际的执行操作结构体的地址
  *(const Base_Struct**)p = class; //两层取值可以取到实际结构体位置
  if(class->ctor)
  {
    va_list params;
    va_start(params, _class); 
    p = class->ctor(p, &params);
    va_end(params); 
  }
  return p;
}
//取地址里存的值 找到对应的地址执行
void Delete(void* _class)
{
  const Base_Struct **class = _class;
  if(_class && (*class) && (*class)->dtor)
  {
    _class = (*class)->dtor(_class);
  }
  free(_class);
}
void Show(void* _class)
{
  const Base_Struct_expand ** class = _class;
  if (_class && (*class) && (*class)->show) {    
        (*class)->show(_class);
    }
}

test_module.h

#ifndef _TEST_MODULE_H_
#define _TEST_MODULE_H_
#include <stdarg.h>
//直接在基础定义模型使用,不扩展其他的函数接口
//定义我们的结构体 我们实际的数据
typedef struct _t_test_struct
{
  int id;
  char name[8];
}TEST_STRUCT;
//定义符合结构的结构体
typedef struct _t_test_module
{
  const void *_;
  TEST_STRUCT * data;
}TEST_MODULE;
//结构定义入参 以此为标准申请内存和指针指向
extern const void *module_test;
#endif

test_module.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "base.h"
#include "test_module.h"
//new函数真正调用这个函数  这里做真正初始化
//new函数实际做了这里真正的指向 和数据的传递
//可以通过传参初始化这里的结构体
static void *test_module_create(void *_self, va_list *params) {
    TEST_MODULE *data_t = (TEST_MODULE*)_self; //这个的实际指向就是moudle_t定义
    TEST_STRUCT * data = (TEST_STRUCT*)malloc(sizeof(TEST_STRUCT));
    data_t->data = data;
    int arg = va_arg(params, int);
    printf("get para is %d. \n",arg);
    data->id = arg;
    memcpy(data->name, "mytest", 6);
    printf("test_module_create :%d, %s \n", data->id, data->name);
    return data_t;
}
//Delete 函数真正掉这个函数  做对应的销毁
static void *test_module_destory(void *_self) {
    TEST_MODULE *data_t = (TEST_MODULE*)_self;
    //目的是销毁create中对应的结构
    printf("test_module_destory :%d, %s \n", data_t->data->id, data_t->data->name);
    if(data_t->data != NULL)
    {
      free(data_t->data);
      printf("free data success \n");
    }
    return data_t;
}
//按照真正的结构进行组织
static const Base_Struct moudle_t = {
  sizeof(TEST_MODULE),
  test_module_create,
  test_module_destory,
};
//把该结构地址共享出去,基类通过地址强行转换执行到这里的函数
const void * module_test = &moudle_t;

test_module_expand.h

#ifndef _TEST_MODULE_EXPAND_H_
#define _TEST_MODULE_EXPAND_H_
#include <stdarg.h>
//结构定义入参 以此为标准申请内存和指针指向
extern const void *module_test_expand;
#endif

test_module_expand.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "base.h"
#include "test_module.h"
//new函数真正调用这个函数  这里做真正初始化
//new函数实际做了这里真正的指向 和数据的传递
//可以通过传参初始化这里的结构体
static void *test_module_expand_create(void *_self, va_list *params) {
    TEST_MODULE *data_t = (TEST_MODULE*)_self;
    TEST_STRUCT * data = (TEST_STRUCT*)malloc(sizeof(TEST_STRUCT));
    int arg = va_arg(params, int);
    data->id = arg;
    memcpy(data->name, "mytest", 6);
    printf("test_module_create :%d, %s \n", data->id, data->name);
    data_t->data = data;
    return data_t;
}
//Delete 函数真正掉这个函数  做对应的销毁
static void *test_module_expand_destory(void *_self) {
    const Base_Struct_expand **class = _self;
    if(_self && (*class) && (*class)->dtor)
    {
      _self = (*class)->show(_self);
    }
    //目的是销毁create中对应的结构
    TEST_MODULE *data_t = (TEST_MODULE*)_self;
    printf("test_module_expand_destory :%d, %s \n", data_t->data->id, data_t->data->name);
    if(data_t->data != NULL)
    {
      free(data_t->data);
      printf("free data success \n");
    }
    return data_t;
}
static void *test_module_expand_show(void *_self)
{
    TEST_MODULE *data_t = (TEST_MODULE*)_self;
    //目的是销毁create中对应的结构
    printf("test_module_expand_show :%d, %s \n", data_t->data->id, data_t->data->name);
    return data_t;
}
//按照真正的结构进行组织
static const Base_Struct_expand moudle_t = {
    sizeof(TEST_STRUCT),
    test_module_expand_create,
    test_module_expand_destory,
    test_module_expand_show,
};
//把该结构地址共享出去,基类通过地址强行转换执行到这里的函数
const void * module_test_expand = &moudle_t;

main.c

#include <stdio.h>
#include <stdlib.h>
#include "test_module.h"
#include "test_module_expand.h"
#include "base.h"
int main()
{
  //根据module_test 去构造  第二个参数实际上是对象实际构造函数的参数
  //会重新申请需要用的数据结构,共用一套函数方法
  void *test = New(module_test, 3); //这里可以多个参数,数据的初始化
  void *test1 = New(module_test_expand, 2);
  Show(test1);
  Delete(test);
  Delete(test1);
  return 0;
}

测试执行结果:

ubuntu@ubuntu:~/test/cmodule$ gcc *.c -o main
ubuntu@ubuntu:~/test/cmodule$ ./main 
get para is 3. 
test_module_create :3, mytest 
test_module_create :2, mytest 
test_module_expand_show :2, mytest 
test_module_destory :3, mytest 
free data success 
test_module_expand_show :2, mytest 
test_module_expand_destory :2, mytest 
free data success


目录
相关文章
|
5月前
|
存储 人工智能 自然语言处理
RAG 调优指南:Spring AI Alibaba 模块化 RAG 原理与使用
通过遵循以上最佳实践,可以构建一个高效、可靠的 RAG 系统,为用户提供准确和专业的回答。这些实践涵盖了从文档处理到系统配置的各个方面,能够帮助开发者构建更好的 RAG 应用。
2543 114
|
10月前
|
API 数据处理 开发工具
云计算在金融行业的应用与挑战
云计算在金融行业的应用与挑战
648 0
|
6月前
|
资源调度 JavaScript 前端开发
前端开发必备!Node.js 18.x LTS保姆级安装教程(附国内镜像源配置)
本文详细介绍了Node.js的安装与配置流程,涵盖环境准备、版本选择(推荐LTS版v18.x)、安装步骤(路径设置、组件选择)、环境验证(命令测试、镜像加速)及常见问题解决方法。同时推荐开发工具链,如VS Code、Yarn等,并提供常用全局包安装指南,帮助开发者快速搭建高效稳定的JavaScript开发环境。内容基于官方正版软件,确保合规性与安全性。
5448 24
遥感数据趋势分析Sen+mk
Sen's Slope估计器和Mann-Kendall趋势检验的结合,为遥感数据的长期趋势分析提供了一个强大的工具。Sen's Slope对异常值不敏感,而Mann-Kendall则能确定趋势的显著性和方向。在遥感数据的处理和分析中,正确应用这两种方法能够有效地挖掘出数据背后的环境和气候变化信息,对于科学研究和决策支持都具有重要价值。
498 3
|
消息中间件 存储 负载均衡
RocketMQ 客户端负载均衡机制详解及最佳实践
本文介绍 RocketMQ 负载均衡机制,主要涉及负载均衡发生的时机、客户端负载均衡对消费的影响(消息堆积/消费毛刺等)并且给出一些最佳实践的推荐。
597 0
RocketMQ  客户端负载均衡机制详解及最佳实践
|
安全 IDE 物联网
|
8天前
|
人工智能 运维 安全
|
6天前
|
人工智能 异构计算
敬请锁定《C位面对面》,洞察通用计算如何在AI时代持续赋能企业创新,助力业务发展!
敬请锁定《C位面对面》,洞察通用计算如何在AI时代持续赋能企业创新,助力业务发展!
|
7天前
|
机器学习/深度学习 人工智能 自然语言处理
B站开源IndexTTS2,用极致表现力颠覆听觉体验
在语音合成技术不断演进的背景下,早期版本的IndexTTS虽然在多场景应用中展现出良好的表现,但在情感表达的细腻度与时长控制的精准性方面仍存在提升空间。为了解决这些问题,并进一步推动零样本语音合成在实际场景中的落地能力,B站语音团队对模型架构与训练策略进行了深度优化,推出了全新一代语音合成模型——IndexTTS2 。
643 22
|
7天前
|
人工智能 测试技术 API
智能体(AI Agent)搭建全攻略:从概念到实践的终极指南
在人工智能浪潮中,智能体(AI Agent)正成为变革性技术。它们具备自主决策、环境感知、任务执行等能力,广泛应用于日常任务与商业流程。本文详解智能体概念、架构及七步搭建指南,助你打造专属智能体,迎接智能自动化新时代。