4.1.3 GNU make 和 makefile
(1) GNU make简介
在大型的开发项目中,通常有几十到上百个的源文件,如果每次均手工键入 gcc 命令进行编译的话,则会非常不方便。因此,人们通常利用 make 工具来自动完成编译工作。这些工作包括:如果仅修改了某几个源文件,则只重新编译这几个源文件;如果某个头文件被修改了,则重新编译所有包含该头文件的源文件。利用这种自动编译可大大简化开发工作,避免不必要的重新编译。
实际上,make 工具通过一个称为 makefile 的文件来完成并自动维护编译工作。makefile 需要按照某种语法进行编写,其中说明了如何编译各个源文件并连接生成可执行文件,并定义了源文件之间的依赖关系。
当修改了其中某个源文件时,如果其他源文件依赖于该文件,则也要重新编译所有依赖该文件的源文件。
默认情况下,GNU make 工具在当前工作目录中按如下顺序搜索 makefile: GNUmakefile,makefile,Makefile。在 UNIX 系统中,习惯使用 Makefile 作为 makfile 文件。
(2) makefile 基本结构
makefile 中一般包含如下内容:
* 需要由 make 工具创建的项目,通常是目标文件和可执行文件。
* 要创建的项目依赖于哪些文件。
* 创建每个项目时需要运行的命令。
例如,假设你现在有一个 C++ 源文件 test.C,该源文件包含有自定义的头文件 test.h,则目标文件 test.o 明确依赖于两个源文件:test.C 和 test.h。另外,你可能只希望利用 g++ 命令来生成 test.o 目标文件。
这时,就可以利用如下的 makefile 来定义 test.o 的创建规则:
test.o: test.C test.h
g++ -c -g test.C
一个 makefile 文件中可定义多个目标,利用 make target 命令可指定要编译的目标,如果不指定目标,则使用第一个目标。通常,makefile 中定义有 clean 目标,可用来清除编译过程中的中间文件,例如:
clean:
rm -f *.o
运行 make clean 时,将执行 rm -f *.o 命令,最终删除所有编译过程中产生的所有中间文件。
(3) makefile 变量
GNU 的 make 工具除提供有建立目标的基本功能之外,还有许多便于表达依赖性关系以及建立目标的命令的特色。其中之一就是变量或宏的定义能力。如果你要以相同的编译选项同时编译十几个 C 源文件,而为每个目标的编译指定冗长的编译选项的话,将是非常乏味的。但利用简单的变量定义,可避免这种乏味的工作,例如:
CC = gcc
CCFLAGS = -D_DEBUG -g -m486
test.o: test.c test.h
$(CC) -c $(CCFLAGS) test.c
在上面的例子中,CC 和 CCFLAGS 就是 make 的变量。GNU make 通常称之为变量,而其他 UNIX 的 make工具称之为宏,实际是同一个东西。在 makefile 中引用变量的值时,只需变量名之前添加 $ 符号。
(4) 运行 make
GNU make 命令还有一些其他选项:
-C DIR 在读取 makefile 之前改变到指定的目录 DIR。
-f FILE 以指定的 FILE 文件作为 makefile。
-h 显示所有的 make 选项。
-i 忽略所有的命令执行错误。
-I DIR 当包含其他 makefile 文件时,可利用该选项指定搜索目录。
-n 只打印要执行的命令,但不执行这些命令。
-p 显示 make 变量数据库和隐含规则。
-s 在执行命令时不显示命令。
-w 在处理 makefile 之前和之后,显示工作目录。
-W FILE 假定文件 FILE 已经被修改。
4.2 GTK图形程序开发
4.2.1 简介
GTK (GIMP Toolkit) 起源於开发用来做为GIMP (General Image Manipulation Program)的一套工具. GTK建立在GDK (GIMP Drawing Kit)的上层, 基本上是将Xlib功能包装起来. 它被称为GIMP toolkit是因为它是为了开发GIMP而写的, 但现在被许多免费软体计划所使用。
4.2.2 一个简单的GTK程序
#include <gtk/gtk.h>
int main( int argc,char *argv[] )
{ GtkWidget *window;
gtk_init (&argc, &argv);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_widget_show (window);
gtk_main ();
return(0);
}
编译方法:gcc -Wall -g base.c -o base `gtk-config --cflags --libs`
该程序是一个最简单的窗口程序。
4.2.3 GTK的层次结构
GIMP
GTK+
GDK
GLIB
XLIB
4.2.4 GTK的事件与信号处理
GTK是一个事件驱动的工具集,一个GTK应用通常在gtk_main上休眠直到一个事件发生,这时控制被传递给合适的函数。
信号处理函数:
gint gtk_signal_connect( GtkObject, gchar,GtkSignalFunc, gpointer);
void gtk_signal_disconnect( GtkObject, gint);
void gtk_signal_handler_block( GtkObject, guint);
void gtk_signal_handler_unblock( GtkObject, guint);
void gtk_signal_emit( GtkObject, guint, ... );
事件与信号不是一回事
4.2.5 GTK的控件
使用一个控件的步骤
l gtk_*_new 初始化一个控件
l 绑定控件与信号及事件
l 把它包装到一个包容器中
l 告诉GTK显示这个控件(gtk_widget_show)
控件体系类似于MFC的类库,子类具有父类的特征,在使用时要使用类型强制转换。
第五章 Netbit浏览器开发与分析
5.1 Netbit 浏览器简介
Netbit Browser 是基于Linux平台的浏览器,使用了gtk作为gui开发工具。项目的目的是要建立一个小型的、快捷的web浏览器,并便于移植到嵌入式系统中。该项目是开放源码项目,由sogo456@263.net负责维护,网址是:http://netbit_browser.myetang.com。
项目目前已完成 0.0.1 demo版,实现了基本界面,html4.0词法分析、支持本地文本文件查看,英文网页浏览(使用菜单open file),支持简单http连接,支持URL访问,如输入http://www.gtk.org/download/top.htm等,支持字体颜色、字号等基本的元素。
Netbit Browser在界面上主要承袭了Gzilla的风格,在技术上主要涉及了词法、语法分析,文档布局,PIXMAP画图,文件IO操作,简单HTTP访问等技术。
界面模块
控制模块
PIXMAP画图模块
5.2 Netbit Browser浏览器各部份的功能
IO模块
词法分析模块
文本文件显示模块
HTML文件显示模块
(1)主程序:用于初始化。
(2)界面模块:窗口、菜单、工具条、滚动条等的初始化。
(3)控制模块:负责命令的响应,消息的处理,是软件控制的中枢。
(4)IO模块:包括http和文件操作。
(5)词法分析模块:HTML的词法分析。
(6)文本文件的显示模块:对Plain text显示的处理,也包含相应的布局算法。
(7)HTML文件的显示模块:在Netbit browser中使用了语法分析与布局同时进行的方式,由此模块来驱动HTML文件的显示。
(8)使用PIXMAP的画图模块:是实际输出采用的方法,被6、7模块调用。
以下将就主要的较复杂的模块进行说明。
5.3 界面模块
(1) interface.c,interface.h:用于界面的初始化和定制。
主要的函数说明:
void a_Interface_init(void);界面初始化的主函数
void a_Interface_status(BrowserWindow *bw, const char *format, ... );
设置状态栏内容。
void a_Interface_openfile_dialog(BrowserWindow *bw);文件打开对话框
void a_Interface_set_Page_title(BrowserWindow *bw, char *title);
设置窗口标题。
void a_Interface_entry_open_url(GtkWidget *widget, BrowserWindow *bw);
打开网址输入栏输入的网址。
BrowserWindow *a_Interface_new_browser_window(gint width, gint height);
新建浏览窗口,是很重要的函数,在其中创建了所有的界面控件,并对按钮等进行了消息绑定。
(2) menu.c,menu.h
主要函数:GtkWidget *a_Menu_mainbar_new (BrowserWindow *bw);
定义了主菜单项,并进行了消息绑定
(3) browser.h
定义了重要的窗口结构如下:
struct _BrowserWindow
{
/* 主窗口的widgets */
GtkWidget *main_window;
GtkWidget *back_button;
GtkWidget *forw_button;
GtkWidget *stop_button;
GtkWidget *location;
GtkWidget *location_button;
GtkWidget *status;
/* 键盘控制表*/
GtkAccelGroup *accel_group;
/* 工具条按钮 */
GtkWidget *back_menuitem;
GtkWidget *forw_menuitem;
GtkWidget *stop_menuitem;
/* 主文档 widget. (用于绘制HTML或其它) */
GtkWidget *layout;
/* 当前光标类型 */
GdkCursorType CursorType;
/* 对话框widgets*/
GtkWidget *open_dialog_window;
GtkWidget *open_dialog_entry;
GtkWidget *openfile_dialog_window;
GtkWidget *quit_dialog_window;
/* 指向保存词法分析结果的数据结构 */
BitTokenContext *global_cx;
/* 文件类型:html or plain text*/
gint file_type;
};
5.4 控制模块
(1)command.c,command.h
主要的函数说明:
void a_Commands_openfile_callback (GtkWidget *widget, gpointer client_data);打开文件的对话框
void a_Commands_openurl_callback (GtkWidget *widget, gpointer client_data);打开URL
void a_Commands_close_callback(GtkWidget * widget, gpointer client_data);关闭窗口
void a_Commands_exit_callback (GtkWidget *widget, gpointer client_data);退出程序
void a_Commands_viewsource_callback (GtkWidget *widget, gpointer client_data);查看HTML源码
void a_Commands_reload_callback (GtkWidget *widget, gpointer client_data);刷新当前网页
void a_Commands_home_callback (GtkWidget *widget, gpointer client_data);显示主页
void a_Commands_helphome_callback (GtkWidget *widget, gpointer client_data); 显示帮助
(2)nav.h,nav.c:是命令对应的与网页操作有关的具体实施
主要的函数说明:
void a_Nav_push(BrowserWindow *bw, const char*);按URL打开一个网址或文件,具有对不完整URL的兼容性。
void a_Nav_reload(BrowserWindow *bw);刷新当前网页
void a_Nav_open_splash(BrowserWindow *bw,char *str);打开起始页(内置页面)
5.5词法分析模块
词法分析的原理和算法在前面已有详述。
(1) BitToken.c,BitToken.h
主要的函数说明:
BitTokenContext * Bit_NewContext(); 创建新的全局结构
int Bit_Tokenize(BitTokenContext *global_cx); 局部词法分析
void Bit_BeginToken(BitTokenContext *global_cx); 全局词法分析
int Bit_DestroyToken(BitTokenContext *global_cx); 释放内存
char *Token_ReadUntil(BitTokenContext *global_cx,char *sUntil);重要的字符处理函数,读取到指定字符后结束
char *Token_GetAttribute(BitTokenContext *global_cx); 取元素属性
void Token_ConvertIfNeed(char * aString); 转义字串的处理
int Token_ConsumTag(BitTokenContext *global_cx);处理元素
int Token_Consum_PlainText(BitTokenContext *global_cx); 处理文本
void Bit_ShowTokenResult(BitTokenContext *global_cx);显示分析结果
void Bit_SaveTokenResult(BitTokenContext *global_cx,char * filename); 保存分析结果
(2) BitHtmlDtd.h,BitHtmlDtd.c
用于存储HTML4.0元素的名称和属性。
(3) BitTokenList.h,BitTokenList.c
元素链表相关
(4) BitTokenAttrList.h,BitTokenAttrList.c
元素属性链表相关
(5) BitStr.h,BitStr.c
字符串处理函数
5.6使用PIXMAP的画图模块
因本部份是HTML文件的显示模块、文本文件的显示模块的基础,所以先予说明。
paint.c,paint.h
主要的函数说明:
gint pixmap_new(GtkWidget *widget,int width,int height);
在此函数中使用
pixmap = gdk_pixmap_new(widget->window,width+30,height+30,-1);来新建一个pixmap。
gint expose_event (GtkWidget *widget, GdkEventExpose *event); 在expose消息到来时,即若界面被破坏需重画时,使用
gdk_draw_pixmap(widget->window,widget->style->fg_gc[GTK_WIDGET_STATE(widget)],pixmap,event->area.x,event->area.y,event->area.x,ent->area.y, event->area.width, event->area.height);来重画。
gint pixmap_repaint(GtkWidget *widget);用于提供手动重画。
gint Browser_Paint(BrowserWindow *bw);layout的主函数,用来根据文件类型来调用HTML文件的显示模块或文本文件的显示,同时初始化滚动条。
5.7 文本文件的显示模块
plain.c,plain.h
char *Plain_handle_tabs(const char *str)将TAB转为空格。
void a_Plain_write(GtkLayout *display,char *Buf1, gint BufSize)主要函数
下面介绍一下文本显示的算法。
指定默认字体
font=gdk_font_load("-adobe-helvetica-medium-r-normal--14-*-*-*-*-*-iso8859-1");
通过预布局来计算页面的长度:
while(i<BufSize)
{ j=0;
while(line_size<SCREEN_WIDTH-20 && Buf[i]!='\n')
{str[j]=Buf[i];
line_size+=gdk_char_width(font,str[j]);
j++;
i++;
}
str[j]='\0';
if(Buf[i]=='\n')i++;
x=X_START;
line_size=x;
y+=16;
}
创建PIXMAP
pixmap_new(drawing_area,SCREEN_WIDTH,y);
gc = gdk_gc_new(drawing_area->window);
进行真实的画图。
pixmap_repaint(drawing_area);
输出到PIXMAP并显示
5.8 HTML文件的显示模块
这部份是整个浏览器最重要的部份之一,综合了语法分析与HTML的布局、输出,其算法的好坏直接关系到网页的显示效果。
主要流程:
while(pTtokenList!=NULL)
{…………
switch(pTtokenList->token->type)
{
case HTML_TITLE:
…………
break;
case HTML_TEXT:
…………
break;
…………
…………
default:
………
break;
} //switch
pTtokenList=pTtokenList->next;
} //while
可以看到,这部份与语词分析结合的十分紧密,利用词法分析的结果,遍历各元素节点,取出其元素属性,根据一定的布局算法来进行布局。
例如:当遇到title元素时,就使用gtk函数来设定窗口标题为指定标题
gtk_window_set_title(GTK_WINDOW(bw->main_window),pTtokenList->token->pData);
其中pTtokenList->token->pData即为词法分析分析出的标题内容。
由于程序结构十分简单清晰,大部份元素的处理都简单易懂,参考源程序即可,下面主要针对<font>和相关标记对字体的设置阐述其算法。
由于<font>标记允许嵌套,所以使用了栈来对font元素进行管理,例如以下的HTML代码:
<font size=4 color=#0000FF>
This program is not <b>free software</b>; you can redistribute it and/or
modify it under the terms of the <font size=5 color=FF0000>GNU General
Public License </font>as published by the Free Software Foundation;
either version 2 of the License, or (at your option) any later version.
</font>
显示的效果应为GNU General Public License的字号为5,颜色为FF0000,即红色;free software应为粗体,受首尾两个呼应的font标记约束,其它字字号均为4,颜色为0000FF,由于free software只被<b></b>这一对加粗符号约束,所以其颜色应受首尾的font标记的约束,即应为0000FF。
这种嵌套的约束方式带来了HTML元素管理的混乱,也容易产生冗余的HTML代码,但既然标准是这么定的,也只能想办法加以解决,固然现在随着样式表的广泛采用,font已面临寿终正寝,但仍然大量存在,特别在对字体的颜色的设置,使用font标记很方便。
栈式管理的主要算法详解:
void html_open_font(GtkWidget *widget,char * style_str,char *color_str,char *size_str,int html_element,int insert_to_list);
该函数用于指定当前的字体属性,其参数包括style,color,size,以及改变字体属性的元素的名称,int insert_to_list用于标记此字体属性是否入栈,通常是入栈的。
这样,在出现font或相关元素的首标记时,我们将词法分析的结果提取出来,即将其元素属性提取出来,作为参数传递给html_open_font函数,该函数将这些属性进行组合,设置成为当前字体属性,并入栈保存;在出现font或相关元素的尾标记时,出于保险(因为存在交错包含关系的元素),首先检验栈顶元素与正在处理的元素尾标记是否匹配(名称相同),如相同则出栈,并将栈内下一字体属性设为系统的当前字体属性。
出栈函数为void html_close_font(GtkWidget *widget,int html_element)
需要注意的是由于并不是所有的font元素都指定所有的属性,可能只指定其中的一个或一部份属性,因此在入栈时必须做这样的处理,即首先获取当前的字体属性,根据哪些属性发生了变化来组合新的字体属性,然后入栈。
所使用的栈的结构很简单,如下。
typedef struct _font_list{
int html_element;
char color_str[15];
char size_str[15];
char style_str[15];
}font_list;
此为font_list的类型定义,描述了字体属性的结构
font_list font_opening[50]; 定义一个数组作为栈的存储形式
int current_font=0; 定义一个整型变量,作为栈顶指针
如此,一个简单的数组就发挥了巨大的作用,配以一点点算法,就带来了丰富多彩的界面效果。
5.9 Netbit实际应用效果及比较
下图为Netbit browser运行时的界面,所打开的页面源代码如下:
<html>
<body>
<h1>
<font color=#FF00FF><b>Netbit Browser Version 0.0.1 Demo</b></font></h1>
<hr>
<h4>License</h4>
<p>
<font size=4 color=#0000FF>
This program is not <b>free software</b>; you can redistribute it and/or
modify it under the terms of the <font size=5 color=FF0000>GNU General
Public License </font>as published by the Free Software Foundation;
either version 2 of the License, or (at your option) any later version.
</font>
<hr>
<h3>Design based on GTK, by sogo and ce!</h3>
</body>
</html>
以下为主菜单
以下为工具条
输入网页的URL,即可进行访问。
以下为打开文件对话框
以下为查看HTML源码对话框
下面对比Netbit Browser,看看其它浏览器查看此网页的效果。
以下为KDE浏览该网页的效果
以下为GZILLA浏览该网页的效果,GZILLA对字体颜色的处理比较差,只有黑色的字体。对字号的支持也不好。
以下为Netscape显示该网页的效果,Netscape默认背景色是灰色。
IE查看该网页的效果,字体不同是由于IE设置的默认字体不同。
可以看到,在对简单英文网页的支持效果上看,Netbit Browser,已接近于成熟浏览器的水平,甚至优于一些小型的嵌入式浏览器如GZILLA,Netfront,但在复杂页面的显示上还有较大的差距。
可以得出的结论是,Netbit Browser 0.0.1 Demo 版已经具有了一定的实用价值,但要对其进行完善,工作量还很巨大。
对比Netscape,IE的漫长的开发历史和巨大的资金投入,Netbit Browser的未来依然生死未卜。
作者:jackxiang@向东博客 专注WEB应用 构架之美 --- 构架之美,在于尽态极妍 | 应用之美,在于药到病除
地址:https://jackxiang.com/post/1919/
版权所有。转载时必须以链接形式注明作者和原始出处及本声明!