开源之夏-5
这周见老师了。驻扎这里学习。
外边世界真的好大好大!
今天是周一,总结一下上周。
我准备把sdf与tsapi部分移出来,新建一个项目。
软实现调通之后,实现命令行应用,之后进行69个接口的再完善。
(自己软实现SDF)我去用Evp接口,手动实现SDF,并在编译/链接时就绑到程序里,那这套 SDF 接口就变成了 “编译期静态绑定” 的多态实现。
(已有软实现SDF)我拿到GmSSL的子项目,学习cmake工具,学习build.info,(Tongsuo和GmSSL构建系统的工具)
把SoftSDF软实现尝试动态加载到Tongsuo,跑通SDF后端实现,完善SDF标准,完成接口解析,完成命令行应用。
这个位置基本处于Tongsuo库的腰部,上边去打通命令行应用,下边完成接口标准适配与动态库解析。
现在是8月18日,好的。时限过半,明确了整个实现思路~。
C的面向对象基础
这个项目很典型的使用了C的面向对象写法。
函数指针结构体是这里的一种实现思路。其它的思路方法老师还提到有泛指针……
需要的能力 | C 原生缺失 | 函数指针结构体如何补 |
---|---|---|
封装 | 无 class |
struct { data..., fn_pointers... } |
回调 | 无闭包/委托 | 成员放 void (*cb)(void *) |
多态 | 无虚函数 | 手动 vtable(结构体里放函数指针表) |
插件 | 无接口/反射 | 结构体当接口,动态库填充后传递 |
函数指针类型的typedef定义
为一个函数指针类型定义一个别名,方便后续使用:
1 |
|
效果上:
1 |
|
语法上:1970 年代就合法,最早出现在 Unix 内核 里,用来在 纯 C 里“山寨”面向对象、回调、虚函数表。
工程上:凡是“需要把数据 + 操作打包在一起”的 C 工程,都会用这一招做“面向对象、回调、多态”。
C 语言没有“类、虚函数、接口、插件机制”这些高级概念,但“数据结构 + 函数指针表”恰好能一次性补齐这些能力,而且代价极低——不依赖语言特性,只靠最普通的指针和结构体。
1 |
|
extern语句
因为在 C 里,不带存储类说明符的“顶层”变量声明就是“定义”,定义就要给它真正分配内存;
eg: 语法规则(C17 §6.9.2/1)
位于文件作用域(translation unit)且不带 extern、static、thread_local 等存储类说明符的变量声明,如果没有初始化器,视为“tentative definition”,最终会被当成“真正的定义”并产生一块对象。
1 |
|
加了 extern 后
1 |
|
1 |
|
这个定义在sdf.h中完成。
extern 之所以在这些场景里“无孔不入”,并不是因为它本身和“面向对象 / 回调 / 多态 / 插件”有什么直接语义关系,而是因为它解决了 “跨文件共享唯一实例” 这一 C 语言在构建大型模块化系统时的刚需。
多态
统一接口,不同实现
在 C++ 里用虚函数表;在 C 里手动造一张表:
1 |
|
这个方法就是,sdf_lib.c里边对软实现/硬实现/插装降级选择机制的实现方法。
插件就是“用动态链接库做实现载体”的运行时多态。
- 多态的核心 = 同一接口(函数签名 / 虚表 / 协议)+ 不同实现。
- 实现方式 可以是:
- 编译期静态绑定(C++ 虚函数表、内核里的 file_operations 常量表)。
- 运行期动态绑定(插件:把接口表里的函数指针换成 dlopen/dlsym 解析到的地址)。
因此,插件只是 “多态 + 动态加载” 的组合,本质上仍是多态。
在这个项目中,x_OpenDevice类函数插桩是一种使用多态技术进行降级实现方法。
而我要实现的去适配厂商库,是使用多态技术进行运行期动态绑定 => 插件
而我的待定任务,软实现SDF,可以选择:
- 编译期静态绑定(C++ 虚函数表、内核里的 file_operations 常量表)。
- 运行期动态绑定(插件:把接口表里的函数指针换成 dlopen/dlsym 解析到的地址)。
对应我现在的解决思路:
我准备把sdf与tsapi部分移出来,新建一个项目。
软实现调通之后,实现命令行应用,之后进行69个接口的再完善。
(自己软实现SDF)我去用Evp接口,手动实现SDF,并在编译/链接时就绑到程序里,那这套 SDF 接口就变成了 “编译期静态绑定” 的多态实现。
(已有软实现SDF)我拿到GmSSL的子项目,学习cmake工具,学习build.info,(Tongsuo和GmSSL构建系统的工具)
把SoftSDF软实现尝试动态加载到Tongsuo,跑通SDF后端实现,完善SDF标准,完成接口解析,完成命令行应用。
这个位置基本处于Tongsuo库的腰部,上边去打通命令行应用,下边完成接口标准适配与动态库解析。
现在是8月18日,好的。时限过半,明确了整个实现思路~。
插件
上文中说 运行时把 vtbl 指向 DogVTable、CatVTable,就达到“同一接口,不同表现”的虚函数效果。
动态替换实现,就是这里“指向”的含义。
插件的本质:运行时把符号解析到新的函数地址;
函数指针结构体正好充当“接口契约”:
- 主程序声明一个
struct plugin_ops
; - 插件
.so
里定义并填充该结构体; dlopen
/dlsym
后把结构体地址交给主程序,主程序无需重新编译即可调用新功能。1
2struct plugin_ops *ops = dlsym(handle, "plugin_entry");
ops->init();
DSO机制
这是用 C 写的插件机制”的完整执行流程里最关键的两步——加载动态库(.so/.dll) 和 把库里实现的函数挂到结构体函数指针表上。
1 |
|
1 |
|
- DSO_load ≈ POSIX 的 dlopen / Windows 的 LoadLibrary:
把磁盘上的 libsdf.so(或 sdf.dll)映射进当前进程地址空间。
1 |
|
- DSO_bind_func ≈ dlsym / GetProcAddress:
在刚才加载的库里查找符号 SDF_OpenDevice 的地址,并填入全局结构体 sdfm 的对应成员(函数指针)。
填完后,sdfm 就成了一张完整的虚函数表,主程序以后通过 sdfm.OpenDevice(…) 就能调用 插件里的实现,而无需在编译期链接到任何具体实现。
插件系统概念 你的代码片段体现 插件文件 libsdf.so
插件入口/接口表 struct sdf_method_st sdfm;
运行时加载 DSO_load
符号解析 DSO_bind_func
统一调用接口 通过 sdfm.xxx(...)
小结
因此,这段代码就是“插件机制在 C 中的落地实现”:
- 编译期只面对
struct sdf_method_st
这张接口表; - 运行期才把真正的实现从动态库里“插进来”,实现了解耦、可替换、可扩展。