1. 如何获得zlib
zlib的主页是:http://www.zlib.net/
2. 用VC++6.0打开
把下载的源代码解压打开,VC6.0的工程已经建好了,在\projects\visualc6. 双击zlib.dsw, 可以在VC++6.0中看到里面有3个工程: zlib 是库文件(编译设置选中 win32 lib debug / release), 工程example 是如何使用 zlib.lib 的示例, 工程minigzip 是如何用 zlib 提供的函数读写.gz文件的示例(*.gz的文件一般Linux下比较常用).
3. 如何加入到我的工程
编译好 zlib.lib 后, 你就得到了调用一个静态库所需要的所有文件了(zlib.lib, zlib.h, zconf.h). 如何调用静态库不用我说了吧.
4. 用zlib能干什么
先来看看 zlib 都提供了那些函数, 都在zlib.h中,看到一堆宏不要晕,其实都是为了兼容各种编译器和一些类型定义.死死抓住那些主要的函数的原型声明就不会受到这些东西的影响了.
关键的函数有那么几个:
(1)int compress (Bytef *dest, uLongf *destLen, const Bytef *source, uLong sourceLen);
把源缓冲压缩成目的缓冲, 就那么简单, 一个函数搞定
(2) int compress2 (Bytef *dest, uLongf *destLen,const Bytef *source, uLong sourceLen,int level);
功能和上一个函数一样,都一个参数可以指定压缩质量和压缩数度之间的关系(0-9)不敢肯定这个参数的话不用太在意它,明白一个道理就好了: 要想得到高的压缩比就要多花时间
(3) uLong compressBound (uLong sourceLen);
计算需要的缓冲区长度. 假设你在压缩之前就想知道你的产度为 sourcelen 的数据压缩后有多大, 可调用这个函数计算一下,这个函数并不能得到精确的结果,但是它可以保证实际输出长度肯定小于它计算出来的长度
(4) int uncompress (Bytef *dest, uLongf *destLen,const Bytef *source, uLong sourceLen);
解压缩(看名字就知道了:)
(5) deflateInit() + deflate() + deflateEnd()
3个函数结合使用完成压缩功能,具体用法看 example.c 的 test_deflate()函数. 其实 compress() 函数内部就是用这3个函数实现的(工程 zlib 的 compress.c 文件)
(6) inflateInit() + inflate() + inflateEnd()
和(5)类似,完成解压缩功能.
(7) gz开头的函数. 用来操作*.gz的文件,和文件stdio调用方式类似. 想知道怎么用的话看example.c 的 test_gzio() 函数,很easy.
(8) 其他诸如获得版本等函数就不说了.
总结: 其实只要有了compress() 和uncompress() 两个函数,在大多数应用中就足够了.
//首先创建dll~~这个没什么说的~~我也是抄习论坛里某位高手的(不好意思不记得名字了
)~
library dllProject1;
{ Important note about DLL memory management: ShareMem must be the
first unit in your library's USES clause AND your project's (select
Project-View Source) USES clause if your DLL exports any procedures or
functions that pass strings as parameters or function results. This
applies to all strings passed to and from your DLL--even those that
are nested in records and classes. ShareMem is the interface unit to
the BORLNDMM.DLL shared memory manager, which must be deployed along
with your DLL. To avoid using BORLNDMM.DLL, pass string information
using PChar or ShortString parameters. }
uses
Windows,
Messages,
SysUtils,
Variants,
Classes,
Graphics,
Controls,
Forms,
Dialogs,
StrUtils,
dllUnit1 in 'dllUnit1.pas' {dllfrom1},
threadUnit1 in 'threadUnit1.pas';
var
hhk:HHOOK=0;
homd:pointer;
hthread:thandle;
//--------------------------勾子子程-----------------------------------
function hookpro(ncode:integer;wparam:WPARAM;lparam:LPARAM):LRESULT;stdcall;
label
toexit;
begin
if ncode<0 then goto toexit;
if ncode<>HC_ACTION then goto toexit;
if (LPARAM and $80000000)=0 then goto toexit;
if WPARAM=VK_F12 then
begin
if dllfrom1<>nil then
begin
if dllfrom1.Visible=true then
dllfrom1.hide
else
dllfrom1.Show;
end
else
dllfrom1:=Tdllfrom1.Create(application);
end;
toexit:
result:=CallNexthookex(hhk,ncode,wparam,lparam);
end;
//--------------------------安装勾子-----------------------------------
function hookon(han:hwnd):longint;stdcall;export;
begin
hthread:=getwindowthreadprocessid(han,homd);
if hthread<>0 then hhk:=setwindowshookex(WH_KEYBOARD,@HOOKPRO,hinstance,hthread);
result:=hhk;
end;
//--------------------------关闭勾子-----------------------------------
function hookoff():boolean;stdcall;export;
begin
if hhk<>0 then
begin
unhookwindowshookex(hhk);
hhk:=0;
result:=true;
end
else
result:=false;
end;
{$R *.res}
exports hookon,hookoff;
begin
end.
再下来就是在DLL里创建一个窗口~这个窗口就是我们注入后在游戏中呼出的窗口~游戏的主要功能都在这个窗口中实现;
unit dllUnit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
Tdllfrom1 = class(TForm)
Label1: TLabel;
Label2: TLabel;
Button1: TButton;
Button2: TButton;
Button3: TButton;
procedure Button1Click(Sender: TObject);
procedure hp();
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
dllfrom1: Tdllfrom1;
hhp:boolean=false;
baseaddr:pointer;
const
jz:cardinal=$981b34;
implementation
uses threadunit1;
var
thread1:rw;//声明线程变量~~
{$R *.dfm}
//-----------------------------取人名-------------------------------------------
procedure Tdllfrom1.Button1Click(Sender: TObject);
var
name:string;
name1:dword;
begin
name1:=pdword(jz)^;
name1:=pdword(name1+$1c)^;
name1:=pdword(name1+$24)^;
name1:=pdword(name1+$3d8)^;
name:=pwidechar(name1);
dllfrom1.caption:=name;
end;
//-----------------取人物HP过程-------------------------------------------
procedure Tdllfrom1.hp();
var
rw_hp:cardinal;
begin
while hhp do
begin
rw_hp:=pdword(pdword(pdword(pdword($981b34)^+$1c)^+$24)^+$260)^;
label2.caption:=inttostr(rw_hp);
sleep(1000);
end;
end;
//----------------------汇篇取人物血-------------------------------------------
//--------------------------------加载人物线程显血-----------------------------
procedure Tdllfrom1.Button2Click(Sender: TObject);
begin
if hhp=false then
begin
hhp:=true;
thread1:=rw.create;//创建线程~~
end else
hhp:=false;
end;
{procedure Tdllfrom1.Button3Click(Sender: TObject);//这个不用看~想实现HOOK人物HP~但没成功(这个太难了~看得
)~在论坛上听兔哥说在这篇文章里看~~http://www.ghoffice.com/bbs/read-htm-tid-63001-keyword-%D2%D1%B7%E8%BF%F1.html
var
dllmodule:Hmodule;
begin
//dllmodule:=GetModuleHandle(Pchar('dllProject1.dll'));
//BaseAddr:=Pointer(GetProcAddress(DllModule,PChar('jx1')));
pbyte($00456088)^:=$e8;
//pdword($00456089)^:=baseaddr;
pbyte($0045608e)^:=$90;
end;}
end.
接下来再创建一个工程~~窗体上有一个列表框各两个按钮~程序一启动就自动读取游戏人名显示在列表框中,选中后点'开始游戏'按钮注入~~在游戏中按F12呼出开始在dll中创建的窗体;
unit Unit1
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
ListBox1: TListBox;
Button1: TButton;
Button2: TButton;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
function hookon(hwn:hwnd):longint;stdcall;external 'dllproject1.dll';
function hookoff():boolean;stdcall;external 'dllproject1.dll';
function GetTitle(hhwnd:thandle;param:pointer):boolean;stdcall;
var
id:array[0..10]of hwnd;
x:cardinal=0;
hhk:longint;
const
jz:dword=$00981b34;
implementation
{$R *.dfm}
//enumwindows函数的参数~用来读出所有武林窗体的句柄~并显示游戏人名;
function GetTitle(hhwnd:thandle;param:pointer):boolean;stdcall;
var
text:string;
dd:cardinal;
hid:thandle;
name:cardinal;
nname:array[0..15]of widechar;
begin
setlength(text,100);
getwindowtext(hhwnd,pchar(text),100);
if pchar(text)='Element Client' then//找到是武林窗体的就写入列表框
begin
id[x]:=hhwnd;//把窗口句柄保存在数组ID中
x:=x+1;
getwindowthreadprocessid(hhwnd,@dd);
hid:=openprocess(PROCESS_ALL_ACCESS,false,dd);
readprocessmemory(hid,pointer(jz),@name,4,dd);
readprocessmemory(hid,pointer(name+$1c),@name,4,dd);//取人名
readprocessmemory(hid,pointer(name+$24),@name,4,dd);
readprocessmemory(hid,pointer(name+$3d8),@name,4,dd);
readprocessmemory(hid,pointer(name+0),@nname,16,dd);
form1.listbox1.items.Add(nname);
closehandle(hid);//关闭打开的进程;
end;
end;
//------------------窗口载入----------------------------------------------------
procedure TForm1.FormCreate(Sender: TObject);
begin
listbox1.Items.Clear;
enumwindows(@gettitle,0);
end;
//-------------------刷新列表框-------------------------------------------------
procedure TForm1.Button1Click(Sender: TObject);
begin
listbox1.Items.Clear;
enumwindows(@gettitle,0);
end;
//------------------------程序退出---------------------------------------------
procedure TForm1.FormDestroy(Sender: TObject);
begin
if hhk<>0 then hookoff();
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
if hhk<>0 then hookoff();
end;
//-----------------------注入--------------------------------------------------
procedure TForm1.Button2Click(Sender: TObject);
var
i:integer;
begin
if listbox1.Items.Count>0 then//列表框总数大于0~也就是要打开游戏~
begin
if listbox1.itemindex<>-1 then//已经选择了要注入的游戏~
begin
i:=listbox1.ItemIndex;
hhk:=hookon(id[i]);//提取数组中的句柄~注入游戏~
showmessage('已注入,请在游戏内按下F12');
end
else
showmessage('请选择要注入的游戏');
end
else
showmessage('请打开游戏');
end;
end.
OK了~~下面是在DLL窗体中创建的一个线程~用来显示人物HP~这个没什么说的~.
unit threadUnit1;
interface
uses
Classes;
type
rw = class(TThread)
private
{ Private declarations }
protected
procedure Execute; override;
public
constructor create;
end;
implementation
{ Important: Methods and properties of objects in visual components can only be
used in a method called using Synchronize, for example,
Synchronize(UpdateCaption);
and UpdateCaption could look like,
procedure rw.UpdateCaption;
begin
Form1.Caption := 'Updated in a thread';
end; }
{ rw }
uses dllunit1;
constructor rw.create;
begin
inherited create(false);
end;
procedure rw.Execute;
begin
{ Place thread code here }
dllfrom1.hp;//线程创建调用HP函数显血`~
end;
end.
|
1-1 多线程的基本概念 多线程带来如下好处:(自己阅读) 多线程的两个概念: 1)进程:也称任务,程序载入内存,并分配资源,称为“一个进程”。 2)线程:是程序的执行单位(线程本身并不包括程序代码,真正拥有代码的是进程),每个进程至少包括一个线程,称为主线程,一个进程如果有多个线程,就可以共享同一进程的资源,并可以并发执行。 请注意: 1-2 Tthread 对象 虽然Windows 提供了比较多的多线程设计的API 函数,但是直接使用API 函数一方面极其不方便,而且使用不当还容易出错。为解决这个问题,Borland 公司率先推出了一种Tthread 对象,来解决多线程设计上的困难,简化了多线程问题的处理。 构造线程: constructor Create(CreateSuspended:boolean) 其中:CreateSuspended=true 也可以用如下方法 inheried Create(CreateSuspended:boolean)
(把线程挂起的次数加一) 唤醒线程: resume (注意:注意这个属性是把线程挂起的次数减一,当次数为0 时,即唤醒。也就是说,线程挂起多少次,唤醒也需要多少次。同时挂起的时候将保持线程的地址指针不变,所以线程挂起后再唤醒,将从挂起的地方开始运行) 析构(清除线程所占用的内存): destroy 终止线程(后面会具体讨论): Terminate 二、线程应用的简单例子: 下面通过一个例子说明上述方法的应用。我们知道,循环是独占性最强的运行方式之一,现在希望建立两个线程对象,实现循环的并行运行。具体方法如下: File---New---Thread Object 这就自动在主Form中建立了一个线程单元(在对话框里写上线程名字),默认的名字是Unit2。同样方法建立第二个线程单元Unit3。 procedure Object.Execute; end; 其中的程序是线程唤醒后自动执行的程序,也可以在里面调用其他自定义的过程和函数。这个过程的结束,意味着线程程序的结束。 type
假定我们给两个线程对象起的名字是: mymath1 这样在Unit1,应该作如下声明: {$R *.DFM} uses unit2,unit3; var thread1:mymath1; 这样在主线程,将可以通过这两个线程变量调用对应的线程方法。 在主线程区构造线程的方法是: thread1:=mymath1.create; 挂起: thread1.suspend; 唤醒: thread1.resume; 析构: 这里需要说明的是,由于线程单元需要调用Form的Edit控件(对象),可以采用两种方法: 1)在线程单元定义一个TEdit对象,例如 edit4:Tedit; 在Execute过程内直接引用 但在Unit1中一定要在FormCreate过程里作一个赋值: procedure TForm1.FormCreate(Sender: TObject); 2)在第二个线程中首先声明调用Unti1,也就是要加上 这样就可以在该线程单元直接调用主Form的控件了,比如在Unit3中可以写: form1.edit2.text:=inttostr(i) 了解了这些基本规则,就可以写出比较复杂的多线程程序了。 这样,往往很多函数和对象在线程单元里不能使用,所以在必要时,应该根据需要User相应的单元,这个例程为了简单,把大部分常用的单元都拷过去了,这并不是推荐的办法,因为这样一来会使程序的垃圾过多,所以,一般要用什么拷什么。 三、常用的API 函数 在处理多线程问题的时候,也经常用到Windows提供的API 函数,需要说明的是,Tthread 对象内部封装的方法,其实主要也是调用API 函数,但是,考虑更全面,更安全。而直接调用API 函数,往往会因为运用不当,出现一些不应有的错误。所以,我个人以为,只要用Tthread 对象的方法能解决的,就不要直接调用API 函数,API 函数只应该在用在Tthread 对象方法解决不了的时候。 书上有这个函数应用的十分清晰的例子,可以自己阅读。 对应suspend(挂起)和resume(唤醒)的两个API 函数为: Function ResumeThread(hThread:Thandle):DWORD; 其中,Thandle被要求控制线程的句柄,函数调用成功,返回挂起的次数,调用不成功。则返回0xFFFFFFFF。 四、线程的终止和退出: 1)自动退出: 一个线程从Execute()过程中退出,即意味着线程的终止,此时将调用Windows的ExitThread()函数来清除线程所占用的堆栈。 2)受控退出: 利用线程对象的Terminate属性,可以由进程或者由其他线程控制线程的退出。只需要简单的调用该线程的Terminate方法,并设直线程对象的Terminate属性为True。
4) 利用挂起线程的方法(suspend) 利用挂起线程的suspend方法,后面跟个Free,也可以释放线程, 书上有相应的例子。
在多线程的情况下,一般要根据线程执行任务的重要性,给线程适当的优先级,一般如果量的线程同时申请CPU 时间,优先级高的线程优先。 在Windows下,给线程的优先级分为30级,而Delphi中Tthread 对象相对简单的把优先级分为七级。也就是在Tthread中声明了一个枚举类型TTthreadPriority: type TTthreadPriority(tpidle,tpLowest,tpLower,tpNormal, 分别对应的是最低(系统空闲时有效,-15),较低(-2),低(-1),正常(普通0),高(1),较高(2),最高(15)。 其中tpidle和tpTimecrital有些特殊,具体情况请阅读书上有关内容。 设置优先级可使用thread对象的priority属性: 这里给出了一个演示多线程优先级的实例: 由于Delphi 6.0的ADO 数据源控件内置了多线程能力,所以,在ADO模式下,使用多线程不需要做更多的工作。用两个ADOTable控件,分别连到两个数据库,并且分别通过DataSource控件,与数据帮定控件联系就可以了,这样就可以实现前后台处理数据库问题。 如果需要使用BDE 模式,那么多线程使用数据库,就要考虑Session的问题。在单线程时,每个数据源的建立就自动生成一个Session,这是这个数据源私有的关于数据库信息的文件。但多线程时,必须统一管理,所以在BDE 中专门提供了一个Tsession对象,它可以同时管理不同的Databas数据源对象。 数据库1---databas(2)----table(Qurey)(3)---datasource
同步机制,实际上是事件驱动机制,意思是让线程平时处于“休眠”状态,除非发生某个事件才触发。 一、使用Synchronize方法 这个方法用于访问VCL 主线程所管理的资源,其方法的应用是: procedure Theater.update; 这里通过 Synchronize使线程方法update同步。
在Delphi的IDE提供的构件中,有一些对象内部提供了线程的同步机制,工作线程可以直接使用这些控件,比如:Tfont,Tpen,TBitmap,TMetafile,Ticon等。另外,一个很重要的控件对象叫TCanvas,提供了一个Lock方法用于线程的同步,当一个线程使用此控件对象的时候,首先调用这个对象的Lock方法,然后对这个控件进行操作,完毕后再调用Unlock方法,释放对控间的控制权。 三、Waitfor方法 Function Waitfor(Const Astring:string):string; thread1.resume; 那么所有的线程都必须等待thread1运行完毕后才能运行,其中包括主线程,可以预想,由于thread1调用了主窗体的Edit控件,那么,在thread1运行中间,Edie1也不会显示。
Windows API函数提供了很多同步技术,下面简要介绍。 1)临界区 var 初始化: initializeCriticalSection(cs); 独占 EnterCriticalSection(cs); LeaveCriticalSection(CS); 使用临界区是比较方便而且概念比较清晰的的线程同步机制,应用比较广泛。 |
|
1-1 多线程的基本概念 多线程带来如下好处:(自己阅读) 多线程的两个概念: 1)进程:也称任务,程序载入内存,并分配资源,称为“一个进程”。 2)线程:是程序的执行单位(线程本身并不包括程序代码,真正拥有代码的是进程),每个进程至少包括一个线程,称为主线程,一个进程如果有多个线程,就可以共享同一进程的资源,并可以并发执行。 请注意: 1-2 Tthread 对象 虽然Windows 提供了比较多的多线程设计的API 函数,但是直接使用API 函数一方面极其不方便,而且使用不当还容易出错。为解决这个问题,Borland 公司率先推出了一种Tthread 对象,来解决多线程设计上的困难,简化了多线程问题的处理。 构造线程: constructor Create(CreateSuspended:boolean) 其中:CreateSuspended=true 也可以用如下方法 inheried Create(CreateSuspended:boolean)
(把线程挂起的次数加一) 唤醒线程: resume (注意:注意这个属性是把线程挂起的次数减一,当次数为0 时,即唤醒。也就是说,线程挂起多少次,唤醒也需要多少次。同时挂起的时候将保持线程的地址指针不变,所以线程挂起后再唤醒,将从挂起的地方开始运行) 析构(清除线程所占用的内存): destroy 终止线程(后面会具体讨论): Terminate 二、线程应用的简单例子: 下面通过一个例子说明上述方法的应用。我们知道,循环是独占性最强的运行方式之一,现在希望建立两个线程对象,实现循环的并行运行。具体方法如下: File---New---Thread Object 这就自动在主Form中建立了一个线程单元(在对话框里写上线程名字),默认的名字是Unit2。同样方法建立第二个线程单元Unit3。 procedure Object.Execute; end; 其中的程序是线程唤醒后自动执行的程序,也可以在里面调用其他自定义的过程和函数。这个过程的结束,意味着线程程序的结束。 type
假定我们给两个线程对象起的名字是: mymath1 这样在Unit1,应该作如下声明: {$R *.DFM} uses unit2,unit3; var thread1:mymath1; 这样在主线程,将可以通过这两个线程变量调用对应的线程方法。 在主线程区构造线程的方法是: thread1:=mymath1.create; 挂起: thread1.suspend; 唤醒: thread1.resume; 析构: 这里需要说明的是,由于线程单元需要调用Form的Edit控件(对象),可以采用两种方法: 1)在线程单元定义一个TEdit对象,例如 edit4:Tedit; 在Execute过程内直接引用 但在Unit1中一定要在FormCreate过程里作一个赋值: procedure TForm1.FormCreate(Sender: TObject); 2)在第二个线程中首先声明调用Unti1,也就是要加上 这样就可以在该线程单元直接调用主Form的控件了,比如在Unit3中可以写: form1.edit2.text:=inttostr(i) 了解了这些基本规则,就可以写出比较复杂的多线程程序了。 这样,往往很多函数和对象在线程单元里不能使用,所以在必要时,应该根据需要User相应的单元,这个例程为了简单,把大部分常用的单元都拷过去了,这并不是推荐的办法,因为这样一来会使程序的垃圾过多,所以,一般要用什么拷什么。 三、常用的API 函数 在处理多线程问题的时候,也经常用到Windows提供的API 函数,需要说明的是,Tthread 对象内部封装的方法,其实主要也是调用API 函数,但是,考虑更全面,更安全。而直接调用API 函数,往往会因为运用不当,出现一些不应有的错误。所以,我个人以为,只要用Tthread 对象的方法能解决的,就不要直接调用API 函数,API 函数只应该在用在Tthread 对象方法解决不了的时候。 书上有这个函数应用的十分清晰的例子,可以自己阅读。 对应suspend(挂起)和resume(唤醒)的两个API 函数为: Function ResumeThread(hThread:Thandle):DWORD; 其中,Thandle被要求控制线程的句柄,函数调用成功,返回挂起的次数,调用不成功。则返回0xFFFFFFFF。 四、线程的终止和退出: 1)自动退出: 一个线程从Execute()过程中退出,即意味着线程的终止,此时将调用Windows的ExitThread()函数来清除线程所占用的堆栈。 2)受控退出: 利用线程对象的Terminate属性,可以由进程或者由其他线程控制线程的退出。只需要简单的调用该线程的Terminate方法,并设直线程对象的Terminate属性为True。
4) 利用挂起线程的方法(suspend) 利用挂起线程的suspend方法,后面跟个Free,也可以释放线程, 书上有相应的例子。
在多线程的情况下,一般要根据线程执行任务的重要性,给线程适当的优先级,一般如果量的线程同时申请CPU 时间,优先级高的线程优先。 在Windows下,给线程的优先级分为30级,而Delphi中Tthread 对象相对简单的把优先级分为七级。也就是在Tthread中声明了一个枚举类型TTthreadPriority: type TTthreadPriority(tpidle,tpLowest,tpLower,tpNormal, 分别对应的是最低(系统空闲时有效,-15),较低(-2),低(-1),正常(普通0),高(1),较高(2),最高(15)。 其中tpidle和tpTimecrital有些特殊,具体情况请阅读书上有关内容。 设置优先级可使用thread对象的priority属性: 这里给出了一个演示多线程优先级的实例: 由于Delphi 6.0的ADO 数据源控件内置了多线程能力,所以,在ADO模式下,使用多线程不需要做更多的工作。用两个ADOTable控件,分别连到两个数据库,并且分别通过DataSource控件,与数据帮定控件联系就可以了,这样就可以实现前后台处理数据库问题。 如果需要使用BDE 模式,那么多线程使用数据库,就要考虑Session的问题。在单线程时,每个数据源的建立就自动生成一个Session,这是这个数据源私有的关于数据库信息的文件。但多线程时,必须统一管理,所以在BDE 中专门提供了一个Tsession对象,它可以同时管理不同的Databas数据源对象。 数据库1---databas(2)----table(Qurey)(3)---datasource
同步机制,实际上是事件驱动机制,意思是让线程平时处于“休眠”状态,除非发生某个事件才触发。 一、使用Synchronize方法 这个方法用于访问VCL 主线程所管理的资源,其方法的应用是: procedure Theater.update; 这里通过 Synchronize使线程方法update同步。
在Delphi的IDE提供的构件中,有一些对象内部提供了线程的同步机制,工作线程可以直接使用这些控件,比如:Tfont,Tpen,TBitmap,TMetafile,Ticon等。另外,一个很重要的控件对象叫TCanvas,提供了一个Lock方法用于线程的同步,当一个线程使用此控件对象的时候,首先调用这个对象的Lock方法,然后对这个控件进行操作,完毕后再调用Unlock方法,释放对控间的控制权。 三、Waitfor方法 Function Waitfor(Const Astring:string):string; thread1.resume; 那么所有的线程都必须等待thread1运行完毕后才能运行,其中包括主线程,可以预想,由于thread1调用了主窗体的Edit控件,那么,在thread1运行中间,Edie1也不会显示。
Windows API函数提供了很多同步技术,下面简要介绍。 1)临界区 var 初始化: initializeCriticalSection(cs); 独占 EnterCriticalSection(cs); LeaveCriticalSection(CS); 使用临界区是比较方便而且概念比较清晰的的线程同步机制,应用比较广泛。 |
|
1-1 多线程的基本概念 多线程带来如下好处:(自己阅读) 多线程的两个概念: 1)进程:也称任务,程序载入内存,并分配资源,称为“一个进程”。 2)线程:是程序的执行单位(线程本身并不包括程序代码,真正拥有代码的是进程),每个进程至少包括一个线程,称为主线程,一个进程如果有多个线程,就可以共享同一进程的资源,并可以并发执行。 请注意: 1-2 Tthread 对象 虽然Windows 提供了比较多的多线程设计的API 函数,但是直接使用API 函数一方面极其不方便,而且使用不当还容易出错。为解决这个问题,Borland 公司率先推出了一种Tthread 对象,来解决多线程设计上的困难,简化了多线程问题的处理。 构造线程: constructor Create(CreateSuspended:boolean) 其中:CreateSuspended=true 也可以用如下方法 inheried Create(CreateSuspended:boolean)
(把线程挂起的次数加一) 唤醒线程: resume (注意:注意这个属性是把线程挂起的次数减一,当次数为0 时,即唤醒。也就是说,线程挂起多少次,唤醒也需要多少次。同时挂起的时候将保持线程的地址指针不变,所以线程挂起后再唤醒,将从挂起的地方开始运行) 析构(清除线程所占用的内存): destroy 终止线程(后面会具体讨论): Terminate 二、线程应用的简单例子: 下面通过一个例子说明上述方法的应用。我们知道,循环是独占性最强的运行方式之一,现在希望建立两个线程对象,实现循环的并行运行。具体方法如下: File---New---Thread Object 这就自动在主Form中建立了一个线程单元(在对话框里写上线程名字),默认的名字是Unit2。同样方法建立第二个线程单元Unit3。 procedure Object.Execute; end; 其中的程序是线程唤醒后自动执行的程序,也可以在里面调用其他自定义的过程和函数。这个过程的结束,意味着线程程序的结束。 type
假定我们给两个线程对象起的名字是: mymath1 这样在Unit1,应该作如下声明: {$R *.DFM} uses unit2,unit3; var thread1:mymath1; 这样在主线程,将可以通过这两个线程变量调用对应的线程方法。 在主线程区构造线程的方法是: thread1:=mymath1.create; 挂起: thread1.suspend; 唤醒: thread1.resume; 析构: 这里需要说明的是,由于线程单元需要调用Form的Edit控件(对象),可以采用两种方法: 1)在线程单元定义一个TEdit对象,例如 edit4:Tedit; 在Execute过程内直接引用 但在Unit1中一定要在FormCreate过程里作一个赋值: procedure TForm1.FormCreate(Sender: TObject); 2)在第二个线程中首先声明调用Unti1,也就是要加上 这样就可以在该线程单元直接调用主Form的控件了,比如在Unit3中可以写: form1.edit2.text:=inttostr(i) 了解了这些基本规则,就可以写出比较复杂的多线程程序了。 这样,往往很多函数和对象在线程单元里不能使用,所以在必要时,应该根据需要User相应的单元,这个例程为了简单,把大部分常用的单元都拷过去了,这并不是推荐的办法,因为这样一来会使程序的垃圾过多,所以,一般要用什么拷什么。 三、常用的API 函数 在处理多线程问题的时候,也经常用到Windows提供的API 函数,需要说明的是,Tthread 对象内部封装的方法,其实主要也是调用API 函数,但是,考虑更全面,更安全。而直接调用API 函数,往往会因为运用不当,出现一些不应有的错误。所以,我个人以为,只要用Tthread 对象的方法能解决的,就不要直接调用API 函数,API 函数只应该在用在Tthread 对象方法解决不了的时候。 书上有这个函数应用的十分清晰的例子,可以自己阅读。 对应suspend(挂起)和resume(唤醒)的两个API 函数为: Function ResumeThread(hThread:Thandle):DWORD; 其中,Thandle被要求控制线程的句柄,函数调用成功,返回挂起的次数,调用不成功。则返回0xFFFFFFFF。 四、线程的终止和退出: 1)自动退出: 一个线程从Execute()过程中退出,即意味着线程的终止,此时将调用Windows的ExitThread()函数来清除线程所占用的堆栈。 2)受控退出: 利用线程对象的Terminate属性,可以由进程或者由其他线程控制线程的退出。只需要简单的调用该线程的Terminate方法,并设直线程对象的Terminate属性为True。
4) 利用挂起线程的方法(suspend) 利用挂起线程的suspend方法,后面跟个Free,也可以释放线程, 书上有相应的例子。
在多线程的情况下,一般要根据线程执行任务的重要性,给线程适当的优先级,一般如果量的线程同时申请CPU 时间,优先级高的线程优先。 在Windows下,给线程的优先级分为30级,而Delphi中Tthread 对象相对简单的把优先级分为七级。也就是在Tthread中声明了一个枚举类型TTthreadPriority: type TTthreadPriority(tpidle,tpLowest,tpLower,tpNormal, 分别对应的是最低(系统空闲时有效,-15),较低(-2),低(-1),正常(普通0),高(1),较高(2),最高(15)。 其中tpidle和tpTimecrital有些特殊,具体情况请阅读书上有关内容。 设置优先级可使用thread对象的priority属性: 这里给出了一个演示多线程优先级的实例: 由于Delphi 6.0的ADO 数据源控件内置了多线程能力,所以,在ADO模式下,使用多线程不需要做更多的工作。用两个ADOTable控件,分别连到两个数据库,并且分别通过DataSource控件,与数据帮定控件联系就可以了,这样就可以实现前后台处理数据库问题。 如果需要使用BDE 模式,那么多线程使用数据库,就要考虑Session的问题。在单线程时,每个数据源的建立就自动生成一个Session,这是这个数据源私有的关于数据库信息的文件。但多线程时,必须统一管理,所以在BDE 中专门提供了一个Tsession对象,它可以同时管理不同的Databas数据源对象。 数据库1---databas(2)----table(Qurey)(3)---datasource
同步机制,实际上是事件驱动机制,意思是让线程平时处于“休眠”状态,除非发生某个事件才触发。 一、使用Synchronize方法 这个方法用于访问VCL 主线程所管理的资源,其方法的应用是: procedure Theater.update; 这里通过 Synchronize使线程方法update同步。
在Delphi的IDE提供的构件中,有一些对象内部提供了线程的同步机制,工作线程可以直接使用这些控件,比如:Tfont,Tpen,TBitmap,TMetafile,Ticon等。另外,一个很重要的控件对象叫TCanvas,提供了一个Lock方法用于线程的同步,当一个线程使用此控件对象的时候,首先调用这个对象的Lock方法,然后对这个控件进行操作,完毕后再调用Unlock方法,释放对控间的控制权。 三、Waitfor方法 Function Waitfor(Const Astring:string):string; thread1.resume; 那么所有的线程都必须等待thread1运行完毕后才能运行,其中包括主线程,可以预想,由于thread1调用了主窗体的Edit控件,那么,在thread1运行中间,Edie1也不会显示。
Windows API函数提供了很多同步技术,下面简要介绍。 1)临界区 var 初始化: initializeCriticalSection(cs); 独占 EnterCriticalSection(cs); LeaveCriticalSection(CS); 使用临界区是比较方便而且概念比较清晰的的线程同步机制,应用比较广泛。 |
TThread是一个抽象类,可以创建几个独立的线程。
类关系 TObject
在一个多线程的应用程序中创建一个TThread的后子类代表一个线程。每一新子类的TThread对象的实例是一个新的线程。从TThread派生的多线程实例可以构成Delphi的多线程应用程序。
当一个应用程序运行时,应用程序就被载入内存准备执行。此时,它成为包含一个或多个线程的进程,每个线程含有数据、代码和系统资源。线程执行应用程序的部分内容,并由系统分配CPU时间。同一进程的所有线程共享同一地址空间,可以访问进程的全局变量。线程通过以下工作改善应用的性能:管理多通信设备的输入。
区分任务的优先级。优先级高的处理紧急的任务。优先级低的处理其他任务。
以下是使用线程的一些建议:
同时跟踪太多的线程消耗CPU时间。对单处理器系统,一个进程最多有16个线程。
当多个线程更新相同的资源时,应使线程同步以避免冲突。
大多数访问VCL对象和更新窗体的方法必须从主VCL线程内部调用。
以下为创建和使用一个新线程的过程:
(1)单击File|New|Thread菜单项,创建一个包含对象的新单元,该对象源于TThread类。
(2)定义新线程对象和Create方法。
(3)通过插入线程执行时需要的代码定义线程对象和Execute方法。
(4)将使用VCL组件的任何调用传递给Synchronize方法,以避免多线程冲突。
属性列表
FreeOnTerminate 线程终止时该对象是否自动删除
Handle 包含线程句柄
Priority 确定该线程相对于进程中其他线程的优先级
ReturnValue 返回线程值
Suspended 指示一线程是否被挂起
Terminated 表明线程被要求终止
ThreadID 标识贯穿系统的线程
方法列表
~TThread 删除线程对象并释放其战用的内存空间
DoTerminate 产生一个OnTerminate事件
Execute 提供包含线程执行时所需代码的抽象方法
Resume 重新执行一个挂起的线程
Suspend 挂起一个运行中的线程
Synchronize 在主VCL线程中执行Method
Terminate 将Ternimated属性设置为True通知线程终止
TThread 创建一个线程对象的实例
WaitFor 等待线程终止并返回ReturnValue属性值
事件列表
OnTerminateExecute 方法已返回且该线程被删除前发生
属性
TThread::FreeOnTerminate
__property bool FreeOnTerminate = {read=FFreeOnTerminate,write=FFreeOnTerminate,nodefault};
确定当线程终止时,该线程对象是否自动删除。
FreeOnTerminate默认值为False,线程对象必须在代码中显示删除。
包含线程句柄。
当调用Win32API函数处理线程时,使用Handle.
TThread::Priority
__property TThreadPriority Priority = {read=GetPriority,write=SetPriority,nodefault};
确定该线程相对于进程中其他线程的优先级。
Priority属性为一枚举类型,其默认为tpNormal.
TThreadPriority类型定义了TThread组件的Priority属性的可能值,如下表所述。Windows根据优先级确定每一个线程的CPU周期。
_____________________________________________________________________
值 含义
_____________________________________________________________________
tpIdle 只有当系统空闲时该线程执行
tpLowest 线程优先级比正常低2点
tpLower 线程优先级比正常低1点
tpNormal 线程优先级为正常值
tpHigher 线程优先级比正常高1点
tpHighest 线程优先级比正常高2点
tpTimeCritical 线程优先级最高
TThread::ReturnValue
__property int ReturnValue = {read=FReturnValue,write=FReturnValue,nodefault};
返回线程值。
使用ReturnValue应用为其他线程指示其成功/失败或数字结果/输出。WaitFor方法返回存储在ReturnValue中的值。
TThread::Suspended
__property bool Suspended = {read=FSuspended,write=SetSuspended,nodefault};
指示一线程是否被挂起。
除非重新执行,否则被挂起的线程不会继续执行。若将Suspended设置为True将挂起一个线程;若设置为False,则继续执行该线程。
TThread::Terminated
__property bool Terminated = {read=FTerminated,nodefault};
表明线程被要求终止。Terminate方法将Terminated属性设置为True。
线程的Execute方法和任何Execute调用的方法将周期性地检查Terminated,当其为True时,将终止执行。
TThread::ThreadID
__property int ThreadID = {read=FhreadID,nodefault};
标识贯穿系统的线程。
当调用Win32API函数处理线程时,ThreadID将十分有用。
注意:ThreadID与Handle属性不同。
方法
TThread::~TThread
__fastcall virvual ~TThread(void);
删除线程对象并释放其战胜的内存空间。
在应用中不要调用~TThread。用delete替代。
~TThread通知线程终止,并在调用Destroy方法前等待该线程返回。
TThread::DoTerminate
virtual void __fastcall DoTerminate(void);
产生一个OnTerminate事件。
DoTerminate调用OnTerminate时间句柄,但并不终止该线程。
TThread::Execute
virtual void __fastcall Execute(void) =0;
提供包含线程执行时所需代码的抽象方法。
Execute查看Terminated属性值以决定该线程是否需要终止。
当CreateSuspended被设置为False,当调用Create时,一线程执行;在线程创建后先调用了Resume且CreateSuspended为True,一线程执行。
注意:不要在线程的Execute方法中直接调用
其他对象的属性和方法。应该将对其他对象的使用分成几个不同的过程,将其作为一个传递到Synchronize方法的参数分别调用。
TThread::Resume
void __fastcall Resume(void);
重新执行一个挂起的线程。
调用Suspend可以嵌套。因此调用Resume必须注意次序。
TThread::Suspend
void __fastcall Suspend(void);
挂起一个运行中的线程。
调用Resume可以继续运行。调用Suspend可以嵌套。因此调用Resume必须次序。
TThread::Synchronize
typedef void __fastcall(__closure* TThreadMethod)(void);
void __fastcall Synchronize (TThreadMethod&Method);
在主VCL线程中执行Method。
Synchronize方法由Method指定的调用被主VCL线程执行。
注意:当在主VCL线程中执行Method时,当前的线程被挂起。
TThread::Terminate
void __fastcall Terminate(void);
通过将Terminated属性设置为True通知线程终止。
线程的Execute方法以及Execute调用的任何方法应周期性的检查Terminated,当其为True时终止运行。
TThread::TThread
__fastcall TThread(bool CreateSuspended);
创建一个线程对象的实例。
在应用中不要直接使用TThread来创建线程。用new替代,传递CreateSuspended参数argument。若CreateSuspended为False,Execute被立即调用。若CreateSuspended为True,Execute直到Resume被调用后才被调用。
TThread::WaitFor
int __fastcall WaitFor(void);
等待线程终止并返回ReturnValue属性值。
直到线程终止时WaitFor才返回,因此线程一定是因为结束了Execute方法或因Terminated属性为了True而终止。如果该线程使用Synchronize,则不要在主VCL线程中调用WaitFor,否则或者引起系统死机,或者产生一个EThread异常。
Synchronize在允许该方法生效前等待主VCL线程进入信息回路。若主VCL线程已经调用了WaitFor,它将不会进入信息回路,Synchronize也永远不会返回。此时,TThread将产生一个EThread意外并使该线程终止;而且如果该意外没有被Execute方法截获,该应用也将终止。如果调用WaitFor时,Synchronize已经在VCL线程中等待,TThread将不会干预,应用程序将死机。
事件
TThread::OnTerminate
__property TNotifyEvent OnTerminate = {read=FOnTerminate,write=FOnTerminate};
当线程的Execute方法已经返回且在该线程被删除之前发生。
OnTerminate事件句柄在主VCL线程被调用。该线程对象也可在该事件中被释放。