上一节我们讲了整个数据的结构设计,并通过数据可视化的步骤,把数据变成了我们展示的界面。
这就体现了我之前一直强调的,编程的本质,就是处理数据,所有程序都是为了处理数据而生的,计算机也是为了这个目的被制造出来的,我们写代码,核心一定要围绕着数据来,其他东西,都是给数据管理打辅助的。
接下来让我们把视角放在源代码视图:
这里源代码只有1000多行,和很多高阶的作者比,这个代码量绝对算是小儿科了。
当然,这并不是 xTask 全部的源代码,因为按键精灵2014的代码编辑器很卡,代码量一旦上了几千行之后,敲代码都卡卡的,所以我一般在这里写代码的时候,都会刻意控制代码数量。
为了方便大家阅读和改写,我当初在开源时已经对代码做好了分类和注释,在代码中,可以看到很多这样的注释:
这些注释把1000多行代码拆成了10个功能块,每个功能块的所有函数都只干一件事情:
程序加载初始化代码:窗口加载时执行的代码,它会初始化环境,创建数据模型,引入基础运行库,然后加载存储在数据库中的 task 列表,以及调用项目管理的功能,扫描 project 目录下的工程,将工程添加到列表。
项目管理相关功能:负责扫描项目列表,将列表更新到界面上,以及事件响应(切换项目时的界面动作)
项目 七日留存 相关功能:就是控制项目注册的角色,7天时间内不断流失注册用户的数据模拟功能,这部分和原始需求有关,反倒不需要大家去深入理解。
这一部分主要负责的也是界面的响应。
项目 数据库管理 相关功能:项目数据库管理界面相关的功能响应。
帐号导入导出 接口代码:账号导入和账号导出两个界面的功能响应。
任务管理相关功能:任务计划界面的事件响应,也负责一些基本逻辑的实现,比如创建、删除、修改任务等。
程序设置 热键设置 相关功能:比较简单的设置界面,可以像按键一样按某个键启动和结束计划任务。
任务监控界面和整体调度功能:这是比较核心的功能,任务的具体执行从这里开始,任务界面的响应代码也都在这里,之后我们会详细讲解。
任务线程创建:也是比较核心的功能,任务的每一个线程都从这里开始创建,WorkThread 函数是框架内所有任务子线程的统一入口。
全局数据监控:这里的代码用于刷新全局数据界面,来把与框架运行有关的所有核心数据都给展示出来。
全局数据:代码看完了,接下来我们看全局数据部分,我们的框架只使用了非常少的变量,因为我们又折叠数据成结构的方法,这个一会再讲,全局变量的定义如下:
- // 全局 xAtom 对象指针
- Dimenv G_Atom_Global_Ptr, G_Atom_Thread_Ptr, G_Atom_Task_Ptr, G_Atom_Proj_Ptr, G_Atom_Context_Ptr
- // 全局数据
- Dimenv G_Index_Proj, G_Index_Task, G_Lock_Thread, G_Path_Root
复制代码我比较喜欢吧所有用到的变量都显式定义一下,基本上数据就这么多了。
可以看到第一行定义的变量都以 _Ptr 结尾,这是指针的意思,可以理解为是一个对象的指针,这个对象里,就以结构化存储着我们所需要的全部数据。
G_Atom_Global_Ptr 是全局数据
G_Atom_Thread_Ptr 是线程列表数据
G_Atom_Task_Ptr 是计划任务列表数据
G_Atom_Proj_Ptr 是子项目列表数据
G_Atom_Context_Ptr 是上下文数据,上下文数据是传递给线程用的,我们每次创建线程的时候,都会把这个线程需要的数据用这个对象传送过去,然后线程就能很方便的访问这些数据了。
然后第二行,是一些普通的全局数据:
G_Index_Proj 当前选择的项目(QUI使用的)
G_Index_Task 当前选择的计划任务(QUI使用的)
G_Lock_Thread 线程锁,创建线程的时候用的,xTask每次创建线程,都会用锁把数据保护起来,就算创建 10000 个线程,也不会出现数据错乱的情况。
G_Path_Root 程序根目录
线程锁的功能,由我编写的DLL实现(所以解析这东西的源代码,也是希望能给大家带来一些有趣的玩意,这里用到的不少东西,都是值得单独拿出来玩的):
- Declare Sub Locking lib "xTask.dll" (m As Long)
- Declare Sub UnLocking lib "xTask.dll" (m As Long)
复制代码到这里代码就分类的差不多了,接下来我们详细讲解一下,当小精灵启动的瞬间,我们的框架都干了些什么。
窗口创建事件代码解析:代码如下:
- // 窗口加载事件
- Event Form1.Load
- // 重置全局数据
- G_Index_Proj = 0
- G_Index_Task = 0
- // 重定向工作目录
- TracePrint "xTask : ☆ 重定向工作目录 ..."
- Dim RootPath
- RootPath = Plugin.File.ReadINI("setup", "work", GetExeDir() & "option.ini")
- RootPath = Replace(RootPath, "{$root}", GetExeDir()) // 重定向工作目录 [支持调试环境]
- G_Path_Root = RootPath
- // 注册运行库
- Call RegeditRtl(RootPath)
- // 创建系统运行环境(加载各种支持库)
- Call LoadLibrary(RootPath)
- // 创建全局Atom对象
- Call CreateAtom()
- // 系统初始化
- Call xTask_System_Init(RootPath)
- // 设置运行停止热键
- Call SetHotKey(RootPath)
- // 加载任务计划选项
- Form1.Con_Task_StepNext.Value = xTask.IniReadInt(RootPath & "option.ini", "option", "StepNext")
- Form1.Con_Task_AutoStop.Value = xTask.IniReadInt(RootPath & "option.ini", "option", "AutoStop")
- Form1.Con_Task_AutoLoop.Value = xTask.IniReadInt(RootPath & "option.ini", "option", "AutoLoop")
- G_Atom_Global("StepNext") = Form1.Con_Task_StepNext.Value
- G_Atom_Global("AutoStop") = Form1.Con_Task_AutoStop.Value
- G_Atom_Global("AutoLoop") = Form1.Con_Task_AutoLoop.Value
- // 刷新项目列表
- Call ScanProjList()
- // 刷新任务列表
- Call LoadTaskList()
- // 加载项目类型
- Form1.Con_TaskType.List = GetTaskTypeList()
- Form1.Con_TaskType.ListIndex = 0
- // 加载导入/导出帐号方案
- Form1.Con_Import_List.List = GetImportUserList()
- Form1.Con_Import_List.ListIndex = 0
- Call ChangeImportUserList(0)
- Form1.Con_Export_List.List = GetExportUserList()
- Form1.Con_Export_List.ListIndex = 0
- Call ChangeExportUserList(0)
- // 刷新全局数据列表 [默认页面]
- Call RefreshAtomListEvent(0)
- End Event
复制代码嗯,代码量并不多,而且注释很丰富,应该不难理解吧?
【重置全局数据】 就是把全局数据都给清0了,避免QUI读取数据设置界面属性的时候出现误判,任何情况下,我们都应该养成这样的好习惯,很多时候BUG就是这么来的。
【重定向工作目录】 读取 option.ini 的 setup 小节的 work 数据,然后把 {$root} 替换成程序所在目录。
这是为了啥?因为我做的这个东西,正常来说它是做成小精灵单独运行的,但我在开发它的时候经常需要在按键精灵环境下运行,如果我要在两个地方都运行,那数据就得整两份,我不想这样,所以加入了这个根目录重定向功能,可以在按键精灵环境下,把根目录重定向到小精灵的目录里,这样我在两个地方运行脚本,环境都是统一的了。
最后我们计算好的根目录,会赋值给 G_Path_Root,以后我们只会通过这个变量作为我们的工作目录了。
【注册运行库】调用了 RegeditRtl 函数,它的代码是这样子的,就是把大漠插件啊,DWX插件啊什么的都给注册到系统里面,顺便检查一下MD5值,因为有段时间感染型病毒挺多的,我怕把dm.dll 感染了,回头运行的时候出错又找不到原因,小心驶得万年船:
- // 注册运行库
- Sub RegeditRtl(RootPath)
- TracePrint "xTask : ☆ 注册 DWX 运行库 ..."
- Dim TmpObj, Ver_DM, Ver_DW, Md5_DM, Md5_DW, Md5_XT, RT_MD5_DM, RT_MD5_DW, RT_MD5_XT, OptFile
- // 读取配置
- OptFile = RootPath & "option.ini"
- Ver_DM = Plugin.File.ReadINI("check", "dmver", OptFile)
- Ver_DW = Plugin.File.ReadINI("check", "dwver", OptFile)
- Md5_DM = Plugin.File.ReadINI("check", "dmmd5", OptFile)
- Md5_DW = Plugin.File.ReadINI("check", "dwmd5", OptFile)
- Md5_XT = Plugin.File.ReadINI("check", "xtmd5", OptFile)
- // 检测组件MD5值(避免病毒感染造成的运行问题)
- RT_MD5_DM = Plugin.Encrypt.Md5File(RootPath & "runtime\dm.dll")
- RT_MD5_DW = Plugin.Encrypt.Md5File(RootPath & "runtime\dwx.dll")
- RT_MD5_XT = Plugin.Encrypt.Md5File(RootPath & "xTask.dll")
- If RT_MD5_DM <> Md5_DM Then
- MsgBox "!!!警告!!! 大漠插件被修改过,如果不是自己替换的,意味着您的机器可能存在感染型病毒!", vbCritical
- TracePrint "大漠插件MD5 : " & RT_MD5_DM
- End If
- If RT_MD5_DW <> Md5_DW Then
- MsgBox "!!!警告!!! DWX 插件被修改过,如果不是自己替换的,意味着您的机器可能存在感染型病毒!", vbCritical
- TracePrint "DWX插件MD5 : " & RT_MD5_DW
- End If
- If RT_MD5_XT <> Md5_XT Then
- MsgBox "!!!警告!!! xTask运行库 被修改过,如果不是自己替换的,意味着您的机器可能存在感染型病毒!", vbCritical
- TracePrint "xTask运行库MD5 : " & RT_MD5_XT
- End If
- // 注册大漠插件
- Set TmpObj = CreateObject("dm.dmsoft")
- If TypeName(TmpObj) <> "Idmsoft" Then
- TracePrint "xTask : ☆ 需要注册大漠插件。"
- If xRegSvr32(RootPath & "runtime\dm.dll", 0) = 0 Then
- MsgBox "大漠插件注册失败,程序可能无法正常工作!", vbCritical
- End If
- ElseIf TmpObj.Ver <> Ver_DM Then
- TracePrint "xTask : ☆ 需要重新注册大漠插件。"
- If xRegSvr32(RootPath & "runtime\dm.dll", 2) = 0 Then
- MsgBox "大漠插件注册失败,程序可能无法正常工作!", vbCritical
- End If
- End If
- Set TmpObj = Nothing
- // 注册 DWX
- Set TmpObj = CreateObject("DynamicWrapperX.2")
- If TypeName(TmpObj) <> "Object" Then
- TracePrint "xTask : ☆ 需要注册 DWX 插件。"
- If xRegSvr32(RootPath & "runtime\dwx.dll", 0) = 0 Then
- MsgBox "DWX 插件注册失败,程序可能无法正常工作!", vbCritical
- End If
- ElseIf TmpObj.Version(0) <> Ver_DW Then
- TracePrint "xTask : ☆ 需要重新注册 DWX 插件。"
- If xRegSvr32(RootPath & "runtime\dwx.dll", 2) = 0 Then
- MsgBox "DWX 插件注册失败,程序可能无法正常工作!", vbCritical
- End If
- End If
- Set TmpObj = Nothing
- End Sub
复制代码【创建系统运行环境(加载各种支持库)】 调用了 LoadLibrary 函数,代码很简单,长这样:
- // 创建系统运行环境(加载各种支持库)
- Sub LoadLibrary(RootPath)
- TracePrint "xTask : ☆ 正在创建系统运行环境 ..."
- Dim fso, fileobj
- Set fso = CreateObject("Scripting.FileSystemObject")
- Set fileobj = fso.OpenTextFile(RootPath & "library\System_Task.vbs", 1, False)
- Call ExecuteGlobal(fileobj.ReadAll)
- Call xTask_System_Create(RootPath)
- Call fileobj.Close()
- Set fileobj = Nothing
- Set fso = Nothing
- End Sub
复制代码这段代码咱需要好好说道说道了,前面不是说这1000多行代码不是全部代码嘛,因为很多代码被我以VBS的形式写在外面了。
这段代码其实就是创建一个 fso 对象,读取 library 目录下的 System_Task.vbs,然后把这个 vbs 在按键环境里运行一下,于是这个文件里的函数,按键就都能用了。
这些做完之后,我还调用了 xTask_System_Create 函数,代码也很简单,它长这样:
- ' 系统创建 [环境未初始化]
- Sub xTask_System_Create(RootPath)
- ' 加载支持库
- Call Include(RootPath & "library\Rtl_Base.vbs")
- Call Include(RootPath & "library\Rtl_xdb.vbs")
- Call Include(RootPath & "library\Rtl_Win32SDK.vbs")
- Call Include(RootPath & "library\Rtl_xTask_lib.vbs")
- Call Include(RootPath & "library\Rtl_Ext.vbs")
- Call Include(RootPath & "library\SysRtl_WorkType.vbs")
- Call Include(RootPath & "library\SysRtl_Frame.vbs")
- Call Include(RootPath & "library\SysRtl_Project.vbs")
- Call Include(RootPath & "library\SysRtl_Task.vbs")
- Call Include(RootPath & "library\SysRtl_RT.vbs")
- Call Include(RootPath & "library\SysRtl_ImportUser.vbs")
- Call Include(RootPath & "library\SysRtl_ExportUser.vbs")
- End Sub
复制代码它又调用了一大堆 include 函数,这东西实际上就是上一个函数的翻版,只不过可以自定义导入的文件了,它长这样:
- ' Include
- Sub Include(ByVal Path)
- Call ExecuteGlobal(ReadFile(Path))
- End Sub
复制代码好嘞,也就是说,这边一行代码,实际上整个library目录下的VBS文件,基本都进来了,这些 vbs 也有个几千行代码了,这也是xTask为什么源代码如此少的原因。
当然,到这一步我们还没有调用任何东西,所以不用太担心它的复杂度。
【创建全局Atom对象】 这部分代码就是把我们全局变量里定义的那些 _Ptr 的数据给创建出来,它调用了 CreateAtom 函数,代码很简单,长这样:
- // 创建全局Atom对象
- Sub CreateAtom()
- TracePrint "xTask : ☆ 创建全局Atom对象 ..."
- Set G_Atom_Global = xVarDict()
- Set G_Atom_Thread = xVarList()
- Set G_Atom_Task = xVarList()
- Set G_Atom_Proj = xVarList()
- G_Atom_Global_Ptr = G_Atom_Global.ObjPtr
- G_Atom_Thread_Ptr = G_Atom_Thread.ObjPtr
- G_Atom_Task_Ptr = G_Atom_Task.ObjPtr
- G_Atom_Proj_Ptr = G_Atom_Proj.ObjPtr
- G_Lock_Thread = xTask.CreateLock()
- TracePrint "xTask : > 创建全局字典句柄 " & G_Atom_Global_Ptr
- TracePrint "xTask : > 创建线程列表句柄 " & G_Atom_Thread_Ptr
- TracePrint "xTask : > 创建任务列表句柄 " & G_Atom_Task_Ptr
- TracePrint "xTask : > 创建项目列表句柄 " & G_Atom_Proj_Ptr
- TracePrint "xTask : > 创建线程锁句柄 " & G_Lock_Thread
- End Sub
复制代码其中 G_Atom_Global 创建为字典,G_Atom_Thread、G_Atom_Task 和 G_Atom_Proj 创建为数组,创建完了之后,把他们的指针保存起来备用,然后创建全局线程锁对象。
这里用到了 xVarDict 和 xVarList 函数,我们在源代码里搜索找不到,实际上这俩函数在 library 目录的 Rtl_xTask_lib.vbs 文件内,它和它的兄弟们长这样:
- Function xVarInt(v)
- Set xVarInt = New xAtom_Base
- Call xVarInt.Create(xTask_Atom_Int)
- xVarInt.Value = v
- End Function
- Function xVarDbl(v)
- Set xVarDbl = New xAtom_Base
- Call xVarDbl.Create(xTask_Atom_Double)
- xVarDbl.Value = v
- End Function
- Function xVarStr(v)
- Set xVarStr = New xAtom_Base
- Call xVarStr.Create(xTask_Atom_Buffer)
- xVarStr.Value = v
- End Function
- Function xVarList()
- Set xVarList = New xAtom_List
- Call xVarList.Create(xTask_Atom_List)
- End Function
- Function xVarDict()
- Set xVarDict = New xAtom_List
- Call xVarDict.Create(xTask_Atom_Dict)
- End Function
复制代码xAtom 相关的功能都写在这个文件里,这是一个我定义的类,VBS支持类的特性,还支持 default 调用,封装一下用起来很爽。
xAtom 这个名字取意原子,就是说这个类下面的所有操作都自带原子特性,不管是一个最简单的数值,还是字符串,还是结构化数据,都带线程锁,多线程同时读写小意思,不会出问题。
是为了 xTask 专门开发的库,感兴趣的兄弟可以看一下源代码,不感兴趣的兄弟,会用就行了,关于这个类的用法,我整理了帮助文档,在 二次开发资料 目录下的 xTask Library 帮助文档.chm 里。
【系统初始化】 它调用了 xTask_System_Init 函数,在按键里是搜不到这个函数的,实际上这个函数在 library 的 System_Task.vbs 里,长这样:
- ' 系统初始化 [环境已初始化]
- Sub xTask_System_Init(RootPath)
- Dim OptFile : OptFile = RootPath & "option.ini"
- ' 初始化运行环境目录
- Call GetWorkPath(RootPath)
- ' 初始化全局数据
- G_Atom_Global("RT_Start") = 0
- ' 加载配置
- G_Atom_Global("LifeMode") = xTask.IniReadInt(OptFile, "life", "mode")
- G_Atom_Global("LifeDay2") = xTask.IniReadInt(OptFile, "life", "day2")
- G_Atom_Global("LifeDay3") = xTask.IniReadInt(OptFile, "life", "day3")
- G_Atom_Global("LifeDay4") = xTask.IniReadInt(OptFile, "life", "day4")
- G_Atom_Global("LifeDay5") = xTask.IniReadInt(OptFile, "life", "day5")
- G_Atom_Global("LifeDay6") = xTask.IniReadInt(OptFile, "life", "day6")
- G_Atom_Global("LifeDay7") = xTask.IniReadInt(OptFile, "life", "day7")
- G_Atom_Global("LifeDayX") = xTask.IniReadInt(OptFile, "life", "dayx")
- End Sub
复制代码也就是读一些配置,没什么稀奇的,这里可以注意一下,G_Atom_Global 和我们按键里定义的 G_Atom_Global_Ptr 是一个意思,它其实是一个类,但是定义了 default 特性,而 default 定义给了一个属性,所以可以这么用,等价于这种写法:G_Atom_Global.List("RT_Start", 0)
但是这个写法看起来好像在访问一个 Table 一样,很优雅,这也是为啥这些功能我要用 VBS 实现的原因。
【设置运行停止热键】 这里没什么稀奇的了,就是读配置,然后设置热键。
【加载任务计划选项】读配置,改界面。
【刷新项目列表】调用了 ScanProjList 函数,长这样:
- // 扫描项目列表
- Function ScanProjList()
- TracePrint "xTask : ☆ 开始扫描项目列表 ..."
- Call xProj_LoadAll()
- // 将项目添加到列表中
- If G_Atom_Proj.Count > 0 Then
- Form1.Con_ProjList.RowCount = G_Atom_Proj.Count + 1
- Dim i : For i = 1 To G_Atom_Proj.Count : Form1.Con_ProjList.SetItemText i, 0, G_Atom_Proj(i)("Title") : Next
- Else
- Form1.Con_ProjList.RowCount = 2 : Form1.Con_ProjList.SetItemText 1, 0, "没有任何项目" : Form1.Con_ProjList.Enabled = False
- End If
- Form1.Con_TaskProj.List = xProj_MakeList()
- Form1.Con_TaskProj.ListIndex = 0
- // 取消项目选择
- Form1.Con_ProjList.SetSelectedRange -1, -1, -1, -1 : G_Index_Proj = 0 : Form1.Proj_NULL.Visible = true
- End Function
复制代码这里调用了 xProj_LoadAll 函数,这个函数保存在 library 目录下的 SysRtl_Project.vbs 里,长这样:
- ' 加载所有项目
- Function xProj_LoadAll()
- Dim fso, fld, sfld, ProjItem
- ' 先卸载项目
- Call xProj_FreeAll()
- ' 扫描项目文件夹
- Set fso = CreateObject("Scripting.FileSystemObject")
- Set fld = fso.GetFolder(G_Atom_Global("Path_Project"))
- For Each sfld In fld.SubFolders
- Set ProjItem = xVarDict()
- If xProj_Load(sfld.Path, ProjItem.ObjPtr) Then
- Call G_Atom_Proj.ListAppend(ProjItem)
- End If
- Set ProjItem = Nothing
- Next
- End Function
复制代码原理也很简单,就是 fso 对象扫文件夹,然后扫到的所有子文件夹调用 xProj_Load 函数加载,这个函数也在同一个文件里,长这样:
- ' 项目加载器
- Function xProj_Load(ByVal sPath, ByVal ObjPtr)
- Dim sName, sType
- sName = xTask.IniReadStr(sPath & "\setup.ini", "setup", "name")
- sType = xTask.IniReadStr(sPath & "\setup.ini", "setup", "type")
- If sName <> "" Then
- Select Case LCase(sType)
- Case "vbs"
- xProj_Load = xProj_Load_VBS(sPath, ObjPtr, sName)
- Case "dll"
- xProj_Load = xProj_Load_DLL(sPath, ObjPtr, sName)
- End Select
- End If
- End Function
复制代码xTask 允许不同编程语言来实现不同的工程,即可以用VBS,也可以用能写DLL的语言做个DLL,或者你自己高兴的话,在这里写一个加载器,也可以用你写的语言来弄,大同小异了,这只是一个路由函数,最终的活其实是 xProj_Load_VBS 干的,它也在这个文件里:
- ' 加载VBS项目
- Function xProj_Load_VBS(ByVal sPath, ByVal ObjPtr, ByVal sName)
- If ObjPtr Then
- Dim ProjItem, ProjProc
- Set ProjItem = xVarAtom(ObjPtr)
- ' 设置项目属性
- ProjItem("Path") = sPath
- ProjItem("Info") = sPath & "\setup.ini"
- ProjItem("Type") = "xTask_VBS"
- ProjItem("PCode") = xPathToFullPath(xTask.IniReadStr(sPath & "\setup.ini", "project", "pcode"), sPath)
- ProjItem("SCode") = xPathToFullPath(xTask.IniReadStr(sPath & "\setup.ini", "project", "scode"), sPath)
- ProjItem("Read") = xPathToFullPath(xTask.IniReadStr(sPath & "\setup.ini", "project", "readme"), sPath)
- ProjItem("Data") = xPathToFullPath(xTask.IniReadStr(sPath & "\setup.ini", "project", "database"), sPath)
- ProjItem("Only") = GetPathFile(sPath)
- ProjItem("Name") = sName
- ProjItem("Title") = sName & " (" & ProjItem("Only") & ")"
- ProjItem("TimeOut") = xTask.IniReadInt(sPath & "\setup.ini", "setup", "timeout")
- ProjItem("UseDB") = xTask.IniReadInt(sPath & "\setup.ini", "setup", "usedb")
- ProjItem("UseGuard") = xTask.IniReadInt(sPath & "\setup.ini", "setup", "useguard")
- ProjItem("UseStart") = xTask.IniReadInt(sPath & "\setup.ini", "setup", "usestart")
- ProjItem("UseLoad") = xTask.IniReadInt(sPath & "\setup.ini", "setup", "useload")
- ProjItem("Vars") = xTask.IniReadStr(sPath & "\setup.ini", "setup", "vars")
- ProjItem("LifeMode") = xTask.IniReadInt(sPath & "\setup.ini", "life" , "mode")
- ProjItem("LifeDay2") = xTask.IniReadInt(sPath & "\setup.ini", "life" , "day2")
- ProjItem("LifeDay3") = xTask.IniReadInt(sPath & "\setup.ini", "life" , "day3")
- ProjItem("LifeDay4") = xTask.IniReadInt(sPath & "\setup.ini", "life" , "day4")
- ProjItem("LifeDay5") = xTask.IniReadInt(sPath & "\setup.ini", "life" , "day5")
- ProjItem("LifeDay6") = xTask.IniReadInt(sPath & "\setup.ini", "life" , "day6")
- ProjItem("LifeDay7") = xTask.IniReadInt(sPath & "\setup.ini", "life" , "day7")
- ProjItem("LifeDayX") = xTask.IniReadInt(sPath & "\setup.ini", "life" , "dayx")
- ' 读取项目代码 (System)
- Set ProjProc = xVarDict()
- Set Matches = RegexFindEx("xTask_DefProc ([^:]+):[\r\n]+(.*)[\r\n]+xTask_EndProc", ReadFile(ProjItem("SCode")))
- For Each Match In Matches
- ProjProc(Match.SubMatches(0)) = Match.SubMatches(1)
- Next
- ProjProc("Sys_Work") = ReadFile(ProjItem("PCode"))
- ProjItem("Proc") = ProjProc
- Set ProjProc = Nothing
- ' 触发加载事件
- If ProjItem("UseLoad") <> 0 Then
- Call xProj_CallProc(ProjItem.ObjPtr, "Sys_Init")
- End If
- Set ProjItem = Nothing
- xProj_Load_VBS = true
- End If
- End Function
复制代码代码并不复杂把,就是读数据,填数据而已,很多朋友可能会好奇,我下载的 xTask 开发包为啥一运行起来就弹个对话框呢?有问题吗?其实没问题,就是在这里弹的,这个工程加载器肯定不会随便弹个窗口出来,但是他加载的工程会呀,你看他是会读取项目代码的,然后运行!
运行的是 Sys_Init 函数,通过 xProj_CallProc 来调用,这个函数就是用来调用工程内指定函数的,代码非常简单:
- ' 运行项目过程 (线程中自动添加日志)
- Function xProj_CallProc(ByVal Ptr, ByVal k)
- Dim ProjItem : Set ProjItem = xVarAtom(Ptr)
- Dim ProcCode : ProcCode = ProjItem("Proc")(k)
- If VarType(ProcCode) = 8 Then
- If xTask_WorkThread Then
- Call OutLog("开始执行项目脚本 (" & k & ")。", 1, 0)
- End If
- Call ExecuteGlobal(ProcCode)
- xProj_CallProc = true
- Else
- If xTask_WorkThread Then
- Call OutLog("项目脚本执行过程 " & k & " 失败,数据不存在或不是可执行的代码。", 3, 0)
- End If
- End If
- Set ProjItem = Nothing
- End Function
- Function xProj_CallProcIdx(ByVal Idx, ByVal k)
- xProj_CallProcIdx = xProj_CallProc(G_Atom_Proj(Idx).ObjPtr, k)
- End Function
- Function xProj_CallThreadProc(ByVal k)
- xProj_CallThreadProc = xProj_CallProc(xTask_Proj.ObjPtr, k)
- End Function
复制代码这一段代码有点长征的意味了把,一层套着一层的,但实际追溯下来,其实也不算复杂,就是把目录扫描一遍,把配置文件都给读出来,存在我们自己的结构化数据里面,然后脚本加载上,调用一下 Sys_Init 函数,说简单点,就是我们用按键精灵又做了一个按键精灵,我们自己的 xTask 当然也有按键精灵的启动事件咯。
到这里我先休息一下,再次强调一个问题不知道你发现了没有,当我们对数据的掌控达到这样的程度之后,似乎一切东西管理起来,也就那么回事了。
所以说呀,数据,数据,数据,数据才是编程的核心,别特么学几条别人封装好的指令觉得自己是大神了到处扯淡,很幼稚。
好好的,把数据管理的基本功给练好了先。
休息完毕,接着说代码【刷新任务列表】 这里和上一个部分大同小异了,从几个文件跳来跳去的,不过这个比项目可简单多了,我觉得你能读懂项目加载那部分,那这里也不成问题:
- // 加载任务数据
- Function LoadTaskList()
- TracePrint "xTask : ☆ 开始加载任务列表 ..."
- Call xTask_LoadAll()
- Call RefreshtTaskGrid()
- End Function
复制代码调用了 xTask_LoadAll 加载任务列表,调用了 RefreshtTaskGrid 刷新QUI,刷新QUI的代码就在 xTask 按键工程里,这里看一下 xTask_LoadAll 吧,这个代码在 library 的 SysRtl_Task.vbs 里:
- ' 加载所有任务
- Function xTask_LoadAll()
- Dim idx, TaskItem
- ' 先清空旧的任务列表
- G_Atom_Task.Clear
- ' 加载任务数据库
- Dim DB : Set DB = New tz_TextDataBase
- If DB.OpenEx(G_Atom_Global("Path_Work") & "\database\task.xdb", false) Then
- DB.MoveFirst
- Do Until DB.EOF
- idx = xProj_GetIndex("Only", DB("path"))
- If idx Then
- ' 读取任务信息
- Set TaskItem = xVarDict()
- TaskItem("Proj") = idx
- TaskItem("Name") = G_Atom_Proj(idx)("Name")
- TaskItem("Title") = G_Atom_Proj(idx)("Title")
- TaskItem("Only") = G_Atom_Proj(idx)("Only")
- TaskItem("Type") = DB("type")
- TaskItem("STime") = CStr(DB("stime"))
- TaskItem("ETime") = CStr(DB("etime"))
- TaskItem("Count") = DB("count")
- TaskItem("Thread") = DB("thread")
- TaskItem("Param") = DB("param")
- Call G_Atom_Task.ListAppend(TaskItem)
- Set TaskItem = Nothing
- Else
- MsgBox "项目 " & DB("path") & " 已失效,对应的任务将被删除。"
- DB.Delete
- End If
- DB.MoveNext
- Loop
- DB.Update
- Else
- If MsgBox("任务数据库打开失败,错误信息 : " & DB.LastError & VBCRLF & VBCRLF & "是否自动修复(修复后之前的任务配置将消失)?", vbQuestion or vbYesNo) = vbYes Then
- Call xTask_ReBuildDB()
- MsgBox "任务数据库修复完毕,如果错误反复出现,请检查程序文件是否完整!", vbInformation
- Call xTask_LoadAll()
- End If
- End If
- Set DB = Nothing
- End Function
复制代码这里我们创建了 DB 变量,作为 tz_TextDataBase 类的实例,tz_TextDataBase 类是一种文本数据库,性能很低,但是用起来比较方便,我闲着没啥事的时候写的。
代码就在同目录的 Rtl_xdb.vbs 里,感兴趣的话就研究一下吧,为啥不用 Access?因为这玩意有些系统破坏了驱动,运行不起来,我可不希望我写的破玩意还挑系统。
为啥不用 SQLite3?因为 SQLite3 想用 VBS 调用的话,得安装一个 ODBC 驱动,我可不希望我写的破玩意还得安装个别的东西才能用,所以,凑活一下咯,我这个数据库也不是完全没有优点,比如弄字段的时候,就挺方便的,也正是因为这个优点,所以我这个 xTask 框架的灵活性也提升了不少。
剩下的代码就马马虎虎了,都是数据操作,没什么稀奇的,一眼就能看明白,不讲了。
【加载项目类型】 这段代码就是 QUI 操作咯。
【加载导入/导出帐号方案】 也是QUI操作,然后调用ChangeImportUserList函数触发了一个事件,就是列表改变的,不然必须切换一下界面才变,不美观,小细节了。
最后是【刷新全局数据列表 [默认页面]】
可算讲完了,卧槽,累死我了。
这里调用了 RefreshAtomListEvent 函数,这函数很简单,长这样:
- // 递归全局数据
- Function RefreshAtomListEvent(idx)
- Select Case idx
- Case 0
- Form1.Con_List_Grid.RowCount = xRecuAtomCount(G_Atom_Global_Ptr) + 1
- Call RefreshAtomList(G_Atom_Global_Ptr, 1, "G_Atom_Global", "-")
- Form1.Con_List_Global_Label.Caption = " 数据获取时间:" & TimeFmt(Now(), "%y年%MM月%dd日 %hh:%mm:%ss")
- Case 1
- Form1.Con_List_Grid.RowCount = xRecuAtomCount(G_Atom_Proj_Ptr) + 1
- Call RefreshAtomList(G_Atom_Proj_Ptr, 1, "G_Atom_Proj", "-")
- Form1.Con_List_Proj_Label.Caption = " 数据获取时间:" & TimeFmt(Now(), "%y年%MM月%dd日 %hh:%mm:%ss")
- Case 2
- Form1.Con_List_Grid.RowCount = xRecuAtomCount(G_Atom_Task_Ptr) + 1
- Call RefreshAtomList(G_Atom_Task_Ptr, 1, "G_Atom_Task", "-")
- Form1.Con_List_Task_Label.Caption = " 数据获取时间:" & TimeFmt(Now(), "%y年%MM月%dd日 %hh:%mm:%ss")
- Case 3
- Form1.Con_List_Grid.RowCount = xRecuAtomCount(G_Atom_Thread_Ptr) + 1
- Call RefreshAtomList(G_Atom_Thread_Ptr, 1, "G_Atom_Thread", "-")
- Form1.Con_List_Thread_Label.Caption = " 数据获取时间:" & TimeFmt(Now(), "%y年%MM月%dd日 %hh:%mm:%ss")
- End Select
- End Function
复制代码它调用了 RefreshAtomList 函数,就是传入一个 xAtom对象指针,然后把这个 xAtom 的数据遍历完了,显示在表格里,代码长这样:
- // 递归Atom指针内所有对象到全局数据列表
- Function RefreshAtomList(AtomPtr, ListIndex, Path, Index)
- Dim i, AtomObj, AtomType, AtomValue
- Set AtomObj = xVarAtom(AtomPtr)
- AtomType = AtomObj.ObjType()
- // 添加数据到列表
- AtomValue = AtomObj.Value : If Len(AtomValue) > 260 Then AtomValue = Left(AtomValue, 200)
- Form1.Con_List_Grid.SetItemText ListIndex, 0, Path
- Form1.Con_List_Grid.SetItemText ListIndex, 1, Index
- Form1.Con_List_Grid.SetItemText ListIndex, 2, AtomObj.TypeName()
- Form1.Con_List_Grid.SetItemText ListIndex, 3, AtomValue
- Form1.Con_List_Grid.SetItemText ListIndex, 4, AtomObj.GetRef() - 1
- ListIndex = ListIndex + 1
- // 递归
- Select Case AtomType
- Case xTask_Atom_List
- For i = 1 To AtomObj.Count
- ListIndex = RefreshAtomList(AtomObj(i).ObjPtr, ListIndex, Path & "(" & i & ")", i)
- Next
- Case xTask_Atom_Dict
- For i = 1 To AtomObj.Count
- ListIndex = RefreshAtomList(AtomObj(i).ObjPtr, ListIndex, Path & "(""" & AtomObj.GetDictName(i) & """)", i)
- Next
- End Select
- RefreshAtomList = ListIndex
- Set AtomObj = Nothing
- End Function
复制代码运行以后的效果长这样:
于是我们就可以开开心心的查看整个框架里所有的数据啦!
路径列就是对应的数据访问方法(直接写在代码里就行)
index 列是说这个数据相对于它的父节点的索引
数据类型就是数据类型了,字符串在这里叫做 Buffer,因为内部实现就是自增长缓冲区,速度还挺快的。
数据内容就是它的实际值,然后引用计数,是用于自动释放的机制,这个数据引用一下就 +1,释放一次就 -1,懂引用计数机制的人应该知道啥意思,不懂耶没影响,不管他就是了。
一个程序的窗口加载就讲了这么多,下一小节我们接着讲吧,先讲讲 library 目录内的这一大堆文件是怎么分类的,在见了您内~