• 按键公众号 :
按键精灵电脑版
立即下载

软件版本:2014.06
软件大小:22.9M
更新时间:2021-12-03

按键精灵安卓版
立即下载

软件版本:3.7.2
软件大小:46.2M
更新时间:2023-05-10

按键精灵iOS版
立即下载

软件版本:1.8.0
软件大小:29.2M
更新时间:2023-03-21

按键手机助手
立即下载

软件版本:3.8.0
软件大小:262M
更新时间:2023-05-30

快捷导航

登录 后使用快捷导航
没有帐号? 注册

发新话题 回复该主题

[笨蛋熊] 【源码解析】从零开始教你开发自己的脚本框架(二) [复制链接]

1#
上一节我们讲了整个数据的结构设计,并通过数据可视化的步骤,把数据变成了我们展示的界面。
这就体现了我之前一直强调的,编程的本质,就是处理数据,所有程序都是为了处理数据而生的,计算机也是为了这个目的被制造出来的,我们写代码,核心一定要围绕着数据来,其他东西,都是给数据管理打辅助的。



接下来让我们把视角放在源代码视图:



这里源代码只有1000多行,和很多高阶的作者比,这个代码量绝对算是小儿科了。

当然,这并不是 xTask 全部的源代码,因为按键精灵2014的代码编辑器很卡,代码量一旦上了几千行之后,敲代码都卡卡的,所以我一般在这里写代码的时候,都会刻意控制代码数量。



为了方便大家阅读和改写,我当初在开源时已经对代码做好了分类和注释,在代码中,可以看到很多这样的注释:





这些注释把1000多行代码拆成了10个功能块,每个功能块的所有函数都只干一件事情:



程序加载初始化代码:

窗口加载时执行的代码,它会初始化环境,创建数据模型,引入基础运行库,然后加载存储在数据库中的 task 列表,以及调用项目管理的功能,扫描 project 目录下的工程,将工程添加到列表。



项目管理相关功能:

负责扫描项目列表,将列表更新到界面上,以及事件响应(切换项目时的界面动作)



项目 七日留存 相关功能:

就是控制项目注册的角色,7天时间内不断流失注册用户的数据模拟功能,这部分和原始需求有关,反倒不需要大家去深入理解。

这一部分主要负责的也是界面的响应。



项目 数据库管理 相关功能:

项目数据库管理界面相关的功能响应。



帐号导入导出 接口代码:

账号导入和账号导出两个界面的功能响应。



任务管理相关功能:

任务计划界面的事件响应,也负责一些基本逻辑的实现,比如创建、删除、修改任务等。



程序设置 热键设置 相关功能:

比较简单的设置界面,可以像按键一样按某个键启动和结束计划任务。



任务监控界面和整体调度功能:

这是比较核心的功能,任务的具体执行从这里开始,任务界面的响应代码也都在这里,之后我们会详细讲解。



任务线程创建:

也是比较核心的功能,任务的每一个线程都从这里开始创建,WorkThread 函数是框架内所有任务子线程的统一入口。



全局数据监控:

这里的代码用于刷新全局数据界面,来把与框架运行有关的所有核心数据都给展示出来。





全局数据:

代码看完了,接下来我们看全局数据部分,我们的框架只使用了非常少的变量,因为我们又折叠数据成结构的方法,这个一会再讲,全局变量的定义如下:
  1. // 全局 xAtom 对象指针
  2. Dimenv G_Atom_Global_Ptr, G_Atom_Thread_Ptr, G_Atom_Task_Ptr, G_Atom_Proj_Ptr, G_Atom_Context_Ptr

  3. // 全局数据
  4. 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实现(所以解析这东西的源代码,也是希望能给大家带来一些有趣的玩意,这里用到的不少东西,都是值得单独拿出来玩的):
  1. Declare Sub Locking lib "xTask.dll" (m As Long)
  2. Declare Sub UnLocking lib "xTask.dll" (m As Long)
复制代码
到这里代码就分类的差不多了,接下来我们详细讲解一下,当小精灵启动的瞬间,我们的框架都干了些什么。



窗口创建事件代码解析:

