首页 > 代码库 > 扩展MUD 的efun函数
扩展MUD 的efun函数
搞定MUDOS (EFUN)by super@xkx
大家对LPC多少都有些熟悉了,这里就讲讲LPC里面的EFUN是怎么来的。
任何一个EFUN都有一个入口,这个入口就是interpret.c里面的eval_instruction()函数,在这里面会把EFUN传入的参数进行初始化,并把LPC中的数据类型转换成相应的结构体。经过eval_instruction()之后,就是EFUN的主体了,关于主体如何声明,将在后面提到。这里有两个全局变量是EFUN里面必不可少的,一个是int st_num_arg和svalue_t * sp,其中st_num_arg是该EFUN传入的参数的个数,然后SP就是参数的堆栈。这里有一点值得注意,就是任何EFUN的主体都是不能直接接参数的,所有的参数都是放在sp这个全局变量里面。
sp的格式如下:
sp 最后一个参数
sp-1 倒数第二个参数
sp-2 倒数第三个参数
..
sp-st_num_arg+1 第一个参数
那么还有人会问,sp是svalue_t类型的,怎么转换成我要的int,char*,或者object_t型呢?在svalue_t里面,定义了一个叫u的联合体,和一个叫type的int值,根据这两个值,我们就能取出我们想要的东西了,比如有一个EFUN叫void foo(int),然后在LPC里面是这样调用的:foo(5),那么在foo这个EFUN的主体里面,我们就这样获得那个5:
int num;
if(sp->type == T_NUMBER) num = sp->u.number;
这个时候num就是5了,sp的type有以下几种:T_NUMBER, T_STRING, T_REAL, T_BUFFER, T_OBJECT, T_MAPPING, T_FUNCTION, T_ARRAY,相对应的结构体分别为:int, char*, float, buffer_t, object_t, mapping_t, function_t 和 array_t,下面来讲讲这几种结构体的使用方法:
buffer_t, 访问buffer_t的方法有下面一些:
read_buffer (buffer_t * buf, int start, int len, int * rlen)
这个返回的是一个char *的东东,意思是从buffer_t里面的start开始读,读len个字节,rlen是读完以后的长度。
write_buffer (buffer_t * buf, int start, char * str, int theLength)
返回1是成功,返回0是失败,这个是把str写入进buffer里,start是开始的点,len是长度。
allocate_buffer (int size)
分配一个size大小的空buffer。
free_buffer (buffer_t * buf)
如果你用了allocate_buffer的话,那么一定要记得用free_buffer,否则可能会内存泄露导致当机,血的教训,俺就有几次忘记free了。
object_t, 对这个结构体进行操作的东西就多了,这里就不一一介绍了,相关的大家可以去看看object.c这个文件。讲讲这个结构体中一些东东的作用吧,比如说我在LPC里面写了一个object ob,我知道ob身上有一个叫int num的全局变量,那么该怎么来获得呢,首先得用
find_global_variable((program_t*)p, "num", &(unsigned short)type /*这个是用来返回类型的*/, 0)
来获得一个index,然后直接ob->variables[index]就能获得一个svalue_t的指针。其他的关于什么inherit_list什么的就不多讲了,大家有空去看看object.c,和object.h里面关于object_t的声明。
mapping_t, 如果大家想在MUDOS层对玩家身上的dbase进行操作的话,那么是一定得学会用mapping_t这个结构体的,首先用上面object_t的方法获得玩家身上的dbase的svalue指针。然后用mapping_t * map = svalue->u.mapping来声明一个指向dbase的指针。然后由于 mapping 是一个hash表数据结构,地址是需要通过hash函数来计算出来的,所以无法直接访问,必须用一个叫find_string_in_mapping(mapping_t, char*)来返回一个svalue的指针,比如说我想获得map里面的"id"的value,就用char * id_value = http://www.mamicode.com/find_string_in_mapping(map,"id"); 就可以找到"id"的值。关于mapping的更多操作这里就不一一介绍了,感兴趣的同胞们就打开mapping.c和mapping.h看看吧。
function_t,这个没有太多可以介绍的,大家只要知道function_t的可以通过:call_program(current_prog, ((function*)funp)->address); 来呼叫就可以了,其中的current_prog是一个program_t类型的变量,这个东东一般是通过((object_t *)ob)->prog来获得的,其实我们平常所谓的binary代码就是把program_t类型的东东写入文件出来的东东。
array_t,要掌握array_t,只要掌握array_t里面的item即可,下面来看段代码:
array_t * arr;
arr = allocate_empty_array(n);
while (--n) {
arr->item[n].type = T_NUMBER;
arr->item[n].subtype= 0;
arr->item[n].u.number = n;
}
这个时候一个大小为n的int型数组就弄完了,其中的元素是:({ 0, 1, 2, 3, .... n })大家明白地干活?
好,这么多类型都讲完了,相信大家基本上都可以完成自己想要的EFUN了,什么?还不能完成。。哦,对,还没告诉大家怎么把计算出来的记过返回出来呢。
MUDOS对EFUN的传入和传出是使用的同一个堆栈,也就是说,读入参数是从根据sp读出来,返回的参数也是在sp里面,返回的时候就需要先把读入进来的参数清空掉,对于非地址类型的参数呢,不需要清空,程序结束,自然就没了,但是对于string,object,buffer,mapping,array之类的参数呢,在传入进来的时候是分配了空间的,这个时候就需要先free再清空了,把传进来的参数一个个free_svalue()掉,然后再把sp的指针指向堆栈头,然后把堆栈顶部的东东指向你要传回的值,就搞定了。。。恩,废话不多说了,来看段例子代码:
char *fname;
int x,y;
array_t * result;
fname = (sp-2)->u.string;
x = (sp-1)->u.number;
y = sp->u.number;
....// 一堆运算过程,然后需要返回result这个array.
free_string_svalue(sp-2);
sp -= 3;
sp->u.type = T_ARRAY;
sp->u.array = arr;
OK,搞定,呵呵,很麻烦,是吧?其实MUDOS里面已经把这一系列的东西都封装好了的,在macros.h里面,可以用pop_n_elems(st_num_arg)来实现free_svalue() + sp-=st_num_arg的功能,然后可以用push_array(arr)来实现sp->u.TYPE和sp->u.array的功能,记得,如果用pop_n_elems的话,就一定要用push_xxx,而不要用put_xxx,大家会说,我看其他EFUN里面也是写的put_xxx()啊,(猪,俺锤你)因为其他EFUN没有用pop_n_elems()啊,因为在put_xxx里面会自动把sp++,所以如果把pop_n_elems()和put_xxx混用的话,就会导致执行完EFUN以后马上crash...呵呵。
好,相信写到这里,大家都知道怎么新加一个EFUN的主体了,那么怎么让这个EFUN生效呢?这里有两种方法,一种是直接写到efun_main.c这个文件里面,然后在efun_main_spec.c里面加一个关于该函数的声明,就马上可以用了,另外一种方法是在package/下面写一个myefun.c然后再写一个myefun_spec.c,把函数主体写在myefun.c里面,然后在myefun_spec.c里面写上函数的声明,然后在options.h里面加一句:
#define PACKAGE_MYEFUN
即可,简单,实在,又好用,呵呵,牙好胃口就好什么的。
恩,下面来说说调试时候的相关问题,大家把EFUN写好了,心情激动吧,重新编译吧,发现编译通过了,高兴吧,然后运行把,发现当机了,SB了吧,呵呵,不要急,crash是正常的,只有crash了才能找到问题的根本。那么怎么调试呢?首先,编译的时候先./BuildMudOS debug然后再gmake,这个时候编译出来的就是debug版,在crash的时候会抛出一串看不懂的东东,呵呵,既然看不懂,有什么用呢,虽然看不懂,不过多少还是抛出了错误,-_-b,不要打脸,不过编译成DEBUG版最大的好处不是它自己抛出的错误,而是在使用gdb调试的时候可以step by step的调试。恩下面简单介绍一下用gdb调试driver的过程。
比如我新写了个EFUN,叫f_heihei,然后编译通过了,获得了driver,执行的时候就这样执行:
gdb driver
b f_heihei
run config.cfg
然后driver就跑起来了,这个时候你再用heihei这个EFUN的时候,driver就会阻塞,等你来输入命令,这个时候可以用print 打印出你想看的变量的值,然后可以用n或者next来执行下一句,当机了的话可以用backtrace来抛出最后一次呼叫的堆栈。好了,先说这么多吧,要把底都掏完了的话偶以后还混个P啊,当然要留两手的说,HOHO。。偶很邪恶吧~~~
附:返回"hello world"的EFUN:
myefun_spec.c
string hello_world(int type);
myefun.c
#include "../std.h"
#ifdef F_HELLO_WORLD
void f_hello_world() //有些EFUN都带PROT什么的,别理它,俺们操作系统认识不带PROT的。
{
char * res;
int type = sp->u.number;
pop_n_elems(st_num_arg);
switch(type)
{
case 1: res = "hello world, I am the first type."; break;
case 2: res = "hello world, I am the second type."; break;
default : res = "hello world, I am nothing.";
}
copy_and_push_string(res);
}
#endif
在 options.h 里面加一个#define PACKAGE_MYEFUN
重新编译,搞定。
扩展MUD 的efun函数