解析易语言窗口控件资源
基础介绍
通过寻找易语言静态编译的程序规律,我们可以找到一处结构体,寻找的特征码是50 64 89 25 00 00 00 00 81 EC AC 01 00 00 53 56 57
,找到后这个结构体大概是下面这个样子:
struct EHead
{
unsigned int dwMagic; //未知,值固定为3
unsigned int szNone2; //未知,值固定为0
unsigned int szNone3; //未知,好像是个随机数,修改不影响程序
unsigned int lpStartCode; //用户代码起始地址,不可修改
unsigned int lpEString; //常量资源,如果没有常量资源,则为0
unsigned int dwEStringSize; //常量资源大小,如果没有常量资源,则为0
unsigned int lpEWindow; //创建组件信息
unsigned int dwEWindowSize; //创建组件信息大小
unsigned int dwLibNum; //支持库数量
unsigned int lpLibEntry; //支持库信息入口
unsigned int dwApiCount; //Api数量
unsigned int lpModuleName; //指向模块名称
unsigned int lpApiName; //指向Api名称
};
可以看到这些结构体包含着易语言程序中的很多重要数据,今天我们要研究的是界面资源部分。
易语言所有的GUI信息都放在lpEWindow
这个指针所指向的位置,dwEWindowSize
则为信息的总大小,通过对这个信息进行解析,理论上我们可以还原与易语言IDE中几乎一模一样的控件设计。
具体解析规则,我就懒得介绍了,请自行参考E-Decompiler
源码中的GuiParser::ParseGUIResource
函数。通过不断尝试进行拆解还原,我们最终大概可以看到这样一个结构:
每个窗口都是一棵树,窗口内的控件则为这个树的子节点,不难理解,通过窗口ID -> 控件ID,我们就能对应到每个控件了。其中值得说明的是窗口本身也属于控件,每个窗口的第一个控件就是窗口控件。
控件类型
在上图中我们可以看到,窗口.取窗口句柄()
这样一个命令的参数中,0x52010001表示控件所属窗口ID,0x6010000表示控件ID,而0x10001这个参数则表示控件的类型ID。
每个控件都有一个控件类型ID,比如窗口的控件类型ID是0x10001,按钮的控件类型是0x1000B。
这个控件类型ID是有规律的,因为易语言有一个支持库机制,我们不妨看看易语言核心支持库的介绍:
提供了51种库定义数据类型,提供了686种命令,提供了335个库定义常量
而编辑框、按钮这些控件就属于核心支持库中的数据类型的一部分,控件ID中的高四位对应的是控件所属支持库在易语言程序中的加载顺序,低四位对应的则是控件在所属支持库中的数据类型数组中的下标,因此窗口控件ID0x10001表示的含义其实是程序所加载的第一个支持库中的第一种控件。
由于系统支持库默认最先加载,所以核心支持库中的控件的高四位必定是1。
控件属性
在易语言中,属性可以分基本属性和附加属性两种,像左边
、顶边
、宽度
、高度
这些几乎每个控件都有的属性就属于基本属性了,这些字段每个控件排列顺序、排列方式都是一样的,我们可以通用解析。基础属性之后跟着的则是每个控件独特的附加属性,以序列化的形式存储,因此如果我们想要把这些属性还原出来,就必须对每个控件的属性进行反序列化解析。
另外,易语言为了节省空间,控件的附加属性都有一个默认值,即如果控件在IDE中的附加属性未进行任何改动,那么控件的信息结构中,附加属性字段占用大小为0。
控件事件
事实上,控件的事件属于控件基本属性的一部分,事件的结构体很简单:
struct ControlEvent
{
int EventIndex; //事件索引
uint32 EventOffset; //事件地址便宜
};
EventIndex
表示事件类型,EventOffset
值等于事件地址相对于用户代码起始地址
的偏移。
每个控件都有自己的一张事件列表,例如0表示窗口_创建完毕
事件,1表示窗口_可否被关闭
事件。
控件的属性,2112 =>
总结
其实还有一些细节,像子控件、父控件,菜单控件之类的,由于作者太懒也没有进行介绍,本文只是一个科普贴。。。。。。
啥也不说了,都在代码里。