代码如下:
  1. // 窗口加载事件
  2. Event Form1.Load
  3.     // 重置全局数据
  4.     G_Index_Proj = 0
  5.     G_Index_Task = 0
  6.     // 重定向工作目录
  7.     TracePrint "xTask : ☆ 重定向工作目录 ..."
  8.     Dim RootPath
  9.     RootPath = Plugin.File.ReadINI("setup", "work", GetExeDir() & "option.ini")
  10.     RootPath = Replace(RootPath, "{$root}", GetExeDir())                // 重定向工作目录 [支持调试环境]
  11.     G_Path_Root = RootPath
  12.     // 注册运行库
  13.     Call RegeditRtl(RootPath)
  14.     // 创建系统运行环境(加载各种支持库)
  15.     Call LoadLibrary(RootPath)
  16.     // 创建全局Atom对象
  17.     Call CreateAtom()
  18.     // 系统初始化
  19.     Call xTask_System_Init(RootPath)
  20.     // 设置运行停止热键
  21.     Call SetHotKey(RootPath)
  22.     // 加载任务计划选项
  23.     Form1.Con_Task_StepNext.Value = xTask.IniReadInt(RootPath & "option.ini", "option", "StepNext")
  24.     Form1.Con_Task_AutoStop.Value = xTask.IniReadInt(RootPath & "option.ini", "option", "AutoStop")
  25.     Form1.Con_Task_AutoLoop.Value = xTask.IniReadInt(RootPath & "option.ini", "option", "AutoLoop")
  26.     G_Atom_Global("StepNext")    = Form1.Con_Task_StepNext.Value
  27.     G_Atom_Global("AutoStop")    = Form1.Con_Task_AutoStop.Value
  28.     G_Atom_Global("AutoLoop")    = Form1.Con_Task_AutoLoop.Value
  29.     // 刷新项目列表
  30.     Call ScanProjList()
  31.     // 刷新任务列表
  32.     Call LoadTaskList()
  33.     // 加载项目类型
  34.     Form1.Con_TaskType.List = GetTaskTypeList()
  35.     Form1.Con_TaskType.ListIndex = 0
  36.     // 加载导入/导出帐号方案
  37.     Form1.Con_Import_List.List = GetImportUserList()
  38.     Form1.Con_Import_List.ListIndex = 0
  39.     Call ChangeImportUserList(0)
  40.     Form1.Con_Export_List.List = GetExportUserList()
  41.     Form1.Con_Export_List.ListIndex = 0
  42.     Call ChangeExportUserList(0)
  43.     // 刷新全局数据列表 [默认页面]
  44.     Call RefreshAtomListEvent(0)
  45. End Event
复制代码
嗯,代码量并不多,而且注释很丰富,应该不难理解吧?

【重置全局数据】 就是把全局数据都给清0了,避免QUI读取数据设置界面属性的时候出现误判,任何情况下,我们都应该养成这样的好习惯,很多时候BUG就是这么来的。

【重定向工作目录】 读取 option.ini 的 setup 小节的 work 数据,然后把 {$root} 替换成程序所在目录。
这是为了啥?因为我做的这个东西,正常来说它是做成小精灵单独运行的,但我在开发它的时候经常需要在按键精灵环境下运行,如果我要在两个地方都运行,那数据就得整两份,我不想这样,所以加入了这个根目录重定向功能,可以在按键精灵环境下,把根目录重定向到小精灵的目录里,这样我在两个地方运行脚本,环境都是统一的了。
最后我们计算好的根目录,会赋值给 G_Path_Root,以后我们只会通过这个变量作为我们的工作目录了。

【注册运行库】调用了 RegeditRtl 函数,它的代码是这样子的,就是把大漠插件啊,DWX插件啊什么的都给注册到系统里面,顺便检查一下MD5值,因为有段时间感染型病毒挺多的,我怕把dm.dll 感染了,回头运行的时候出错又找不到原因,小心驶得万年船:
  1. // 注册运行库
  2. Sub RegeditRtl(RootPath)
  3.     TracePrint "xTask : ☆ 注册 DWX 运行库 ..."
  4.     Dim TmpObj, Ver_DM, Ver_DW, Md5_DM, Md5_DW, Md5_XT, RT_MD5_DM, RT_MD5_DW, RT_MD5_XT, OptFile
  5.     // 读取配置
  6.     OptFile    = RootPath & "option.ini"
  7.     Ver_DM    = Plugin.File.ReadINI("check", "dmver", OptFile)
  8.     Ver_DW    = Plugin.File.ReadINI("check", "dwver", OptFile)
  9.     Md5_DM    = Plugin.File.ReadINI("check", "dmmd5", OptFile)
  10.     Md5_DW    = Plugin.File.ReadINI("check", "dwmd5", OptFile)
  11.     Md5_XT    = Plugin.File.ReadINI("check", "xtmd5", OptFile)
  12.     // 检测组件MD5值(避免病毒感染造成的运行问题)
  13.     RT_MD5_DM = Plugin.Encrypt.Md5File(RootPath & "runtime\dm.dll")
  14.     RT_MD5_DW = Plugin.Encrypt.Md5File(RootPath & "runtime\dwx.dll")
  15.     RT_MD5_XT = Plugin.Encrypt.Md5File(RootPath & "xTask.dll")
  16.     If RT_MD5_DM <> Md5_DM Then
  17.         MsgBox "!!!警告!!! 大漠插件被修改过,如果不是自己替换的,意味着您的机器可能存在感染型病毒!", vbCritical
  18.         TracePrint "大漠插件MD5 : " & RT_MD5_DM
  19.     End If
  20.     If RT_MD5_DW <> Md5_DW Then
  21.         MsgBox "!!!警告!!! DWX 插件被修改过,如果不是自己替换的,意味着您的机器可能存在感染型病毒!", vbCritical
  22.         TracePrint "DWX插件MD5 : " & RT_MD5_DW
  23.     End If
  24.     If RT_MD5_XT <> Md5_XT Then
  25.         MsgBox "!!!警告!!! xTask运行库 被修改过,如果不是自己替换的,意味着您的机器可能存在感染型病毒!", vbCritical
  26.         TracePrint "xTask运行库MD5 : " & RT_MD5_XT
  27.     End If
  28.     // 注册大漠插件
  29.     Set TmpObj = CreateObject("dm.dmsoft")
  30.     If TypeName(TmpObj) <> "Idmsoft" Then
  31.         TracePrint "xTask : ☆ 需要注册大漠插件。"
  32.         If xRegSvr32(RootPath & "runtime\dm.dll", 0) = 0 Then
  33.             MsgBox "大漠插件注册失败,程序可能无法正常工作!", vbCritical
  34.         End If
  35.     ElseIf TmpObj.Ver <> Ver_DM Then
  36.         TracePrint "xTask : ☆ 需要重新注册大漠插件。"
  37.         If xRegSvr32(RootPath & "runtime\dm.dll", 2) = 0 Then
  38.             MsgBox "大漠插件注册失败,程序可能无法正常工作!", vbCritical
  39.         End If
  40.     End If
  41.     Set TmpObj = Nothing
  42.     // 注册 DWX
  43.     Set TmpObj = CreateObject("DynamicWrapperX.2")
  44.     If TypeName(TmpObj) <> "Object" Then
  45.         TracePrint "xTask : ☆ 需要注册 DWX 插件。"
  46.         If xRegSvr32(RootPath & "runtime\dwx.dll", 0) = 0 Then
  47.             MsgBox "DWX 插件注册失败,程序可能无法正常工作!", vbCritical
  48.         End If
  49.     ElseIf TmpObj.Version(0) <> Ver_DW Then
  50.         TracePrint "xTask : ☆ 需要重新注册 DWX 插件。"
  51.         If xRegSvr32(RootPath & "runtime\dwx.dll", 2) = 0 Then
  52.             MsgBox "DWX 插件注册失败,程序可能无法正常工作!", vbCritical
  53.         End If
  54.     End If
  55.     Set TmpObj = Nothing
  56. End Sub
