用C语言实现interface

[复制链接]
 楼主| keer_zu 发表于 2021-11-22 09:33 | 显示全部楼层 |阅读模式
本文介绍C语言中如何实现接口(interface),应用场景为:多个提供者提供的服务接口函数完全相同,但实现不同,因而在性能、可靠性等方面有所差异。比如,两个HashMap的实现,一个性能非常快,但内存占用大;另一个内存占用非常少,但性能一般。但二者提供的服务接口函数都是一样的,如get、put等。

通过interface,我们可以把服务提供者的服务界面抽象成一致的函数群,调用者只需要对接口进行编程即可,无需关心接口下面的具体实现。这是对函数群的封装。

 楼主| keer_zu 发表于 2021-11-22 09:34 | 显示全部楼层
接口(interface)技术,是非常强大的技术。通过接口,调用者可以在对服务提供者完全无感知,甚至调用者的代码写完之后,还可以继续增加新的服务提供者,无需调用者的代码做任何更改。比如,一个操作系统需要管理各种文件系统,但文件系统的种类是非常多的,无法把每一种文件系统都写入操作系统的内核中,因此内核可以对文件系统进行接口抽象,只要满足了这个接口要求,后续新的文件系统都可以注册进内核,无需对内核的代码进行任何修改。

与上面类似的业务场景,非常多,比如I/O的多种调度策略、复杂服务程序支持的各种插件接口、GUI界面对Window、Widget的接口抽象等等。

 楼主| keer_zu 发表于 2021-11-22 09:35 | 显示全部楼层
我们用文件系统的场景,来展示C语言中如何设计和实现接口。

操作系统内核是调用者,各种文件系统是服务实现者,这些文件系统都实现了如下接口:
  1. interface IFileSystem {
  2.         int create_file(const char* path, int flags, int mode);
  3.         int open_file(const char* path, int flags);
  4.         int read_file(int fd, char* buf, int len);
  5.         int write_file(int fd, const char* buf, int len);
  6.         int close_file(int fd);
  7.         // ...
  8. }


C语言中,没有interface这个语法,因此上面的代码在C语言中,需要用struct 来实现。具体包括:

1. 每个接口函数,需要声明一个单独的函数指针类型;

2. 整个interface的方法集,用一个struct来表示,struct的成员为各个函数指针

3. 每个文件系统的实现者,各自需要一个struct来表示,这个struct的类型对调用者不可见。各个文件系统有自己的struct结构,彼此互不相同,也互不可见。

4. 接口的实现,包括两部分:1)接口函数的实现;2)文件系统的struct实例。这两部分放在一起,构成了接口的实现。我们用一个struct来把这两部分组合在一起。

5. 由于各个文件系统的struct结构,对调用者不可见,因此文件系统用void*把自己的struct指针传递给调用者。
 楼主| keer_zu 发表于 2021-11-22 09:38 | 显示全部楼层
具体的代码实现,如下:(为了保持篇幅简洁,只列了open_file和read_file 两个方法)


  1. //fs_interface.h
  2. #ifndef FS_INTERFACE_H
  3. #define FS_INTERFACE_H


  4. // 接口函数指针类型
  5. typedef int (*open_file_fn)(void* pfs, const char* path, int flags);
  6. typedef int (*read_file_fn)(void* pfs, int fd, char* buf, int len);




  7. // 接口方法集
  8. typedef struct fs_methods_t {
  9.         open_file_fn open_file;
  10.         read_file_fn read_file;
  11. } fs_methods_t;


  12. // 接口的实现体
  13. typedef struct file_system_interface {
  14.         void* pfs;                              // 文件系统的具体实现struct
  15.         fs_methods_t* pmethods; // 这个文件系统的具体接口方式实现
  16. } file_system_interface;


  17. // 各个文件系统,通过 register_file_system 将自己注册进内核
  18. // const char* pname;           // 文件系统的名称,如ext2, xfs...
  19. int register_file_system(const char* pname, file_system_interface fsi);
