文档中心

TD开发平台_基础

04.简单对象系统

当需要调用第三方提供的代码库并使用其中的对象时,存在两个问题: 1.需要对方库的头文件,里面必须有对象的完整定义,这就导致调用者可以查阅对象内部的细节,并可能会修改对象的数据破坏对象内部的一致性 ;并且也违背了模块封装的原则。 2.对于相同的头文件,不同的编译器也可能产生不同的内存布局,并且对于函数的名字改编(函数重载),不同的编译器采用的方案也可能不同。为了解决这两个问题,引入了面向组件的编程技术和接口的概念。接口就是一组函数定义,并且被映射到对象的虚函数上(假定不同的编译器对虚函数的实现都是相同的)。对象要实现一个或多个接口就是继承相应的接口对象,这样把函数定义和函数实现分开,并且开发人员看不到实现对象的内部数据。

在本章中只介绍如何使用已经有的对象类型。
对象的使用包括以下内容:

1)对象的生存期管理
void * TObjectCreate(T_ID type, TTable *in); /*创建一个对象*/
void TObjectDestroy(void *object); /*销毁一个对象*/
void TObjectHold(void *object); /*增加对象的引用计数*/
void TObjectDrop(void *object); /*减少对象的引用计数*/
对象的创建是复杂的,因为不同类型的对象创建需要的参数不同,即使相同类型的对象也可能有多个构造函数(参数不同)。所以这里用 TTable来封装创建的参数,这样就可以用同一个函数来创建所有类型的对象。如果没有初始化参数,TObjectCreate的参数 in传NULL。例如要创建一个 button类型的对象,这种对象在创建时需要位置( w,y)、大小(w,h)和显示文字( text)等信息,创建代码如下:
void * create_button_object (Tint x, Tint y, Tint w, Tint h, const char
*text)
{
    void *button_obj;
    TTable *arg_t;
    arg_t = TTableCreate();
    TTableAddInt(arg_t, T_STRING_ID(x), x);
    TTableAddInt(arg_t, T_STRING_ID(y), y);
    TTableAddInt(arg_t, T_STRING_ID(w), w);
    TTableAddInt(arg_t, T_STRING_ID(h), h);
    TTableAddString(arg_t, T_STRING_ID(text), text, -1);
    button_obj = TObjectCreate (T_STRING_ID(button), arg_t);
    TTableDestroy(arg_t);
    return button_obj;
}
再如有一个网络下载文件的对象,这种对象在创建时需要下载的文件链接地址,创建
代码如下:
void *create_download_object (const char *file_address)
{
    void *url_obj;
    TTable * in;
    in = TTableCreate();
    TTableAddString(in,T_STRING_ID(url),file_address,-1);
    url_obj=TObjectCreate(T_STRING_ID(url), in);
    TTableDestroy(in);
    return url_obj;
}
所有对象内部都有一个引用计数,被创建时引用计数为 1;TObjectHold函数会对引用计数加 1;TObjectDrop函数会对引用计数减 1,并且如果减 1后等于 0,就会自动销毁该对象(即执行对象的析构函数并释放内存)。
TObjectDestroy函数用于销毁对象,它首先执行该对象的析构函数,然后对引用计数减 1,如果等于 0就释放内存,否则不释放内存。因为增加引用计数和减少引用计数必须成对出现,所以由 TObjectDrop来释放内存(不会重复调用析构函数)。
2)对象的方法调用
不同类型的对象支持不同的调用接口函数,如图形界面中的所有控件( button,window等)都支持一组操作函数(即控件接口),这些函数的形式都已经稳定下来不会改变,并且经常被调用,如:
void TwShow(void *obj); /*控件的显示*/
void TwHide(void *obj); /*控件的隐藏*/
void TwResize(void *obj, Tint new_w, Tint new_h); /*控件改变大小*/
void TwMove(void *obj, Tint new_x, Tint new_y); /*控件移动位置*/
注意这里没有提供一个通用的函数调用接口,比如 Tint TObjectCustomMethod(void *obj, T_ID method, TTable *arg)。
3)对象的事件机制
事件机制并不是面向对象语言本身提供的,如按钮有点击事件,开发人员并不知道什么时间发生点击,但是希望点击的时侯能够通知开发人员程序(通知开发人员就是自动调用开发人员的事件处理函数),事件就像操作系统里的中断,事件处理函数就像中断处理函数,但是允许同一个事件有多个处理函数,多个处理函数的调用顺序是不确定的。所以事件机制从本质上讲是一种灵活的函数回调机制。
首先开发人员需要注册事件,即告诉对象我对你的什么事件感兴趣,以及我的事件处理函数:
typedef void (*TObjectEvent_Func)(void *obj, T_ID event, TTable *in,
void *arg, TExist *exist);
Tint TObjectAddEventHandler(void *object, T_ID event,
TObjectEvent_Func func, void *arg);

调用 TObjectAddEventHandler()注册事件,参数 object指定对象,参数 event标识一个事件,参数 func就是事件处理函数(原型为 TObjectEvent_Func,它的参数 in封装了事件的参数,arg就等于 TObjectAddEventHandler的参数 arg),参数 exist和多线程有关。可以对同一个对象的同一个事件注册多个事件处理函数,但是:相同的 object、相同的 event、相同的 func、相同的 arg不能重复注册。
然后就是触发事件:
Tint TObjectEvent(void *object, T_ID event, TTable *in);
TObjectEvent()函数就是串行调用指定对象和事件的所有的事件处理函数,执行完毕后才返回。参数 object指定对象,参数 event指定要触发的事件,参数 in封装这次事件的参数。因为这个函数谁都可以调用,所以可以是对象内部的实现代码在发生事件时调用,触发自己的事件,也可以由开发人员调用,模拟触发事件。
需要说明的是: 1、同一个对象的相同事件不能重复触发; 2、不能在触发事件的过程中再次注册相同对象的这个事件。
最后可以调用 TObjectDeleteEventHandler()删除注册的事件:
Tint TObjectDeleteEventHandler(void *object, T_ID event,TObjectEvent_Func func, void *arg);

4)对象的命名
可以给对象设置一个名字,方便通过名字来获取和管理对象,当然对象也可以没有名字,只要有控件的指针就可以访问该控件。
Tint TObjectSetName(void *obj, const char *name); //给控件设置一个名字
const char *TObjectGetName(void *obj); //得到控件的名字,没有返回NULL
void *TObjectGetFromName(const char *name); //通过名字查找控件,返回控
注意:所有的对象都不能重名,否则TObjectSetName 将设置失败。