复制代码
【创建系统运行环境(加载各种支持库)】 调用了 LoadLibrary 函数,代码很简单,长这样:
  1. // 创建系统运行环境(加载各种支持库)
  2. Sub LoadLibrary(RootPath)
  3.     TracePrint "xTask : ☆ 正在创建系统运行环境 ..."
  4.     Dim fso, fileobj
  5.     Set fso = CreateObject("Scripting.FileSystemObject")
  6.     Set fileobj = fso.OpenTextFile(RootPath & "library\System_Task.vbs", 1, False)
  7.     Call ExecuteGlobal(fileobj.ReadAll)
  8.     Call xTask_System_Create(RootPath)
  9.     Call fileobj.Close()
  10.     Set fileobj = Nothing
  11.     Set fso = Nothing
  12. End Sub
复制代码
这段代码咱需要好好说道说道了,前面不是说这1000多行代码不是全部代码嘛,因为很多代码被我以VBS的形式写在外面了。
这段代码其实就是创建一个 fso 对象,读取 library 目录下的 System_Task.vbs,然后把这个 vbs 在按键环境里运行一下,于是这个文件里的函数,按键就都能用了。
这些做完之后,我还调用了 xTask_System_Create 函数,代码也很简单,它长这样:
  1. ' 系统创建 [环境未初始化]
  2. Sub xTask_System_Create(RootPath)
  3.     ' 加载支持库
  4.     Call Include(RootPath & "library\Rtl_Base.vbs")
  5.     Call Include(RootPath & "library\Rtl_xdb.vbs")
  6.     Call Include(RootPath & "library\Rtl_Win32SDK.vbs")
  7.     Call Include(RootPath & "library\Rtl_xTask_lib.vbs")
  8.     Call Include(RootPath & "library\Rtl_Ext.vbs")
  9.     Call Include(RootPath & "library\SysRtl_WorkType.vbs")
  10.     Call Include(RootPath & "library\SysRtl_Frame.vbs")
  11.     Call Include(RootPath & "library\SysRtl_Project.vbs")
  12.     Call Include(RootPath & "library\SysRtl_Task.vbs")
  13.     Call Include(RootPath & "library\SysRtl_RT.vbs")
  14.     Call Include(RootPath & "library\SysRtl_ImportUser.vbs")
  15.     Call Include(RootPath & "library\SysRtl_ExportUser.vbs")
  16. End Sub
复制代码
它又调用了一大堆 include 函数,这东西实际上就是上一个函数的翻版,只不过可以自定义导入的文件了,它长这样:
  1. ' Include
  2. Sub Include(ByVal Path)
  3.     Call ExecuteGlobal(ReadFile(Path))
  4. End Sub
复制代码
好嘞,也就是说,这边一行代码,实际上整个library目录下的VBS文件,基本都进来了,这些 vbs 也有个几千行代码了,这也是xTask为什么源代码如此少的原因。
当然,到这一步我们还没有调用任何东西,所以不用太担心它的复杂度。

