第一篇:Mudos编程总结[推荐]
Mudos编程总结
1,Mudos系统调用系统 MudLib系统文件 的过程和一些特点
Mudos启动以后先要寻找一个配置文件,用来配置MudLib文件系统的一些信息(这里时config.cfg),找不到就无法启动。找到以后根据配置文件里面的参数进行初始化Mudos,然后调用配置文件里面的两个入口文件,即simul_efun.c和master.c。首先载入simul_efun文件并生成一个特殊的全局对象,这个对象用于定义一些全局使用的函数,也可以重载Mudos中的Efun函数,这些新定义的文件使用起来和Efun是一样的。然后系统会调用master.c文件并生成一个全局对象(主控对象),主控对象用于系统的全局控制,包括全局对象的加载,错误信息的跟踪处理等。接下来,系统会根据主控对象中的定义,载入一些系统需要用到的全局对象。到此,Mudos系统就启动完成了。
2、用户连接系统后的处理过程。。
当用户通过客户端进行连接以后,Mudos系统会调用master对象特有的connect()函数,通过这个函数编程人员需要创建并返回一个用户对象,系统会将用户连接到这个用户对象上,即是说这个用户对象就代表了这个用户。到此,就算完成了用户的连接过程。
3、用户的登陆后的处理过程
当Mudos系统调用master的connect函数创建并返回一个用户对象以后,用户对象会调用特有的logon()函数,这个函数用来把用户的设置都设置好并进行游戏。注:最好在这里重新建立一个用户对象,然后通过exec函数把用户的连接转移到新的对象上,然后删除这个对象。
4、Mudos系统中的系统中 对象
simul_efun对象、master主控对象和用户对象这三种对象都是系统提供的特殊对象,其 中simul_efun对象和master主控对象在系统中只有一个实例,即不能被clone也不能new创建,而用户对象主要的作用就是用来创建用户并完成初始化的对象,用户每次登陆都会自动生成一个,用户登陆完成后最好删除它。这里需要指出主控对象和用户对象都有特有的一些函数,这些函数提供给Mudos系统来调用的,用来完成一些系统需要处理的事情。
5、Master主控对象
object connect()程序连接后调用的函数,创建并返回一个用户对象。string *epilog(int load_empty)返回一个包含物件文件名称的数组, 其中所有的文件为启动游戏之前必须预先载入的对象.void preload(string file)系统按照epilog函数返回的数组载入全局对象后调用次函数,用来判断对象是否成功创建 static void crash(string error, object command_giver, object current_object)当系统异常终止(crash)时, 就调用主控物件中的此函数,用来记录一些系统crash的log信息。void log_error(string file, string message)编译程序发生任何错误系统都会调用此函数,用于发现是哪个对象出了什么错误。用于记录编译程序时出现的错误信息。
string error_handler(mapping error, int caught)主控物件处理错误的函数,此函数让 mudlib 代替系统处理错误情形。用来处理系统运行时出现的错误信息。string get_root_uid()取得
root 使用者识别名称需要获取系统的uid时调用此函数。string get_bb_uid()取得骨架使用者识别名称。string creator_file(string str)系统创建任何对象时都会调用此函数,用来获得系统初始化对象的uid值。mixed compile_object(string file)提供虚拟对象,当系统无法按照给定的路径载入对象时调用此函数。如果返回值是0,系统将不会载入这个对象,如果返回值是一个对象,系统会把这个对象当作是系统要载入的对象。一般来说,这里会返回一个void对象。string object_name(object ob)系统调用此函数以知晓一个物件的名称。string domain_file(string str)返回一个指定对象所属的区域,系统调用此函数来获得对象的区域。string author_file(string str)返回一个指定对象所属的作者,系统调用此函数来获得对象的作者。int save_ed_setup(object who, int code)ed()函数储存一个使用者的编辑程序设定或组态设定时调用 int retrieve_ed_setup(object who)ed()函数取得使用者的编辑程序设定或组态设定 string make_path_absolute(string file)ed()函数调用此函数以转换文件的相对路径名称为绝对路径名称 string get_save_file_name(string fname)ed()函数使用者不正常断线时, 此函数应返回与原来文件不同的名称, 以免覆写原来的文件 string privs_file(string file)为新创造的对象指定一个私有字符串。创建任何对象时系统都会调用此函数.对象的文件名称为其参数,返回的字符串就用作此对象的私有字符串。void slow_shutdown(int minutes)告知 mud 目前正处于缓慢关闭的过程中.当系统无法从堆中配置更多的内存, 而只能使用保留的内存区块时, 主控物件会调用此函数.此函数只有在组态文件中设定了「内存区块保留大小」才会被调用.距离关闭时间还剩下几分钟则为此函数的参数.void flag(string)当系统启动时, 处理 mudlib 所指定的特定标志.这个函数暂时还不是很了解。int valid_override(string file, string name)是否允许使用 efun:: 的情形,即是否允许运行efun重载前的函数。int valid_seteuid(object ob, string str)是否允许设定一个对象的euid。int valid_socket(object eff_user, string fun, mixed *info)是否允许调用socket 外部函数 int valid_asm(string file)
是否能使用asm{}来控制 LPC->C 编译的物件 int valid_compile_to_c(string file)是否允许由 LPC->C 的方式编译 int valid_hide(object who)是否允许一个对象具有匿踪和看到匿踪对象的能力 int valid_object(object ob)是否允许载入某个对象。int valid_save_binary(string filename)是否允许一个对象储存它的源代码 int valid_bind(object binder, object old_owner, object new_owner)是否允许bind()函数 即把某个对象的函数指针指向其他对象。int valid_write(string file, mixed user, string func)是否允许一个人有权限写入一个文件 int valid_read(string file, mixed user, string func)是否允许一个人有权限读取一个文件 int valid_shadow(object ob)控制哪些对象可以作为投影。int valid_link(string original, string reference)是否允许link()函数 int view_errors(object user)是否允许一个使用者看到错误消息 int valid_compile(string file)是否允许预先编译文件,这是22.2b14新加的设置6、、、、用户对象
void logon()主控对象conncet函数返回的用户对象会直接调用次函数,用以对用户对象的初始化处理。string process_input(string str)用户输入信息后调用此函数,字符串处理后再传递给本用户对象,用来处理用户输入信息。void net_dead()当用户对象断线时, 系统调用此函数。用来处理用户断线后的事情。void terminal_type(string term_type)用户对象登陆后调用,获得用户登陆的终端型号。void receive_message(string type, string str)用户对象的消息处理函数,系统的message()函数会把消息分配给各个符合条件的用户对象,用户对象接收到消息后调用此函数来接收并处理消息。void catch_tell(string message);无论系统对
一个对象送出任何消息都会调用此函数来处理这些消息.消息可以依照需要显示、丢弃、修改.此函数可以当作耳罩挡住某些消息, 也可以用于消息处理程序.void receive_snoop(string message);一个用户对象窥视另一个用户对象时, 所有窥视的文字会传递给用户对象中的这个函数.在此函数中, 您可以处理这些文字.次函数的内容, 通常会再传递给 receive()函数.void telnet_suboption(string buffer);
获得用户终端的一些设置。void write_prompt(void);如果在用户对象中有定义了次函数,则预设的提示符号该显示时, 系统将调用此函数.当用户正处于 input_to(输入文字)和 ed(编辑程序)状态时, 系统不会调用此函数7、、、、所有对象
void create(void);对象的构造函数,对象被系统载入后调用此函数来初始化对象。复制的对象也会调用。需要说明的是,系统第一次载入对象后会自动运行对象和其所有的父类中的create,复制的对象只运行对象自己的create。int id(string an_id);此函数被系统的present()函数调用, 以判断一个指定的对象是否以字符串an_id为识别名称id.如果an_id是此对象的识别名称之一, 就返回 1, 不是则返回 0。void init(void);当系统把对象A放入对象B时(即调用move_object()函数),如果A是人物对象,让A在B里调用A的init函数,同时调用B里面所有对象的init函数。不管A是不是人物对象,让B里所有人物对象调用A的init函数。int move_or_destruct(object dest);如果一个对象的环境对象被摧毁了, 就调用此函数, 并用于它的内容对象.dest是正要被摧毁的对象.如果 dest 不存在, 则 dest 为 0.如果 dest 中的对象没有把自己移出被摧毁的对象, 则也会被一起摧毁.int clean_up(int inherited);系统每过一段时间,对非激活对象调用 clean_up()函数.inherited指出此对象是否有别的对象继承.如果返回 0,此对象将永远不再调用 clean_up().如果返回 1, 则继续判断调用。通常一个对象在 clean_up()中所作的事, 是摧毁自己以节省内存.void reset(void);系统在每次reset之后(时间由系统设定),所有游戏中现存的对象都会调用reset()函数.reset()常用于检查宝物或怪物是否还在某个房间, 以判断要不要重新产生一份.8、、、、一些概念的说明
用户对象用户对象用户对象用户对象((((interactive()):):):):用户连接的对象就是用户对象,系统会调用用户对象的一些方法来实现用户的输入输出和消息处理等。如果用户对象断开连接,用户对象就变成为普通对象。但是系统会记录曾经是用户对象的对象,通过userp()函数可以判断一个对象是否曾经是用户对象。人物人物人物人物对象对象对象对象(living())::::人物对象是曾经呼叫过 enable_commands()的对象,当然用户对象也是人物对象。环境对象环境对象环境对象环境对象(environment())::::环境对象是通过move_object()函数激活的对象。激活激活激活激活对象对象对象对象::::在系统规定的(clear_up)时间内,曾经调用过init()函数的对象。复制复制复制复制对象对象对象对象(clonep())::::系统在第一次载入某个对象后会建立一个初始对象并存于系统内存中,以后每次重新建立对象包括new()、clone_object()、call_other()等都会通过拷贝这个初始对象来建立新的对象,每个拷贝出来的对象,系统都会指定一个递增的数字标识此对象。这里需要说明的是,new()等函数建立对象时,如果初始对象不存在的话,系统会自动载入并初始化此对象,并把此对象定为初始对象,然后再拷贝一个对象作为new()等函数的返回值。系统建立初始对象的时候会由父到子的调用对象所有的create()函数,而拷贝出来的对象只调用对象本身的create()函数。游戏中存在的对象都是复制出来的对象。初始对象只有系统才能调用。
对象对象对象对象uid值值值值::::这个值只能通过主控对象中的creator_file()函数赋值或者export_uid()函数传递。指明当前对象的使用者名称。对象对象对象对象euid值值值值::::对象有效的使用者名称,可以通过seteuid和geteuid函数设置和读取。对象的对象的对象的对象的继承继承继承继承、、、、构造及初始化构造及初始化构造及初始化构造及初始化::::Mudos启动以后,可以自动或者通过函数调用来创建和复制各种对象,Mudos系统的作用就是用来管理这些对象。Mudos在生成对象的时候有两种方式,一种是载入对象,一种是复制已经载入的对象。首先,Mudos是通过对象的文件名来寻找和定位对象的,当需要载入对象的时候,先检查对象是否已经载入,如果没有载入会检查对象是否有需要继承别的对象,如果需要就先载入需要继承的对象,然后再载入并生成需要载入的对象。对象的继承实际上是子对象先浅拷贝一份父对象(但不初始化),然后再构造自己。任何一个对象被载入内存或被复制出来都会执行create函数来初始化自己。9、、、、EFUN函数说明
This_object():这个函数返回由当前文件所建立或拷贝的对象。如果这个文件继承了另一个文件,那么另一个文件中的这个函数也返回由这个文件所建立或拷贝的对象。previous_object(n):返回调用此函数的第前n个对象,previous_object(0)= previous_object(),表示返回调用此函数的对象。This_interactive():返回调用此函数的用户对象。This_player():返回调用此函数的人物对象。This_player(1)返回This_interactive()Load_object():如果已经载入则返回已经载入的对象,否则载入对象并返回它。New():如果对象没载入则载入对象,并复制一个对象返回,否则复制已经载入的对象返回。这里需要指出的是复制对象的过程并不会载入被复制对象所要继承的对象。Clone_object():和new一样,目前不知道有什么区别。replace_program():这个函数是用当前对象的继承对象来替换当前对象,但是保留当前对象的全局变量。就是说如果被替换以后,当前对象就只有继承对象的方法,而当前对象所定义的方法都不存在了。
第二篇:刀具和编程总结
① 白钢刀(即高速钢刀具)因其通体银白色而得名,主要用于直壁加工。白钢刀价格便宜,但切削寿命短、吃刀量小、进给速度低、加工效率低,在数控加工中较少使用。
② 飞刀(即镶嵌式刀具)主要为机夹式可转位刀具,这种刀具刚性好、切削速度高,在数控加工中应用非常广泛,用于模胚的开粗、平面和曲面粗精加工效果均很好。
③ 合金刀(通常指的是整体式硬质合金刀具)精度高、切削速度高,但价格昂贵,一般用于精加工。
数控刀具与普通机床上所用的刀具相比,有以下不同的要求。
(1)刚性好(尤其是粗加工刀具)、精度高、抗振及热变形小。
(2)互换性好,便于快速换刀。
(3)寿命高,切削性能稳定、可靠。
(4)刀具的尺寸便于调整,以减少换刀调整时间。
(5)刀具应能可靠地断屑或卷屑,以利于切屑的排除。(6)系列化、标准化,以利于编程和刀具管理。
① 刀具直径越大,转速越慢;同一类型的刀具,刀杆越长,吃刀量就要减小,否则容易弹刀而产生过切。
② 白钢刀转速不可过快,进给速度不可过大。
③ 白钢刀容易磨损,开粗时少用白钢刀。
① 以上的飞刀参数只能作为参考,因为不同的飞刀材料其参数值也不相同,不同的刀具厂生产的飞刀其长度也略有不同。另外,刀具的参数值也因数控铣床或加工中心的性能和加工材料的不同而不同,所以刀具的参数一定要根据工厂的实际情况来设定。
② 飞刀的刚性好,吃刀量大,最适合模胚的开粗。另外,飞刀精加工陡峭面的质量也非常好。③ 飞刀主要是镶刀粒的,没有侧刃,① 合金刀刚性好,不易产生弹刀,用于精加工模具的效果最好。
② 合金刀和白钢刀一样有侧刃,精铣铜公直壁时往往使用其侧刃。
① 刀具的名称一般根据刀具的直径和圆角半径来定义,例如,直径为30,圆角半径为5的飞刀,其名称定义为D30R5;直径为12的平底刀,其名称定义为D12;半径为5的球刀,其名称定义为R5。
② 输入刀具名称时,只需要输入小写字母即可,系统会自动将字母转为大写状态。③ 设置刀具参数时,只需要设置刀具的直径和底圆角半径即可,其他参数按默认即可。加工时,编程人员还需要编写加工工艺说明卡,注明刀具的类型和实际长度。
机床坐标一般在工件顶面的中心位置,所以创建机床坐标时,最好先设置好当前坐标,然后在〖CSYS〗对话框中设置“参考”为WCS。
加工模具时,其开粗余量多设为0.5,但如果是加工铜公余量就不一样了,因为铜公(铜公是火花机放电加工用的电极)最后的结果是要留负余量的。
模具加工要求越高时,其对应的公差值就应该越小。
进行实体模拟验证前,必须设置加工工件和毛坯,否则无法进行实体模拟。
第三篇:编程题总结
C作业汇总
1.short a,b=32767;/*short类型在内存中占2B*/ a=b+1;问:a的值是多少?并分析原因。
2.有一4位数整数,假设用abcd表示,请把这个4位数的每个数位用表达式表示出来。3.从键盘输入圆的半径r,计算并输出圆的面积s(要求:半径r定义为float型;圆周率定义为符号常量;面积s保留2位小数)#define PI 3.14159 #include
4.输入m>=3的正整数,判断m是否素数。画出算法流程图及NS图
5.有一函数:
x1 x y2x1 1x10
3x-11 x10 写一段程序,输入x,输出y值。
要求x,y声明为float类型,y保留2位小数。#include
if(x<1)
y=x;else
if(x<10)
y=2*x-1;
else
y=3*x-11;
}
printf(“y=%.2fn”,y);
x3x5x7x9,6.课后习题4.17(P159)利用泰勒级数sinxx计算sinx的3!5!7!9!值。要求最后一项的绝对值小于10,并统计出此时累加了多少项。#include
/*记录每个项数*/ int n=1,count=0;/*count记录累加了多少项*/
printf(“请输入x值(弧度):n”);scanf(“%f”,&x);
term=x/n;while(fabs(term)>1e-5)
/* while循环*/ {
sinx+=term;
count++;
n+=2;
term=-term*x*x/((n-1)*n);}
/* do
/*do while循环*/ {
sinx+=term;
count++;
n+=2;
term=-term*x*x/((n-1)*n);}while(fabs(term)>1e-5);
*/
printf(“sin(%.2f)=%.4fn”,x,sinx);printf(“一共累加了:%d项。n”,count);}
7.用牛顿迭代法求下面方程在1.5附近的根:
2x4x3x60 325
牛顿迭代公式:
x1x0f(x0)f(x0)
#include
/*y1记录f(x0),y2记录f(x0)的导数*/
do {
x0=x1;
y1=2*x0*x0*x0-4*x0*x0+3*x0-6;
y2=6*x0*x0-8*x0+3;
x1=x0-y1/y2;}while(fabs(x1-x0)>1e-5);
printf(“the root is:%.2fn”,x1);}
8.写一函数,输入一个16进制整数,输出相应的10进制数。例:从键盘输入2a,输出结果是42。
要求:若输入数据不合法,则输出提示信息。如输入了35g,输出“您输入的16进制数不合法!”。
#include
printf(“请输入一个16进制数字:n”);
while((c=getchar())!='n'){
if(c>='0' && c<='9')
sum=sum*16+c-'0';
else
if(c>='a' && c<='f')
sum=sum*16+c-87;
else
if(c>='A' && c<='F')
sum=sum*16+c-55;
else
{
printf(“您输入的16进制不合法.n”);
exit(0);
} }
printf(“相应的10进制数是:%dn”,sum);} 方法2:用字符串处理的方式 #include
printf(“请输入一个16进制数字:n”);gets(str);
for(i=0;str[i];i++){
if(str[i]>='0' && str[i]<='9')
sum=sum*16+str[i]-'0';
else
if(str[i]>='a' && str[i]<='f')
sum=sum*16+str[i]-87;
else
if(str[i]>='A' && str[i]<='F')
sum=sum*16+str[i]-55;
else
{
printf(“您输入的16进制不合法.n”);
exit(0);
} }
printf(“相应的10进制数是:%dn”,sum);} 方法3:用字符数组及指针处理的方式 #include
{ char str[20],*p=str;int sum=0;
printf(“请输入一个16进制数字:n”);gets(p);
while(*p){
if(*p>='0' && *p<='9')
sum=sum*16+*p-'0';
else
if(*p>='a' && *p<='f')
sum=sum*16+*p-87;
else
if(*p>='A' && *p<='F')
sum=sum*16+*p-55;
else
{
printf(“您输入的16进制不合法.n”);
exit(0);
}
p++;}
printf(“相应的10进制数是:%dn”,sum);} 9.编写一个小函数,其功能是计算两个整数的平均值,该函数要在主函数中调用。
#include
avg=average(x,y);
printf(“%d,%d的平均值是:%.2fn”,x,y,avg);}
float average(int x,int y)
{ return(x+y)/2.0;}
10.有N(N用宏定义为符号常量)个元素的一维整型数组,该数组中各元素值从键盘随机输入。然后,将这个整型数组中的值逆序存放。例如,原来5个元素的顺序为8、1、4、6、5,逆序之后各元素的值是5、6、4、1、8 #define N 5 #include
printf(“输入%d个整数,用空格或回车分隔:n”,N);for(i=0;i scanf(“%d”,&a[i]); printf(“数组原来的值是:n”);for(i=0;i printf(“%dt”,a[i]); for(i=0;i t=a[i]; a[i]=a[N-1-i]; a[N-1-i]=t;} printf(“n逆序之后数组的值是:n”);for(i=0;i printf(“%dt”,a[i]); printf(“n”);} 11.有N(N用宏定义为符号常量)个元素的一维整型数组,该数组中各元素值从键盘随机输入。然后,对该数组元素进行由小到大排序(要求,该功能用函数实现),输出数组中各元素值。最后,从键盘随机输入一个整数,并把该整数插入上述数组中(该功能用函数实现),使得插入该整数后的数组仍然有序,输出数组中各元素的值。#define N 5 #include int i,x;void sort(int array[],int n);void insert(int array[],int n,int x); printf(“输入%d个整数,用空格或回车分隔:n”,N);for(i=0;i scanf(“%d”,&a[i]); sort(a,N); /*调用sort对数组进行排序*/ printf(“n升序排序之后数组的值是:n”);for(i=0;i printf(“%d ”,a[i]); printf(“n输入一个x值插入到数组中:n”);scanf(“%d”,&x); insert(a,N,x); printf(“n插入%d之后数组的值是:n”,x);for(i=0;i printf(“%d ”,a[i]); printf(“n”);} void sort(int array[],int n)/*用选择法对数组array升序排序*/ { int i,j,t,min; for(i=0;i min=i; for(j=i+1;j if(array[j] min=j; if(min!=i) { t=array[i]; array[i]=array[min]; array[min]=t; } } } void insert(int array[],int n,int x){ int i,pos; for(i=0;i pos=i; for(i=n-1;i>=pos;i--) array[i+1]=array[i]; array[pos]=x;} 12.有一整型数组,N(N用宏定义为符号常量)个元素,该数组中各元素值从键盘随机输入。从键盘随机输入一个整数x,删除该数组中值与x相同的所有元素(该功能用函数实现),输出数组中各元素的值。#define N 5 #include printf(“输入%d个整数,用空格或回车分隔:n”,N);for(i=0;i scanf(“%d”,&a[i]); printf(“数组原来的值是:n”);for(i=0;i printf(“%dt”,a[i]); printf(“n请输入要删除的值x:n”);scanf(“%d”,&x); n=delet(a,N,x); /*n值是删除与x相同的元素后,数组剩余元素的个数。*/ printf(“删除%d之后数组的值是:n”,x);for(i=0;i printf(“%d ”,a[i]); printf(“n”); } int delet(int a[],int n,int x){ int i,j; for(i=0,j=0;i if(a[i]!=x) a[j++]=a[i]; return j;} 13.从键盘随机输入一字符串,将所有ASCII值为偶数的字符输出。例如:输入abc123,输出结果是b2(因为b的ASCII值是98,2的ASCII值是50,其他字符的ASCII值都是奇数) #include printf(“输入字符串:n”);gets(str); printf(“ASCII码是偶数的字符有:”);for(i=0;str[i];i++) if(str[i]%2==0)putchar(str[i]); printf(“n”);} 14.从键盘输入两个字符串s1,s2,把s2连接到s1的末尾。不能用strcat函数 #include printf(“输入两个字符串,输入回车键结束:n”);gets(str1);gets(str2); mystrcat(str1,str2); printf(“连接在一起的字符串是:n”);puts(str1); } void mystrcat(char *p1,char *p2){ while(*p1)p1++;while(*p2) *p1++=*p2++;*p1=' ';} 15.从键盘输入一个字符串,把其中最长的单词输出。单词定义为连续的一串英文字母。如输入I am a student.输出结果为student #include { char str[80];int i,start,len=0,lenthest=0,lenstart=0;int word=0; printf(“input a string:n”);gets(str); for(i=0;str[i];i++){ if(str[i]>='a' && str[i]<='z' || str[i]>='A'&&str[i]<='Z') if(!word) { word=1; start=i; } else { len++; } else if(word) { word=0; } } if(len>lenthest){ lenthest=len;lenstart=start;} len=0; printf(“the lenthest word is:n”);for(i=lenstart;i<=lenstart+lenthest;i++) putchar(str[i]); printf(“n”);} 16.有一整型数组,N(N用宏定义为符号常量)个元素,该数组中各元素值从键盘随机输入。编写函数calculate,计算出所有元素的最大值、最小值、平均值,结果在主函数中输出。#define N 5 #include printf(“输入%d个整数,用空格或回车分隔:n”,N);for(i=0;i scanf(“%d”,&a[i]); calculate(a,&max,&min,&avg); printf(“数组中所有元素的最大值、最小值、平均值分别是:n”);printf(“最大值max=%d,n”,max);if(word)if(len>lenthest){ lenthest=len; lenstart=start;} printf(“最小值min=%d,n”,min);printf(“平均值avg=%.2f,n”,avg); } void calculate(int a[],int *pmax,int *pmin,float *pavg){ int i;int sum; *pmax=*pmin=sum=a[0]; for(i=1;i if(a[i]>*pmax) *pmax=a[i]; if(a[i]<*pmin) *pmin=a[i]; sum=sum+a[i];} *pavg=(float)sum/N;} 数据库编程总结 当前各种主流数据库有很多,包括Oracle, MS SQL Server, Sybase, Informix, MySQL, DB2, Interbase / Firebird, PostgreSQL, SQLite, SAP/DB, TimesTen, MS ACCESS等等。数据库编程是对数据库的创建、读写等一列的操作。数据库编程分为数据库客户端编程与数据库服务器端编程。数据库客户端编程主要使用ODBC API、ADO、ADO.NET、OCI、OTL等方法;数据库服务端编程主要使用OLE DB等方法。数据库编程需要掌握一些访问数据库技术方法,还需要注意怎么设计高效的数据库、数据库管理与运行的优化、数据库语句的优化。 一、访问数据库技术方法 数据库编程分为数据库客户端编程与数据库服务器端编程。数据库客户端编程主要使用ODBC API、ADO、ADO.NET、OCI、OTL等方法;数据库服务端编程主要使用OLE DB等方法。 1、几种是数据库访问方法比较 ODBC API是一种适合数据库底层开发的编程方法,ODBC API提供大量对数据源的操作,ODBC API能够灵活地操作游标,支持各种帮定选项,在所有ODBC相关编程中,API编程具有最高的执行速度。DAO提供了很好的数据库编程的对象模型.但是,对数据库的所有调用以及输出的数据都必须通过Access/Jet数据库引擎,这对于使用数据库应用程序,是严重的瓶颈。 OLE DB提供了COM接口,与传统的数据库接口相比,有更好的健壮性和灵活性,具有很强的错误处理能力,能够同非关系数据源进行通信。 ADO最主要的优点在于易于使用、速度快、内存支出少和磁盘遗迹小。 ADO.NET 是利用数据集的概念将数据库数据读入内存中,然后在内存中对数据进行操作,最后将数据集数据回写到源数据库中。 OTL 是 Oracle, Odbc and DB2-CLI Template Library 的缩写,是一个C++编译中操控关系数据库的模板库,OTL中直接操作Oracle主要是通过Oracle提供的OCI接口进行,进行操作DB2数据库则是通过CLI接口来进行,至于MS的数据库和其它一些数据库,则OTL只提供了ODBC来操作的方式。当然Oracle和DB2也可以由OTL间接使用ODBC的方式来进行操纵。具有以下优点:跨平台;运行效率高,与C语言直接调用API相当;开发效率高,起码比ADO.net使用起来更简单,更简洁;部署容易,不需要ADO组件,不需要.net framework 等。 2、VC数据库编程几种方法 VC数据库编程几种方法,包括ODBC连接、MFC ODBC连接、DAO连接、OLE DB、OLE DB Templates连接、ADO、Oracle专用方法(OCI(Oracle Call Interface)访问、Oracle Object OLE C++ Class Library)。 <1.>通用方法 1.ODBC连接 ODBC(Open DataBase Connectivity)是MSOA的一部分,是一个标准数据库接口。它提供对关系数据库访问的统一接口,实现对异构数据源的一致访问。ODBC数据访问由以下部分组成: <1>句柄(Handles):ODBC使用句柄来标识ODBC环境、连接、语句和描述器.<2>缓存区(Buffers): <3>数据类型(Data types) <4>一致性级别(Conformance levels) 用ODBC设计客户端的一般步骤: <1>分配ODBC环境 <2>分配连接句柄 <3>连接数据源 <4>构造和执行SQL语句 <5>获得查询结果 <6>断开数据源的连接 <7>释放ODBC环境 ODBC API是一种适合数据库底层开发的编程方法,ODBC API提供大量对数据源的操作,ODBC API能够灵活地操作游标,支持各种帮定选项,在所有ODBC相关编程中,API编程具有最高的执行速度.因此,ODBC API编程属于底层编程。 2.MFC ODBC连接 MFC ODBC是MFC对ODBC进行的封装,以简化对ODBC API的 调用,从而实现面向对象的数据库编程接口.MFC ODBC的封装主要开发了CDatabase类和CRecordSet类 (1)CDatabase类 CDatabase类用于应用程序建立同数据源的连接。CDatabase类中包含一个m_hdbc变量,它代表了数据源的连接句柄。如果要建立CDatabase类的实例,应先调用该类的构造函数,再调用Open函数,通过调用,初始化环境变量,并执行与数据源的连接。在通过Close函数关闭数据源。 CDatabase类提供了对数据库进行操作的函数及事务操作。 (2)CRecordSet类 CRecordSet类定义了从数据库接收或者发送数据到数据库的成员变量,以实现对数据集的数据操作。 CRecordSet类的成员变量m_hstmt代表了定义该记录集的SQL语句句柄,m_nFields为记录集中字段的个数,m_nParams为记录集所使用的参数个数。 CRecordSet的记录集通过CDatabase实例的指针实现同数据源的连接,即CRecordSet的成员变量m_pDatabase.MFC ODBC编程更适合于界面型数据库应用程序的开发,但由于CDatabase类和CRecordSet类提供的数据库操作函数有限,支持的游标类型也有限,限制了高效的数据库开发。在编程层次上属于高级编程。 应用实例: 1.打开数据库 CDatabase database; database.OpenEx(_T(“DSN=zhuxue”),CDatabase::noOdbcDialog);//zhuxue为数据源名称 2.关联记录集 CRecordset recset(&database); 3.查询记录 CString sSql1=“"; sSql1 = ”SELECT * FROM tablename“; recset.Open(CRecordset::forwardOnly, sSql1, CRecordset::readOnly); int ti=0; CDBVariant var;//var可以转换为其他类型的值 while(!recset.IsEOF()) { //读取Excel内部数值 recset.GetFieldValue(”id“,var); jiangxiang[ti].id=var.m_iVal; recset.GetFieldValue(”name“, jiangxiang[ti].name); ti++; recset.MoveNext(); } recset.Close();//关闭记录集 4.执行sql语句 CString sSql=”“; sSql+=”delete * from 院系审核“;//清空表 database.ExecuteSQL(sSql); sSql也可以为Insert ,Update等语句 5.读取字段名 sSql = ”SELECT * FROM Sheet1“; //读取的文件有Sheet1表的定义,或为本程序生成的表.// 执行查询语句 recset.Open(CRecordset::forwardOnly, sSql, CRecordset::readOnly); int excelColCount=recset.GetODBCFieldCount();//列数 CString excelfield[30]; //得到记录集的字段集合中的字段的总个数 for(i=0;i { CODBCFieldInfo fieldinfo; recset.GetODBCFieldInfo(i,fieldinfo); excelfield[i].name =fieldinfo.m_strName;//字段名 } 6.打开excel文件 CString sDriver = ”MICROSOFT EXCEL DRIVER(*.XLS)“;// Excel安装驱动 CString sSql,sExcelFile;//sExcelFile为excel的文件路径 TRY { // 创建进行存取的字符串 sSql.Format(”DRIVER={%s};DSN='';FIRSTROWHASNAMES=1;READONLY=FALSE;CREATE_DB=/“%s/”;DBQ=%s“,sDriver, sExcelFile, sExcelFile); // 创建数据库(既Excel表格文件) if(database.OpenEx(sSql,CDatabase::noOdbcDialog)) { //可以把excel作为一个数据库操作 } } catch(e) { TRACE1(”Excel驱动没有安装: %s“,sDriver); AfxMessageBox(”读取失败,请检查是否定义数据区Sheet1“); } 3.DAO连接 DAO(Data Access Object)是一组Microsoft Access/Jet数据库引擎的COM自动化接口.DAO直接与Access/Jet数据库通信.通过Jet数据库引擎,DAO也可以同其他数据库进行通信。DAO还封装了Access数据库的结构单元,通过DAO可以直接修改Access数据库的结构,而不必使用SQL的数据定义语言(DDL)。 DAO的体系结构如下: DAO封装的类: (1)CdaoWorkspace:对DAO工作区(数据库处理事务管理器)的封装 (2)CdaoDatabase:对DAO数据库对象的封装,负责数据库连接.(3)CdaoRecordset:对DAO记录集对象的封装,代表所选的一组记录.(4)CdaoTableDef:对表定义对象的封装,代表基本表或附加表定义.(5)CdaoQueryDef:对查询对象的封装,包含所有查询的定义.(6)CdaoException:DAO用于接收数据库操作异常的类.(7)CDaoFieldExchange DAO提供了很好的数据库编程的对象模型.但是,对数据库的所有调用以及输出的数据都必须通过Access/Jet数据库引擎,这对于使用数据库应用程序,是严重的瓶颈。 DAO相对于ODBC来说,属于高层的数据库接口.4.OLE DB连接 OLE DB对ODBC进行了两方面的扩展:一是提供了数据库编程的OLE接口即COM,二是提供了一个可用于关系型和非关系型数据源的接口。 OLE DB提供了COM接口,与传统的数据库接口相比,有更好的健壮性和灵活性,具有很强的错误处理能力,能够同非关系数据源进行通信。 与ODBC API一样,OLE DB也属于底层的数据库编程接口,OLE DB结合了ODBC对关系数据库的操作功能,并进行扩展,可以访问非关系数据库。 OLE DB访问数据库的原理如下: OLE DB程序结构: OLE DB由客户(Consumer)和服务器(Provider)。客户是使用数据的应用程序,它通过OLE DB接口对数据提供者的数据进行访问和控制。OLE DB服务器是提供OLE DB接口的软件组件。根据提供的内容可以分为数据提供程序(Data Provider)和服务提供程序(Service Provider)。 程序结构原理图如下: <1>数据提供程序 数据提供程序拥有自己的数据并把数据以表格的形式呈现给使用者使用.<2>服务提供程序 服务提供程序是数据提供程序和使用者的结合。它是OLE DB体系结构中的中间件,它是OLE DB数据源的使用者和数据使用程序的提供者 <3>数据使用程序 数据使用程序对存储在数据提供程序中的数据进行使用和控制.OLE DB开发程序的一般步骤: <1>初始化COM环境 <2>连接数据源 <3>打开对话 <4>执行命令 <5>处理结果 <6>清除对象 应用实例: 使用OLEDB编写数据库应用程序 1 概述 OLE DB的存在为用户提供了一种统一的方法来访问所有不同种类的数据源。OLE DB可以在不同的数据源中进行转换。利用OLE DB,客户端的开发人员在进行数据访问时只需把精力集中在很少的一些细节上,而不必弄懂大量不同数据库的访问协议。OLE DB是一套通过COM接口访问数据的ActiveX接口。这个OLE DB接口相当通用,足以提供一种访问数据的统一手段,而不管存储数据所使用的方法如何。同时,OLE DB还允许开发人员继续利用基础数据库技术的优点,而不必为了利用这些优点而把数据移出来。 使用ATL使用OLE DB数据使用程序 由于直接使用OLE DB的对象和接口设计数据库应用程序需要书写大量的代码。为了简化程序设计,Visual C++提供了ATL模板用于设计OLE DB数据应用程序和数据提供程序。利用ATL模板可以很容易地将OLE DB与MFC结合起来,使数据库的参数查询等复杂的编程得到简化。MFC提供的数据库类使OLE DB的编程更具有面向对象的特性。Viual C++所提供用于OLE DB的ATL模板可分为数据提供程序的模板和数据使用程序的模板。 使用ATL模板创建数据应用程序一般有以下几步骤: 1)、创建应用框架 2)、加入ATL产生的模板类 3)、在应用中使用产生的数据访问对象3 不用ATL使用OLE DB数据使用程序 利用ATL模板产生数据使用程序较为简单,但适用性不广,不能动态适应数据库的变化。下面我们介绍直接使用MFC OLE DB类来生成数据使用程序。模板的使用 OLE DB数据使用者模板是由一些模板组成的,包括如下一些模板,下面对一些常用类作一些介绍。1)、会话类 CDataSource类 CDataSource类与OLE DB的数据源对象相对应。这个类代表了OLE DB数据提供程序和数据源之间的连接。只有当数据源的连接被建立之后,才能产生会话对象,可以调用Open来打开数据源的连接。CSession类 CSession所创建的对象代表了一个单独的数据库访问的会话。一个用CDataSource类产生的数据源对象可以创建一个或者多个会话,要在数据源对象上产生一个会话对象,需要调用函数Open()来打开。同时,会话对象还可用于创建事务操作。 CEnumeratorAccessor类 CEnumeratorAccessor类是用来访问枚举器查询后所产生的行集中可用数据提供程序的信息的访问器,可提供当前可用的数据提供程序和可见的访问器。2)、访问器类 CAcessor类 CAccessor类代表与访问器的类型。当用户知道数据库的类型和结构时,可以使用此类。它支持对一个行集采用多个访问器,并且,存放数据的缓冲区是由用户分配的。CDynamicAccessor类 CDynamicAccessor类用来在程序运行时动态的创建访问器。当系统运行时,可以动态地从行集中获得列的信息,可根据此信息动态地创建访问器。CManualAccessor类 CManualAccessor类中以在程序运行时将列与变量绑定或者是将参数与变量捆定。3)、行集类 CRowSet类 CRowSet类封装了行集对象和相应的接口,并且提供了一些方法用于查询、设置数据等。可以用Move()等函数进行记录移动,用GetData()函数读取数据,用Insert()、Delete()、SetData()来更新数据。CBulkRowset类 CBulkRowset类用于在一次调用中取回多个行句柄或者对多个行进行操作。CArrayRowset类 CArrayRowset类提供用数组下标进行数据访问。4)、命令类 CTable类 CTable类用于对数据库的简单访问,用数据源的名称得到行集,从而得到数据。CCommand类 CCommand类用于支持命令的数据源。可以用Open()函数来执行SQL命令,也可以Prepare()函数先对命令进行准备,对于支持命令的数据源,可以提高程序的灵活性和健壮性。 在stdafx.h头文件里,加入如下代码。#include 在打开数据源,会话,行集对象后就可以获取数据了。所获取的数据类型取决于所用的存取程序,可能需要绑定列。按以下步骤。 1、用正确的命令打开行集对象。 2、如果使用CManualAccessor,在使用之前与相应列进行绑定。要绑定列,可以用函数GetColumnInfo,如下所示: // Get the column information ULONG ulColumns = 0;DBCOLUMNINFO* pColumnInfo = NULL;LPOLESTR pStrings = NULL;if(rs.GetColumnInfo(&ulColumns, &pColumnInfo, &pStrings)!= S_OK)AfxThrowOLEDBException(rs.m_pRowset, IID_IColumnsInfo);struct MYBIND* pBind = new MYBIND[ulColumns];rs.CreateAccessor(ulColumns, &pBind[0], sizeof(MYBIND)*ulColumns);for(ULONG l=0;l 3、用while循环来取数据。在循环中,调用MoveNext来测试光标的返回值是否为S_OK,如下所示: while(rs.MoveNext()== S_OK){ // Add code to fetch data here // If you are not using an auto accessor, call rs.GetData()} 4、在while循环内,可以通过不同的存取程序获取数据。1)如果使用的是CAccessor类,可以通过使用它们的数据成员进行直接访问。如下所示: 2)如果使用的是CDynamicAccessor 或CDynamicParameterAccessor 类,可以通过GetValue或GetColumn函数来获取数据。可以用GetType来获取所用数据类型。如下所示: while(rs.MoveNext()== S_OK){ // Use the dynamic accessor functions to retrieve your // data ULONG ulColumns = rs.GetColumnCount(); for(ULONG i=0;i { rs.GetValue(i); } } 3)如果使用的是CManualAccessor,可以指定自己的数据成员,绑定它们。就可以直接存取。如下所示: while(rs.MoveNext()== S_OK){ // Use the data members you specified in the calls to // AddBindEntry.wsprintf(”%s“, szFoo);} 决定行集的数据类型 在运行时决定数据类型,要用动态或手工的存取程序。如果用的是手工存取程序,可以用GetColumnInfo函数得到行集的列信息。从这里可以得到数据类型。4 总结 由于现在有多种数据源,想要对这些数据进行访问管理的唯一途径就是通过一些同类机制来实现,如OLE DB。高级OLE DB结构分成两部分:客户和提供者。客户使用由提供者生成的数据。 就像其它基于COM的多数结构一样,OLE DB的开发人员需要实现很多的接口,其中大部分是模板文件。 当生成一个客户对象时,可以通过ATL对象向导指向一个数据源而创建一个简单的客户。ATL对象向导将会检查数据源并创建数据库的客户端代理。从那里,可以通过OLE DB客户模板使用标准的浏览函数。 当生成一个提供者时,向导提供了一个很好的开端,它们仅仅是生成了一个简单的提供者来列举某一目录下的文件。然后,提供者模板包含了OLE DB支持的完全补充内容。在这种支持下,用户可以创建OLE DB提供者,来实现行集定位策略、数据的读写以及建立书签。应用案例: Visual C++中使用OLE DB读写SQL Server 在需要对数据库进行操作时,OLE DB总是被认为是一种效率最高但最难的方法。但是以我最近使用OLE DB的经验看来,OLE DB的效率高则高矣,但却一点都不难。说它难恐怕主要是因为可参考的中文资料太少,为了帮助以后需要接触OLE DB的同行,我撰写了这篇文章。本文包含如下内容: 1.OLE DB写数据库; 2.OLE DB读数据库; 3.OLE DB对二进制数据(text、ntext、image等)的处理。 首先来看看对SQL Server进行写操作的代码,有一定VC基础的读者应该可以很顺利地看懂。OLE DB写数据库,就是这么简单! 注: 1.以下代码中使用的模板类EAutoReleasePtr 2.以下代码均在UNICODE环境下编译,因为执行的SQL语句必须是UNICODE的。设置工程为UNICODE的方法是:首先在project->settings->C/C++的属性页中的Preprocessor中,删除_MBCS写入UNICODE,_UNICODE。然后在link属性页中Category中选择output,在Entry-Point symbol 中添加wWinMainCRTStartup。 EAutoReleasePtr //失败,可能是因为数据库没有启动、用户名密码错等等 return;}EAutoReleasePtr //出错 return;}EAutoReleasePtr //出错 return;}hResult = ExecuteSQL(pICommand, pICommandText, _T(”USE PBDATA“));if(FAILED(hResult)){ //如果这里失败,那就是SQL语句执行失败。在此处,就是PBDATA还未创建 return;} // 创建表 ExecuteSQL(pICommand, pICommandText, _T(”CREATE TABLE 2005_1(Volume real NOT NULL,ID int NOT NULL IDENTITY)“)); // 添加记录 ExecuteSQL(pICommand, pICommandText, _T(”INSERT INTO 2005_1 VALUES(100.0)“));//...其中几个函数的代码如下: HRESULT ConnectDatabase(IDBInitialize** ppIDBInitialize, LPCTSTR pszDataSource, LPCTSTR pszUserID, LPCTSTR pszPassword){ ASSERT(ppIDBInitialize!= NULL && pszDataSource!= NULL && pszUserID!= NULL && pszPassword!= NULL); UINT uTimeout = 15U;// 连接数据库超时(秒) TCHAR szInitStr[1024]; VERIFY(1023 >= wsprintf(szInitStr, _T(”Provider=SQLOLEDB;Data Source=%s;Initial Catalog=master;User Id=%s;Password=%s;Connect Timeout=%u“), pszDataSource, pszUserID, pszPassword, uTimeout)); //Initial Catalog=master指明连接成功后,”USE master“。 EAutoReleasePtr HRESULT hResult = ::CoCreateInstance(CLSID_MSDAINITIALIZE, NULL, CLSCTX_INPROC_SERVER,IID_IDataInitialize,(void**)&pIDataInitialize); if(FAILED(hResult)) { return hResult; } EAutoReleasePtr hResult = pIDataInitialize->GetDataSource(NULL, CLSCTX_INPROC_SERVER,(LPCOLESTR)szInitStr,IID_IDBInitialize,(IUnknown**)&pIDBInitialize); if(FAILED(hResult)) { return hResult; } hResult = pIDBInitialize->Initialize(); if(FAILED(hResult)) { return hResult; } * ppIDBInitialize = pIDBInitialize.Detach(); return S_OK;} HRESULT CreateSession(IDBInitialize* pIDBInitialize, IOpenRowset** ppIOpenRowset){ ASSERT(pIDBInitialize!= NULL && ppIOpenRowset!= NULL); EAutoReleasePtr HRESULT hResult = pIDBInitialize->QueryInterface(IID_IDBCreateSession,(void**)&pSession); if(FAILED(hResult)) { return hResult; } EAutoReleasePtr hResult = pSession->CreateSession(NULL, IID_IOpenRowset,(IUnknown**)&pIOpenRowset); if(FAILED(hResult)) { return hResult; } * ppIOpenRowset = pIOpenRowset.Detach(); return S_OK;} HRESULT CreateCommand(IOpenRowset* pIOpenRowset, ICommand** ppICommand, ICommandText** ppICommandText){ ASSERT(pIOpenRowset!= NULL && ppICommand!= NULL && ppICommandText!= NULL); HRESULT hResult; EAutoReleasePtr { EAutoReleasePtr hResult = pIOpenRowset->QueryInterface(IID_IDBCreateCommand,(void**)&pICreateCommand); if(FAILED(hResult)) { return hResult; } hResult = pICreateCommand->CreateCommand(NULL, IID_ICommand,(IUnknown**)&pICommand); if(FAILED(hResult)) { return hResult; } } EAutoReleasePtr hResult = pICommand->QueryInterface(&pICommandText); if(FAILED(hResult)) { return hResult; } * ppICommand = pICommand.Detach(); * ppICommandText = pICommandText.Detach(); return S_OK;} HRESULT ExecuteSQL(ICommand* pICommand, ICommandText* pICommandText, LPCTSTR pszCommand, LONG* plRowsAffected){ ASSERT(pICommand!= NULL && pICommandText!= NULL && pszCommand!= NULL && pszCommand[0]!= 0); HRESULT hResult = pICommandText->SetCommandText(DBGUID_DBSQL,(LPCOLESTR)pszCommand); if(FAILED(hResult)) { return hResult; } LONG lAffected; hResult = pICommand->Execute(NULL, IID_NULL, NULL, plRowsAffected == NULL ? &lAffected : plRowsAffected,(IUnknown**)NULL); return hResult;} 以上就是写数据库的全部代码了,是不是很简单呢?下面再来读的。 // 先用与上面代码中一样的步骤获取pICommand,pICommandText。此处省略 HRESULT hResult = pICommandText->SetCommandText(DBGUID_DBSQL,(LPCOLESTR)_T(”SELECT Volume FROM 2005_1 WHERE ID = @@IDENTITY"));//取我们刚刚添加的那一条记录 if(FAILED(hResult)){ return;} LONG lAffected;EAutoReleasePtr return;} EAutoReleasePtr return;} // 一个根据表中各字段的数值类型而定义的结构,用于存储返回的各字段的值 struct CLoadLastFromDB { DBSTATUS dwdsVolume; DWORD dwLenVolume; float fVolume;}; // 此处我们只查询了一个字段。如果要查询多个字段,CLoadLastFromDB中要添加相应的字段定义,下面的dbBinding也要相应扩充。dbBinding[].iOrdinal要分别指向各个字段,dbBinding[].wType要根据字段类型赋合适的值。 DBBINDING dbBinding[1];dbBinding[0].iOrdinal = 1; // Volume 字段的位置,从 1 开始 dbBinding[0].obValue = offsetof(CLoadLastFromDB, fVolume);dbBinding[0].obLength = offsetof(CLoadLastFromDB, dwLenVolume);dbBinding[0].obStatus = offsetof(CLoadLastFromDB, dwdsVolume);dbBinding[0].pTypeInfo = NULL;dbBinding[0].pObject = NULL;dbBinding[0].pBindExt = NULL;dbBinding[0].dwPart = DBPART_VALUE | DBPART_STATUS | DBPART_LENGTH;dbBinding[0].dwMemOwner = DBMEMOWNER_CLIENTOWNED;dbBinding[0].eParamIO = DBPARAMIO_NOTPARAM;dbBinding[0].cbMaxLen = 0;dbBinding[0].dwFlags = 0; 一点Duilib编程总结 1.duilib简介 duilib是一个开源的DirectUI界面库,简洁但是功能强大。而且还是BSD的license,所以即便是在商业上,大家也可以安心使用。 现在大家可以从这个网站获取到他们所有的源码:/p/duilib/ 为了让我们能更简单的了解其机制,我们按照如下顺序一步一步的来对他进行观察: 工具库:用于支撑整个项目的基础 控件库:这是dui最关键的部分之一,相信也是大家最关注的部分之一,另外这里也来看看它是如何管理这些控件的 消息流转:有了控件库,我们需要将Windows窗口的原生消息流转给这些控件,另外在这里也来看看Focus,Capture等等的实现 资源组织和皮肤加载:有了上面所有的这些,我们再来看看它是如何自动创建皮肤的 简单使用:最后,来看看到底要如何使用它 以下是duilib工程带的一副总体设计图,在看代码之前看看这幅图,对看代码会很有帮助。 duilib: 2.工具库 由于duilib没有对外部的任何库进行依赖,所以在其内部实现了很多用于支撑项目的基础类,这些类分布在Util文件夹中: UI相关:CPoint/CSize/CDuiRect 简单容器:CStdPtrArray/CStdValArray/CStdString/CStdStringPtrMap 上面这些类看名字就基本能够理解其具体的含义了,当然除了基本的基础库,还有一些和窗口使用相关的工具的封装: 窗口工具:WindowImplBase,这个工具我们在这里不详述,后面会再次提到。3.控件库 控件库在duilib的实现中被分为了两块:Core和Control: Core中包含的是所有控件公用的部分,里面主要是一些基类和绘制的封装。Control中包含的就是各个不同的控件的行为了。Core部分和控件相关的类图非常简单: duilib-core: 3.1.控件基类:CControlUI CControlUI在整个控件体系中非常重要,它是所有控件的基类,也是组成控件树的基本元素,控件树中所有的节点都是一个CControlUI。 他基本包括了所有控件公共的属性,如:位置,大小,颜色,是否有焦点,是否被启用,等等等等。当然这个类中还提供了非常多的基础函数,用于重载来实现子控件,如获取控件名称和ClassName,是否显示,等等等等。 另外为了方便从XML中直接解析出控件的各个属性,这个类中还在提供了一个SetAttribute的方法,传入字符串的属性名称和值对特定的属性进行设置,内部其实就是挨个比较字符串去完成的,所以平时使用的时候就还是不要使用的比较好了,因为每个属性实际上都有特定的方法来获取和设置。 另外每个控件中还有几个事件管理的对象——CEventSource,这些对象会在特定的时机被触发,如OnInit,调用其中保存的各个回调函数。3.1.1.控件类型转换 这里我们就碰到一个问题,控件树中的每一个节点都是CControlUI,但是其实这些节点可能是文字,可能是图像,也有可能是列表,那么他怎么在这些控件指针之间进行转换呢? 强制转型不是一个好的选择,duilib中使用的是CControlUI::GetInterface,传入一个字符串,传出指向控件的指针。类似于COM的QueryInterface。 LPVOIDCControlUI::GetInterface(LPCTSTRpstrName){ if(_tcscmp(pstrName,_T(“Control”))==0)returnthis;returnNULL;} 3.2.容器基类:CContainerUI 有了基本的控件基类之后,我们就需要容器来将他管理起来,这个容器就是CContainerUI,其内部用一个数组来保存所有的CControlUI的对象,后续的所有工作,就都是基于这个对象来进行的了。 这样在CContainerUI里面,主要实现了一下几个功能: 子控件的查找:CContainerUI::FindControl 子控件的生命周期管理:是否销毁(在Remove的时候自动销毁)/是否延迟销毁(交给CPaintMangerUI去一起销毁)。 滚动条:所有的容器都支持滚动条,在其内部会对键盘和鼠标滚轮事件进行处理(CContainerUI::DoEvent),对其内部所有的元素调整位置,最后在绘制的时候实现滚动的效果 绘制:由于容器中有很多元素,所以为了加快容器的绘制,绘制的时候会获取其真正需要绘制的区域,如果子控件不在此区域中,那么就不予绘制了 3.3.控件实现 有了普通的基类和容器的基类之后,我们就可以在其之上搭建控件了。其类图大致如下: duilib-control: 3.3.1.基本控件 duilib实现了非常多的基本控件,他们分布在Control文件夹下,每一个头文件就是一个控件,主要有: CLabelUI/CTextUI/CEditUI/CRichEditUI CButtonUI/CCheckBoxUI/COptionUI(RadioButton)CScrollBarUI/CProgressUI/CSliderUI CListUI CDateTimeUI/CActiveXUI/CWebBrowserUI 3.3.2.Layout 除了基本控件之外,duilib为了辅助大家对界面元素进行布局,还在中间实现了专门用于Layout的元素: CChildLayoutUI CHorizontalLayoutUI/CVerticalLayoutUI/CTileLayoutUI:纵向排列,横向排列格子排列 CTabLayoutUI:Tab 3.3.3.控件绘制 绘制控件实际上有很多代码都是可以抽取出来的,比如:九宫格拉伸图片,平铺图片等等工作,我们实际上都不需要每次都去重写。所以这部分代码被抽取出来,形成了CRenderEngine,这个类在Core/UIRender下。在这个里面,我们可以看到很多的用于绘制方法。 classUILIB_APICRenderEngine { public: //......staticvoidDrawLine(HDChDC,constRECT&rc,intnSize,DWORDdwPenColor);staticvoidDrawRect(HDChDC,constRECT&rc,intnSize,DWORDdwPenColor);staticvoidDrawRoundRect(HDChDC,constRECT&rc,intwidth,intheight,intnSize,DWORDdwPenColor);staticvoidDrawText(HDChDC,CPaintManagerUI*pManager,RECT&rc,LPCTSTRpstrText, DWORDdwTextColor,intiFont,UINTuStyle);staticvoidDrawHtmlText(HDChDC,CPaintManagerUI*pManager,RECT&rc,LPCTSTRpstrText, DWORDdwTextColor,RECT*pLinks,CDuiString*sLinks,int&nLinkRects,UINTuStyle);//......};3.4.控件管理:CPaintManagerUI 当所有这些基本的控件都准备好了之后,我们就只要将这些控件管理起来,这样一个基本的控件库就完成了,而这个管理就是CPaintManagerUI来负责的。 在duilib中,一个Windows的原生窗口和一个CPaintManagerUI一一对应。其主要负责如下几个内容,后面会分开来细说,现在先了解一个概念就行: 控件管理 资源管理 转化并分发Windows原生的窗口消息 为了实现上面这些功能,其中有几个用于管理控件和资源的关键的数据结构: m_pRoot:保存根控件的节点 m_mNameHash:保存控件名称Hash和控件对象指针的关系 m_mOptionGroup:保存控件相关的Group,这个Group并不是TabOrder,他用于实现Option控件 m_aCustomFonts:用来管理字体资源 m_mImageHash:用来管理图片资源 这些结构基本都可以看作是一堆列表和Map,这样可以用其来实现控件和资源的管理了。 4.消息流转 有了控件,现在我们的问题是,如何将原生的窗口消息分发给界面中所有的控件,使其行为和原生的一样呢? 4.1.窗口基础类:CWindowWnd 在duilib中,用来表示窗口的最基础的类是CWindowWnd,在这个类中实现了如下基本的内容: 原生窗口的创建(CWindowWnd::Create)Subclass(CWindowWnd::Subclass) 最基本的消息处理函数(CWindowWnd::__WndProc)和消息分发(CWindowWnd::HandleMessage) 模态窗口(CWindowWnd::ShowModal) duilib通过这个类,将原生窗口的消息分发给其派生类,最后传给整个控件体系。另外在duilib中,需要进行消息处理的基本控件,都是从这个类继承出来的。 4.2.消息分发 一旦我们使用CWindowWnd类创建了窗口之后,消息就会通过CWindowWnd::HandleMessage进行分发,我们可以和WTL等其他的库一样,在此对原始的窗口消息进行处理。 LRESULTCWindowWnd::HandleMessage(UINTuMsg,WPARAMwParam,LPARAMlParam){ return�0�2::CallWindowProc(m_OldWndProc,m_hWnd,uMsg,wParam,lParam);} 当然如果我们觉得这样麻烦,我们也可以使用CPaintManagerUI来对其进行默认处理。我们上面提到CPaintManagerUI还会对所有的控件进行管理,这样,消息就传递给了窗口内部特定的控件了。 这些默认处理集中在CPaintManagerUI::MessageHandler()中,其内部会对很多窗口消息进行处理,并将其分发到对应的控件上去,比如对WM_LBUTTONDOWN的处理。 caseWM_LBUTTONDOWN: { //......POINTpt={GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam)};m_ptLastMousePos=pt;CControlUI*pControl=FindControl(pt);//......TEventUIevent={0};event.Type=UIEVENT_BUTTONDOWN;//......pControl->Event(event);} break;4.2.1.Focus&Capture 通过上面这个最简单的例子,我们基本可以猜到duilib对Focus和Capture的处理方法了:用一个成员变量保存对应的控件,在消息到达时直接转发消息。 在CPaintMainagerUI中,大家可以找到一个成员变量:m_pFocus,这个就是用来保存焦点控件的。在WM_KEYDOWN等键盘消息发生时,duilib就会模拟Windows行为,将消息直接转给当前Focus的控件。caseWM_KEYDOWN: { if(m_pFocus==NULL)break;TEventUIevent={0};event.Type=UIEVENT_KEYDOWN;//...m_pFocus->Event(event);//...} break;但是很奇怪的是,duilib里面并没有对Capture做处理,分发鼠标消息到对应的子控件上,可能是还没有完善的原因。 4.2.2.其他消息分发方式 除了Event以外,CPaintManagerUI还提供了其他几种用于处理消息的方法: Notifier:在窗口上处理一些控件的逻辑,可以将其看成和WM_NOTIFY差不多的功能 PreMessageFilter:消息预处理,这个大家肯定不陌生了。PostPaint:绘制后的回调 TranslateAccelerator:快捷键的处理 这里需要注意的是:PreMessageFilter和TranslateAccelerator是通过全局数组来实现的,这并不符合多线程的窗口编程要求,所以duilib对多线程的支持并不是很好! 4.3.WindowImplBase 为了简化duilib的使用,库中提供了一个非常方便的工具:WindowImplBase。这个类将常用的功能封装在其内部,比如Notifier和PreMessageFilter,并在其中提供了各种默认的虚回调函数,供派生类重载。通过这个类,我们可以非常方便的来实现一个简单的界面。 classUILIB_APIWindowImplBase :publicCWindowWnd ,publicCNotifyPump ,publicINotifyUI ,publicIMessageFilterUI ,publicIDialogBuilderCallback { //......virtualUINTGetClassStyle()const;//......virtualLRESULTOnClose(UINT/*uMsg*/,WPARAM/*wParam*/,LPARAM/*lParam*/,BOOL&bHandled);virtualLRESULTOnDestroy(UINT/*uMsg*/,WPARAM/*wParam*/,LPARAM/*lParam*/,BOOL&bHandled);//......5.资源组织和皮肤加载 好了,现在我们已经有了控件管理和控件库,现在我们需要让UI框架来帮忙组织这些资源,并且自动的来帮我们创建皮肤,减少我们的开发量。 duilib中的皮肤文件主要有几个部分组成: xml描述文件:描述窗口中控件的布局和样式 各种资源如图片 我们把这些资源放在一个文件夹中,这样就形成了基础的皮肤包。当然我们还可以将其组合成一个zip包,从而加快IO访问,但是修改起来就会相对麻烦。所以我们可以在debug中使用前者,而在release中使用后者。 我们可以在binskin下面找到duilib中自带demo的所有的皮肤包。皮肤中,最关键的部分就是这个xml描述文件了,一个xml描述文件对应着一个窗口的信息,如:控件的类型和样式等等。为了有一个直观的印象,我截取了duilib中ListDemo的xml描述文件的一部分放在这里: ... 为了通过配置文件自动创建皮肤,duilib提供了一个类:CDialogBuilder(DuiLibCoreUIDlgBuilder.h)。 这个类提供了从皮肤包(文件夹和zip格式)中的xml中创建皮肤的方法:CDialogBuilder::Create。内部实际上就是一个xml的解析,依次创建各式控件。除了创建控件,这个类还将一些可以复用的资源提取出来放入CPaintManagerUI中统一管理,如字体和图片等等。 6.简单使用 由于项目里面实在是带了太多太多的demo,而且在duilib的工程中,还有一个doc的目录,里面也非常详细的描述了要如何使用duilib来创建一个简单的工程。 所以关于duilib的简单使用,这里就不再详述了,这里就只列出GameDemo的main函数,这个函数非常的简单,但是已经基本可以表达了。 intAPIENTRYWinMain(HINSTANCEhInstance,HINSTANCE/*hPrevInstance*/,LPSTR/*lpCmdLine*/,intnCmdShow){ CPaintManagerUI::SetInstance(hInstance);CPaintManagerUI::SetResourcePath(CPaintManagerUI::GetInstancePath()+_T(“skin”));CPaintManagerUI::SetResourceZip(_T(“GameRes.zip”));HRESULTHr=�0�2::CoInitialize(NULL);if(FAILED(Hr))return0;CGameFrameWnd*pFrame=newCGameFrameWnd();if(pFrame==NULL)return0;pFrame->Create(NULL,_T(""),UI_WNDSTYLE_FRAME,0L,0,0,1024,738);pFrame->CenterWindow();�0�2::ShowWindow(*pFrame,SW_SHOWMAXIMIZED);CPaintManagerUI::MessageLoop();�0�2::CoUninitialize();return0;} 7.总结 总的来说,duilib还是一个很小巧好用的皮肤引擎的,但是他仍然有其不好的地方:对多线程的支持不好,不支持动画。但是无论如何,它还是不错的,所以如果你已经看到了这里,那么接下来跑到vs里面建一个工程,玩一把才是正经事~第四篇:数据库编程总结(推荐)
第五篇:一点Duilib编程总结