上面的代码中,有几个地方需要注意:
1)每个接口函数类型声明中,都比interface中的函数多了一个参数:void* pfs, 这个参数指向具体的文件系统的struct。
这样,内核才能真正对这个struct对象发起调用。
2)file_system_interface 是interface的具体实现体,里面包括2个指针:一个是指向文件系统实现体struct的指针pfs, 另一个指针指向文件系统实现的接口函数的集合。

 楼主| keer_zu 发表于 2021-11-22 09:38 | 显示全部楼层
这样,interface就是一个简单的struct,可以像简单变量一样声明、赋值和参数传递,其按值拷贝传递即可,无需传指针引用。



这样,通过 file_system_interface,内核就可以对文件系统发起调用。例如,调用ext2 文件系统的open_file:
  1. // find ext2 file_system_interface by name "ext2"
  2. file_system_interface fsi = ....
  3. // call open_file
  4. int fd = fsi.pmethods->open_file(fsi.pfs, path, flags);
  5. return fd;



 楼主| keer_zu 发表于 2021-11-22 09:39 | 显示全部楼层
再看看 ext2 文件系统,如何实现这个接口:

ext2_fs.h 的内容如下:

  1. // ext2_fs.h
  2. #ifndef EXT2_FS_H
  3. #define EXT2_FS_H


  4. #include "fs_interface.h"


  5. typedef struct ext2_fs_t {
  6.         // ......
  7. } ext2_fs_t;


  8. int ext2_open_file(ext2_fs_t* pfs, const char* path, int flags);
  9. int ext2_read_file(ext2_fs_t* pfs, int fd, char* buf, int len);


  10. int ext2_init(void);


  11. #endif
 楼主| keer_zu 发表于 2021-11-22 09:39 | 显示全部楼层
ext2_fs.c 的实现代码:

  1. // ext2_fs.c
  2. #include "ext2_fs.h"


  3. // 接口方法实现集合
  4. fs_methods_t ext2_methods = {
  5.         .open_file = (open_file_fn)ext2_open_file,
  6.         .read_file = (read_file_fn)ext2_read_file,
  7. };


  8. // 服务实例
  9. ext2_fs_t g_ext2_fs;


  10. int ext2_init(void) {
  11.         // init g_ext2_fs struct ....
  12.         // ....


  13.         file_system_interface fsi = { .pfs = &g_ext2_fs, .pmethods = &ext2_methods };
  14.         int ret = register_file_system("ext2", fsi);
  15.         return ret;
  16. }




  17. int ext2_open_file(ext2_fs_t* pfs, const char* path, int flags) {
  18.         // ....
  19. }


  20. int ext2_read_file(ext2_fs_t* pfs, int fd, char* buf, int len) {
  21.         // ....
  22. }


其它文件系统,如xfs,ext4等,实现的代码与上面的ext2类似,这样就都可以注册进内核中。通过接口,内核可以对这些实现无感知。
 楼主| keer_zu 发表于 2021-11-22 09:39 | 显示全部楼层
接口,是非常强大的封装手段,在业务场景中经常遇到,希望通过本文的代码展示,让大家都学会这种技术。
qin552011373 发表于 2021-11-26 10:25 | 显示全部楼层
赞一个,支持连更

评论

多谢支持  发表于 2021-11-26 11:42
 楼主| keer_zu 发表于 2021-11-26 11:42 | 显示全部楼层

欢迎关注本版,后面我再给个实际例子。
qin552011373 发表于 2021-11-26 14:03 | 显示全部楼层
keer_zu 发表于 2021-11-26 11:42
欢迎关注本版,后面我再给个实际例子。

持续关注中
 楼主| keer_zu 发表于 2021-11-30 09:24 | 显示全部楼层
qin552011373 发表于 2021-11-30 09:29 | 显示全部楼层
keer_zu 发表于 2021-11-30 09:24
https://bbs.21ic.com/icview-3182228-1-1.html
这里再提供一个实例

去看看
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:qq群:49734243 Email:zukeqiang@gmail.com

1478

主题

12917

帖子

55

粉丝
快速回复 在线客服 返回列表 返回顶部