6 库(harib24f)

如果像上一节那样,把函数拆分开来,并用连接器来进行连接的话,我们需要创建很多很多个.obj文件。当然,如果不拆分函数,而是做成一个大的.obj文件也可以(如同a_nask.obj),但这样的话应用程序没有引用的函数也会被包含进去,生成的应用程序文件就会像之前那样无端增大很多。

作为一个操作系统来说,现在我们的“纸娃娃系统”规模还不算大,但如果我们要实现Windows和Linux这样的操作系统中的全部API函数,最终需要多少个.obj文件呢?大概得有几千个吧,只是想想头就大了。

要解决这个问题,我们可以使用“库”。库的英文名称是“library”,原本是图书馆的意思,在这里它的用途是将很多个.obj文件打包成一个文件(这种管理方式的确有点像图书馆吧),这样一来文件的数量就变少了,整个系统的结构也精简了。

要创建一个库,我们首先需要.obj文件作为原材料,除此之外,我们还需要一个叫做库管理器的程序。库管理器英文是“librarian”,原本是图书馆管理员的意思。其实, tolset中已经包含笔者编写的库管理器了,大家不用担心。

■■■■■

好了,我们马上来创建一个库吧。在Makefile中加上下面的代码。

本次的Makefile节选

  1. GOLIB = $(TOOLPATH)golib00.exe
  2. apilib.lib : Makefile $(OBJS_API)
  3. $(GOLIB) $(OBJS_API) out:apilib.lib

完工了,通过短短几行代码我们就得到了apilib.lib这样一个文件。

我们可以在obj2bim中指定刚刚生成的这个apilib.lib来代替那一串.obj文件。仅从Makefile来看的话好像也没有太大的好处,不过只用一个.lib文件就能代替那么多.obj文件,怎么说都是很酷的哦。

本次的Makefile节选

  1. a.bim : a.obj apilib.lib Makefile
  2. $(OBJ2BIM) @$(RULEFILE) out:a.bim map:a.map a.obj apilib.lib

■■■■■

借此机会,我们顺便写一个apilib.h。

本次的apilib.h

  1. void api_putchar(int c);
  2. void api_putstr0(char *s);
  3. void api_putstr1(char *s, int l);
  4. void api_end(void);
  5. int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
  6. void api_putstrwin(int win, int x, int y, int col, int len, char *str);
  7. void api_boxfilwin(int win, int x0, int y0, int x1, int y1, int col);
  8. void api_initmalloc(void);
  9. char *api_malloc(int size);
  10. void api_free(char *addr, int size);
  11. void api_point(int win, int x, int y, int col);
  12. void api_refreshwin(int win, int x0, int y0, int x1, int y1);
  13. void api_linewin(int win, int x0, int y0, int x1, int y1, int col);
  14. void api_closewin(int win);
  15. int api_getkey(int mode);
  16. int api_alloctimer(void);
  17. void api_inittimer(int timer, int data);
  18. void api_settimer(int timer, int time);
  19. void api_freetimer(int timer);
  20. void api_beep(int tone);

有了它,我们用一句话就可以搞定应用程序开头的API函数声明了。

  1. #include "apilib.h"

例如,beepdown.c就可以简化成下面这样了。

本次的beepdown.c节选

  1. #include "apilib.h" /*这里!*/
  2. void HariMain(void)
  3. {
  4. (中略)
  5. }

这样的代码非常易读,因此我们把其他应用程序也改成这样了,看起来多清爽,耶!

■■■■■

下面我们来详细讲解一下库的知识。

在很久很久以前,程序都是一个整块,也就是说,编程的时候必须完整地从头写到尾,这种编程的方法自然是十分麻烦的。

后来,有人提出了一种新的编程方法:将程序的功能拆分为小的部件(函数),然后再组合起来构成一个完整的程序。这样,编写好的部件还可以保存起来,下次可以用在其他程序上面。这种编程方法被称为“结构化编程”。“纸娃娃系统”的核心以及各个应用程序就是由无数个函数组合而成的,这便是结构化编程理念的体现。

依据结构化编程的思想,把将来可以用于其他程序的部件组织起来就构成了库。结构化编程中库是一个很宽泛的概念,除了.lib文件,.obj文件本身也可以被称为库(总之凡是能作为部件使用的都是库)。

我们不仅在a.c中使用过用来调用API的函数,在其他程序的开发中也使用过。之前我们也没有特别提过,大家可能认为前面编写过的部分后面可以直接拿过来用是理所当然的,但其实这就是结构化编程的技术之一。

不断扩充的库就像自己所拥有的财产一样,手上拥有的部件种类越多,后面的各种开发工作就会越轻松、迅速。而且库不一定要自己来编写,我们也可以使用别人编写的库。例如,在“纸娃娃系统”中,我们所用到的sprintf和rand等函数就属于这一类。我们并没有在本书中特地编写这些函数,但依然能够正常地使用它们。因此,库也可以说是人类的公共财产(就像公共图书馆一样)。

在结构化编程的思想中,库越容易使用越好。如果文件数量太多,一不小心丢掉了其中一个又没有发现就非常麻烦了。此外,在将库分享给别人时,自然也是文件数量越少越好,与人方便,自己方便,因此像这种.lib形式的库是十分常用的。

■■■■■

对了,sprintf和rand其实包含在tolset的z_tool/haribote目录下的golibc.lib中。

顺便说一句,随着结构化编程的普及,人类的程序开发能力大幅提升,众多的开发者编写出了无数的库。但随着时代进步,函数的数量实在太多了,无法对每个函数的使用方法进行有效的管理。为了解决这个问题,“面向对象编程”的新思想应运而生(当然,这其实是结构化编程的扩展版,是在结构化编程思想的基础上发展而来的)。关于面向对象编程的具体内容,在这里就不详细讲解了(因为本书中并没有用面向对象编程的方法所开发的程序)。