【创建全局Atom对象】 这部分代码就是把我们全局变量里定义的那些 _Ptr 的数据给创建出来,它调用了 CreateAtom 函数,代码很简单,长这样:
  1. // 创建全局Atom对象
  2. Sub CreateAtom()
  3.     TracePrint "xTask : ☆ 创建全局Atom对象 ..."
  4.     Set G_Atom_Global    = xVarDict()
  5.     Set G_Atom_Thread    = xVarList()
  6.     Set G_Atom_Task     = xVarList()
  7.     Set G_Atom_Proj        = xVarList()
  8.     G_Atom_Global_Ptr    = G_Atom_Global.ObjPtr
  9.     G_Atom_Thread_Ptr    = G_Atom_Thread.ObjPtr
  10.     G_Atom_Task_Ptr        = G_Atom_Task.ObjPtr
  11.     G_Atom_Proj_Ptr        = G_Atom_Proj.ObjPtr
  12.     G_Lock_Thread        = xTask.CreateLock()
  13.     TracePrint "xTask : > 创建全局字典句柄 " & G_Atom_Global_Ptr
  14.     TracePrint "xTask : > 创建线程列表句柄 " & G_Atom_Thread_Ptr
  15.     TracePrint "xTask : > 创建任务列表句柄 " & G_Atom_Task_Ptr
  16.     TracePrint "xTask : > 创建项目列表句柄 " & G_Atom_Proj_Ptr
  17.     TracePrint "xTask : > 创建线程锁句柄 " & G_Lock_Thread
  18. End Sub
复制代码
其中 G_Atom_Global 创建为字典,G_Atom_Thread、G_Atom_Task 和 G_Atom_Proj 创建为数组,创建完了之后,把他们的指针保存起来备用,然后创建全局线程锁对象。
这里用到了 xVarDict 和 xVarList 函数,我们在源代码里搜索找不到,实际上这俩函数在 library 目录的 Rtl_xTask_lib.vbs 文件内,它和它的兄弟们长这样:
  1. Function xVarInt(v)
  2.     Set xVarInt = New xAtom_Base
  3.     Call xVarInt.Create(xTask_Atom_Int)
  4.     xVarInt.Value = v
  5. End Function

  6. Function xVarDbl(v)
  7.     Set xVarDbl = New xAtom_Base
  8.     Call xVarDbl.Create(xTask_Atom_Double)
  9.     xVarDbl.Value = v
  10. End Function

  11. Function xVarStr(v)
  12.     Set xVarStr = New xAtom_Base
  13.     Call xVarStr.Create(xTask_Atom_Buffer)
  14.     xVarStr.Value = v
  15. End Function

  16. Function xVarList()
  17.     Set xVarList = New xAtom_List
  18.     Call xVarList.Create(xTask_Atom_List)
  19. End Function

  20. Function xVarDict()
  21.     Set xVarDict = New xAtom_List
  22.     Call xVarDict.Create(xTask_Atom_Dict)
  23. End Function
复制代码
xAtom 相关的功能都写在这个文件里,这是一个我定义的类,VBS支持类的特性,还支持 default 调用,封装一下用起来很爽。
xAtom 这个名字取意原子,就是说这个类下面的所有操作都自带原子特性,不管是一个最简单的数值,还是字符串,还是结构化数据,都带线程锁,多线程同时读写小意思,不会出问题。
是为了 xTask 专门开发的库,感兴趣的兄弟可以看一下源代码,不感兴趣的兄弟,会用就行了,关于这个类的用法,我整理了帮助文档,在 二次开发资料 目录下的 xTask Library 帮助文档.chm 里。

【系统初始化】 它调用了 xTask_System_Init 函数,在按键里是搜不到这个函数的,实际上这个函数在 library 的 System_Task.vbs 里,长这样:
  1. ' 系统初始化 [环境已初始化]
  2. Sub xTask_System_Init(RootPath)
  3.     Dim OptFile : OptFile = RootPath & "option.ini"
  4.     ' 初始化运行环境目录
  5.     Call GetWorkPath(RootPath)
  6.     ' 初始化全局数据
  7.     G_Atom_Global("RT_Start")    = 0
  8.     ' 加载配置
  9.     G_Atom_Global("LifeMode")    = xTask.IniReadInt(OptFile, "life", "mode")
  10.     G_Atom_Global("LifeDay2")    = xTask.IniReadInt(OptFile, "life", "day2")
  11.     G_Atom_Global("LifeDay3")    = xTask.IniReadInt(OptFile, "life", "day3")
  12.     G_Atom_Global("LifeDay4")    = xTask.IniReadInt(OptFile, "life", "day4")
  13.     G_Atom_Global("LifeDay5")    = xTask.IniReadInt(OptFile, "life", "day5")
  14.     G_Atom_Global("LifeDay6")    = xTask.IniReadInt(OptFile, "life", "day6")
  15.     G_Atom_Global("LifeDay7")    = xTask.IniReadInt(OptFile, "life", "day7")
  16.     G_Atom_Global("LifeDayX")    = xTask.IniReadInt(OptFile, "life", "dayx")
  17. End Sub
