本站创建于2006年,博主为Delphi老码农,暗黑忠实粉丝,主要用于学习经验分享
该日志由 samool 发表于 2020-11-18 13:58:00
使用PHPgrace做了一个简单的短网址服务,总共代码不到100行。
首先创建一个4字段表,并配置数据库链接(url用来存原始网址,alias用来存短参数)
CREATE TABLE `url` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`alias` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`addtime` datetime(0) NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP(0),
PRIMARY KEY (`id`) USING BTREE
) ENGINE = MyISAM AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
将以下代码放app\views\index_index.php 视图文件里即可,文末有源码下载。
测试地址:http://m4k.cn/0mbr1K
目标网址:https://mp.weixin.qq.com/s/JFpq9Nnfxj6GcBGNsfx90w
生成短网址接口:
http://xx.cn/getshorturl/?key=4e9f587fc2f0de21&url=http://www.sczwfw.gov.cn/?areaCode=510000000000
返回参数:{"status":"ok","data":"http://xx.cn/nj14GG"}
(将xx.cn改为你的短网址域名)
该日志标签: 表, 目标, 数据库, 文件, 接口, 链接, 代码, 网址, url, 域名, KEY, int, varchar, 参数, NULL
该日志由 samool 发表于 2020-09-27 14:35:04
作者:ivansli,腾讯 IEG 运营开发工程师
在深入学习 Golang 的 runtime 和标准库实现的时候发现,如果对 Golang 汇编没有一定了解的话,很难深入了解其底层实现机制。在这里整理总结了一份基础的 Golang 汇编入门知识,通过学习之后能够对其底层实现有一定的认识。
平时业务中一直使用 PHP 编写代码,但是一直对 Golang 比较感兴趣,闲暇、周末之余会看一些 Go 底层源码。
近日在分析 go 的某些特性底层功能实现时发现:有些又跟 runtime 运行时有关,而要掌握这一部分的话,有一道坎是绕不过去的,那就是 Go 汇编。索性就查阅了很多大佬们写的资料,在阅读之余整理总结了一下,并在这里分享给大家。
本文使用 Go 版本为 go1.14.1
众所周知,在计算机的世界里,只有 2 种类型。那就是:0 和 1。
计算机工作是由一系列的机器指令进行驱动的,这些指令又是一组二进制数字,其对应计算机的高低电平。而这些机器指令的集合就是机器语言,这些机器语言在最底层是与硬件一一对应的。
显而易见,这样的机器指令有一个致命的缺点:可阅读性太差
(恐怕也只有天才和疯子才有能力把控得了)。
为了解决可读性的问题以及代码编辑的需求,于是就诞生了最接近机器的语言:汇编语言(在我看来,汇编语言更像一种助记符,这些人们容易记住的每一条助记符都映射着一条不容易记住的由 0、1 组成的机器指令。你觉得像不像域名与 IP 地址的关系呢?)。
以 C 语言为例来说,从 hello.c 的源码文件到 hello 可执行文件,经过编译器处理,大致分为几个阶段:
编译器在不同的阶段会做不同的事情,但是有一步是可以确定的,那就是:源码会被编译成汇编,最后才是二进制。
源码经过编译之后,得到一个二进制的可执行 文件
。文件
这两个字也就表明,目前得到的这个文件跟其他文件对比,除了是具有一定的格式(Linux 中是 ELF 格式,即:可运行可链接。executable linkable formate)的二进制组成,并没什么区别。
在 Linux 中文件类型大致分为 7 种:
b: 块设备文件c:字符设备文件d:目录-:普通文件l:链接s:socketp:管道
通过上面可以看到,可执行文件 main 与源码文件 main.go,都是同一种类型,属于普通文件。(当然了,在 Unix 中有一句很经典的话:一切皆文件
)。
那么,问题来了:
维基百科告诉我们:程序
是指一组指示计算机或其他具有消息处理能力设备每一步动作的指令,通常用某种程序设计语言编写,运行于某种目标体系结构上。
从某个层面来看,可以把程序分为静态程序、动态程序:静态程序:单纯的指具有一定格式的可执行二进制文件。动态程序:则是静态可执行程序文件被加载到内存之后的一种运行时模型(又称为进程)。
首先,要知道的是,进程
是分配系统资源的最小单位,线程
(带有时间片的函数)是系统调度的最小单位。进程包含线程,线程所属于进程。
创建进程一般使用 fork 方法(通常会有个拉起程序,先 fork 自身生成一个子进程。然后,在该子进程中通过 exec 函数把对应程序加载进来,进而启动目标进程。当然,实际上要复杂得多),而创建线程则是使用 pthread 线程库。
以 32 位 Linux 操作系统为例,进程经典的虚拟内存结构模型如下图所示:
其中,有两处结构是静态程序所不具有的,那就是 运行时堆(heap)
与 运行时栈(stack)
。
运行时堆
从低地址向高地址增长,申请的内存空间需要程序员自己或者由 GC 释放。运行时栈
从高地址向低地址增长,内存空间在当前栈桢调用结束之后自动释放(并不是清除其所占用内存中数据,而是通过栈顶指针 SP 的移动,来标识哪些内存是正在使用的)。
对于 Go 编译器而言,其输出的结果是一种抽象可移植的汇编代码,这种汇编(Go 的汇编是基于 Plan9 的汇编)并不对应某种真实的硬件架构。Go 的汇编器会使用这种伪汇编,再为目标硬件生成具体的机器指令。
伪汇编
这一个额外层可以带来很多好处,最主要的一点是方便将 Go 移植到新的架构上。
相关的信息可以参考 Rob Pike
的 The Design of the Go Assembler
。
要了解 Go 的汇编器最重要的是要知道 Go 的汇编器不是对底层机器的直接表示,即 Go 的汇编器没有直接使用目标机器的汇编指令。Go 汇编器所用的指令,一部分与目标机器的指令一一对应,而另外一部分则不是。这是因为编译器套件不需要汇编器直接参与常规的编译过程。
相反,编译器使用了一种半抽象的指令集,并且部分指令是在代码生成后才被选择的。汇编器基于这种半抽象的形式工作,所以虽然你看到的是一条 MOV 指令,但是工具链针对对这条指令实际生成可能完全不是一个移动指令,也许会是清除或者加载。也有可能精确的对应目标平台上同名的指令。概括来说,特定于机器的指令会以他们的本尊出现, 然而对于一些通用的操作,如内存的移动以及子程序的调用以及返回通常都做了抽象。细节因架构不同而不一样,我们对这样的不精确性表示歉意,情况并不明确。
汇编器程序的工作是对这样半抽象指令集进行解析并将其转变为可以输入到链接器的指令。
The most important thing to know about Go’s assembler is that it is not a direct representation of the underlying machine. Some of the details map precisely to the machine, but some do not. This is because the compiler suite needs no assembler pass in the usual pipeline. Instead, the compiler operates on a kind of semi-abstract instruction set, and instruction selection occurs partly after code generation. The assembler works on the semi-abstract form, so when you see an instruction like MOV what the toolchain actually generates for that operation might not be a move instruction at all, perhaps a clear or load.
Or it might correspond exactly to the machine instruction with that name. In general, machine-specific operations tend to appear as themselves, while more general concepts like memory move and subroutine call and return are more abstract. The details vary with architecture, and we apologize for the imprecision; the situation is not well-defined.
The assembler program is a way to parse a description of that semi-abstract instruction set and turn it into instructions to be input to the linker.
Go 汇编使用的是 caller-save
模式,被调用函数的入参参数、返回值都由调用者维护、准备。因此,当需要调用一个函数时,需要先将这些工作准备好,才调用下一个函数,另外这些都需要进行内存对齐,对齐的大小是 sizeof(uintptr)。
在深入了解 Go 汇编之前,需要知道的几个概念:
go 汇编中有 4 个核心的伪寄存器,这 4 个寄存器是编译器用来维护上下文、特殊标识等作用的:
寄存器 | 说明 |
---|---|
SB(Static base pointer) | global symbols |
FP(Frame pointer) | arguments and locals |
PC(Program counter) | jumps and branches |
SP(Stack pointer) | top of stack |
symbol+offset(FP)
的方式,引用 callee 函数的入参参数。例如arg0+0(FP),arg1+8(FP)
,使用 FP 必须加 symbol ,否则无法通过编译(从汇编层面来看,symbol 没有什么用,加 symbol 主要是为了提升代码可读性)。另外,需要注意的是:往往在编写 go 汇编代码时,要站在 callee 的角度来看(FP),在 callee 看来,(FP)指向的是 caller 调用 callee 时传递的第一个参数的位置。假如当前的 callee 函数是 add,在 add 的代码中引用 FP,该 FP 指向的位置不在 callee 的 stack frame 之内。而是在 caller 的 stack frame 上,指向调用 add 函数时传递的第一个参数的位置,经常在 callee 中用symbol+offset(FP)
来获取入参的参数值。务必注意
:对于编译输出(go tool compile -S / go tool objdump)的代码来讲,所有的 SP 都是硬件 SP 寄存器,无论是否带 symbol(这一点非常具有迷惑性,需要慢慢理解。往往在分析编译输出的汇编时,看到的就是硬件 SP 寄存器)。通过上面的讲解,想必已经对 4 个核心寄存器的区别有了一定的认识(或者是更加的迷惑、一头雾水)。那么,需要留意的是:如果是在分析编译输出的汇编代码时,要重点看 SP、SB 寄存器(FP 寄存器在这里是看不到的)。如果是,在手写汇编代码,那么要重点看 FP、SP 寄存器。
下图描述了栈桢与各个寄存器的内存关系模型,值得注意的是要站在 callee 的角度来看。
有一点需要注意的是,return addr 也是在 caller 的栈上的,不过往栈上插 return addr 的过程是由 CALL 指令完成的(在分析汇编时,是看不到关于 addr 相关空间信息的。在分配栈空间时,addr 所占用空间大小不包含在栈帧大小内)。
在 AMD64 环境,伪 PC 寄存器其实是 IP 指令计数器寄存器的别名。伪 FP 寄存器对应的是 caller 函数的帧指针,一般用来访问 callee 函数的入参参数和返回值。伪 SP 栈指针对应的是当前 callee 函数栈帧的底部(不包括参数和返回值部分),一般用于定位局部变量。伪 SP 是一个比较特殊的寄存器,因为还存在一个同名的 SP 真寄存器,真 SP 寄存器对应的是栈的顶部。
在编写 Go 汇编时,当需要区分伪寄存器和真寄存器的时候只需要记住一点:伪寄存器一般需要一个标识符和偏移量为前缀,如果没有标识符前缀则是真寄存器。比如(SP)、+8(SP)没有标识符前缀为真 SP 寄存器,而 a(SP)、b+8(SP)有标识符为前缀表示伪寄存器。
我们这里对容易混淆的几点简单进行说明:
在 plan9 汇编里还可以直接使用的 amd64 的通用寄存器,应用代码层面会用到的通用寄存器主要是: rax, rbx, rcx, rdx, rdi, rsi, r8~r15 这些寄存器,虽然 rbp 和 rsp 也可以用,不过 bp 和 sp 会被用来管理栈顶和栈底,最好不要拿来进行运算。
plan9 中使用寄存器不需要带 r 或 e 的前缀,例如 rax,只要写 AX 即可: MOVQ $101, AX = mov rax, 101
下面是通用通用寄存器的名字在 IA64 和 plan9 中的对应关系:
下面列出了常用的几个汇编指令(指令后缀 Q
说明是 64 位上的汇编指令)
助记符 | 指令种类 | 用途 | 示例 |
---|---|---|---|
MOVQ | 传送 | 数据传送 | MOVQ 48, AX // 把 48 传送到 AX |
LEAQ | 传送 | 地址传送 | LEAQ AX, BX // 把 AX 有效地址传送到 BX |
PUSHQ | 传送 | 栈压入 | PUSHQ AX // 将 AX 内容送入栈顶位置 |
POPQ | 传送 | 栈弹出 | POPQ AX // 弹出栈顶数据后修改栈顶指针 |
ADDQ | 运算 | 相加并赋值 | ADDQ BX, AX // 等价于 AX+=BX |
SUBQ | 运算 | 相减并赋值 | SUBQ BX, AX // 等价于 AX-=BX |
CMPQ | 运算 | 比较大小 | CMPQ SI CX // 比较 SI 和 CX 的大小 |
CALL | 转移 | 调用函数 | CALL runtime.printnl(SB) // 发起调用 |
JMP | 转移 | 无条件转移指令 | JMP 0x0185 //无条件转至 0x0185 地址处 |
JLS | 转移 | 条件转移指令 | JLS 0x0185 //左边小于右边,则跳到 0x0185 |
说了那么多,it is code show time。
对于写好的 go 源码,生成对应的 Go 汇编,大概有下面几种
go build -gcflags "-N -l" main.go
生成对应的可执行二进制文件 再使用go tool objdump -s "main\." main
反编译获取对应的汇编反编译时 "main\."
表示只输出 main 包中相关的汇编 "main\.main"
则表示只输出 main 包中 main 方法相关的汇编
go tool compile -S -N -l main.go
这种方式直接输出汇编go build -gcflags="-N -l -S" main.go
直接输出汇编注意:在使用这些命令时,加上对应的 flag,否则某些逻辑会被编译器优化掉,而看不到对应完整的汇编代码
-l 禁止内联 -N 编译时,禁止优化 -S 输出汇编代码
go 示例代码
package main func add(a, b int) int{ sum := 0 // 不设置该局部变量sum,add栈空间大小会是0 sum = a+b return sum} func main(){ println(add(1,2))}
编译 go 源代码,输出汇编
go tool compile -N -l -S main.go
截取主要汇编如下:
"".add STEXT nosplit size=60 args=0x18 locals=0x10 0x0000 00000 (main.go:3) TEXT "".add(SB), NOSPLIT, $16-24 0x0000 00000 (main.go:3) SUBQ $16, SP ;;生成add栈空间 0x0004 00004 (main.go:3) MOVQ BP, 8(SP) 0x0009 00009 (main.go:3) LEAQ 8(SP), BP ;; ...omitted FUNCDATA stuff... 0x000e 00014 (main.go:3) MOVQ $0, "".~r2+40(SP) ;;初始化返回值 0x0017 00023 (main.go:4) MOVQ $0, "".sum(SP) ;;局部变量sum赋为0 0x001f 00031 (main.go:5) MOVQ "".a+24(SP), AX ;;取参数a 0x0024 00036 (main.go:5) ADDQ "".b+32(SP), AX ;;等价于AX=a+b 0x0029 00041 (main.go:5) MOVQ AX, "".sum(SP) ;;赋值局部变量sum 0x002d 00045 (main.go:6) MOVQ AX, "".~r2+40(SP) ;;设置返回值 0x0032 00050 (main.go:6) MOVQ 8(SP), BP 0x0037 00055 (main.go:6) ADDQ $16, SP ;;清除add栈空间 0x003b 00059 (main.go:6) RET ...... "".main STEXT size=107 args=0x0 locals=0x28 0x0000 00000 (main.go:9) TEXT "".main(SB), $40-0 ...... 0x000f 00015 (main.go:9) SUBQ $40, SP ;; 生成main栈空间 0x0013 00019 (main.go:9) MOVQ BP, 32(SP) 0x0018 00024 (main.go:9) LEAQ 32(SP), BP ;; ...omitted FUNCDATA stuff... 0x001d 00029 (main.go:10) MOVQ $1, (SP) ;;add入参:1 0x0025 00037 (main.go:10) MOVQ $2, 8(SP) ;;add入参:2 0x002e 00046 (main.go:10) CALL "".add(SB) ;;调用add函数 0x0033 00051 (main.go:10) MOVQ 16(SP), AX 0x0038 00056 (main.go:10) MOVQ AX, ""..autotmp_0+24(SP) 0x003d 00061 (main.go:10) CALL runtime.printlock(SB) 0x0042 00066 (main.go:10) MOVQ ""..autotmp_0+24(SP), AX 0x0047 00071 (main.go:10) MOVQ AX, (SP) 0x004b 00075 (main.go:10) CALL runtime.printint(SB) 0x0050 00080 (main.go:10) CALL runtime.printnl(SB) 0x0055 00085 (main.go:10) CALL runtime.printunlock(SB) 0x005a 00090 (main.go:11) MOVQ 32(SP), BP 0x005f 00095 (main.go:11) ADDQ $40, SP ;;清除main栈空间 0x0063 00099 (main.go:11) RET ......
这里列举了一个简单的 int 类型 加法
示例,实际开发中会遇到各种参数类型,要复杂的多,这里只是抛砖引玉 :)
针对 4.2 输出汇编,对重要核心代码进行分析。
TEXT "".add(SB), NOSPLIT|ABIInternal, $16-24
TEXT "".add
TEXT 指令声明了 "".add
是 .text 代码段的一部分,并表明跟在这个声明后的是函数的函数体。在链接期,""
这个空字符会被替换为当前的包名: 也就是说,"".add
在链接到二进制文件后会变成 main.add
(SB)
SB 是一个虚拟的伪寄存器,保存静态基地址(static-base) 指针,即我们程序地址空间的开始地址。"".add(SB)
表明我们的符号位于某个固定的相对地址空间起始处的偏移位置 (最终是由链接器计算得到的)。换句话来讲,它有一个直接的绝对地址: 是一个全局的函数符号。
NOSPLIT:
向编译器表明不应该插入 stack-split 的用来检查栈需要扩张的前导指令。在我们 add 函数的这种情况下,编译器自己帮我们插入了这个标记: 它足够聪明地意识到,由于 add 没有任何局部变量且没有它自己的栈帧,所以一定不会超出当前的栈。不然,每次调用函数时,在这里执行栈检查就是完全浪费 CPU 时间了。
$0-16
24 指定了调用方传入的参数+返回值大小(24 字节=入参 a、b 大小 8字节*2
+ 返回值8字节
)> 通常来讲,帧大小后一般都跟随着一个参数大小,用减号分隔。(这不是一个减法操作,只是一种特殊的语法) 帧大小 $24-8 意味着这个函数有 24 个字节的帧以及 8 个字节的参数,位于调用者的帧上。如果 NOSPLIT 没有在 TEXT 中指定,则必须提供参数大小。对于 Go 原型的汇编函数,go vet 会检查参数大小是否正确。
In the general case, the frame size is followed by an argument size, separated by a minus sign. (It’s not a subtraction, just idiosyncratic syntax.) The frame size $24-8 states that the function has a 24-byte frame and is called with 8 bytes of argument, which live on the caller’s frame. If NOSPLIT is not specified for the TEXT, the argument size must be provided. For assembly functions with Go prototypes, go vet will check that the argument size is correct.
SUBQ $16, SP
SP 为栈顶指针,该语句等价于 SP-=16(由于栈空间是向下增长的,所以开辟栈空间时为减操作),表示生成 16 字节大小的栈空间。MOVQ $0, "".~r2+40(SP)
此时的 SP 为 add 函数栈的栈顶指针,40(SP)的位置则是 add 返回值的位置,该位置位于 main 函数栈空间内。该语句设置返回值类型的 0 值,即初始化返回值,防止得到脏数据(返回值类型为 int,int 的 0 值为 0)。MOVQ "".a+24(SP), AX
从 main 函数栈空间获取入参 a 的值,存到寄存器 AXADDQ "".b+32(SP), AX
从 main 函数栈空间获取入参 b 的值,与寄存器 AX 中存储的 a 值相加,结果存到 AX。相当于 AX=a+bMOVQ AX, "".~r2+40(SP)
把 a+b 的结果放到 main 函数栈中, add(a+b)返回值所在的位置ADDQ $16, SP
归还 add 函数占用的栈空间根据 4.2 对应汇编绘制的函数栈桢结构模型
还记得前面提到的,Go 汇编使用的是 caller-save
模式,被调用函数的参数、返回值、栈位置都需要由调用者维护、准备吗?
在函数栈桢结构中可以看到,add()函数的入参以及返回值都由调用者 main()函数维护。也正是因为如此,GO 有了其他语言不具有的,支持多个返回值的特性。
这里重点讲一下函数声明、变量声明。
来看一个典型的 Go 汇编函数定义
// func add(a, b int) int// 该add函数声明定义在同一个 package name 下的任意 .go文件中// 只有函数头,没有实现 // add函数的Go汇编实现// pkgname 默认是 ""TEXT pkgname·add(SB), NOSPLIT, $16-24 MOVQ a+0(FP), AX ADDQ b+8(FP), AX MOVQ AX, ret+16(FP) RET
Go 汇编实现为什么是 TEXT
开头?仔细观察上面的进程内存布局图就会发现,我们的代码在是存储在.text 段中的,这里也就是一种约定俗成的起名方式。实际上在 plan9 中 TEXT 是一个指令,用来定义一个函数。
定义中的 pkgname 是可以省略的,(非想写也可以写上,不过写上 pkgname 的话,在重命名 package 之后还需要改代码,默认为 ""
) 编译器会在链接期自动加上所属的包名称。
中点 ·
比较特殊,是一个 unicode 的中点,该点在 mac 下的输入方法是 option+shift+9。在程序被链接之后,所有的中点 ·
都会被替换为句号 .
,比如你的方法是 runtime·main
,在编译之后的程序里的符号则是 runtime.main
。
简单总结一下, Go 汇编实现函数声明,格式为:
静态基地址(static-base) 指针 | | add函数入参+返回值总大小 | |TEXT pkgname·add(SB),NOSPLIT,$16-24 | | |函数所属包名 函数名 add函数栈帧大小
"".add(SB)
表明我们的符号位于某个固定的相对地址空间起始处的偏移位置 (最终是由链接器计算得到的)。换句话来讲,它有一个直接的绝对地址: 是一个全局的函数符号。汇编里的全局变量,一般是存储在 .rodata
或者 .data
段中。对应到 Go 代码,就是已初始化过的全局的 const、var 变量/常量。
使用 DATA 结合 GLOBL 来定义一个变量。
DATA 的用法为:
DATA symbol+offset(SB)/width, value
大多数参数都是字面意思,不过这个 offset 需要注意:其含义是该值相对于符号 symbol 的偏移,而不是相对于全局某个地址的偏移。
GLOBL 汇编指令用于定义名为 symbol 的全局变量,变量对应的内存宽度为 width,内存宽度部分必须用常量初始化。
GLOBL ·symbol(SB), width
下面是定义了多个变量的例子:
DATA ·age+0(SB)/4, $8 ;; 数值8为 4字节GLOBL ·age(SB), RODATA, $4 DATA ·pi+0(SB)/8, $3.1415926 ;; 数值3.1415926为float64, 8字节GLOBL ·pi(SB), RODATA, $8 DATA ·year+0(SB)/4, $2020 ;; 数值2020为 4字节GLOBL ·year(SB), RODATA, $4 ;; 变量hello 使用2个DATA来定义DATA ·hello+0(SB)/8, $"hello my" ;; `hello my` 共8个字节DATA ·hello+8(SB)/8, $" world" ;; ` world` 共8个字节(3个空格)GLOBL ·hello(SB), RODATA, $16 ;; `hello my world` 共16个字节 DATA ·hello<>+0(SB)/8, $"hello my" ;; `hello my` 共8个字节DATA ·hello<>+8(SB)/8, $" world" ;; ` world` 共8个字节(3个空格)GLOBL ·hello<>(SB), RODATA, $16 ;; `hello my world` 共16个字节
大部分都比较好理解,不过这里引入了新的标记 <>
,这个跟在符号名之后,表示该全局变量只在当前文件中生效,类似于 C 语言中的 static。如果在另外文件中引用该变量的话,会报 relocation target not found 的错误。
在 Go 源码中会看到一些汇编写的代码,这些代码跟其他 go 代码一起组成了整个 go 的底层功能实现。下面,我们通过一个简单的 Go 汇编代码示例来实现两数相加功能。
Go 代码
package main func add(a, b int64) int64 func main(){ println(add(2,3))}
Go 源码中 add()函数只有函数签名,没有具体的实现(使用 GO 汇编实现)
使用 Go 汇编实现的 add()函数
TEXT ·add(SB), $0-24 ;; add栈空间为0,入参+返回值大小=24字节 MOVQ x+0(FP), AX ;; 从main中取参数:2 ADDQ y+8(FP), AX ;; 从main中取参数:3 MOVQ AX, ret+16(FP) ;; 保存结果到返回值 RET
把 Go 源码与 Go 汇编编译到一起(我这里,这两个文件在同一个目录)
go build -gcflags "-N -l" .
我这里目录为 demo1,所以得到可执行程序 demo1,运行得到结果:5
对 5.1 中得到的可执行程序 demo1 使用 objdump 进行反编译,获取汇编代码
go tool objdump -s "main\." demo1
得到汇编
......TEXT main.main(SB) /root/go/src/demo1/main.go main.go:5 0x4581d0 64488b0c25f8ffffff MOVQ FS:0xfffffff8, CX main.go:5 0x4581d9 483b6110 CMPQ 0x10(CX), SP main.go:5 0x4581dd 7655 JBE 0x458234 main.go:5 0x4581df 4883ec28 SUBQ $0x28, SP ;;生成main栈桢 main.go:5 0x4581e3 48896c2420 MOVQ BP, 0x20(SP) main.go:5 0x4581e8 488d6c2420 LEAQ 0x20(SP), BP main.go:6 0x4581ed 48c7042402000000 MOVQ $0x2, 0(SP) ;;参数值 2 main.go:6 0x4581f5 48c744240803000000 MOVQ $0x3, 0x8(SP) ;;参数值 3 main.go:6 0x4581fe e83d000000 CALL main.add(SB);;call add main.go:6 0x458203 488b442410 MOVQ 0x10(SP), AX main.go:6 0x458208 4889442418 MOVQ AX, 0x18(SP) main.go:6 0x45820d e8fe2dfdff CALL runtime.printlock(SB) main.go:6 0x458212 488b442418 MOVQ 0x18(SP), AX main.go:6 0x458217 48890424 MOVQ AX, 0(SP) main.go:6 0x45821b e87035fdff CALL runtime.printint(SB) main.go:6 0x458220 e87b30fdff CALL runtime.printnl(SB) main.go:6 0x458225 e8662efdff CALL runtime.printunlock(SB) main.go:7 0x45822a 488b6c2420 MOVQ 0x20(SP), BP main.go:7 0x45822f 4883c428 ADDQ $0x28, SP main.go:7 0x458233 c3 RET main.go:5 0x458234 e89797ffff CALL runtime.morestack_noctxt(SB) main.go:5 0x458239 eb95 JMP main.main(SB) ;; 反编译得到的汇编与add_amd64.s文件中的汇编大致操作一致TEXT main.add(SB) /root/go/src/demo1/add_amd64.s add_amd64.s:2 0x458240 488b442408 MOVQ 0x8(SP), AX ;; 获取第一个参数 add_amd64.s:3 0x458245 4803442410 ADDQ 0x10(SP), AX ;;参数a+参数b add_amd64.s:5 0x45824a 4889442418 MOVQ AX, 0x18(SP) ;;保存计算结果 add_amd64.s:7 0x45824f c3 RET
通过上面操作,可知:
这里推荐 2 个 Go 代码调试工具。
测试代码
package main type Ier interface{ add(a, b int) int sub(a, b int) int} type data struct{ a, b int} func (*data) add(a, b int) int{ return a+b} func (*data) sub(a, b int) int{ return a-b} func main(){ var t Ier = &data{3,4} println(t.add(1,2)) println(t.sub(3,2))}
编译 go build -gcflags "-N -l" -o main
使用 GDB 调试
> gdb main GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-80.el7Copyright (C) 2013 Free Software Foundation, Inc.License GPLv3+: GNU GPL version 3 or later http://gnu.org/licenses/gpl.htmlThis is free software: you are free to change and redistribute it.There is NO WARRANTY, to the extent permitted by law. Type "show copying"and "show warranty" for details.This GDB was configured as "x86_64-redhat-linux-gnu".For bug reporting instructions, please see:<http://www.gnu.org/software/gdb/bugs/>...Reading symbols from /root/go/src/interface/main...done.Loading Go Runtime support.(gdb) list // 显示源码14 func (*data) add(a, b int) int{15 return a+b16 }1718 func (*data) sub(a, b int) int{19 return a-b20 }212223 func main(){(gdb) list24 var t Ier = &data{3,4}2526 println(t.add(1,2))27 println(t.sub(3,2))28 }29(gdb) b 26 // 在源码26行处设置断点Breakpoint 1 at 0x45827c: file /root/go/src/interface/main.go, line 26.(gdb) rStarting program: /root/go/src/interface/main Breakpoint 1, main.main () at /root/go/src/interface/main.go:2626 println(t.add(1,2))(gdb) info locals // 显示变量t = {tab = 0x487020 <data,main.Ier>, data = 0xc000096000}(gdb) ptype t // 打印t的结构type = struct runtime.iface { runtime.itab *tab; void *data;}(gdb) p *t.tab.inter // 打印t.tab.inter指针指向的数据$2 = {typ = {size = 16, ptrdata = 16, hash = 2491815843, tflag = 7 '\a', align = 8 '\b', fieldAlign = 8 '\b', kind = 20 '\024', equal = {void (void *, void *, bool *)} 0x466ec0, gcdata = 0x484351 "\002\003\004\005\006\a\b\t\n\f\r\016\017\020\022\025\026\030\033\034\036\037\"&(,-5<BUXx\216\231\330\335\377", str = 6568, ptrToThis = 23808}, pkgpath = {bytes = 0x4592b4 ""}, mhdr = []runtime.imethod = {{name = 277, ityp = 48608}, {name = 649, ityp = 48608}}}(gdb) disass // 显示汇编Dump of assembler code for function main.main: 0x0000000000458210 <+0>: mov %fs:0xfffffffffffffff8,%rcx 0x0000000000458219 <+9>: cmp 0x10(%rcx),%rsp 0x000000000045821d <+13>: jbe 0x458324 <main.main+276> 0x0000000000458223 <+19>: sub $0x50,%rsp 0x0000000000458227 <+23>: mov %rbp,0x48(%rsp) 0x000000000045822c <+28>: lea 0x48(%rsp),%rbp 0x0000000000458231 <+33>: lea 0x10dc8(%rip),%rax # 0x469000 0x0000000000458238 <+40>: mov %rax,(%rsp) 0x000000000045823c <+44>: callq 0x40a5c0 <runtime.newobject>
常用的 gdb 调试命令
除了 gdb,另外推荐一款 gdb 的增强版调试工具 cgdb
https://cgdb.github.io/
效果如下图所示,分两个窗口:上面显示源代码,下面是具体的命令行调试界面(跟 gdb 一样):
#### 6.2 delve 调试代码
delve 项目地址
https://github.com/go-delve/delve
带图形化界面的 dlv 项目地址
https://github.com/aarzilli/gdlv
dlv 的安装使用,这里不再做过多讲解,感兴趣的可以尝试一下。
对于 Go 汇编基础大致需要熟悉下面几个方面:
通过上面的例子相信已经让你对 Go 的汇编有了一定的理解。当然,对于大部分业务开发人员来说,只要看的懂即可。如果想进一步的了解,可以阅读相关的资料或者书籍。
最后想说的是:鉴于个人能力有限,在阅读过程中你可能会发现存在的一些问题或者缺陷,欢迎各位大佬指正。如果感兴趣的话,也可以一起私下交流。
8. 参考资料
在整理的过程中,部分参考、引用下面链接地址内容。有一些写的还是不错的,感兴趣的同学可以阅读。
[1] https://github.com/cch123/golang-notes/blob/master/assembly.md plan9 assembly
[2] https://segmentfault.com/a/1190000019753885 汇编入门
[3] https://www.davidwong.fr/goasm/ Go Assembly by Example
[4] https://juejin.im/post/6844904005630443533#heading-3
[5] https://github.com/go-internals-cn/go-internals/blob/master/chapter1_assembly_primer/README.md
[6] https://lrita.github.io/2017/12/12/golang-asm/
[7] https://chai2010.cn/advanced-go-programming-book/ch3-asm/ch3-01-basic.html
该日志标签: sp, 字符, 函数, 消息, 进程, 动作, 腾讯, 工作, 目标, 移动, 目录, 汇编, 系统, 静态, 驱动, 经典, ip, 工具, 错误, 世界, 内容, live, 文件, it, 设计, 开发, 方法, 程序, 运行, 数字, 宽度, 链接, 输入, 代码, 资料, c, 信息, bug, 域名, 问题, 命令, map, 数据, 时间, 保存, 运算, 工程师, 程序员, 环境, file, int, 特性, 细节, 参数, 变量, 指令, unicode, 内存, root, type, 调试, input, o, 运营, 缺点, 时候, 语法, 文档, 语言, main, add, 寄存器
该日志由 samool 发表于 2020-09-22 12:01:45
去http://download.microsoft.com/download/7/0/3/703ffbcb-dc0c-4e19-b0da-1463960fdcdb/AccessDatabaseEngine.exe下载。然后安装就行了。
本错误是由于你使用了ACCESS2007版本建立的数据库,但服务器中没有相配合使用的程序,所以出错.
未在本地计算机上注册“microsoft.ACE.oledb.12.0”提供程序。
说明: 执行当前 Web 请求期间,出现未处理的异常。请检查堆栈跟踪信息,以了解有关该错误以及代码中导致错误的出处的详细信息。
异常详细信息: System.InvalidOperationException: 未在本地计算机上注册“microsoft.ACE.oledb.12.0”提供程序。
下载2007 Office system 驱动程序:数据连接组件安装
此下载将安装一组组件,非 Microsoft Office 应用程序可以使用它们从 2007 Office system 文件中读取数据,
例如从 Microsoft Office Access 2007(mdb 和 accdb)文件以及 Microsoft Office Excel 2007(xls、xlsx 和 xlsb)文件中读取数据。
这些组件还支持与 Microsoft Windows SharePoint Services 和文本文件建立连接。
此外,还会安装 ODBC 和 OLEDB 驱动程序,供应用程序开发人员在开发与 Office 文件格式连接的应用程序时使用。
使用此下载:
如果您是应用程序用户,请查阅应用程序文档以获得有关如何使用相应的驱动程序的详细信息。
如果您是使用 OLEDB 的应用程序开发人员,请将 ConnectionString 属性的 Provider 参数设置为“Microsoft.ACE.OLEDB.12.0”
如果要连接到 Microsoft Office Excel 数据,请将“Excel 12.0”添加到 OLEDB 连接字符串的扩展属性中。
如果您是使用 ODBC 连接到 Microsoft Office Access 数据的应用程序开发人员,
请将连接字符串设置为“Driver={Microsoft Access Driver (.mdb, .accdb)};DBQ=path to mdb/accdb file”
如果您是使用 ODBC 连接到 Microsoft Office Excel 数据的应用程序开发人员,
请将连接字符串设置为“Driver={Microsoft Excel Driver (.xls, .xlsx, .xlsm, .xlsb)};DBQ=path to xls/xlsx/xlsm/xlsb file”
或者:
解决办法1 (验证可以了)
选择 该应用程序的 应用程序池 ------>选择高级设置 --------->启用32位应用程序 ------->true
解决办法2
Microsoft.ACE.OLEDB.12.0是不能再x64上使用的,你要强制把你的web application编译成x86再发布到Win08 x64上,记得在Application pool上设置Enable 32bit Application = true。
(没有验证)
该日志标签: 字符, microsoft, 驱动, 注册, 错误, 数据库, 文件, 开发, 验证, 程序, 代码, 信息, xls, 数据, web, file, 参数, office, bit, 用户, path, 文档, 解决, oledb, ace
该日志由 samool 发表于 2020-09-13 17:14:29
首先要说,这并不是一篇教你如何学习的文章,因为到今天为止我也没有找到一种通用的方法来解决如何学习的问题。但是在探索的道路上,我确实产生过一些思路,我想把这些思考的过程分享出来让大家探讨。如果这对你有帮助的话,那我会非常高兴。
我最近在学习 Rust ,这是一门很酷但是相对冷门的语言(学习冷门语言可能是我的一大爱好,比如我就非常喜欢 CoffeeScript ,这是 JavaScript 的一门方言)。自从在某些语言的舒适区待久之后,经常会产生自己无所不能的错觉,这也是我不断想学习新语言的一大动力。
而我的学习过程跟大多数人类似,就是先在网上找文档然后自学。而在看文档的过程中,我就发现了一些问题。
“去看文档喽。”这是老鸟在面对新手时最喜欢扔的一句话,我通常也是这么扔给自己的。但当你打开一门语言的手册,你会发现它除了教你语法外,几乎没有教你什么其它东西。于是你会陷入这样一个境地:
“背下来了么?”
“背不下来,太难懂了。”
“再看一遍,给我背下来。”
。。。
“背下来了么?”
“大部分背下来了。”
“给我写个程序,实现个XX功能。”
“不会。。。”
“。。。”
在一个高级开发者眼里,你会了语法理所当然就应该会写程序了,但实际情况并非如此。这让我想起前段时间跟一个朋友关于目前编程教育市场的一个讨论。我出于自己的经验提出一个想法:让大牛开发者来教新手入门。在我的设想中,大牛开发者拥有强大的实战经验,以及丰富的专业知识,更是自带光环,这不比现在市场上那些半吊子的讲师好多了。在我为自己“伟大”的想法得意的时候,朋友给我浇了一盆冷水。他告诉我这样根本行不通,大牛根本不知道菜鸟需要什么知识,你可能以为基础语法讲清楚就好了,人家却连编辑器是什么都不清楚。设想一下,让一个大学教授去教一群小学生,这对两者来说都是一种灾难吧。
这些语言的创造者,或者文档的作者,无疑都是一些大神。它们在撰写一个语言手册的过程中,只能尽量负责地把这个语言的全貌准确地有组织地展现给你。然而这种全面的展现,对于一个没有任何引导的初学者来说并不完全是一件好事。简单来说就是,你会在一些次要的事情上浪费太多时间,而一些主要的概念又没有理解透彻。
当觉得文档满足不了你的时候,老鸟们往往会扔给你第二招:看代码。看什么代码呢?那还用说,当然是被业界奉为经典的,在 GitHub 上至少有一万颗星的知名开源项目代码啦。
当你怀着崇敬的心情,打开扔给你的网址,看着满屏幕的代码时,你会对自己问出那三个经典的哲学问题。这是什么东西?它是怎么做到的?为什么要这么写?搞不好某些人还会因为惊吓过度,从而失去了学习下去的信心。
那么让我们一起来看看这些代码里有什么鬼东西,会吓得新手信心不足。
说一件事你们不要笑,在我还是个萌新的时候,我曾经对这些穿插在代码中的神秘文字产生了深深的困惑,我还以为它们对代码有某种加成作用,以至于我还试验过把注释去掉会对代码执行产生什么影响。而现实中好的代码会让后面的维护者方便很多,但不好的甚至错误的注释会让人迷惑不已。
语法糖是个好东西,它大大简化了我们的编程过程,高手用起语法糖写起代码来简直不要太爽,所以越强大的项目这玩意儿越多。但是对于初学者来说,语法糖隐藏了一些细节,而且让语法看起来很怪异。有些代码如果你用标准语法来写是很好懂的,但如果用语法糖来写的话很难让人一下子明白。
初学者为了弄懂这些语法往往要花大量时间,但其实这些时间在这个阶段是没必要的浪费。你看不懂它觉得它是一个很重要的东西,其实它只是一个做工精巧的小玩意儿,离开了它这些代码照样能工作。而随着你的经验丰富,也可以随时随地用起来,用的方法可能也不尽相同。
对于一个开源项目来说,往往 50% 的代码可能都是在适配各种不同的运行环境,将系统的 API 抽象成项目里通用的接口,这部分代码除非你自己要做类似的项目,要不然的话对初学者来说参考意义不大。更何况,为了适配某些系统奇葩的运行环境,开发者往往会大开脑洞,创造出一些非常奇怪的代码。这些旁门左道充满了玄学和不确定性,初学者看多了可能会发生如武侠小说里练功出差错的结果:走火入魔。
剩下的代码里 20% 是项目内部的接口抽象和定义,最后 30% 才是真正值得看的东西,它们往往散落在各个地方,但又为了一个设计核心服务。让初学者识别出这些代码来,未免太强人所难。
这是我自己的一个定义,我把一切非官方的开发文档都称为野文档。初学者会在搜索引擎里得到大量的相关文档,他们很多是学习心得,很多人在写这篇文章时水平可能比你也高不了多少。这就造成了这些文档的水平参差不齐,所面向的重点也不同,也许你花了大把时间弄懂的是一个错误的或者过时的知识。而大部分号称教你入门的文章,可能也就是告诉了你如何搭建一个运行环境,这门语言的精髓和重点作者自己估计也没弄明白。
而如果你碰到一篇被奉为经典的好的入门文章,那你真的要好好感谢作者。因为这意味着作者付出了大量的深入思考,深入浅出这四个字说起来简单,做起来可是需要相当的功底的。
在这么多语言的学习过程中,我也总结了一些相对比较好的学习方法。我认为看代码是非常有必要的,因为光死记语法是无法掌握好这门语言的。但是去看一些大型项目代码的缺点我在上面也说了,到底该如何是好呢?
我建议大家可以去看官方给出的,专门供初学者学习的 Step-By-Step 代码,这种代码一般在官方的文档页就可以找到链接入口,它有如下好处:
我建议大家一边看手册一边看代码,这样印象会更深刻。眼睛看了之后,我认为要尽快找一些好的例子来练手,不需要一上来就搞比较复杂的大型项目,也是由浅入深。这种练手项目去哪里找呢,很多语言的 tutorial 板块就是干这个的,做的比较完善的甚至还提供在线的教学体验环境,大家应该好好利用。
这篇讨论学习方法的文章引发了我的一些额外思考。我经常在开发者社区里看到老鸟和初学者互怼,写这篇文章的过程也让我理性思考了产生这些矛盾的原因。总的来说就是一些信息的不对称造成的。老鸟认为这个问题根本不是问题,网上一搜一大把,不愿多讲甚至冷嘲热讽。而初学者却觉得这些信息根本理解不能,老鸟的嘲讽就是一种羞辱。
我认为要打破这种不对称需要双方付出耐心,而这种耐心的付出是相互的,你付出的多回报的就越多。而最先迈出付出这一步的,应该是初学者,毕竟从情理上来说是你请求人家办事。而你需要付出的不过是把自己的问题讲明白,说出你的思考过程,附上必要的信息。一个好学的人是不会让人讨厌的,但前提是你得有一个不让人讨厌的姿态展现出来。
文章来源:https://joyqi.com/develop/how-to-learn-a-programming-language.html
该日志标签: 升级, 朋友, 工作, 心情, 系统, 来源, 经典, 心得, 搜索引擎, 社区, 设计, 开发, 接口, 学习, 方法, 程序, 运行, 在线, 链接, 代码, 编程, 信息, 网址, 灾难, 问题, 时间, 环境, 特性, 细节, 组织, API, 文章, GitHub, 缺点, 时候, 初学者, 语法, 文档, 语言
该日志由 samool 发表于 2020-09-01 16:39:49
下载地址:https://studygolang.com/dl
本次下载 go1.14.1.linux-amd64.tar.gz
wget https://studygolang.com/dl/golang/go1.14.1.linux-amd64.tar.gz
tar -C /usr/local -xzf go1.14.1.linux-amd64.tar.gz
解压后在目录 /usr/local/go中
设置GOPATH 目录
mkdir -p /home/gocode
go命令依赖一个重要的环境变量:$GOPATH
GOPATH允许多个目录,当有多个目录时,请注意分隔符,多个目录的时候Windows是分号;,Linux系统是冒号:
当有多个GOPATH时默认将go get获取的包存放在第一个目录下
$GOPATH目录约定有三个子目录
src存放源代码(比如:.go .c .h .s等)
pkg编译时生成的中间文件(比如:.a)
bin编译后生成的可执行文件(为了方便,可以把此目录加入到 PATH变量中,如果有多个gopath,那么使用PATH变量中,如果有多个gopath,那么使用{GOPATH//://bin:}/bin添加所有的bin目录)
编辑环境
vim /etc/profile
在最后一行加入 按i插入
export GOROOT=/usr/local/go #设置为go安装的路径
export GOPATH=/home/gocode #默认安装包的路径
export PATH=PATH:GOROOT/bin:$GOPATH/bin
按esc退出插入模式 :wq!强制保存退出
vim命令编辑模式命令如下
:q 不保存退出
:q! 不保存强制退出
:wq 保存退出,w表示写入,不论是否修改,都会更改时间戳
:x 保存退出,如果内容未改,不会更改时间戳
保存后执行 使环境生效
source /etc/profile
该日志标签: 目录, 系统, 内容, 文件, 代码, c, gz, tar, 命令, export, 时间, 保存, 环境, get, 变量, gopath, bin, path
该日志由 samool 发表于 2020-06-27 13:27:10
concat() 连接两个或更多的数组,并返回结果。
join() 把数组的所有元素放入一个字符串。元素通过指定的分隔符进行分隔。
toString() 把数组转换为字符串,并返回结果。
push() 向数组的末尾添加一个或更多元素,并返回新的长度。
unshift() 向数组的开头添加一个或更多元素,并返回新的长度。
reverse() 颠倒数组中元素的顺序。
shift() 删除并返回数组的第一个元素
pop() 删除并返回数组的最后一个元素
splice() 删除元素,并向数组添加新元素。
slice() 从某个已有的数组返回选定的元素
sort() 对数组的元素进行排序
toSource() 返回该对象的源代码。
toLocaleString() 把数组转换为本地数组,并返回结果。
该日志由 samool 发表于 2020-06-19 23:56:00
JS 仿知乎复制文章末尾自动添加字符串
当别人复制你的文章内容时,会在文章末尾自动添加一串字符。
将此代码放置在body之前即可
<!--自动添加版权信息 -->
<script>
$("body").bind('copy', function (e) {
if (typeof window.getSelection == "undefined") return; //IE8 及更老的版本不兼容
var body_element = document.getElementsByTagName('body')[0];
var selection = window.getSelection();
//创建一个DIV的可见区域之外
//并填写选定的文本
var newdiv = document.createElement('div');
newdiv.style.position = 'absolute';
newdiv.style.left = '-99999px';
body_element.appendChild(newdiv);
newdiv.appendChild(selection.getRangeAt(0).cloneContents());
//我们需要<pre>标签解决方案
//其他的文本在<pre>失去了所有的行符!
if (selection.getRangeAt(0).commonAncestorContainer.nodeName == "PRE") {
newdiv.innerHTML = "<pre>" + newdiv.innerHTML + "</pre>";
}
newdiv.innerHTML += "<br /><br />文章来自: 《傻猫日志》 <a href='"
+ document.location.href + "'>"
+ document.location.href + "</a>";
selection.selectAllChildren(newdiv);
window.setTimeout(function () { body_element.removeChild(newdiv); }, 200);
});
</script>
该日志由 samool 发表于 2020-06-19 23:30:06
将以下代码放在网站代码里,可实现页面自动推送,支持https和http
增加网站收录利器啊!使用有效果请留言感觉我!!
<!--百度推送 -->
<script>
(function(){
var bp = document.createElement('script');
var curProtocol = window.location.protocol.split(':')[0];
if (curProtocol === 'https') {
bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';
}
else {
bp.src = 'http://push.zhanzhang.baidu.com/push.js';
}
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(bp, s);
})();
</script>
该日志由 samool 发表于 2017-08-23 16:04:26
CnPack 2009-09-14 SVN 包,包括以下内容:
以下是 cnpack\Source 目录内容说明:
\Common -公共库目录
CnBase64.pas
Base64 编码解码算法单元
CnCalClass.pas
历法计算类库
该日志由 samool 发表于 2015-09-07 13:00:00
{*******************************************************}
{ }
{ Delphi公用函数单元 }
{ }
{ 版权所有 (C) 2008 }
{ }
{*******************************************************}
unit YzDelphiFunc;
interface
uses
ComCtrls, Forms, Windows, Classes, SysUtils, ComObj, ActiveX, ShlObj, Messages,
Graphics, Registry, Dialogs, Controls, uProcess, uCpuUsage, StrUtils, CommCtrl,
jpeg, WinInet, ShellAPI, SHFolder, ADODB, WinSock;
{ 保存日志文件 }
procedure YzWriteLogFile(Msg: String);
{ 延时函数,单位为毫秒 }
procedure YzDelayTime(MSecs: Longint);
{ 判断字符串是否为数字 }
function YzStrIsNum(Str: string):boolean;
{ 判断文件是否正在使用 }
function YzIsFileInUse(fName: string): boolean;
{ 删除字符串列表中的空字符串 }
procedure YzDelEmptyChar(AList: TStringList);
{ 删除文件列表中的"Thumbs.db"文件 }
procedure YzDelThumbsFile(AList: TStrings);
{ 返回一个整数指定位数的带"0"字符串 }
function YzIntToZeroStr(Value, ALength: Integer): string;
{ 取日期年份分量 }
function YzGetYear(Date: TDate): Integer;
<!--more-->
{ 取日期月份分量 }
function YzGetMonth(Date: TDate): Integer;
{ 取日期天数分量 }
function YzGetDay(Date: TDate): Integer;
{ 取时间小时分量 }
function YzGetHour(Time: TTime): Integer;
{ 取时间分钟分量 }
function YzGetMinute(Time: TTime): Integer;
{ 取时间秒钟分量 }
function YzGetSecond(Time: TTime): Integer;
{ 返回时间分量字符串 }
function YzGetTimeStr(ATime: TTime;AFlag: string): string;
{ 返回日期时间字符串 }
function YzGetDateTimeStr(ATime: TTime;ADate: TDate): string;
{ 获取计算机名称 }
function YzGetComputerName(): string;
{ 通过窗体子串查找窗体 }
procedure YzFindSpecWindow(ASubTitle: string);
{ 判断进程CPU占用率 }
procedure YzJudgeCPU(ProcessName: string;CPUUsage: Single);
{ 分割字符串 }
procedure YzSeparateTerms(Source: string;Separator: Char;Terms: TStringList);
{ 切换页面控件的活动页面 }
procedure YzGotoNewPage(AOwerPage: TPageControl;ANewPage: TTabSheet);
{ 设置页面控件标签的可见性 }
procedure YzSetTableVisible(PageControl: TPageControl;ShowFlag: Boolean);
{ 根据产品名称获取产品编号 }
function YzGetLevelCode(AName:string;ProductList: TStringList): string;
{ 取文件的主文件名 }
function YzGetMainFileName(AFileName: string): string;
{ 按下一个键 }
procedure YzPressOneKey(AByteCode: Byte);overload;
{ 按下一个指定次数的键 }
procedure YzPressOneKey(AByteCode: Byte;ATimes: Integer);overload;
{ 按下二个键 }
procedure YzPressTwoKeys(AFirstByteCode, ASecByteCode: Byte);
{ 按下三个键 }
procedure YzPressThreeKeys(AFirstByteCode, ASecByteCode, AThirdByteCode: Byte);
{ 创建桌面快捷方式 }
procedure YzCreateShortCut(const sPath: string; sShortCutName: WideString);
{ 删除桌面快捷方式 }
procedure YzDeleteShortCut(sShortCutName: WideString);
{ 通过光标位置进行鼠标左键单击 }
procedure YzMouseLeftClick(X, Y: Integer);overload;
{ 鼠标左键双击 }
procedure YzMouseDoubleClick(X, Y: Integer);
{ 通过窗口句柄进行鼠标左键单击 }
procedure YzMouseLeftClick(lpClassName, lpWindowName: PAnsiChar);overload;
{ 通过光标位置查找窗口句柄 }
function YzWindowFromPoint(X, Y: Integer): THandle;
{ 等待窗口在指定时间后出现 }
function YzWaitWindowExist(lpClassName, lpWindowName: PAnsiChar;
ASecond: Integer = 0): THandle;overload;
{ 通光标位置,窗口类名与标题查找窗口是否存在 }
function YzWaitWindowExist(X, Y: Integer;AClassName, AWinName: string;
ASecond: Integer = 0):THandle; overload;
{ 等待指定窗口消失 }
procedure YzWaitWindowClose(lpClassName, lpWindowName: PAnsiChar;
ASecond: Integer = 0);
{ 通过窗口句柄设置文本框控件文本 }
procedure YzSetEditText(lpClassName, lpWindowName: PAnsiChar;
AText: string);overload;
{ 通过光标位置设置文本框控件文本 }
procedure YzSetEditText(X, Y: Integer;AText: string);overload;
{ 获取Window操作系统语言 }
function YzGetWindowsLanguageStr: String;
{ 清空动态数组 }
procedure YzDynArraySetZero(var A);
{ 动态设置屏幕分辨率 }
function YzDynamicResolution(X, Y: WORD): Boolean;
{ 检测系统屏幕分辨率 }
function YzCheckDisplayInfo(X, Y: Integer): Boolean;
type
TFontedControl = class(TControl)
public
property Font;
end;
TFontMapping = record
SWidth : Integer;
SHeight: Integer;
FName: string;
FSize: Integer;
end;
procedure YzFixForm(AForm: TForm);
procedure YzSetFontMapping;
{---------------------------------------------------
以下是关于获取系统软件卸载的信息的类型声明和函数
----------------------------------------------------}
type
TUninstallInfo = array of record
RegProgramName: string;
ProgramName : string;
UninstallPath : string;
Publisher : string;
PublisherURL : string;
Version : string;
HelpLink : string;
UpdateInfoURL : string;
RegCompany : string;
RegOwner : string;
end;
{ GetUninstallInfo 返回系统软件卸载的信息 }
function YzGetUninstallInfo : TUninstallInfo;
{ 检测Java安装信息 }
function YzCheckJavaInfo(AUninstallInfo: TUninstallInfo;CheckJava6 : Boolean = False): Boolean;
{ 窗口自适应屏幕大小 }
procedure YzAdjustForm(Form: TForm;const OrgWidth, OrgHeight: integer);
{ 设置窗口为当前窗体 }
procedure YzBringMyAppToFront(AppHandle: THandle);
{ 获取文件夹大小 }
function YzGetDirSize(Dir: string;SubDir: Boolean): LongInt;
{ 获取文件夹文件数量 }
function YzGetDirFiles(Dir: string;SubDir: Boolean): LongInt;
{ 获取文件大小(KB) }
function YzGetFileSize(const FileName: String): LongInt;
{ 获取文件大小(字节) }
function YzGetFileSize_Byte(const FileName: String): LongInt;
{ 算术舍入法的四舍五入取整函数 }
function YzRoundEx (const Value: Real): LongInt;
{ 弹出选择目录对话框 }
function YzSelectDir(const iMode: integer;const sInfo: string): string;
{ 获取指定路径下文件夹的个数 }
procedure YzListFolders(const Path: String; const ShowPath: Boolean;var List: TStrings);
{ 禁用窗器控件的所有子控件 }
procedure YzSetSubCtlState(AOwer: TWinControl;AState: Boolean);
{ 模拟键盘按键操作(处理字节码) }
procedure YzFKeyent(byteCard: byte); overload;
{ 模拟键盘按键操作(处理字符串 }
procedure YzFKeyent(strCard: string); overload;
{ 锁定窗口位置 }
procedure YzLockWindow(ClassName,WinName: PChar;poX,poY: Integer);
{ 注册一个DLL形式或OCX形式的OLE/COM控件
参数strOleFileName为一个DLL或OCX文件名,
参数OleAction表示注册操作类型,1表示注册,0表示卸载
返回值True表示操作执行成功,False表示操作执行失败
}
function YzRegisterOleFile (strOleFileName: STRING;OleAction: Byte): BOOLEAN;
function YzListViewColumnCount(mHandle: THandle): Integer;
function YzGetListViewText(mHandle: THandle; mStrings: TStrings): Boolean;
{ 删除目录树 }
function YzDeleteDirectoryTree(Path: string): boolean;
{ Jpg格式转换为bmp格式 }
function JpgToBmp(Jpg: TJpegImage): TBitmap;
{ 设置程序自启动函数 }
function YzSetAutoRun(AFilePath: string;AFlag: Boolean): boolean;
{ 检测URL地址是否有效 }
function YzCheckUrl(url: string): Boolean;
{ 获取程序可执行文件名 }
function YzGetExeFName: string;
{ 目录浏览对话框函数 }
function YzBrowseFolder(AOwer: TWinControl;ATitle: string):string;
{ 重启计算机 }
function YzShutDownSystem(AFlag: Integer):BOOL;
{ 程序运行后删除自身 }
procedure YzDeleteSelf;
{ 程序重启 }
procedure YzAppRestart;
{ 压缩Access数据库 }
function YzCompactAccessDB(const AFileName, APassWord: string): Boolean;
{ 标题:获取其他进程中TreeView的文本 }
function YzTreeNodeGetNext(mHandle: THandle; mTreeItem: HTreeItem): HTreeItem;
function YzTreeNodeGetLevel(mHandle: THandle; mTreeItem: HTreeItem): Integer;
function YzGetTreeViewText(mHandle: THandle; mStrings: TStrings): Boolean;
{ 获取本地Application Data目录路径 }
function YzLocalAppDataPath : string;
{ 获取Windows当前登录的用户名 }
function YzGetWindwosUserName: String;
{枚举托盘图标 }
function YzEnumTrayNotifyWnd(AFindList: TStringList;ADestStr: string): BOOL;
{ 获取SQL Server用户数据库列表 }
procedure YzGetSQLServerDBList(ADBHostIP, ALoginPwd: string;ADBList: TStringList);
{ 读取据库中所有的表 }
procedure YzGetTableList(ConncetStr: string;ATableList: TStringList);
{ 将域名解释成IP地址 }
function YzDomainToIP(HostName: string): string;
{ 等待进程结束 }
procedure YzWaitProcessExit(AProcessName: string);
{ 移去系统托盘失效图标 }
procedure YzRemoveDeadIcons();
{ 转移程序占用内存至虚拟内存 }
procedure YzClearMemory;
{ 检测允许试用的天数是否已到期 }
function YzCheckTrialDays(AllowDays: Integer): Boolean;
{ 指定长度的随机小写字符串函数 }
function YzRandomStr(aLength: Longint): string;
var
FontMapping : array of TFontMapping;
implementation
uses
uMain;
{ 保存日志文件 }
procedure YzWriteLogFile(Msg: String);
var
FileStream: TFileStream;
LogFile : String;
begin
try
{ 每天一个日志文件 }
Msg := '[' + DateTimeToStr(Now)+ '] '+ Msg;
LogFile := ExtractFilePath(Application.ExeName) + '/Logs/' + DateToStr(Now) + '.log';
if not DirectoryExists(ExtractFilePath(LogFile)) then
CreateDir(ExtractFilePath(LogFile));
if FileExists(LogFile) then
FileStream := TFileStream.Create(LogFile, fmOpenWrite or fmShareDenyNone)
else
FileStream:=TFileStream.Create(LogFile,fmCreate or fmShareDenyNone);
FileStream.Position:=FileStream.Size;
Msg := Msg + #13#10;
FileStream.Write(PChar(Msg)^, Length(Msg));
FileStream.Free;
except
end;
end;
{ 延时函数,单位为毫秒 }
procedure YZDelayTime(MSecs: Longint);
var
FirstTickCount, Now: Longint;
begin
FirstTickCount := GetTickCount();
repeat
Application.ProcessMessages;
Now := GetTickCount();
until (Now - FirstTickCount>=MSecs) or (Now < FirstTickCount);
end;
{ 判断字符串是否为数字 }
function YzStrIsNum(Str: string):boolean;
var
I: integer;
begin
if Str = '' then
begin
Result := False;
Exit;
end;
for I:=1 to length(str) do
if not (Str[I] in ['0'..'9']) then
begin
Result := False;
Exit;
end;
Result := True;
end;
{ 判断文件是否正在使用 }
function YzIsFileInUse(fName: string): boolean;
var
HFileRes: HFILE;
begin
Result := false;
if not FileExists(fName) then exit;
HFileRes := CreateFile(pchar(fName), GENERIC_READ or GENERIC_WRITE, 0, nil,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
Result := (HFileRes = INVALID_HANDLE_VALUE);
if not Result then CloseHandle(HFileRes);
end;
{ 删除字符串列表中的空字符串 }
procedure YzDelEmptyChar(AList: TStringList);
var
I: Integer;
TmpList: TStringList;
begin
TmpList := TStringList.Create;
for I := 0 to AList.Count - 1 do
if AList.Strings[I] <> '' then TmpList.Add(AList.Strings[I]);
AList.Clear;
AList.Text := TmpList.Text;
TmpList.Free;
end;
{ 删除文件列表中的"Thumbs.db"文件 }
procedure YzDelThumbsFile(AList: TStrings);
var
I: Integer;
TmpList: TStringList;
begin
TmpList := TStringList.Create;
for I := 0 to AList.Count - 1 do
if ExtractFileName(AList.Strings[I]) <> 'Thumbs.db' then
TmpList.Add(AList.Strings[I]);
AList.Clear;
AList.Text := TmpList.Text;
TmpList.Free;
end;
{-------------------------------------------------------------
功能: 返回一个整数指定位数的带"0"字符串
参数: Value:要转换的整数 ALength:字符串长度
返回值: string
--------------------------------------------------------------}
function YzIntToZeroStr(Value, ALength: Integer): string;
var
I, ACount: Integer;
begin
Result := '';
ACount := Length(IntToStr(Value));
if ACount >= ALength then Result := IntToStr(Value)
else
begin
for I := 1 to ALength-ACount do
Result := Result + '0';
Result := Result + IntToStr(Value)
end;
end;
{ 取日期年份分量 }
function YzGetYear(Date: TDate): Integer;
var
y, m, d: WORD;
begin
DecodeDate(Date, y, m, d);
Result := y;
end;
{ 取日期月份分量 }
function YzGetMonth(Date: TDate): Integer;
var
y, m, d: WORD;
begin
DecodeDate(Date, y, m, d);
Result := m;
end;
{ 取日期天数分量 }
function YzGetDay(Date: TDate): Integer;
var
y, m, d: WORD;
begin
DecodeDate(Date, y, m, d);
Result := d;
end;
{ 取时间小时分量 }
function YzGetHour(Time: TTime): Integer;
var
h, m, s, ms: WORD;
begin
DecodeTime(Time, h, m, s, ms);
Result := h;
end;
{ 取时间分钟分量 }
function YzGetMinute(Time: TTime): Integer;
var
h, m, s, ms: WORD;
begin
DecodeTime(Time, h, m, s, ms);
Result := m;
end;
{ 取时间秒钟分量 }
function YzGetSecond(Time: TTime): Integer;
var
h, m, s, ms: WORD;
begin
DecodeTime(Time, h, m, s, ms);
Result := s;
end;
{ 返回时间分量字符串 }
function YzGetTimeStr(ATime: TTime;AFlag: string): string;
var
wTimeStr: string;
FH, FM, FS, FMS: WORD;
const
HOURTYPE = 'Hour';
MINUTETYPE = 'Minute';
SECONDTYPE = 'Second';
MSECONDTYPE = 'MSecond';
begin
wTimeStr := TimeToStr(ATime);
if Pos('上午', wTimeStr) <> 0 then
wTimeStr := Copy(wTimeStr, Pos('上午', wTimeStr) + 4, 10)
else if Pos('下午', wTimeStr) <> 0 then
wTimeStr := Copy(wTimeStr, Pos('下午', wTimeStr) + 4, 10);
DecodeTime(ATime, FH, FM, FS, FMS);
if AFlag = HOURTYPE then
begin
{ 如果是12小时制则下午的小时分量加12 }
if Pos('下午', wTimeStr) <> 0 then
Result := YzIntToZeroStr(FH + 12, 2)
else
Result := YzIntToZeroStr(FH, 2);
end;
if AFlag = MINUTETYPE then Result := YzIntToZeroStr(FM, 2);
if AFlag = SECONDTYPE then Result := YzIntToZeroStr(FS, 2);
if AFlag = MSECONDTYPE then Result := YzIntToZeroStr(FMS, 2);
end;
{ 返回日期时间字符串 }
function YzGetDateTimeStr(ATime: TTime;ADate: TDate): string;
var
wYear, wMonth, wDay: string;
wHour, wMinute, wSecond: string;
begin
wYear := RightStr(YzIntToZeroStr(YzGetYear(ADate), 4), 2);
wMonth := YzIntToZeroStr(YzGetMonth(ADate), 2);
wDay := YzIntToZeroStr(YzGetDay(ADate), 2);
wHour := YzGetTimeStr(ATime, 'Hour');
wMinute := YzGetTimeStr(ATime, 'Minute');
wSecond := YzGetTimeStr(ATime, 'Second');
Result := wYear + wMonth + wDay + wHour + wMinute + wSecond;
end;
{ 通过窗体子串查找窗体 }
procedure YzFindSpecWindow(ASubTitle: string);
function EnumWndProc(AWnd: THandle;AWinName: string): Boolean;stdcall;
var
WindowText: array[0..255] of Char;
WindowStr: string;
begin
GetWindowText(AWnd, WindowText, 255);
WindowStr := StrPas(WindowText);
WindowStr := COPY(WindowStr, 1, StrLen(PChar(AWinName)));
if CompareText(AWinName, WindowStr) = 0 then
begin
SetForegroundWindow(AWnd);
Result := False; Exit;
end;
Result := True;
end;
begin
EnumWindows(@EnumWndProc, LongInt(@ASubTitle));
YzDelayTime(1000);
end;
{ 获取计算机名称 }
function YzGetComputerName(): string;
var
pcComputer: PChar;
dwCSize: DWORD;
begin
dwCSize := MAX_COMPUTERNAME_LENGTH + 1;
Result := '';
GetMem(pcComputer, dwCSize);
try
if Windows.GetComputerName(pcComputer, dwCSize) then
Result := pcComputer;
finally
FreeMem(pcComputer);
end;
end;
{ 判断进程CPU占用率 }
procedure YzJudgeCPU(ProcessName: string;CPUUsage: Single);
var
cnt: PCPUUsageData;
usage: Single;
begin
cnt := wsCreateUsageCounter(FindProcess(ProcessName));
while True do
begin
usage := wsGetCpuUsage(cnt);
if usage <= CPUUsage then
begin
wsDestroyUsageCounter(cnt);
YzDelayTime(2000);
Break;
end;
YzDelayTime(10);
Application.ProcessMessages;
end;
end;
{ 分割字符串 }
procedure YzSeparateTerms(Source: string;Separator: Char;Terms: TStringList);
var
TmpStr: string;
PO: integer;
begin
Terms.Clear;
if Length(Source) = 0 then Exit; { 长度为0则退出 }
PO := Pos(Separator, Source);
if PO = 0 then
begin
Terms.Add(Source);
Exit;
end;
while PO <> 0 do
begin
TmpStr := Copy(Source, 1, PO - 1);{ 复制字符 }
Terms.Add(TmpStr); { 添加到列表 }
Delete(Source, 1, PO); { 删除字符和分割符 }
PO := Pos(Separator, Source); { 查找分割符 }
end;
if Length(Source) > 0 then
Terms.Add(Source); { 添加剩下的条目 }
end;
{ 切换页面控件的活动页面 }
procedure YzGotoNewPage(AOwerPage: TPageControl;ANewPage: TTabSheet);
begin
if AOwerPage.ActivePage <> ANewPage then AOwerPage.ActivePage := ANewPage;
end;
{ 设置页面控件标签的可见性 }
procedure YzSetTableVisible(PageControl: TPageControl;ShowFlag: Boolean);
var
I: Integer;
begin
for I := 0 to PageControl.PageCount -1 do
PageControl.Pages[I].TabVisible := ShowFlag;
end;
{ 根据产品名称获取产品编号 }
function YZGetLevelCode(AName:string;ProductList: TStringList): string;
var
I: Integer;
TmpStr: string;
begin
Result := '';
if ProductList.Count <= 0 then Exit;
for I := 0 to ProductList.Count-1 do
begin
TmpStr := ProductList.Strings[I];
if AName = Copy(TmpStr,1, Pos('_', TmpStr)-1) then
begin
Result := Copy(TmpStr, Pos('_', TmpStr)+1, 10);
Break;
end;
end;
end;
{ 取文件的主文件名 }
function YzGetMainFileName(AFileName:string): string;
var
TmpStr: string;
begin
if AFileName = '' then Exit;
TmpStr := ExtractFileName(AFileName);
Result := Copy(TmpStr, 1, Pos('.', TmpStr) - 1);
end;
{ 按下一个键 }
procedure YzPressOneKey(AByteCode: Byte);
begin
keybd_event(AByteCode, 0, 0, 0);
YzDelayTime(100);
keybd_event(AByteCode, 0, KEYEVENTF_KEYUP, 0);
YzDelayTime(400);
end;
{ 按下一个指定次数的键 }
procedure YzPressOneKey(AByteCode: Byte;ATimes: Integer);overload;
var
I: Integer;
begin
for I := 1 to ATimes do
begin
keybd_event(AByteCode, 0, 0, 0);
YzDelayTime(10);
keybd_event(AByteCode, 0, KEYEVENTF_KEYUP, 0);
YzDelayTime(150);
end;
end;
{ 按下二个键 }
procedure YzPressTwoKeys(AFirstByteCode, ASecByteCode: Byte);
begin
keybd_event(AFirstByteCode, 0, 0, 0);
keybd_event(ASecByteCode, 0, 0, 0);
YzDelayTime(100);
keybd_event(ASecByteCode, 0, KEYEVENTF_KEYUP, 0);
keybd_event(AFirstByteCode, 0, KEYEVENTF_KEYUP, 0);
YzDelayTime(400);
end;
{ 按下三个键 }
procedure YzPressThreeKeys(AFirstByteCode, ASecByteCode, AThirdByteCode: Byte);
begin
keybd_event(AFirstByteCode, 0, 0, 0);
keybd_event(ASecByteCode, 0, 0, 0);
keybd_event(AThirdByteCode, 0, 0, 0);
YzDelayTime(100);
keybd_event(AThirdByteCode, 0, KEYEVENTF_KEYUP, 0);
keybd_event(ASecByteCode, 0, KEYEVENTF_KEYUP, 0);
keybd_event(AFirstByteCode, 0, KEYEVENTF_KEYUP, 0);
YzDelayTime(400);
end;
{ 创建桌面快捷方式 }
procedure YzCreateShortCut(const sPath: string; sShortCutName: WideString);
var
tmpObject: IUnknown;
tmpSLink: IShellLink;
tmpPFile: IPersistFile;
PIDL: PItemIDList;
StartupDirectory: array[0..MAX_PATH] of Char;
StartupFilename: String;
LinkFilename: WideString;
begin
StartupFilename := sPath;
tmpObject := CreateComObject(CLSID_ShellLink); { 创建建立快捷方式的外壳扩展 }
tmpSLink := tmpObject as IShellLink; { 取得接口 }
tmpPFile := tmpObject as IPersistFile; { 用来储存*.lnk文件的接口 }
tmpSLink.SetPath(pChar(StartupFilename)); { 设定notepad.exe所在路径 }
tmpSLink.SetWorkingDirectory(pChar(ExtractFilePath(StartupFilename))); {设定工作目录 }
SHGetSpecialFolderLocation(0, CSIDL_DESKTOPDIRECTORY, PIDL); { 获得桌面的Itemidlist }
SHGetPathFromIDList(PIDL, StartupDirectory); { 获得桌面路径 }
sShortCutName := '/' + sShortCutName + '.lnk';
LinkFilename := StartupDirectory + sShortCutName;
tmpPFile.Save(pWChar(LinkFilename), FALSE); { 保存*.lnk文件 }
end;
{ 删除桌面快捷方式 }
procedure YzDeleteShortCut(sShortCutName: WideString);
var
PIDL : PItemIDList;
StartupDirectory: array[0..MAX_PATH] of Char;
LinkFilename: WideString;
begin
SHGetSpecialFolderLocation(0,CSIDL_DESKTOPDIRECTORY,PIDL);
SHGetPathFromIDList(PIDL,StartupDirectory);
LinkFilename := StrPas(StartupDirectory) + '/' + sShortCutName + '.lnk';
DeleteFile(LinkFilename);
end;
{ 通过光标位置进行鼠标左键单击 }
procedure YzMouseLeftClick(X, Y: Integer);
begin
SetCursorPos(X, Y);
YzDelayTime(100);
mouse_event(MOUSEEVENTF_LEFTDOWN,0,0,0,0);
mouse_event(MOUSEEVENTF_LEFTUP,0,0,0,0);
YzDelayTime(400);
end;
{ 鼠标左键双击 }
procedure YzMouseDoubleClick(X, Y: Integer);
begin
SetCursorPos(X, Y);
YzDelayTime(100);
mouse_event(MOUSEEVENTF_LEFTDOWN,0,0,0,0);
mouse_event(MOUSEEVENTF_LEFTUP,0,0,0,0);
YzDelayTime(100);
mouse_event(MOUSEEVENTF_LEFTDOWN,0,0,0,0);
mouse_event(MOUSEEVENTF_LEFTUP,0,0,0,0);
YzDelayTime(400);
end;
{ 通过窗口句柄进行鼠标左键单击 }
procedure YzMouseLeftClick(lpClassName, lpWindowName: PAnsiChar);overload;
var
AHandel: THandle;
begin
AHandel := FindWindow(lpClassName, lpWindowName);
SendMessage(AHandel, WM_LBUTTONDOWN, 0, 0);
SendMessage(AHandel, WM_LBUTTONUP, 0, 0);
YzDelayTime(500);
end;
{ 等待进程结束 }
procedure YzWaitProcessExit(AProcessName: string);
begin
while True do
begin
KillByPID(FindProcess(AProcessName));
if FindProcess(AProcessName) = 0 then Break;
YzDelayTime(10);
Application.ProcessMessages;
end;
end;
{-------------------------------------------------------------
功 能: 等待窗口在指定时间后出现
参 数: lpClassName: 窗口类名
lpWindowName: 窗口标题
ASecond: 要等待的时间,"0"代表永久等待
返回值: 无
备 注: 如果指定的等待时间未到窗口已出现则立即退出
--------------------------------------------------------------}
function YzWaitWindowExist(lpClassName, lpWindowName: PAnsiChar;
ASecond: Integer = 0): THandle;overload;
var
StartTickCount, PassTickCount: LongWord;
begin
Result := 0;
{ 永久等待 }
if ASecond = 0 then
begin
while True do
begin
Result := FindWindow(lpClassName, lpWindowName);
if Result <> 0 then Break;
YzDelayTime(10);
Application.ProcessMessages;
end;
end
else { 等待指定时间 }
begin
StartTickCount := GetTickCount;
while True do
begin
Result := FindWindow(lpClassName, lpWindowName);
{ 窗口已出现则立即退出 }
if Result <> 0 then Break
else
begin
PassTickCount := GetTickCount;
{ 等待时间已到则退出 }
if (PassTickCount - StartTickCount) div 1000 >= ASecond then Break;
end;
YzDelayTime(10);
Application.ProcessMessages;
end;
end;
YzDelayTime(1000);
end;
{ 等待指定窗口消失 }
procedure YzWaitWindowClose(lpClassName, lpWindowName: PAnsiChar;
ASecond: Integer = 0);
var
StartTickCount, PassTickCount: LongWord;
begin
if ASecond = 0 then
begin
while True do
begin
if FindWindow(lpClassName, lpWindowName) = 0 then Break;
YzDelayTime(10);
Application.ProcessMessages;
end
end
else
begin
StartTickCount := GetTickCount;
while True do
begin
{ 窗口已关闭则立即退出 }
if FindWindow(lpClassName, lpWindowName)= 0 then Break
else
begin
PassTickCount := GetTickCount;
{ 等待时间已到则退出 }
if (PassTickCount - StartTickCount) div 1000 >= ASecond then Break;
end;
YzDelayTime(10);
Application.ProcessMessages;
end;
end;
YzDelayTime(500);
end;
{ 通过光标位置查找窗口句柄 }
function YzWindowFromPoint(X, Y: Integer): THandle;
var
MousePoint: TPoint;
CurWindow: THandle;
hRect: TRect;
Canvas: TCanvas;
begin
MousePoint.X := X;
MousePoint.Y := Y;
CurWindow := WindowFromPoint(MousePoint);
GetWindowRect(Curwindow, hRect);
if Curwindow <> 0 then
begin
Canvas := TCanvas.Create;
Canvas.Handle := GetWindowDC(Curwindow);
Canvas.Pen.Width := 2;
Canvas.Pen.Color := clRed;
Canvas.Pen.Mode := pmNotXor;
Canvas.Brush.Style := bsClear;
Canvas.Rectangle(0, 0, hRect.Right-hRect.Left, hRect.Bottom-hRect.Top);
Canvas.Free;
end;
Result := CurWindow;
end;
{ 通光标位置,窗口类名与标题查找窗口是否存在 }
function YzWaitWindowExist(X, Y: Integer;AClassName, AWinName: string;
ASecond: Integer):THandle;overload;
var
MousePo: TPoint;
CurWindow: THandle;
bufClassName: array[0..MAXBYTE-1] of Char;
bufWinName: array[0..MAXBYTE-1] of Char;
StartTickCount, PassTickCount: LongWord;
begin
Result := 0;
{ 永久等待 }
if ASecond = 0 then
begin
while True do
begin
MousePo.X := X;
MousePo.Y := Y;
CurWindow := WindowFromPoint(MousePo);
GetClassName(CurWindow, bufClassName, MAXBYTE);
GetWindowText(CurWindow, bufWinname, MAXBYTE);
if (CompareText(StrPas(bufClassName), AClassName) = 0 ) and
(CompareText(StrPas(bufWinName), AWinName) = 0) then
begin
Result := CurWindow;
Break;
end;
YzDelayTime(10);
Application.ProcessMessages;
end;
end
else { 等待指定时间 }
begin
StartTickCount := GetTickCount;
while True do
begin
{ 窗口已出现则立即退出 }
MousePo.X := X;
MousePo.Y := Y;
CurWindow := WindowFromPoint(MousePo);
GetClassName(CurWindow, bufClassName, MAXBYTE);
GetWindowText(CurWindow, bufWinname, MAXBYTE);
if (CompareText(StrPas(bufClassName), AClassName) = 0 ) and
(CompareText(StrPas(bufWinName), AWinName) = 0) then
begin
Result := CurWindow; Break;
end
else
begin
PassTickCount := GetTickCount;
{ 等待时间已到则退出 }
if (PassTickCount - StartTickCount) div 1000 >= ASecond then Break;
end;
YzDelayTime(10);
Application.ProcessMessages;
end;
end;
YzDelayTime(1000);
end;
{ 通过窗口句柄设置文本框控件文本 }
procedure YzSetEditText(lpClassName, lpWindowName: PAnsiChar;
AText: string);overload;
var
CurWindow: THandle;
begin
CurWindow := FindWindow(lpClassName, lpWindowName);
SendMessage(CurWindow ,WM_SETTEXT, 0, Integer(PChar(AText)));
YzDelayTime(500);
end;
{ 通过光标位置设置文本框控件文本 }
procedure YzSetEditText(X, Y: Integer;AText: string);overload;
var
CurWindow: THandle;
begin
CurWindow := YzWindowFromPoint(X, Y);
SendMessage(CurWindow, WM_SETTEXT, 0, Integer(PChar(AText)));
YzMouseLeftClick(X, Y);
end;
{ 获取Window操作系统语言 }
function YzGetWindowsLanguageStr: String;
var
WinLanguage: array [0..50] of char;
begin
VerLanguageName(GetSystemDefaultLangID, WinLanguage, 50);
Result := StrPas(WinLanguage);
end;
procedure YzDynArraySetZero(var A);
var
P: PLongint; { 4个字节 }
begin
P := PLongint(A); { 指向 A 的地址 }
Dec(P); { P地址偏移量是 sizeof(A),指向了数组长度 }
P^ := 0; { 数组长度清空 }
Dec(P); { 指向数组引用计数 }
P^ := 0; { 数组计数清空 }
end;
{ 动态设置分辨率 }
function YzDynamicResolution(x, y: WORD): Boolean;
var
lpDevMode: TDeviceMode;
begin
Result := EnumDisplaySettings(nil, 0, lpDevMode);
if Result then
begin
lpDevMode.dmFields := DM_PELSWIDTH or DM_PELSHEIGHT;
lpDevMode.dmPelsWidth := x;
lpDevMode.dmPelsHeight := y;
Result := ChangeDisplaySettings(lpDevMode, 0) = DISP_CHANGE_SUCCESSFUL;
end;
end;
procedure YzSetFontMapping;
begin
SetLength(FontMapping, 3);
{ 800 x 600 }
FontMapping[0].SWidth := 800;
FontMapping[0].SHeight := 600;
FontMapping[0].FName := '宋体';
FontMapping[0].FSize := 7;
{ 1024 x 768 }
FontMapping[1].SWidth := 1024;
FontMapping[1].SHeight := 768;
FontMapping[1].FName := '宋体';
FontMapping[1].FSize := 9;
{ 1280 x 1024 }
FontMapping[2].SWidth := 1280;
FontMapping[2].SHeight := 1024;
FontMapping[2].FName := '宋体';
FontMapping[2].FSize := 11;
end;
{ 程序窗体及控件自适应分辨率(有问题) }
procedure YzFixForm(AForm: TForm);
var
I, J: integer;
T: TControl;
begin
with AForm do
begin
for I := 0 to ComponentCount - 1 do
begin
try
T := TControl(Components[I]);
T.left := Trunc(T.left * (Screen.width / 1024));
T.top := Trunc(T.Top * (Screen.Height / 768));
T.Width := Trunc(T.Width * (Screen.Width / 1024));
T.Height := Trunc(T.Height * (Screen.Height / 768));
except
end; { try }
end; { for I }
for I:= 0 to Length(FontMapping) - 1 do
begin
if (Screen.Width = FontMapping[I].SWidth) and (Screen.Height =
FontMapping[I].SHeight) then
begin
for J := 0 to ComponentCount - 1 do
begin
try
TFontedControl(Components[J]).Font.Name := FontMapping[I].FName;
TFontedControl(Components[J]).FONT.Size := FontMapping[I].FSize;
except
end; { try }
end; { for J }
end; { if }
end; { for I }
end; { with }
end;
{ 检测系统屏幕分辨率 }
function YzCheckDisplayInfo(X, Y: Integer): Boolean;
begin
Result := True;
if (Screen.Width <> X) and (Screen.Height <> Y) then
begin
if MessageBox(Application.Handle, PChar( '系统检测到您的屏幕分辨率不是 '
+ IntToStr(X) + '×' + IntToStr(Y) + ',这将影响到系统的正常运行,'
+ '是否要自动调整屏幕分辨率?'), '提示', MB_YESNO + MB_ICONQUESTION
+ MB_TOPMOST) = 6 then YzDynamicResolution(1024, 768)
else Result := False;
end;
end;
function YzGetUninstallInfo: TUninstallInfo;
const
Key = '/SOFTWARE/Microsoft/Windows/CurrentVersion/Uninstall/';
var
S : TStrings;
I : Integer;
J : Integer;
begin
with TRegistry.Create do
begin
S := TStringlist.Create;
J := 0;
try
RootKey:= HKEY_LOCAL_MACHINE;
OpenKeyReadOnly(Key);
GetKeyNames(S);
Setlength(Result, S.Count);
for I:= 0 to S.Count - 1 do
begin
If OpenKeyReadOnly(Key + S[I]) then
If ValueExists('DisplayName') and ValueExists('UninstallString') then
begin
Result[J].RegProgramName:= S[I];
Result[J].ProgramName:= ReadString('DisplayName');
Result[J].UninstallPath:= ReadString('UninstallString');
If ValueExists('Publisher') then
Result[J].Publisher:= ReadString('Publisher');
If ValueExists('URLInfoAbout') then
Result[J].PublisherURL:= ReadString('URLInfoAbout');
If ValueExists('DisplayVersion') then
Result[J].Version:= ReadString('DisplayVersion');
If ValueExists('HelpLink') then
Result[J].HelpLink:= ReadString('HelpLink');
If ValueExists('URLUpdateInfo') then
Result[J].UpdateInfoURL:= ReadString('URLUpdateInfo');
If ValueExists('RegCompany') then
Result[J].RegCompany:= ReadString('RegCompany');
If ValueExists('RegOwner') then
Result[J].RegOwner:= ReadString('RegOwner');
Inc(J);
end;
end;
finally
Free;
S.Free;
SetLength(Result, J);
end;
end;
end;
{ 检测Java安装信息 }
function YzCheckJavaInfo(AUninstallInfo: TUninstallInfo;CheckJava6 : Boolean = False): Boolean;
var
I: Integer;
Java6Exist: Boolean;
AUninstall: TUninstallInfo;
AProgramList: TStringList;
AJavaVersion, AFilePath: string;
begin
Result := True;
Java6Exist := False;
AJavaVersion := 'J2SE Runtime Environment 5.0 Update 14';
AUninstall := YzGetUninstallInfo;
AProgramList := TStringList.Create;
for I := Low(AUninstall) to High(AUninstall) do
begin
if Pos('J2SE', AUninstall[I].ProgramName) <> 0 then
AProgramList.Add(AUninstall[I].ProgramName);
if Pos('Java(TM)', AUninstall[I].ProgramName) <> 0 then
Java6Exist := True;
end;
if Java6Exist then
begin
if CheckJava6 then
begin
MessageBox(Application.Handle, '系统检测到您机器上安装了Java6以上的版本,'
+ '如果影响到系统的正常运行请先将其卸载再重新启动系统!', '提示',
MB_OK + MB_ICONINFORMATION + MB_TOPMOST);
Result := False;
end;
end
else if AProgramList.Count = 0 then
begin
MessageBox(Application.Handle, '系统检测到您机器上没有安装Java运行环境,'
+ '请点击 "确定" 安装Java运行环境后再重新运行程序!',
'提示', MB_OK + MB_ICONINFORMATION + MB_TOPMOST);
AFilePath := ExtractFilePath(ParamStr(0)) + 'java' + '/'
+ 'jre-1_5_0_14-windows-i586-p.exe';
if FileExists(AFilePath) then WinExec(PChar(AFilePath), SW_SHOWNORMAL)
else
MessageBox(Application.Handle, '找不到Java安装文件,请您手动安装!',
'提示', MB_OK + MB_ICONINFORMATION + MB_TOPMOST);
Result := False;
end;
AProgramList.Free;
end;
{-------------------------------------------------------------
功能: 窗口自适应屏幕大小
参数: Form: 需要调整的Form
OrgWidth:开发时屏幕的宽度
OrgHeight:开发时屏幕的高度
--------------------------------------------------------------}
procedure YzAdjustForm(Form: TForm;const OrgWidth, OrgHeight: integer);
begin
with Form do
begin
if (Screen.width <> OrgWidth) then
begin
Scaled := True;
Height := longint(Height) * longint(Screen.height) div OrgHeight;
Width := longint(Width) * longint(Screen.Width) div OrgWidth;
ScaleBy(Screen.Width, OrgWidth);
end;
end;
end;
{ 设置窗口为当前窗体 }
procedure YzBringMyAppToFront(AppHandle: THandle);
var
Th1, Th2: Cardinal;
begin
Th1 := GetCurrentThreadId;
Th2 := GetWindowThreadProcessId(GetForegroundWindow, NIL);
AttachThreadInput(Th2, Th1, TRUE);
try
SetForegroundWindow(AppHandle);
finally
AttachThreadInput(Th2, Th1, TRUE);
end;
end;
{ 获取文件夹文件数量 }
function YzGetDirFiles(Dir: string;SubDir: Boolean): LongInt;
var
SearchRec: TSearchRec;
Founded: integer;
begin
Result := 0;
if Dir[length(Dir)] <> '/' then Dir := Dir + '/';
Founded := FindFirst(Dir + '*.*', faAnyFile, SearchRec);
while Founded = 0 do
begin
Inc(Result);
if (SearchRec.Attr and faDirectory > 0) and (SearchRec.Name[1] <> '.') and
(SubDir = True) then
Inc(Result, YzGetDirFiles(Dir + SearchRec.Name, True));
Founded := FindNext(SearchRec);
end;
FindClose(SearchRec);
end;
{ 算术舍入法的四舍五入取整函数 }
function YzRoundEx (const Value: Real): LongInt;
var
x: Real;
begin
x := Value - Trunc(Value);
if x >= 0.5 then
Result := Trunc(Value) + 1
else Result := Trunc(Value);
end;
{ 获取文件大小(KB) }
function YzGetFileSize(const FileName: String): LongInt;
var
SearchRec: TSearchRec;
begin
if FindFirst(ExpandFileName(FileName), faAnyFile, SearchRec) = 0 then
Result := SearchRec.Size
else
Result := -1;
Result := YzRoundEx(Result / 1024);
end;
{ 获取文件大小(字节) }
function YzGetFileSize_Byte(const FileName: String): LongInt;
var
SearchRec: TSearchRec;
begin
if FindFirst(ExpandFileName(FileName), faAnyFile, SearchRec) = 0 then
Result := SearchRec.Size
else
Result := -1;
end;
{ 获取文件夹大小 }
function YzGetDirSize(Dir: string;SubDir: Boolean): LongInt;
var
SearchRec: TSearchRec;
Founded: integer;
begin
Result := 0;
if Dir[length(Dir)] <> '/' then Dir := Dir + '/';
Founded := FindFirst(Dir + '*.*', faAnyFile, SearchRec);
while Founded = 0 do
begin
Inc(Result, SearchRec.size);
if (SearchRec.Attr and faDirectory > 0) and (SearchRec.Name[1] <> '.') and
(SubDir = True) then
Inc(Result, YzGetDirSize(Dir + SearchRec.Name, True));
Founded := FindNext(SearchRec);
end;
FindClose(SearchRec);
Result := YzRoundEx(Result / 1024);
end;
{-------------------------------------------------------------
功能: 弹出选择目录对话框
参数: const iMode: 选择模式
const sInfo: 对话框提示信息
返回值: 如果取消取返回为空,否则返回选中的路径
--------------------------------------------------------------}
function YzSelectDir(const iMode: integer;const sInfo: string): string;
var
Info: TBrowseInfo;
IDList: pItemIDList;
Buffer: PChar;
begin
Result:='';
Buffer := StrAlloc(MAX_PATH);
with Info do
begin
hwndOwner := application.mainform.Handle; { 目录对话框所属的窗口句柄 }
pidlRoot := nil; { 起始位置,缺省为我的电脑 }
pszDisplayName := Buffer; { 用于存放选择目录的指针 }
lpszTitle := PChar(sInfo);
{ 此处表示显示目录和文件,如果只显示目录则将后一个去掉即可 }
if iMode = 1 then
ulFlags := BIF_RETURNONLYFSDIRS or BIF_BROWSEINCLUDEFILES
else
ulFlags := BIF_RETURNONLYFSDIRS;
lpfn := nil; { 指定回调函数指针 }
lParam := 0; { 传递给回调函数参数 }
IDList := SHBrowseForFolder(Info); { 读取目录信息 }
end;
if IDList <> nil then
begin
SHGetPathFromIDList(IDList, Buffer); { 将目录信息转化为路径字符串 }
Result := strpas(Buffer);
end;
StrDispose(buffer);
end;
{ 获取指定路径下文件夹的个数 }
procedure YzListFolders(const Path: String; const ShowPath: Boolean;var List: TStrings);
var
SRec: TSearchRec;
begin
if not Assigned(List) then List:= TStringList.Create;
FindFirst(Path + '*.*', faDirectory, SRec);
if ShowPath then
List.Add(Path + SRec.Name)
else
List.Add(SRec.Name);
while FindNext(SRec) = 0 do
if ShowPath then
List.Add(Path + SRec.Name)
else
List.Add(SRec.Name);
FindClose(SRec);
end;
{ 禁用窗器控件的所有子控件 }
procedure YzSetSubCtlState(AOwer: TWinControl;AState: Boolean);
var
I: Integer;
begin
for I := 0 to AOwer.ControlCount - 1 do
AOwer.Controls[I].Enabled := AState;
end;
{ 模拟键盘按键操作(处理字节码) }
procedure YzFKeyent(byteCard: byte);
var
vkkey: integer;
begin
vkkey := VkKeyScan(chr(byteCard));
if (chr(byteCard) in ['A'..'Z']) then
begin
keybd_event(VK_SHIFT, 0, 0, 0);
keybd_event(byte(byteCard), 0, 0, 0);
keybd_event(VK_SHIFT, 0, 2, 0);
end
else if chr(byteCard) in ['!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
'_', '+', '|', '{', '}', ':', '"', '<', '>', '?', '~'] then
begin
keybd_event(VK_SHIFT, 0, 0, 0);
keybd_event(byte(vkkey), 0, 0, 0);
keybd_event(VK_SHIFT, 0, 2, 0);
end
else { if byteCard in [8,13,27,32] }
begin
keybd_event(byte(vkkey), 0, 0, 0);
end;
end;
{ 模拟键盘按键(处理字符) }
procedure YzFKeyent(strCard: string);
var
str: string;
strLength: integer;
I: integer;
byteSend: byte;
begin
str := strCard;
strLength := length(str);
for I := 1 to strLength do
begin
byteSend := byte(str[I]);
YzFKeyent(byteSend);
end;
end;
{ 锁定窗口位置 }
procedure YzLockWindow(ClassName,WinName: PChar;poX,poY: Integer);
var
CurWindow: THandle;
_wndRect: TRect;
begin
CurWindow := 0;
while True do
begin
CurWindow := FindWindow(ClassName,WinName);
if CurWindow <> 0 then Break;
YzDelayTime(10);
Application.ProcessMessages;
end;
GetWindowRect(CurWindow,_wndRect);
if ( _wndRect.Left <> poX) or ( _wndRect.Top <> poY) then
begin
MoveWindow(CurWindow,
poX,
poY,
(_wndRect.Right-_wndRect.Left),
(_wndRect.Bottom-_wndRect.Top),
TRUE);
end;
YzDelayTime(1000);
end;
{
注册一个DLL形式或OCX形式的OLE/COM控件
参数strOleFileName为一个DLL或OCX文件名,
参数OleAction表示注册操作类型,1表示注册,0表示卸载
返回值True表示操作执行成功,False表示操作执行失败
}
function YzRegisterOleFile (strOleFileName: STRING;OleAction: Byte): BOOLEAN;
const
RegisterOle = 1; { 注册 }
UnRegisterOle = 0; { 卸载 }
type
TOleRegisterFunction = function: HResult; { 注册或卸载函数的原型 }
var
hLibraryHandle: THandle; { 由LoadLibrary返回的DLL或OCX句柄 }
hFunctionAddress: TFarProc; { DLL或OCX中的函数句柄,由GetProcAddress返回 }
RegFunction: TOleRegisterFunction; { 注册或卸载函数指针 }
begin
Result := FALSE;
{ 打开OLE/DCOM文件,返回的DLL或OCX句柄 }
hLibraryHandle := LoadLibrary(PCHAR(strOleFileName));
if (hLibraryHandle > 0) then { DLL或OCX句柄正确 }
try
{ 返回注册或卸载函数的指针 }
if (OleAction = RegisterOle) then { 返回注册函数的指针 }
hFunctionAddress := GetProcAddress(hLibraryHandle, pchar('DllRegisterServer'))
{ 返回卸载函数的指针 }
else
hFunctionAddress := GetProcAddress(hLibraryHandle, pchar('DllUnregisterServer'));
if (hFunctionAddress <> NIL) then { 注册或卸载函数存在 }
begin
{ 获取操作函数的指针 }
RegFunction := TOleRegisterFunction(hFunctionAddress);
{ 执行注册或卸载操作,返回值>=0表示执行成功 }
if RegFunction >= 0 then
Result := true;
end;
finally
{ 关闭已打开的OLE/DCOM文件 }
FreeLibrary(hLibraryHandle);
end;
end;
function YzListViewColumnCount(mHandle: THandle): Integer;
begin
Result := Header_GetItemCount(ListView_GetHeader(mHandle));
end; { ListViewColumnCount }
function YzGetListViewText(mHandle: THandle; mStrings: TStrings): Boolean;
var
vColumnCount: Integer;
vItemCount: Integer;
I, J: Integer;
vBuffer: array[0..255] of Char;
vProcessId: DWORD;
vProcess: THandle;
vPointer: Pointer;
vNumberOfBytesRead: Cardinal;
S: string; vItem: TLVItem;
begin
Result := False;
if not Assigned(mStrings) then Exit;
vColumnCount := YzListViewColumnCount(mHandle);
if vColumnCount <= 0 then Exit;
vItemCount := ListView_GetItemCount(mHandle);
GetWindowThreadProcessId(mHandle, @vProcessId);
vProcess := OpenProcess(PROCESS_VM_OPERATION or PROCESS_VM_READ
or PROCESS_VM_WRITE, False, vProcessId);
vPointer := VirtualAllocEx(vProcess, nil, 4096, MEM_RESERVE or MEM_COMMIT,
PAGE_READWRITE);
mStrings.BeginUpdate;
try
mStrings.Clear;
for I := 0 to vItemCount - 1 do
begin
S := '';
for J := 0 to vColumnCount - 1 do
begin
with vItem do
begin
mask := LVIF_TEXT;
iItem := I;
iSubItem := J;
cchTextMax := SizeOf(vBuffer);
pszText := Pointer(Cardinal(vPointer) + SizeOf(TLVItem));
end;
WriteProcessMemory(vProcess, vPointer, @vItem,
SizeOf(TLVItem), vNumberOfBytesRead);
SendMessage(mHandle, LVM_GETITEM, I, lparam(vPointer));
ReadProcessMemory(vProcess, Pointer(Cardinal(vPointer) + SizeOf(TLVItem)),
@vBuffer[0], SizeOf(vBuffer), vNumberOfBytesRead);
S := S + #9 + vBuffer;
end;
Delete(S, 1, 1);
mStrings.Add(S);
end;
finally
VirtualFreeEx(vProcess, vPointer, 0, MEM_RELEASE);
CloseHandle(vProcess); mStrings.EndUpdate;
end;
Result := True;
end; { GetListViewText }
{ 删除目录树 }
function YzDeleteDirectoryTree(Path: string): boolean;
var
SearchRec: TSearchRec;
SFI: string;
begin
Result := False;
if (Path = '') or (not DirectoryExists(Path)) then exit;
if Path[length(Path)] <> '/' then Path := Path + '/';
SFI := Path + '*.*';
if FindFirst(SFI, faAnyFile, SearchRec) = 0 then
begin
repeat
begin
if (SearchRec.Name = '.') or (SearchRec.Name = '..') then
Continue;
if (SearchRec.Attr and faDirectory <> 0) then
begin
if not YzDeleteDirectoryTree(Path + SearchRec.name) then
Result := FALSE;
end
else
begin
FileSetAttr(Path + SearchRec.Name, 128);
DeleteFile(Path + SearchRec.Name);
end;
end
until FindNext(SearchRec) <> 0;
FindClose(SearchRec);
end;
FileSetAttr(Path, 0);
if RemoveDir(Path) then
Result := TRUE
else
Result := FALSE;
end;
{ Jpg格式转换为bmp格式 }
function JpgToBmp(Jpg: TJpegImage): TBitmap;
begin
Result := nil;
if Assigned(Jpg) then
begin
Result := TBitmap.Create;
Jpg.DIBNeeded;
Result.Assign(Jpg);
end;
end;
{ 设置程序自启动函数 }
function YzSetAutoRun(AFilePath: string;AFlag: Boolean): boolean;
var
AMainFName: string;
Reg: TRegistry;
begin
Result := true;
AMainFName := YzGetMainFileName(AFilePath);
Reg := TRegistry.Create;
Reg.RootKey := HKEY_LOCAL_MACHINE;
try
Reg.OpenKey('SOFTWARE/Microsoft/Windows/CurrentVersion/Run', True);
if AFlag = False then { 取消自启动 }
Reg.DeleteValue(AMainFName)
else { 设置自启动 }
Reg.WriteString(AMainFName, '"' + AFilePath + '"')
except
Result := False;
end;
Reg.CloseKey;
Reg.Free;
end;
{ 检测URL地址是否有效 }
function YzCheckUrl(url: string): Boolean;
var
hSession, hfile, hRequest: HINTERNET;
dwindex, dwcodelen: dword;
dwcode: array[1..20] of Char;
res: PChar;
begin
Result := False;
try
if Pos('http://',LowerCase(url)) = 0 then url := 'http://' + url;
{ Open an internet session }
hSession:=InternetOpen('InetURL:/1.0',INTERNET_OPEN_TYPE_PRECONFIG,nil,nil, 0);
if Assigned(hsession) then
begin
hfile := InternetOpenUrl(hsession, PChar(url), nil, 0,INTERNET_FLAG_RELOAD, 0);
dwIndex := 0;
dwCodeLen := 10;
HttpQueryInfo(hfile,HTTP_QUERY_STATUS_CODE,@dwcode,dwcodeLen,dwIndex);
res := PChar(@dwcode);
Result := (res = '200') or (res = '302');
if Assigned(hfile) then InternetCloseHandle(hfile);
InternetCloseHandle(hsession);
end;
except
end;
end;
{ 获取程序可执行文件名 }
function YzGetExeFName: string;
begin
Result := ExtractFileName(Application.ExeName);
end;
{ 目录浏览对话框函数 }
function YzBrowseFolder(AOwer: TWinControl;ATitle: string):string;
var
Info: TBrowseInfo;
Dir: array[0..260] of char;
ItemId: PItemIDList;
begin
with Info do
begin
hwndOwner := AOwer.Handle;
pidlRoot := nil;
pszDisplayName := nil;
lpszTitle := PChar(ATitle);
ulFlags := 0;
lpfn := nil;
lParam := 0;
iImage := 0;
end;
ItemId := SHBrowseForFolder(Info);
SHGetPathFromIDList(ItemId,@Dir);
Result := string(Dir);
end;
{ 重启计算机 }
function YzShutDownSystem(AFlag: Integer):BOOL;
var
hProcess,hAccessToken: THandle;
LUID_AND_ATTRIBUTES: TLUIDAndAttributes;
TOKEN_PRIVILEGES: TTokenPrivileges;
BufferIsNull: DWORD;
Const
SE_SHUTDOWN_NAME='SeShutdownPrivilege';
begin
hProcess:=GetCurrentProcess();
OpenProcessToken(hprocess, TOKEN_ADJUST_PRIVILEGES+TOKEN_QUERY, hAccessToken);
LookupPrivilegeValue(Nil, SE_SHUTDOWN_NAME, LUID_AND_ATTRIBUTES.Luid);
LUID_AND_ATTRIBUTES.Attributes := SE_PRIVILEGE_ENABLED;
TOKEN_PRIVILEGES.PrivilegeCount := 1;
TOKEN_PRIVILEGES.Privileges[0] := LUID_AND_ATTRIBUTES;
BufferIsNull := 0;
AdjustTokenPrivileges(hAccessToken, False, TOKEN_PRIVILEGES, sizeof(
TOKEN_PRIVILEGES) ,Nil, BufferIsNull);
Result := ExitWindowsEx(AFlag, 0);
end;
{ 程序运行后删除自身 }
procedure YzDeleteSelf;
var
hModule: THandle;
buff: array[0..255] of Char;
hKernel32: THandle;
pExitProcess, pDeleteFileA, pUnmapViewOfFile: Pointer;
begin
hModule := GetModuleHandle(nil);
GetModuleFileName(hModule, buff, sizeof(buff));
CloseHandle(THandle(4));
hKernel32 := GetModuleHandle('KERNEL32');
pExitProcess := GetProcAddress(hKernel32, 'ExitProcess');
pDeleteFileA := GetProcAddress(hKernel32, 'DeleteFileA');
pUnmapViewOfFile := GetProcAddress(hKernel32, 'UnmapViewOfFile');
asm
LEA EAX, buff
PUSH 0
PUSH 0
PUSH EAX
PUSH pExitProcess
PUSH hModule
PUSH pDeleteFileA
PUSH pUnmapViewOfFile
RET
end;
end;
{ 程序重启 }
procedure YzAppRestart;
var
AppName : PChar;
begin
AppName := PChar(Application.ExeName) ;
ShellExecute(Application.Handle,'open', AppName, nil, nil, SW_SHOWNORMAL);
KillByPID(GetCurrentProcessId);
end;
{ 压缩Access数据库 }
function YzCompactAccessDB(const AFileName, APassWord: string): Boolean;
var
SPath, FConStr, TmpConStr: string;
SFile: array[0..254] of Char;
STempFileName: string;
JE: OleVariant;
function GetTempDir: string;
var
Buffer: array[0..MAX_PATH] of Char;
begin
ZeroMemory(@Buffer, MAX_PATH);
GetTempPath(MAX_PATH, Buffer);
Result := IncludeTrailingBackslash(StrPas(Buffer));
end;
begin
Result := False;
SPath := GetTempDir; { 取得Windows的Temp路径 }
{ 取得Temp文件名,Windows将自动建立0字节文件 }
GetTempFileName(PChar(SPath), '~ACP', 0, SFile);
STempFileName := SFile;
{ 删除Windows建立的0字节文件 }
if not DeleteFile(STempFileName) then Exit;
try
JE := CreateOleObject('JRO.JetEngine');
{ 压缩数据库 }
FConStr := 'Provider=Microsoft.Jet.OLEDB.4.0;' + 'Data Source=' + AFileName
+ ';Jet OLEDB:DataBase PassWord=' + APassWord;
TmpConStr := 'Provider=Microsoft.Jet.OLEDB.4.0;' + 'Data Source=' + STempFileName
+ ';Jet OLEDB:DataBase PassWord=' + APassWord;
JE.CompactDatabase(FConStr, TmpConStr);
{ 覆盖源数据库文件 }
Result := CopyFile(PChar(STempFileName), PChar(AFileName), False);
{ 删除临时文件 }
DeleteFile(STempFileName);
except
Application.MessageBox('压缩数据库失败!', '提示', MB_OK +
MB_ICONINFORMATION);
end;
end;
{ 标题:获取其他进程中TreeView的文本 }
function YzTreeNodeGetNext(mHandle: THandle; mTreeItem: HTreeItem): HTreeItem;
var
vParentID: HTreeItem;
begin
Result := nil;
if (mHandle <> 0) and (mTreeItem <> nil) then
begin
Result := TreeView_GetChild(mHandle, mTreeItem);
if Result = nil then
Result := TreeView_GetNextSibling(mHandle, mTreeItem);
vParentID := mTreeItem;
while (Result = nil) and (vParentID <> nil) do
begin
vParentID := TreeView_GetParent(mHandle, vParentID);
Result := TreeView_GetNextSibling(mHandle, vParentID);
end;
end;
end; { TreeNodeGetNext }
function YzTreeNodeGetLevel(mHandle: THandle; mTreeItem: HTreeItem): Integer;
var
vParentID: HTreeItem;
begin
Result := -1;
if (mHandle <> 0) and (mTreeItem <> nil) then
begin
vParentID := mTreeItem;
repeat
Inc(Result);
vParentID := TreeView_GetParent(mHandle, vParentID);
until vParentID = nil;
end;
end; { TreeNodeGetLevel }
function YzGetTreeViewText(mHandle: THandle; mStrings: TStrings): Boolean;
var
vItemCount: Integer;
vBuffer: array[0..255] of Char;
vProcessId: DWORD;
vProcess: THandle;
vPointer: Pointer;
vNumberOfBytesRead: Cardinal;
I: Integer;
vItem: TTVItem;
vTreeItem: HTreeItem;
begin
Result := False;
if not Assigned(mStrings) then Exit;
GetWindowThreadProcessId(mHandle, @vProcessId);
vProcess := OpenProcess(PROCESS_VM_OPERATION or PROCESS_VM_READ or
PROCESS_VM_WRITE, False, vProcessId);
vPointer := VirtualAllocEx(vProcess, nil, 4096, MEM_RESERVE or
MEM_COMMIT, PAGE_READWRITE);
mStrings.BeginUpdate;
try
mStrings.Clear;
vItemCount := TreeView_GetCount(mHandle);
vTreeItem := TreeView_GetRoot(mHandle);
for I := 0 to vItemCount - 1 do
begin
with vItem do begin
mask := TVIF_TEXT; cchTextMax := SizeOf(vBuffer);
pszText := Pointer(Cardinal(vPointer) + SizeOf(vItem));
hItem := vTreeItem;
end;
WriteProcessMemory(vProcess, vPointer, @vItem, SizeOf(vItem),
vNumberOfBytesRead);
SendMessage(mHandle, TVM_GETITEM, 0, lparam(vPointer));
ReadProcessMemory(vProcess, Pointer(Cardinal(vPointer) + SizeOf(TLVItem)),
@vBuffer[0], SizeOf(vBuffer), vNumberOfBytesRead);
mStrings.Add(StringOfChar(#9, YzTreeNodeGetLevel(mHandle, vTreeItem)) + vBuffer);
vTreeItem := YzTreeNodeGetNext(mHandle, vTreeItem);
end;
finally
VirtualFreeEx(vProcess, vPointer, 0, MEM_RELEASE);
CloseHandle(vProcess); mStrings.EndUpdate;
end;
Result := True;
end; { GetTreeViewText }
{ 获取其他进程中ListBox和ComboBox的内容 }
function YzGetListBoxText(mHandle: THandle; mStrings: TStrings): Boolean;
var
vItemCount: Integer;
I: Integer;
S: string;
begin
Result := False;
if not Assigned(mStrings) then Exit;
mStrings.BeginUpdate;
try
mStrings.Clear;
vItemCount := SendMessage(mHandle, LB_GETCOUNT, 0, 0);
for I := 0 to vItemCount - 1 do
begin
SetLength(S, SendMessage(mHandle, LB_GETTEXTLEN, I, 0));
SendMessage(mHandle, LB_GETTEXT, I, Integer(@S[1]));
mStrings.Add(S);
end;
SetLength(S, 0);
finally
mStrings.EndUpdate;
end;
Result := True;
end; { GetListBoxText }
function YzGetComboBoxText(mHandle: THandle; mStrings: TStrings): Boolean;
var
vItemCount: Integer;
I: Integer;
S: string;
begin
Result := False;
if not Assigned(mStrings) then Exit;
mStrings.BeginUpdate;
try
mStrings.Clear;
vItemCount := SendMessage(mHandle, CB_GETCOUNT, 0, 0);
for I := 0 to vItemCount - 1 do
begin
SetLength(S, SendMessage(mHandle, CB_GETLBTEXTLEN, I, 0));
SendMessage(mHandle, CB_GETLBTEXT, I, Integer(@S[1]));
mStrings.Add(S);
end;
SetLength(S, 0);
finally
mStrings.EndUpdate;
end;
Result := True;
end; { GetComboBoxText }
{ 获取本地Application Data目录路径 }
function YzLocalAppDataPath : string;
const
SHGFP_TYPE_CURRENT = 0;
var
Path: array [0..MAX_PATH] of char;
begin
SHGetFolderPath(0, CSIDL_LOCAL_APPDATA, 0, SHGFP_TYPE_CURRENT, @path[0]) ;
Result := Path;
end;
{ 获取Windows当前登录的用户名 }
function YzGetWindwosUserName: String;
var
pcUser: PChar;
dwUSize: DWORD;
begin
dwUSize := 21;
result := '';
GetMem(pcUser, dwUSize);
try
if Windows.GetUserName(pcUser, dwUSize) then
Result := pcUser
finally
FreeMem(pcUser);
end;
end;
{-------------------------------------------------------------
功 能: delphi 枚举托盘图标
参 数: AFindList: 返回找到的托盘列表信息
返回值: 成功为True,反之为False
备 注: 返回的格式为: 位置_名称_窗口句柄_进程ID
--------------------------------------------------------------}
function YzEnumTrayNotifyWnd(AFindList: TStringList;ADestStr: string): BOOL;
var
wd: HWND;
wtd: HWND;
wd1: HWND;
pid: DWORD;
hd: THandle;
num, i: integer;
n: ULONG;
p: TTBBUTTON;
pp: ^TTBBUTTON;
x: string;
name: array[0..255] of WCHAR;
whd, proid: ulong;
temp: string;
sp: ^TTBBUTTON;
_sp: TTBButton;
begin
Result := False;
wd := FindWindow('Shell_TrayWnd', nil);
if (wd = 0) then Exit;
wtd := FindWindowEx(wd, 0, 'TrayNotifyWnd', nil);
if (wtd = 0) then Exit;
wtd := FindWindowEx(wtd, 0, 'SysPager', nil);
if (wtd = 0) then Exit;
wd1 := FindWindowEx(wtd, 0, 'ToolbarWindow32', nil);
if (wd1 = 0) then Exit;
pid := 0;
GetWindowThreadProcessId(wd1, @pid);
if (pid = 0) then Exit;
hd := OpenProcess(PROCESS_ALL_ACCESS, true, pid);
if (hd = 0) then Exit;
num := SendMessage(wd1, TB_BUTTONCOUNT, 0, 0);
sp := @_sp;
for i := 0 to num do
begin
SendMessage(wd1, TB_GETBUTTON, i, integer(sp));
pp := @p;
ReadProcessMemory(hd, sp, pp, sizeof(p), n);
name[0] := Char(0);
if (Cardinal(p.iString) <> $FFFFFFFF) then
begin
try
ReadProcessMemory(hd, pointer(p.iString), @name, 255, n);
name[n] := Char(0);
except
end;
temp := name;
try
whd := 0;
ReadProcessMemory(hd, pointer(p.dwData), @whd, 4, n);
except
end;
proid := 0;
GetWindowThreadProcessId(whd, @proid);
AFindList.Add(Format('%d_%s_%x_%x', [i, temp, whd, proid]));
if CompareStr(temp, ADestStr) = 0 then Result := True;
end;
end;
end;
{ 获取SQL Server用户数据库列表 }
procedure YzGetSQLServerDBList(ADBHostIP, ALoginPwd: string;ADBList: TStringList);
var
PQuery: TADOQuery;
ConnectStr: string;
begin
ConnectStr := 'Provider=SQLOLEDB.1;Password=' + ALoginPwd
+ ';Persist Security Info=True;User ID=sa;Initial Catalog=master'
+ ';Data Source=' + ADBHostIP;
ADBList.Clear;
PQuery := TADOQuery.Create(nil);
try
PQuery.ConnectionString := ConnectStr;
PQuery.SQL.Text:='select name from sysdatabases where dbid > 6';
PQuery.Open;
while not PQuery.Eof do
begin
ADBList.add(PQuery.Fields[0].AsString);
PQuery.Next;
end;
finally
PQuery.Free;
end;
end;
{ 检测数据库中是否存在给定的表 }
procedure YzGetTableList(ConncetStr: string;ATableList: TStringList);
var
FConnection: TADOConnection;
begin
FConnection := TADOConnection.Create(nil);
try
FConnection.LoginPrompt := False;
FConnection.Connected := False;
FConnection.ConnectionString := ConncetStr;
FConnection.Connected := True;
FConnection.GetTableNames(ATableList, False);
finally
FConnection.Free;
end;
end;
{ 将域名解释成IP地址 }
function YzDomainToIP(HostName: string): string;
type
tAddr = array[0..100] of PInAddr;
pAddr = ^tAddr;
var
I: Integer;
WSA: TWSAData;
PHE: PHostEnt;
P: pAddr;
begin
Result := '';
WSAStartUp($101, WSA);
try
PHE := GetHostByName(pChar(HostName));
if (PHE <> nil) then
begin
P := pAddr(PHE^.h_addr_list);
I := 0;
while (P^[I] <> nil) do
begin
Result := (inet_nToa(P^[I]^));
Inc(I);
end;
end;
except
end;
WSACleanUp;
end;
{ 移去系统托盘失效图标 }
procedure YzRemoveDeadIcons();
var
hTrayWindow: HWND;
rctTrayIcon: TRECT;
nIconWidth, nIconHeight:integer;
CursorPos: TPoint;
nRow, nCol: Integer;
Begin
//Get tray window handle and bounding rectangle
hTrayWindow := FindWindowEx(FindWindow('Shell_TrayWnd ', nil), 0, 'TrayNotifyWnd ', nil);
if Not (GetWindowRect(hTrayWindow, rctTrayIcon)) then Exit;
//Get small icon metrics
nIconWidth := GetSystemMetrics(SM_CXSMICON);
nIconHeight := GetSystemMetrics(SM_CYSMICON);
//Save current mouse position }
GetCursorPos(CursorPos);
//Sweep the mouse cursor over each icon in the tray in both dimensions
for nRow := 0 To ((rctTrayIcon.bottom - rctTrayIcon.top) div nIconHeight) Do
Begin
for nCol := 0 To ((rctTrayIcon.right - rctTrayIcon.left) div nIconWidth) Do
Begin
SetCursorPos(rctTrayIcon.left + nCol * nIconWidth + 5,
rctTrayIcon.top + nRow * nIconHeight + 5);
Sleep(0);
end;
end;
//Restore mouse position
SetCursorPos(CursorPos.x, CursorPos.x);
//Redraw tray window(to fix bug in multi-line tray area)
RedrawWindow(hTrayWindow, nil, 0, RDW_INVALIDATE Or RDW_ERASE Or RDW_UPDATENOW);
end;
{ 转移程序占用内存至虚拟内存 }
procedure YzClearMemory;
begin
if Win32Platform = VER_PLATFORM_WIN32_NT then
begin
SetProcessWorkingSetSize(GetCurrentProcess, $FFFFFFFF, $FFFFFFFF);
Application.ProcessMessages;
end;
end;
{ 检测允许试用的天数是否已到期 }
function YzCheckTrialDays(AllowDays: Integer): Boolean;
var
Reg_ID, Pre_ID: TDateTime;
FRegister: TRegistry;
begin
{ 初始化为试用没有到期 }
Result := True;
FRegister := TRegistry.Create;
try
with FRegister do
begin
RootKey := HKEY_LOCAL_MACHINE;
if OpenKey('Software/Microsoft/Windows/CurrentSoftware/'
+ YzGetMainFileName(Application.ExeName), True) then
begin
if ValueExists('DateTag') then
begin
Reg_ID := ReadDate('DateTag');
if Reg_ID = 0 then Exit;
Pre_ID := ReadDate('PreDate');
{ 允许使用的时间到 }
if ((Reg_ID <> 0) and (Now - Reg_ID > AllowDays)) or
(Pre_ID <> Reg_ID) or (Reg_ID > Now) then
begin
{ 防止向前更改日期 }
WriteDateTime('PreDate', Now + 20000);
Result := False;
end;
end
else
begin
{ 首次运行时保存初始化数据 }
WriteDateTime('PreDate', Now);
WriteDateTime('DateTag', Now);
end;
end;
end;
finally
FRegister.Free;
end;
end;
{ 指定长度的随机小写字符串函数 }
function YzRandomStr(aLength: Longint): string;
var
X: Longint;
begin
if aLength <= 0 then exit;
SetLength(Result, aLength);
for X := 1 to aLength do
Result[X] := Chr(Random(26) + 65);
Result := LowerCase(Result);
end;
end.
该日志标签: 代码