需求说明
在程序程序UI界面设计的时候,经常会遇到这样的情形,需要一个临时变量来保存一个值,这个值还会被窗口的其他消息响应函数用到,窗口销毁这个变量也就没有用了,也就是说这个变量只在窗口生命周期存在,类比线程局部变量(Thread Local)的概念,我们可以把它叫做窗口局部变量(Widget Local)。
然而不论是Windows上的MFC控件库,还是QT,还是现在我们项目中使用的MiniGUI,现行的所有GUI框架都没有为窗口对象提供动态定义变量的功能。
之前遇到这种需要,我只能用一个全局静态变量(static
)来代替,但这种方式是不安全的,如果同一个窗口拥有两个以上实例的时候更是不能使用。如果大量无顾忌的使用,会为项目的稳定性埋下隐患。
最近UI层与业务层数据绑定功能的时候,再次遇到了这个问题,而且无论如何绕不过去了,只能下决心,自行设计了窗口局部变量(Widget Local)机制。
实现原理
其原理说道起来并不复杂,就是通过一个哈希表来保存每个窗口创建的任意多个局部变量(Widget Local),并侦听窗口的MSG_DESTROY消息,当窗口销毁时自动销毁所有局部变量。每个窗口的局部变量数据都保存一个独立的哈希表中。有了这个机制,就可以安全的在窗口中定义局部变量,而不用关心变量的销毁问题,还可以同时访问不同窗口的局部变量。
代码实现
哈希表
对WidetLocal
变量的读写在代码实现这一层其实就是对哈希表的读写操作,那么C下面如何实现哈希表呢?
难道要自己写一个?
其实MiniGUI/mgncs
1.2.0版本,将原本其内部使用的哈希表(hashtable.h
)开放出来了,所以C下面如何实现哈希表不用操心了,直接使用mgncs
自带的就好了。
下面是实现代码 fl_widget_local.c
/*
* fl_widget_local.c
*
* Created on: Aug 13, 2018
* Author: gyd
*/
#include "fl_widget_local.h"
/*
* 保存所有窗口 WidgetLocal变量的哈希表,程序结束时自动销毁
* key type mWidget*
* value type fl_owner_data_t*
* */
static HashTable * ht_widgets = NULL;
#define MAX_WIDGET_COUNT 37
#define MAX_WL_VAR 7
#ifdef _MGRM_THREADS
static pthread_mutex_t _wl_locker;
#define WL_LOCK() (pthread_mutex_lock(&_wl_locker))
#define WL_UNLOCK() (pthread_mutex_unlock(&_wl_locker))
#else
#define WL_LOCK()
#define WL_UNLOCK()
#endif
// 窗口数据
typedef struct _fl_owner_data_t{
/* 窗口(widget)原有的 MSG_DESTROY 消息处理函数 */
NCS_CB_ONVOID ondestory_handler;
/* 保存所有局部变量(widget lcoal)的哈希表
* key type ht_key_t
* value type fl_wlocal_t*
* */
HashTable* vars;
}fl_owner_data_t;
static void free_wl_entry_obj(fl_wlocal_t* obj)
{
if(obj){
// 如果有free函数则执行
if(obj->free_value)
obj->free_value((void*)obj->value);
free(obj);
}
}
static void free_owner_entry_obj(fl_owner_data_t* obj)
{
if(obj){
// 销毁(哈希表中的)所有变量
hash_free(obj->vars);
free(obj);
}
}
// 用于拦截窗口的 MSG_DESTROY 消息
static void ondestroy_listener(mWidget*self, UINT message)
{
fl_owner_data_t* owner_data = (fl_owner_data_t*)hash_get(ht_widgets, (ht_key_t)self);
assert(owner_data);
// 先执行原有的 MSG_DESTROY 处理函数
if(owner_data->ondestory_handler)
owner_data->ondestory_handler(self,message);
if(MSG_DESTROY == message)// 多一层判断增加安全性
hash_delete(ht_widgets,(ht_key_t)self);/* 销毁mWidget对象所有的widget local变量 */
}
static void fl_widget_local_set(mWidget* owner,ht_key_t key,fl_wlocal_t *wlocal)
{
// 检查输入参数,为0返回
if(!owner || !key)return;
void do_finally( )
{
WL_UNLOCK();
return ;
}
WL_LOCK();
// lazy initialize 哈希表延迟初始化
if(ht_widgets == NULL)
{
ht_widgets = hash_new(MAX_WIDGET_COUNT, (freeObj)free_owner_entry_obj);
}
fl_owner_data_t* owner_data = (fl_owner_data_t*)hash_get(ht_widgets, (ht_key_t)owner);
if(!owner_data){
// 创建 widget local hashTable,并向mWidget绑定 MSG_DESTROY 消息侦听器函数
owner_data = (fl_owner_data_t*)calloc(1,sizeof(fl_owner_data_t));
owner_data->vars = hash_new(MAX_WL_VAR, (freeObj)free_wl_entry_obj);
// 替换控件的MSG_DESTROY消息处理函数
owner_data->ondestory_handler = ncsSetComponentHandler((mComponent*)owner, MSG_DESTROY,ondestroy_listener);
hash_insert(ht_widgets, (ht_key_t)owner, owner_data);
}
if(wlocal)
{
fl_wlocal_t *var = (fl_wlocal_t *)hash_get(owner_data->vars,key);
// 如果变量已经存在且相等则跳过赋值,避免hash_insert时执行freeObject
// 注意:这里是判断value变量所有8个字节完全相等
if(var && !memcmp(&var->value,&wlocal->value,sizeof(var->value)))
return do_finally();
var = (fl_wlocal_t *)calloc(1,sizeof(fl_wlocal_t));
assert(var);
*var = *wlocal;
// 变量赋值
hash_insert(owner_data->vars,key,var);
}
else
{
// 为NULL删除变量
hash_delete(owner_data->vars,key);
}
return do_finally();
}
static fl_wlocal_t* fl_widget_local_get(mWidget* owner,ht_key_t key)
{
WL_LOCK();
fl_wlocal_t * var = NULL;
fl_owner_data_t* owner_data = (fl_owner_data_t*)hash_get(ht_widgets, (ht_key_t)owner);
if(owner_data)
{
var = (fl_wlocal_t*)hash_get(owner_data->vars,key);
}
WL_UNLOCK();
return var;
}
#define FL_DERIVED_CLASS_CONSTRUCTOR_PRIORITY 201
// 执行哈希表 ht_widget_local 实例初始化
void wl_instance_init() __attribute__((constructor(FL_DERIVED_CLASS_CONSTRUCTOR_PRIORITY + 1)));
// 释放ht_widget_local
void wl_instance_uninit() __attribute__((destructor(FL_DERIVED_CLASS_CONSTRUCTOR_PRIORITY + 1)));
void wl_instance_init() {
}
void wl_instance_uninit(){
hash_free(ht_widgets);
}
下面是头文件定义fl_widget_local.h
/*
* fl_widget_local.h
*
* Created on: Aug 13, 2018
* Author: gyd
*/
#ifndef MGCOMMON_FL_WIDGET_LOCAL_H_
#define MGCOMMON_FL_WIDGET_LOCAL_H_
#include <minigui/common.h>
#include <minigui/minigui.h>
#include <minigui/gdi.h>
#include <minigui/window.h>
#include <mgncs/mcommon.h>
#include <mgncs/mgncs.h>
/**
* key的类型为unsigned long,可以调用MiniGUI库函数 Str2Key 将字符串转为ht_key_t
* */
// widget local局部变量类型定义
typedef struct _fl_wlocal_t {
/* 变量值可为任意类型但长度不可超过 8 bytes */
int64_t value;
/* 变量释放函数指针,由定义变量时定义,用于销毁变量时执行释放变量占用的资源,可为NULL */
freeObj free_value;
}fl_wlocal_t;
/* 设置widget local变量
* wlocal为NULL时删除变量
* */
void fl_widget_local_set(mWidget* owner,ht_key_t key,fl_wlocal_t *wlocal);
/* 返回 widget local变量, 没找到则返回 NULL */
fl_wlocal_t* fl_widget_local_get(mWidget* owner,ht_key_t key);
// 删除key指定的widget local
#define WLOCAL_DEL(owner,key) fl_widget_local_set((mWidget*)owner,(ht_key_t)key,NULL)
// 设置key指定的widget local
#define WLOCAL_SET(owner,key,_value,cb_free) \
{\
fl_wlocal_t wl = {.value=(int64_t)_value,.free_value=(freeObj)cb_free}; \
fl_widget_local_set((mWidget*)owner,(ht_key_t)key,&wl);\
}\
// 返回key指定的widget local的值的地址,未定义则返回NULL
// type 为 widget local的类型
#define WLOCAL_GET(owner,key,type) \
({\
fl_wlocal_t* wl = fl_widget_local_get((mWidget*)owner,(ht_key_t)key);\
(type*)(wl?&wl->value:NULL);\
})\
// 定义一个不需要free的简单变量
#define WLOCAL_SET_SIMPLE(owner,key,value) WLOCAL_SET(owner,key,value,NULL)
// 定义一个自动销毁的mObject 变量
#define WLOCAL_mObject(owner,value) WLOCAL_SET(owner,value,value,deleteObject)
// 定义一个可以直接调用(free)销毁的内存指针变量
#define WLOCAL_allocptr(owner,allocptr) WLOCAL_SET(owner,allocptr,allocptr,free)
#endif /* MGCOMMON_FL_WIDGET_LOCAL_H_ */
调用示例
mObject obj = NEW(myObjClass);
// 如下就将mObject对象定义为一个widget local变量,后续不需要去负责DELETE,
// self窗口销毁时会自动调用deleteObject函数销毁对象
WLOCAL_mObject(self,obj);