复制代码
也就是读一些配置,没什么稀奇的,这里可以注意一下,G_Atom_Global 和我们按键里定义的 G_Atom_Global_Ptr 是一个意思,它其实是一个类,但是定义了 default 特性,而 default 定义给了一个属性,所以可以这么用,等价于这种写法:G_Atom_Global.List("RT_Start", 0)
但是这个写法看起来好像在访问一个 Table 一样,很优雅,这也是为啥这些功能我要用 VBS 实现的原因。

【设置运行停止热键】 这里没什么稀奇的了,就是读配置,然后设置热键。

【加载任务计划选项】读配置,改界面。

【刷新项目列表】调用了 ScanProjList 函数,长这样:
  1. // 扫描项目列表
  2. Function ScanProjList()
  3.     TracePrint "xTask : ☆ 开始扫描项目列表 ..."
  4.     Call xProj_LoadAll()
  5.     // 将项目添加到列表中
  6.     If G_Atom_Proj.Count > 0 Then
  7.         Form1.Con_ProjList.RowCount = G_Atom_Proj.Count + 1
  8.         Dim i : For i = 1 To G_Atom_Proj.Count : Form1.Con_ProjList.SetItemText i, 0, G_Atom_Proj(i)("Title") : Next
  9.     Else
  10.         Form1.Con_ProjList.RowCount = 2 : Form1.Con_ProjList.SetItemText 1, 0, "没有任何项目" : Form1.Con_ProjList.Enabled = False
  11.     End If
  12.     Form1.Con_TaskProj.List = xProj_MakeList()
  13.     Form1.Con_TaskProj.ListIndex = 0
  14.     // 取消项目选择
  15.     Form1.Con_ProjList.SetSelectedRange -1, -1, -1, -1 : G_Index_Proj = 0 : Form1.Proj_NULL.Visible = true
  16. End Function
复制代码
这里调用了 xProj_LoadAll 函数,这个函数保存在 library 目录下的 SysRtl_Project.vbs 里,长这样:
  1. ' 加载所有项目
  2. Function xProj_LoadAll()
  3.     Dim fso, fld, sfld, ProjItem
  4.     ' 先卸载项目
  5.     Call xProj_FreeAll()
  6.     ' 扫描项目文件夹
  7.     Set fso = CreateObject("Scripting.FileSystemObject")
  8.     Set fld = fso.GetFolder(G_Atom_Global("Path_Project"))
  9.     For Each sfld In fld.SubFolders
  10.         Set ProjItem = xVarDict()
  11.         If xProj_Load(sfld.Path, ProjItem.ObjPtr) Then
  12.             Call G_Atom_Proj.ListAppend(ProjItem)
  13.         End If
  14.         Set ProjItem = Nothing
  15.     Next
  16. End Function
复制代码
原理也很简单,就是 fso 对象扫文件夹,然后扫到的所有子文件夹调用 xProj_Load 函数加载,这个函数也在同一个文件里,长这样:
  1. ' 项目加载器
  2. Function xProj_Load(ByVal sPath, ByVal ObjPtr)
  3.     Dim sName, sType
  4.     sName = xTask.IniReadStr(sPath & "\setup.ini", "setup", "name")
  5.     sType = xTask.IniReadStr(sPath & "\setup.ini", "setup", "type")
  6.     If sName <> "" Then
  7.         Select Case LCase(sType)
  8.             Case "vbs"
  9.                 xProj_Load = xProj_Load_VBS(sPath, ObjPtr, sName)
  10.             Case "dll"
  11.                 xProj_Load = xProj_Load_DLL(sPath, ObjPtr, sName)
  12.         End Select
  13.     End If
  14. End Function
复制代码
xTask 允许不同编程语言来实现不同的工程,即可以用VBS,也可以用能写DLL的语言做个DLL,或者你自己高兴的话,在这里写一个加载器,也可以用你写的语言来弄,大同小异了,这只是一个路由函数,最终的活其实是 xProj_Load_VBS 干的,它也在这个文件里:
  1. ' 加载VBS项目
  2. Function xProj_Load_VBS(ByVal sPath, ByVal ObjPtr, ByVal sName)
  3.     If ObjPtr Then
  4.         Dim ProjItem, ProjProc
  5.         Set ProjItem = xVarAtom(ObjPtr)
  6.         ' 设置项目属性
  7.         ProjItem("Path")        = sPath
  8.         ProjItem("Info")        = sPath & "\setup.ini"
  9.         ProjItem("Type")        = "xTask_VBS"
  10.         ProjItem("PCode")        = xPathToFullPath(xTask.IniReadStr(sPath & "\setup.ini", "project", "pcode"), sPath)
  11.         ProjItem("SCode")        = xPathToFullPath(xTask.IniReadStr(sPath & "\setup.ini", "project", "scode"), sPath)
  12.         ProjItem("Read")        = xPathToFullPath(xTask.IniReadStr(sPath & "\setup.ini", "project", "readme"), sPath)
  13.         ProjItem("Data")        = xPathToFullPath(xTask.IniReadStr(sPath & "\setup.ini", "project", "database"), sPath)
  14.         ProjItem("Only")        = GetPathFile(sPath)
  15.         ProjItem("Name")        = sName
  16.         ProjItem("Title")        = sName & " (" & ProjItem("Only") & ")"
  17.         ProjItem("TimeOut")        = xTask.IniReadInt(sPath & "\setup.ini", "setup", "timeout")
  18.         ProjItem("UseDB")        = xTask.IniReadInt(sPath & "\setup.ini", "setup", "usedb")
  19.         ProjItem("UseGuard")    = xTask.IniReadInt(sPath & "\setup.ini", "setup", "useguard")
  20.         ProjItem("UseStart")    = xTask.IniReadInt(sPath & "\setup.ini", "setup", "usestart")
  21.         ProjItem("UseLoad")        = xTask.IniReadInt(sPath & "\setup.ini", "setup", "useload")
  22.         ProjItem("Vars")        = xTask.IniReadStr(sPath & "\setup.ini", "setup", "vars")
  23.         ProjItem("LifeMode")    = xTask.IniReadInt(sPath & "\setup.ini", "life" , "mode")
  24.         ProjItem("LifeDay2")    = xTask.IniReadInt(sPath & "\setup.ini", "life" , "day2")
  25.         ProjItem("LifeDay3")    = xTask.IniReadInt(sPath & "\setup.ini", "life" , "day3")
  26.         ProjItem("LifeDay4")    = xTask.IniReadInt(sPath & "\setup.ini", "life" , "day4")
  27.         ProjItem("LifeDay5")    = xTask.IniReadInt(sPath & "\setup.ini", "life" , "day5")
  28.         ProjItem("LifeDay6")    = xTask.IniReadInt(sPath & "\setup.ini", "life" , "day6")
  29.         ProjItem("LifeDay7")    = xTask.IniReadInt(sPath & "\setup.ini", "life" , "day7")
  30.         ProjItem("LifeDayX")    = xTask.IniReadInt(sPath & "\setup.ini", "life" , "dayx")
  31.         ' 读取项目代码 (System)
  32.         Set ProjProc = xVarDict()
  33.         Set Matches = RegexFindEx("xTask_DefProc ([^:]+):[\r\n]+(.*)[\r\n]+xTask_EndProc", ReadFile(ProjItem("SCode")))
  34.         For Each Match In Matches
  35.             ProjProc(Match.SubMatches(0))    = Match.SubMatches(1)
  36.         Next
  37.         ProjProc("Sys_Work")    = ReadFile(ProjItem("PCode"))
  38.         ProjItem("Proc")        = ProjProc
  39.         Set ProjProc = Nothing
  40.         ' 触发加载事件
  41.         If ProjItem("UseLoad") <> 0 Then
  42.             Call xProj_CallProc(ProjItem.ObjPtr, "Sys_Init")
  43.         End If
  44.         Set ProjItem = Nothing
  45.         xProj_Load_VBS = true
  46.     End If
  47. End Function
复制代码
代码并不复杂把,就是读数据,填数据而已,很多朋友可能会好奇,我下载的 xTask 开发包为啥一运行起来就弹个对话框呢?有问题吗?其实没问题,就是在这里弹的,这个工程加载器肯定不会随便弹个窗口出来,但是他加载的工程会呀,你看他是会读取项目代码的,然后运行!
运行的是 Sys_Init 函数,通过 xProj_CallProc 来调用,这个函数就是用来调用工程内指定函数的,代码非常简单:
  1. ' 运行项目过程 (线程中自动添加日志)
  2. Function xProj_CallProc(ByVal Ptr, ByVal k)
  3.     Dim ProjItem : Set ProjItem = xVarAtom(Ptr)
  4.     Dim ProcCode : ProcCode = ProjItem("Proc")(k)
  5.     If VarType(ProcCode) = 8 Then
  6.         If xTask_WorkThread Then
  7.             Call OutLog("开始执行项目脚本 (" & k & ")。", 1, 0)
  8.         End If
  9.         Call ExecuteGlobal(ProcCode)
  10.         xProj_CallProc = true
  11.     Else
  12.         If xTask_WorkThread Then
  13.             Call OutLog("项目脚本执行过程 " & k & " 失败,数据不存在或不是可执行的代码。", 3, 0)
  14.         End If
  15.     End If
  16.     Set ProjItem = Nothing
  17. End Function
  18. Function xProj_CallProcIdx(ByVal Idx, ByVal k)
  19.     xProj_CallProcIdx = xProj_CallProc(G_Atom_Proj(Idx).ObjPtr, k)
  20. End Function
  21. Function xProj_CallThreadProc(ByVal k)
  22.     xProj_CallThreadProc = xProj_CallProc(xTask_Proj.ObjPtr, k)
  23. End Function
复制代码
这一段代码有点长征的意味了把,一层套着一层的,但实际追溯下来,其实也不算复杂,就是把目录扫描一遍,把配置文件都给读出来,存在我们自己的结构化数据里面,然后脚本加载上,调用一下 Sys_Init 函数,说简单点,就是我们用按键精灵又做了一个按键精灵,我们自己的 xTask 当然也有按键精灵的启动事件咯。

到这里我先休息一下,再次强调一个问题

不知道你发现了没有,当我们对数据的掌控达到这样的程度之后,似乎一切东西管理起来,也就那么回事了。
所以说呀,数据,数据,数据,数据才是编程的核心,别特么学几条别人封装好的指令觉得自己是大神了到处扯淡,很幼稚。
好好的,把数据管理的基本功给练好了先。

休息完毕,接着说代码

【刷新任务列表】 这里和上一个部分大同小异了,从几个文件跳来跳去的,不过这个比项目可简单多了,我觉得你能读懂项目加载那部分,那这里也不成问题:
  1. // 加载任务数据
  2. Function LoadTaskList()
  3.     TracePrint "xTask : ☆ 开始加载任务列表 ..."
  4.     Call xTask_LoadAll()
  5.     Call RefreshtTaskGrid()
  6. End Function
复制代码
调用了 xTask_LoadAll 加载任务列表,调用了 RefreshtTaskGrid 刷新QUI,刷新QUI的代码就在 xTask 按键工程里,这里看一下 xTask_LoadAll 吧,这个代码在 library 的 SysRtl_Task.vbs 里:
  1. ' 加载所有任务
  2. Function xTask_LoadAll()
  3.     Dim idx, TaskItem
  4.     ' 先清空旧的任务列表
  5.     G_Atom_Task.Clear
  6.     ' 加载任务数据库
  7.     Dim DB : Set DB = New tz_TextDataBase
  8.     If DB.OpenEx(G_Atom_Global("Path_Work") & "\database\task.xdb", false) Then
  9.         DB.MoveFirst
  10.         Do Until DB.EOF
  11.             idx = xProj_GetIndex("Only", DB("path"))
  12.             If idx Then
  13.                 ' 读取任务信息
  14.                 Set TaskItem = xVarDict()
  15.                 TaskItem("Proj")        = idx
  16.                 TaskItem("Name")        = G_Atom_Proj(idx)("Name")
  17.                 TaskItem("Title")        = G_Atom_Proj(idx)("Title")
  18.                 TaskItem("Only")        = G_Atom_Proj(idx)("Only")
  19.                 TaskItem("Type")        = DB("type")
  20.                 TaskItem("STime")        = CStr(DB("stime"))
  21.                 TaskItem("ETime")        = CStr(DB("etime"))
  22.                 TaskItem("Count")        = DB("count")
  23.                 TaskItem("Thread")        = DB("thread")
  24.                 TaskItem("Param")        = DB("param")
  25.                 Call G_Atom_Task.ListAppend(TaskItem)
  26.                 Set TaskItem = Nothing
  27.             Else
  28.                 MsgBox "项目 " & DB("path") & " 已失效,对应的任务将被删除。"
  29.                 DB.Delete
  30.             End If
  31.             DB.MoveNext
  32.         Loop
  33.         DB.Update
  34.     Else
  35.         If MsgBox("任务数据库打开失败,错误信息 : " & DB.LastError & VBCRLF & VBCRLF & "是否自动修复(修复后之前的任务配置将消失)?", vbQuestion or vbYesNo) = vbYes Then
  36.             Call xTask_ReBuildDB()
  37.             MsgBox "任务数据库修复完毕,如果错误反复出现,请检查程序文件是否完整!", vbInformation
  38.             Call xTask_LoadAll()
  39.         End If
  40.     End If
  41.     Set DB = Nothing
  42. End Function
复制代码
这里我们创建了 DB 变量,作为 tz_TextDataBase 类的实例,tz_TextDataBase 类是一种文本数据库,性能很低,但是用起来比较方便,我闲着没啥事的时候写的。
代码就在同目录的 Rtl_xdb.vbs 里,感兴趣的话就研究一下吧,为啥不用 Access?因为这玩意有些系统破坏了驱动,运行不起来,我可不希望我写的破玩意还挑系统。
为啥不用 SQLite3?因为 SQLite3 想用 VBS 调用的话,得安装一个 ODBC 驱动,我可不希望我写的破玩意还得安装个别的东西才能用,所以,凑活一下咯,我这个数据库也不是完全没有优点,比如弄字段的时候,就挺方便的,也正是因为这个优点,所以我这个 xTask 框架的灵活性也提升了不少。
剩下的代码就马马虎虎了,都是数据操作,没什么稀奇的,一眼就能看明白,不讲了。

【加载项目类型】 这段代码就是 QUI 操作咯。

【加载导入/导出帐号方案】 也是QUI操作,然后调用ChangeImportUserList函数触发了一个事件,就是列表改变的,不然必须切换一下界面才变,不美观,小细节了。

最后是【刷新全局数据列表 [默认页面]】

可算讲完了,卧槽,累死我了。

这里调用了 RefreshAtomListEvent 函数,这函数很简单,长这样:
  1. // 递归全局数据
  2. Function RefreshAtomListEvent(idx)
  3.     Select Case idx
  4.         Case 0
  5.             Form1.Con_List_Grid.RowCount = xRecuAtomCount(G_Atom_Global_Ptr) + 1
  6.             Call RefreshAtomList(G_Atom_Global_Ptr, 1, "G_Atom_Global", "-")
  7.             Form1.Con_List_Global_Label.Caption = " 数据获取时间:" & TimeFmt(Now(), "%y年%MM月%dd日 %hh:%mm:%ss")
  8.         Case 1
  9.             Form1.Con_List_Grid.RowCount = xRecuAtomCount(G_Atom_Proj_Ptr) + 1
  10.             Call RefreshAtomList(G_Atom_Proj_Ptr, 1, "G_Atom_Proj", "-")
  11.             Form1.Con_List_Proj_Label.Caption = " 数据获取时间:" & TimeFmt(Now(), "%y年%MM月%dd日 %hh:%mm:%ss")
  12.         Case 2
  13.             Form1.Con_List_Grid.RowCount = xRecuAtomCount(G_Atom_Task_Ptr) + 1
  14.             Call RefreshAtomList(G_Atom_Task_Ptr, 1, "G_Atom_Task", "-")
  15.             Form1.Con_List_Task_Label.Caption = " 数据获取时间:" & TimeFmt(Now(), "%y年%MM月%dd日 %hh:%mm:%ss")
  16.         Case 3
  17.             Form1.Con_List_Grid.RowCount = xRecuAtomCount(G_Atom_Thread_Ptr) + 1
  18.             Call RefreshAtomList(G_Atom_Thread_Ptr, 1, "G_Atom_Thread", "-")
  19.             Form1.Con_List_Thread_Label.Caption = " 数据获取时间:" & TimeFmt(Now(), "%y年%MM月%dd日 %hh:%mm:%ss")
  20.     End Select
  21. End Function
复制代码
它调用了 RefreshAtomList 函数,就是传入一个 xAtom对象指针,然后把这个 xAtom 的数据遍历完了,显示在表格里,代码长这样:
  1. // 递归Atom指针内所有对象到全局数据列表
  2. Function RefreshAtomList(AtomPtr, ListIndex, Path, Index)
  3.     Dim i, AtomObj, AtomType, AtomValue
  4.     Set AtomObj = xVarAtom(AtomPtr)
  5.     AtomType = AtomObj.ObjType()
  6.     // 添加数据到列表
  7.     AtomValue = AtomObj.Value : If Len(AtomValue) > 260 Then AtomValue = Left(AtomValue, 200)
  8.     Form1.Con_List_Grid.SetItemText ListIndex, 0, Path
  9.     Form1.Con_List_Grid.SetItemText ListIndex, 1, Index
  10.     Form1.Con_List_Grid.SetItemText ListIndex, 2, AtomObj.TypeName()
  11.     Form1.Con_List_Grid.SetItemText ListIndex, 3, AtomValue
  12.     Form1.Con_List_Grid.SetItemText ListIndex, 4, AtomObj.GetRef() - 1
  13.     ListIndex = ListIndex + 1
  14.     // 递归
  15.     Select Case AtomType
  16.         Case xTask_Atom_List
  17.             For i = 1 To AtomObj.Count
  18.                 ListIndex = RefreshAtomList(AtomObj(i).ObjPtr, ListIndex, Path & "(" & i & ")", i)
  19.             Next
  20.         Case xTask_Atom_Dict
  21.             For i = 1 To AtomObj.Count
  22.                 ListIndex = RefreshAtomList(AtomObj(i).ObjPtr, ListIndex, Path & "(""" & AtomObj.GetDictName(i) & """)", i)
  23.             Next
  24.     End Select
  25.     RefreshAtomList = ListIndex
  26.     Set AtomObj = Nothing
  27. End Function
复制代码
运行以后的效果长这样:



于是我们就可以开开心心的查看整个框架里所有的数据啦!

路径列就是对应的数据访问方法(直接写在代码里就行)
index 列是说这个数据相对于它的父节点的索引
数据类型就是数据类型了,字符串在这里叫做 Buffer,因为内部实现就是自增长缓冲区,速度还挺快的。
数据内容就是它的实际值,然后引用计数,是用于自动释放的机制,这个数据引用一下就 +1,释放一次就 -1,懂引用计数机制的人应该知道啥意思,不懂耶没影响,不管他就是了。



一个程序的窗口加载就讲了这么多,下一小节我们接着讲吧,先讲讲 library 目录内的这一大堆文件是怎么分类的,在见了您内~

2#

今天的工作量直接堪比生产队的驴了,顶不住顶不住,手都快打抽筋了,聊妹子都没写教程积极,卧槽,歇一歇歇一歇,过几天再更。

3#

这就是传说中的框架 和 架构师嘛...................................

4#

老大,你把前面说的坑填上

发新话题 回复该主题