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() 两个函数,在大多数应用中就足够了.

posted @ 2009-12-29 23:22 装配中的脑袋 阅读(1022) 评论(1) 编辑

 

//首先创建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.

posted @ 2009-11-07 11:42 装配中的脑袋 阅读(492) 评论(0) 编辑
procedure TForm1.Timer1Timer(Sender: TObject);
var
  WHandle:hwnd;
  hPID:hwnd;
  pHandle:hwnd;
  base,ybase,ybase1,gjms1,num,mapAddr,rnameAddr:dword;
  hp,maxhp,mp,maxmp,px,py,yhp,ymaxhp,ymp,ymaxmp:cardinal;
  map,rname,gjms:array[0..32]of char;
begin
WHandle := FindWindow('TFrmMain', 'legend of mir2');
GetWindowThreadProcessId(WHandle, @hPID);
PHandle := OpenProcess(PROCESS_ALL_ACCESS, False, hPID);
//主号信息
ReadProcessMemory(PHandle,Pointer($59C7CC),@Base, 4, Num);
ReadProcessMemory(PHandle,Pint(base+$4C),@HP,4,Num);
ReadProcessMemory(PHandle,Pint(base+$4C+$4),@maxhP,4,Num) ;
ReadProcessMemory(PHandle,Pint(base+$4C+$8),@mP,4,Num) ;
ReadProcessMemory(PHandle,Pint(base+$4C+$c),@maxmP,4,Num) ;
ReadProcessMemory(PHandle,Pint(base+$4C+$8c),@px,4,Num) ;
ReadProcessMemory(PHandle,Pint(base+$4C+$90),@py,4,Num) ;
//当前坐标
ReadProcessMemory(PHandle,pointer($59C7CC+$10), @mapAddr, 4, Num);
ReadProcessMemory(PHandle,pointer(mapAddr), @map[0], 20, Num);
ReadProcessMemory(PHandle,pointer($59C7CC-$58508), @rnameAddr, 4, Num);
ReadProcessMemory(PHandle,pointer(rnameAddr), @rname[0], 20, Num);
ReadProcessMemory(PHandle,pointer($59C7CC+$2300), @gjms1, 4, Num);
ReadProcessMemory(PHandle,pointer(gjms1), @gjms[0], 14, Num);
//英雄信息
ReadProcessMemory(PHandle, Pointer($59C7CC-$58FF4),@yBase1, 4, Num);
ReadProcessMemory(PHandle, Pointer(ybase1+$2c),@yBase, 4, Num);
ReadProcessMemory(PHandle,Pint(ybase+$4C),@yHP,4,Num);
ReadProcessMemory(PHandle,Pint(ybase+$4C+$4),@ymaxhP,4,Num) ;
ReadProcessMemory(PHandle,Pint(ybase+$4C+$8),@ymP,4,Num) ;
ReadProcessMemory(PHandle,Pint(ybase+$4C+$c),@ymaxmP,4,Num) ;
form1.StaticText1.Caption:='['+string(rname)+' '+String(map)+' '+inttostr(px)+':'+inttostr(py)+'] '+string(gjms);
form1.label1.Caption:='主 HP:'+inttostr(hp)+'/'+inttostr(maxhp)+' MP:'+inttostr(mp)+'/'+inttostr(maxmp)+'  英 HP:'+inttostr(yhp)+'/'+inttostr(ymaxhp)+' MP:'+inttostr(ymp)+'/'+inttostr(ymaxmp);


我个人认为这些地址还蛮重要的,当然包包里的物品也找到了,只是暂时还没想好用做什么,所以先搁置着!

posted @ 2009-11-06 11:51 装配中的脑袋 阅读(420) 评论(0) 编辑
delphi多线程
2009-10-29 23:08

1-1 多线程的基本概念

多线程带来如下好处:(自己阅读)
1)避免瓶颈;
2)并行操作;
3)提高效率;
在多线程中,通过优先级管理,可以使重要的程序优先操作,提高了任务管理的灵活性。
另一方面,在多CPU 系统中,可以把不同的线程在不同的CPU 中执行,真正做到同时处理多任务(Win 98 只是模拟的,而Win/NT/2000是真正的多CPU同时操作)。

多线程的两个概念:

1)进程:也称任务,程序载入内存,并分配资源,称为“一个进程”。
注意:进程本身并不一定要正在执行。进程由以下几部分组成:
a>一个私有的地址空间,它是进程可以使用的一组虚拟内存地址空间;
b>程序的相关代码、数据源;
c>系统资源,比如操作系统同步对象等;
d>至少包含一个线程(主线程);

2)线程:是程序的执行单位(线程本身并不包括程序代码,真正拥有代码的是进程),每个进程至少包括一个线程,称为主线程,一个进程如果有多个线程,就可以共享同一进程的资源,并可以并发执行。
线程是进程的一个执行单元,是操作系统分配CPU 时间的基本实体,线程主要由如下两部分组成:
a>数据结构;
b>CPU 寄存器和堆栈;
一个进程中的线程,可以独立运行,也可以控制另一个线程的运行。

请注意:
多线程不能滥用,书上提到了多线程的几个缺点(自阅)。

1-2 Tthread 对象

虽然Windows 提供了比较多的多线程设计的API 函数,但是直接使用API 函数一方面极其不方便,而且使用不当还容易出错。为解决这个问题,Borland 公司率先推出了一种Tthread 对象,来解决多线程设计上的困难,简化了多线程问题的处理。
应该注意,Tthread 对象是没有实例的,它和界面的交流,主要依靠主窗体(主VCL线程),这和其他对象使用上有些区别。

一、Tthread 对象的主要方法

构造线程:

constructor Create(CreateSuspended:boolean)

其中:CreateSuspended=true 构造但不唤醒
false 构造的同时即唤醒

也可以用如下方法

inheried Create(CreateSuspended:boolean)


挂起线程:suspend

(把线程挂起的次数加一)

唤醒线程:

resume

(注意:注意这个属性是把线程挂起的次数减一,当次数为0 时,即唤醒。也就是说,线程挂起多少次,唤醒也需要多少次。同时挂起的时候将保持线程的地址指针不变,所以线程挂起后再唤醒,将从挂起的地方开始运行)

析构(清除线程所占用的内存):

destroy

终止线程(后面会具体讨论):

Terminate

二、线程应用的简单例子:

下面通过一个例子说明上述方法的应用。我们知道,循环是独占性最强的运行方式之一,现在希望建立两个线程对象,实现循环的并行运行。具体方法如下:

File---New---Thread Object

这就自动在主Form中建立了一个线程单元(在对话框里写上线程名字),默认的名字是Unit2。同样方法建立第二个线程单元Unit3。

要注意的是:Unit2和Unit3中有一个给定的过程:

procedure Object.Execute;
begin

end;

其中的程序是线程唤醒后自动执行的程序,也可以在里面调用其他自定义的过程和函数。这个过程的结束,意味着线程程序的结束。
为了构造线程,在interface的Type区,定义一个构造过程:

type
Object = class(TThread) //自动给出的,也可以直接改

private

protected

procedure Execute; override;

public
constructor create; //自己写的

并且在implementation区域写上:

constructor Object.create;
begin
inherited create(true);
end

其中Object 为线程对象的名字。所以这么写,是希望在主Form中调用这个构造过程。
Create()的参数用True,表明构造出的线程为挂起状态。
注意一下,在同一个线程对象里,如果两次构造,将产生两个独立的线程,不但运行是独立的,而且使用线程的局部变量也是独立的。但这里为了简化问题,还是建立了两个独立的线程对象,而且两个循环数是不同的,在并行运算时容易判断出是两个不同的程序在运行。

假定我们给两个线程对象起的名字是:

mymath1
mymath2

这样在Unit1,应该作如下声明:

implementation

{$R *.DFM}

uses unit2,unit3;

var thread1:mymath1;
thread2:mymath2;

这样在主线程,将可以通过这两个线程变量调用对应的线程方法。

在主线程区构造线程的方法是:

thread1:=mymath1.create;
thread2:=mymath2.create;

挂起:

thread1.suspend;
thread2.suspend;

唤醒:

thread1.resume;
thread2.resume;

析构:

thread1.destroy;
thread2.destroy;

这里需要说明的是,由于线程单元需要调用Form的Edit控件(对象),可以采用两种方法:

1)在线程单元定义一个TEdit对象,例如

edit4:Tedit;

在Execute过程内直接引用

但在Unit1中一定要在FormCreate过程里作一个赋值:

procedure TForm1.FormCreate(Sender: TObject);
begin
thread1.edit4:=edit1;
end;

这样,就把第一线程的edit4与Form上的edit1联系来。

2)在第二个线程中首先声明调用Unti1,也就是要加上
Uses Unit1;

这样就可以在该线程单元直接调用主Form的控件了,比如在Unit3中可以写:

form1.edit2.text:=inttostr(i)

了解了这些基本规则,就可以写出比较复杂的多线程程序了。
还有一点要说明的,默认生成的线程单元,调用的单元只有一个:

Uses Classes;

这样,往往很多函数和对象在线程单元里不能使用,所以在必要时,应该根据需要User相应的单元,这个例程为了简单,把大部分常用的单元都拷过去了,这并不是推荐的办法,因为这样一来会使程序的垃圾过多,所以,一般要用什么拷什么。

三、常用的API 函数

在处理多线程问题的时候,也经常用到Windows提供的API 函数,需要说明的是,Tthread 对象内部封装的方法,其实主要也是调用API 函数,但是,考虑更全面,更安全。而直接调用API 函数,往往会因为运用不当,出现一些不应有的错误。所以,我个人以为,只要用Tthread 对象的方法能解决的,就不要直接调用API 函数,API 函数只应该在用在Tthread 对象方法解决不了的时候。
例如Tthread 对象方法内部调用API 函数的时候,一般使用推荐的默认值,但需要更精细的控制时,就可以直接使用API 函数。
其实,Tthread 对象方法已经受到了大多数程序设计者的认可,比如,原来VB是不具备直接处理多线程的能力的,但是,现在VB.Net就宣称,它具备了简单处理多线程问题的能力,这就很说明问题。
下面简单介绍几种API 函数,为了清晰方便,这里着重在于说明,函数正确的描述可以自己阅读书上的例子和手册:
构建线程:

CreateThread(参数1,--安全属性(一般=Nil,默认安全属性)

参数2,--线程堆栈尺寸(一般=0,与主线程相同长度,而且可以根据需要自动变化)
参数3,--指向函数名指针,@函数名,这个参数十分重要,不正确将无法调用成功。
参数4,--用户需要向线程传递的参数,是一个指向结构的指针,不需传递参数时,为Nil。
参数5)--传入与线程有关的一些参数,例如:
CREATE_SUSPENDED 创建一个挂起的线程;
0 创建后立即激活。

书上有这个函数应用的十分清晰的例子,可以自己阅读。
一般并不推荐使用 CreateTheard函数,而推荐使用RTL 库里的System单元中定义的 BeginTheard函数,因为这除了能创建一个线程和一个入口函数以外,还增加了几项保护措施,具体的请参阅书上的第10页说明。

对应suspend(挂起)和resume(唤醒)的两个API 函数为:

Function SuspendThread(hThread:Thandle):DWORD;

Function ResumeThread(hThread:Thandle):DWORD;

其中,Thandle被要求控制线程的句柄,函数调用成功,返回挂起的次数,调用不成功。则返回0xFFFFFFFF。

四、线程的终止和退出:

1)自动退出:

一个线程从Execute()过程中退出,即意味着线程的终止,此时将调用Windows的ExitThread()函数来清除线程所占用的堆栈。
如果线程对象的 FreeOnTerminate 属性设为True,则线程对象将自动删除,并释放线程所占用的资源。
这是消除线程对象最简单的办法。

2)受控退出:

利用线程对象的Terminate属性,可以由进程或者由其他线程控制线程的退出。只需要简单的调用该线程的Terminate方法,并设直线程对象的Terminate属性为True。
在线程中,应该不断监视Terminate的值,一旦发现为True,则退出,例如在Execute()过程中可以这样写:

While not Terminate do
begin
........
end;

3)退出的API 函数:

关于线程退出的API 函数声明如下:code

Function TerminateThread(hThread:Thandle;dwExitCode:DWORD);

不过,这个函数会使代码立刻终止,而不管程序中有没有

try....finally

机制,可能会导致错误,不到万不得已,最好不要使用。

4) 利用挂起线程的方法(suspend)

利用挂起线程的suspend方法,后面跟个Free,也可以释放线程,
例如:

thread1.suspend; //挂起
thread2.free; //释放

书上有相应的例子。


五、线程的优先级:

在多线程的情况下,一般要根据线程执行任务的重要性,给线程适当的优先级,一般如果量的线程同时申请CPU 时间,优先级高的线程优先。

在Windows下,给线程的优先级分为30级,而Delphi中Tthread 对象相对简单的把优先级分为七级。也就是在Tthread中声明了一个枚举类型TTthreadPriority:

type

TTthreadPriority(tpidle,tpLowest,tpLower,tpNormal,
tpHight,tpHighest,tpTimecrital)

分别对应的是最低(系统空闲时有效,-15),较低(-2),低(-1),正常(普通0),高(1),较高(2),最高(15)。

其中tpidle和tpTimecrital有些特殊,具体情况请阅读书上有关内容。

设置优先级可使用thread对象的priority属性:

threadObject.priority:=Tthreadpriority(级别);

这里给出了一个演示多线程优先级的实例:


1-3 在数据库中使用多线程

一)使用ADO模式

由于Delphi 6.0的ADO 数据源控件内置了多线程能力,所以,在ADO模式下,使用多线程不需要做更多的工作。用两个ADOTable控件,分别连到两个数据库,并且分别通过DataSource控件,与数据帮定控件联系就可以了,这样就可以实现前后台处理数据库问题。

二)使用BDE模式和Tseeion对象

如果需要使用BDE 模式,那么多线程使用数据库,就要考虑Session的问题。在单线程时,每个数据源的建立就自动生成一个Session,这是这个数据源私有的关于数据库信息的文件。但多线程时,必须统一管理,所以在BDE 中专门提供了一个Tsession对象,它可以同时管理不同的Databas数据源对象。
Databas数据源可以接受来自不同数据平台的数据库。

数据库1---databas(2)----table(Qurey)(3)---datasource
| |
| |
|--------- Tsession(1)
| |
| |
数据库2---databas(2)----table(Qurey)(3)---datasource


方法:
1)Tsession
属性:SessionName=名(自起)
Active=true (激活)
2)Database(可以有多个)
属性:SessionName=Tsession名
Dataname=名(自起,作为Table的标识)
AliasName=数据库别名
Connected=True (激活)
3)Table或Qurey
属性:SessionName=Tsession名(不要用默认值)
DatabaseName=如果前面起了名,这里就会出现Database
的名字。
Tablename=表名
Active=true (激活)
以后比如加入Datasoucre和其他一样,这样就可以构造两个前后台处理的数据库管理系统了。

2-4 多线程的同步机制

同步机制,实际上是事件驱动机制,意思是让线程平时处于“休眠”状态,除非发生某个事件才触发。
例如一个拷贝文件,拷贝线程完成一个程序块后,再唤醒进程条线程做一个格的填充。
研究多线程的同步机制的必要性在于,多线程同步工作时,如果同时调用相同的资源,就可能会出现问题,一般读出是不会有问题的,但是,如果写入(全局变量、数据库),就会发生冲突,甚至产生死
锁和竞争问题。

一、使用Synchronize方法

这个方法用于访问VCL 主线程所管理的资源,其方法的应用是:
第一步:把访问主窗口(或主窗口控件资源)的代码放到线程的一个方法中;
第二步:是在线程对象的Execute方法中,通过Synchronize方法使用该方法。
实例:
procedure Theater.Execute;
begin
Synchronize(update);
end;

procedure Theater.update;
begin
.........
end;

这里通过 Synchronize使线程方法update同步。


二、使用VCL类的Look方法

在Delphi的IDE提供的构件中,有一些对象内部提供了线程的同步机制,工作线程可以直接使用这些控件,比如:Tfont,Tpen,TBitmap,TMetafile,Ticon等。另外,一个很重要的控件对象叫TCanvas,提供了一个Lock方法用于线程的同步,当一个线程使用此控件对象的时候,首先调用这个对象的Lock方法,然后对这个控件进行操作,完毕后再调用Unlock方法,释放对控间的控制权。
例如:
CanversObject.look;
try
画图
finally
CanversObject.unlock;
end;
{使用这个保护机制,保证不论有没有异常,unlock都会被执行否则很可能会发生死锁。在多线程设计的时候,应该很注意发生死锁的问题}

三、Waitfor方法
当一个线程应该等待另一个线程结束时,可以调用Waitfor方法。这个方法属于等待线程对象,Waitfor方法的原型如下:

Function Waitfor(Const Astring:string):string;

比如在前面最基本的线程的例子中,唤醒线程的语句中加上

thread1.resume;
thread1.waitfor;
thread2.resume;

那么所有的线程都必须等待thread1运行完毕后才能运行,其中包括主线程,可以预想,由于thread1调用了主窗体的Edit控件,那么,在thread1运行中间,Edie1也不会显示。
这就告诉我们,这样的代码是不能作为主线程的一部分的,如果与主窗体连接的线程内等待另一个线程结束,而另一个线程又要等待访问用户界面,就可能是程序陷于死锁。
这点在应用的时候要谨慎。


四、利用Windows的API 实现同步

Windows API函数提供了很多同步技术,下面简要介绍。

1)临界区

使用线程的时候,遇到的一个基本的问题,就是多个线程访问同一个对象,比如访问相同的文件、DLL、相同的通讯资源,特别是数据库的访问,当多个线程对同一数据库字段写入的时候,其结果会出
现不确定性。
临界区用于解决这个问题,它可以保证线程使用敏感数据的时候,阻赛其他的线程访问名干数据,使用时首先要初始化,其声明一个TRTLCriticalSection类型的变量:

var
CS:TRTLCriticalSection;

初始化:

initializeCriticalSection(cs);

独占

EnterCriticalSection(cs);

解除独占

LeaveCriticalSection(CS);

使用临界区是比较方便而且概念比较清晰的的线程同步机制,应用比较广泛。

delphi多线程
2009-10-29 23:08

1-1 多线程的基本概念

多线程带来如下好处:(自己阅读)
1)避免瓶颈;
2)并行操作;
3)提高效率;
在多线程中,通过优先级管理,可以使重要的程序优先操作,提高了任务管理的灵活性。
另一方面,在多CPU 系统中,可以把不同的线程在不同的CPU 中执行,真正做到同时处理多任务(Win 98 只是模拟的,而Win/NT/2000是真正的多CPU同时操作)。

多线程的两个概念:

1)进程:也称任务,程序载入内存,并分配资源,称为“一个进程”。
注意:进程本身并不一定要正在执行。进程由以下几部分组成:
a>一个私有的地址空间,它是进程可以使用的一组虚拟内存地址空间;
b>程序的相关代码、数据源;
c>系统资源,比如操作系统同步对象等;
d>至少包含一个线程(主线程);

2)线程:是程序的执行单位(线程本身并不包括程序代码,真正拥有代码的是进程),每个进程至少包括一个线程,称为主线程,一个进程如果有多个线程,就可以共享同一进程的资源,并可以并发执行。
线程是进程的一个执行单元,是操作系统分配CPU 时间的基本实体,线程主要由如下两部分组成:
a>数据结构;
b>CPU 寄存器和堆栈;
一个进程中的线程,可以独立运行,也可以控制另一个线程的运行。

请注意:
多线程不能滥用,书上提到了多线程的几个缺点(自阅)。

1-2 Tthread 对象

虽然Windows 提供了比较多的多线程设计的API 函数,但是直接使用API 函数一方面极其不方便,而且使用不当还容易出错。为解决这个问题,Borland 公司率先推出了一种Tthread 对象,来解决多线程设计上的困难,简化了多线程问题的处理。
应该注意,Tthread 对象是没有实例的,它和界面的交流,主要依靠主窗体(主VCL线程),这和其他对象使用上有些区别。

一、Tthread 对象的主要方法

构造线程:

constructor Create(CreateSuspended:boolean)

其中:CreateSuspended=true 构造但不唤醒
false 构造的同时即唤醒

也可以用如下方法

inheried Create(CreateSuspended:boolean)


挂起线程:suspend

(把线程挂起的次数加一)

唤醒线程:

resume

(注意:注意这个属性是把线程挂起的次数减一,当次数为0 时,即唤醒。也就是说,线程挂起多少次,唤醒也需要多少次。同时挂起的时候将保持线程的地址指针不变,所以线程挂起后再唤醒,将从挂起的地方开始运行)

析构(清除线程所占用的内存):

destroy

终止线程(后面会具体讨论):

Terminate

二、线程应用的简单例子:

下面通过一个例子说明上述方法的应用。我们知道,循环是独占性最强的运行方式之一,现在希望建立两个线程对象,实现循环的并行运行。具体方法如下:

File---New---Thread Object

这就自动在主Form中建立了一个线程单元(在对话框里写上线程名字),默认的名字是Unit2。同样方法建立第二个线程单元Unit3。

要注意的是:Unit2和Unit3中有一个给定的过程:

procedure Object.Execute;
begin

end;

其中的程序是线程唤醒后自动执行的程序,也可以在里面调用其他自定义的过程和函数。这个过程的结束,意味着线程程序的结束。
为了构造线程,在interface的Type区,定义一个构造过程:

type
Object = class(TThread) //自动给出的,也可以直接改

private

protected

procedure Execute; override;

public
constructor create; //自己写的

并且在implementation区域写上:

constructor Object.create;
begin
inherited create(true);
end

其中Object 为线程对象的名字。所以这么写,是希望在主Form中调用这个构造过程。
Create()的参数用True,表明构造出的线程为挂起状态。
注意一下,在同一个线程对象里,如果两次构造,将产生两个独立的线程,不但运行是独立的,而且使用线程的局部变量也是独立的。但这里为了简化问题,还是建立了两个独立的线程对象,而且两个循环数是不同的,在并行运算时容易判断出是两个不同的程序在运行。

假定我们给两个线程对象起的名字是:

mymath1
mymath2

这样在Unit1,应该作如下声明:

implementation

{$R *.DFM}

uses unit2,unit3;

var thread1:mymath1;
thread2:mymath2;

这样在主线程,将可以通过这两个线程变量调用对应的线程方法。

在主线程区构造线程的方法是:

thread1:=mymath1.create;
thread2:=mymath2.create;

挂起:

thread1.suspend;
thread2.suspend;

唤醒:

thread1.resume;
thread2.resume;

析构:

thread1.destroy;
thread2.destroy;

这里需要说明的是,由于线程单元需要调用Form的Edit控件(对象),可以采用两种方法:

1)在线程单元定义一个TEdit对象,例如

edit4:Tedit;

在Execute过程内直接引用

但在Unit1中一定要在FormCreate过程里作一个赋值:

procedure TForm1.FormCreate(Sender: TObject);
begin
thread1.edit4:=edit1;
end;

这样,就把第一线程的edit4与Form上的edit1联系来。

2)在第二个线程中首先声明调用Unti1,也就是要加上
Uses Unit1;

这样就可以在该线程单元直接调用主Form的控件了,比如在Unit3中可以写:

form1.edit2.text:=inttostr(i)

了解了这些基本规则,就可以写出比较复杂的多线程程序了。
还有一点要说明的,默认生成的线程单元,调用的单元只有一个:

Uses Classes;

这样,往往很多函数和对象在线程单元里不能使用,所以在必要时,应该根据需要User相应的单元,这个例程为了简单,把大部分常用的单元都拷过去了,这并不是推荐的办法,因为这样一来会使程序的垃圾过多,所以,一般要用什么拷什么。

三、常用的API 函数

在处理多线程问题的时候,也经常用到Windows提供的API 函数,需要说明的是,Tthread 对象内部封装的方法,其实主要也是调用API 函数,但是,考虑更全面,更安全。而直接调用API 函数,往往会因为运用不当,出现一些不应有的错误。所以,我个人以为,只要用Tthread 对象的方法能解决的,就不要直接调用API 函数,API 函数只应该在用在Tthread 对象方法解决不了的时候。
例如Tthread 对象方法内部调用API 函数的时候,一般使用推荐的默认值,但需要更精细的控制时,就可以直接使用API 函数。
其实,Tthread 对象方法已经受到了大多数程序设计者的认可,比如,原来VB是不具备直接处理多线程的能力的,但是,现在VB.Net就宣称,它具备了简单处理多线程问题的能力,这就很说明问题。
下面简单介绍几种API 函数,为了清晰方便,这里着重在于说明,函数正确的描述可以自己阅读书上的例子和手册:
构建线程:

CreateThread(参数1,--安全属性(一般=Nil,默认安全属性)

参数2,--线程堆栈尺寸(一般=0,与主线程相同长度,而且可以根据需要自动变化)
参数3,--指向函数名指针,@函数名,这个参数十分重要,不正确将无法调用成功。
参数4,--用户需要向线程传递的参数,是一个指向结构的指针,不需传递参数时,为Nil。
参数5)--传入与线程有关的一些参数,例如:
CREATE_SUSPENDED 创建一个挂起的线程;
0 创建后立即激活。

书上有这个函数应用的十分清晰的例子,可以自己阅读。
一般并不推荐使用 CreateTheard函数,而推荐使用RTL 库里的System单元中定义的 BeginTheard函数,因为这除了能创建一个线程和一个入口函数以外,还增加了几项保护措施,具体的请参阅书上的第10页说明。

对应suspend(挂起)和resume(唤醒)的两个API 函数为:

Function SuspendThread(hThread:Thandle):DWORD;

Function ResumeThread(hThread:Thandle):DWORD;

其中,Thandle被要求控制线程的句柄,函数调用成功,返回挂起的次数,调用不成功。则返回0xFFFFFFFF。

四、线程的终止和退出:

1)自动退出:

一个线程从Execute()过程中退出,即意味着线程的终止,此时将调用Windows的ExitThread()函数来清除线程所占用的堆栈。
如果线程对象的 FreeOnTerminate 属性设为True,则线程对象将自动删除,并释放线程所占用的资源。
这是消除线程对象最简单的办法。

2)受控退出:

利用线程对象的Terminate属性,可以由进程或者由其他线程控制线程的退出。只需要简单的调用该线程的Terminate方法,并设直线程对象的Terminate属性为True。
在线程中,应该不断监视Terminate的值,一旦发现为True,则退出,例如在Execute()过程中可以这样写:

While not Terminate do
begin
........
end;

3)退出的API 函数:

关于线程退出的API 函数声明如下:code

Function TerminateThread(hThread:Thandle;dwExitCode:DWORD);

不过,这个函数会使代码立刻终止,而不管程序中有没有

try....finally

机制,可能会导致错误,不到万不得已,最好不要使用。

4) 利用挂起线程的方法(suspend)

利用挂起线程的suspend方法,后面跟个Free,也可以释放线程,
例如:

thread1.suspend; //挂起
thread2.free; //释放

书上有相应的例子。


五、线程的优先级:

在多线程的情况下,一般要根据线程执行任务的重要性,给线程适当的优先级,一般如果量的线程同时申请CPU 时间,优先级高的线程优先。

在Windows下,给线程的优先级分为30级,而Delphi中Tthread 对象相对简单的把优先级分为七级。也就是在Tthread中声明了一个枚举类型TTthreadPriority:

type

TTthreadPriority(tpidle,tpLowest,tpLower,tpNormal,
tpHight,tpHighest,tpTimecrital)

分别对应的是最低(系统空闲时有效,-15),较低(-2),低(-1),正常(普通0),高(1),较高(2),最高(15)。

其中tpidle和tpTimecrital有些特殊,具体情况请阅读书上有关内容。

设置优先级可使用thread对象的priority属性:

threadObject.priority:=Tthreadpriority(级别);

这里给出了一个演示多线程优先级的实例:


1-3 在数据库中使用多线程

一)使用ADO模式

由于Delphi 6.0的ADO 数据源控件内置了多线程能力,所以,在ADO模式下,使用多线程不需要做更多的工作。用两个ADOTable控件,分别连到两个数据库,并且分别通过DataSource控件,与数据帮定控件联系就可以了,这样就可以实现前后台处理数据库问题。

二)使用BDE模式和Tseeion对象

如果需要使用BDE 模式,那么多线程使用数据库,就要考虑Session的问题。在单线程时,每个数据源的建立就自动生成一个Session,这是这个数据源私有的关于数据库信息的文件。但多线程时,必须统一管理,所以在BDE 中专门提供了一个Tsession对象,它可以同时管理不同的Databas数据源对象。
Databas数据源可以接受来自不同数据平台的数据库。

数据库1---databas(2)----table(Qurey)(3)---datasource
| |
| |
|--------- Tsession(1)
| |
| |
数据库2---databas(2)----table(Qurey)(3)---datasource


方法:
1)Tsession
属性:SessionName=名(自起)
Active=true (激活)
2)Database(可以有多个)
属性:SessionName=Tsession名
Dataname=名(自起,作为Table的标识)
AliasName=数据库别名
Connected=True (激活)
3)Table或Qurey
属性:SessionName=Tsession名(不要用默认值)
DatabaseName=如果前面起了名,这里就会出现Database
的名字。
Tablename=表名
Active=true (激活)
以后比如加入Datasoucre和其他一样,这样就可以构造两个前后台处理的数据库管理系统了。

2-4 多线程的同步机制

同步机制,实际上是事件驱动机制,意思是让线程平时处于“休眠”状态,除非发生某个事件才触发。
例如一个拷贝文件,拷贝线程完成一个程序块后,再唤醒进程条线程做一个格的填充。
研究多线程的同步机制的必要性在于,多线程同步工作时,如果同时调用相同的资源,就可能会出现问题,一般读出是不会有问题的,但是,如果写入(全局变量、数据库),就会发生冲突,甚至产生死
锁和竞争问题。

一、使用Synchronize方法

这个方法用于访问VCL 主线程所管理的资源,其方法的应用是:
第一步:把访问主窗口(或主窗口控件资源)的代码放到线程的一个方法中;
第二步:是在线程对象的Execute方法中,通过Synchronize方法使用该方法。
实例:
procedure Theater.Execute;
begin
Synchronize(update);
end;

procedure Theater.update;
begin
.........
end;

这里通过 Synchronize使线程方法update同步。


二、使用VCL类的Look方法

在Delphi的IDE提供的构件中,有一些对象内部提供了线程的同步机制,工作线程可以直接使用这些控件,比如:Tfont,Tpen,TBitmap,TMetafile,Ticon等。另外,一个很重要的控件对象叫TCanvas,提供了一个Lock方法用于线程的同步,当一个线程使用此控件对象的时候,首先调用这个对象的Lock方法,然后对这个控件进行操作,完毕后再调用Unlock方法,释放对控间的控制权。
例如:
CanversObject.look;
try
画图
finally
CanversObject.unlock;
end;
{使用这个保护机制,保证不论有没有异常,unlock都会被执行否则很可能会发生死锁。在多线程设计的时候,应该很注意发生死锁的问题}

三、Waitfor方法
当一个线程应该等待另一个线程结束时,可以调用Waitfor方法。这个方法属于等待线程对象,Waitfor方法的原型如下:

Function Waitfor(Const Astring:string):string;

比如在前面最基本的线程的例子中,唤醒线程的语句中加上

thread1.resume;
thread1.waitfor;
thread2.resume;

那么所有的线程都必须等待thread1运行完毕后才能运行,其中包括主线程,可以预想,由于thread1调用了主窗体的Edit控件,那么,在thread1运行中间,Edie1也不会显示。
这就告诉我们,这样的代码是不能作为主线程的一部分的,如果与主窗体连接的线程内等待另一个线程结束,而另一个线程又要等待访问用户界面,就可能是程序陷于死锁。
这点在应用的时候要谨慎。


四、利用Windows的API 实现同步

Windows API函数提供了很多同步技术,下面简要介绍。

1)临界区

使用线程的时候,遇到的一个基本的问题,就是多个线程访问同一个对象,比如访问相同的文件、DLL、相同的通讯资源,特别是数据库的访问,当多个线程对同一数据库字段写入的时候,其结果会出
现不确定性。
临界区用于解决这个问题,它可以保证线程使用敏感数据的时候,阻赛其他的线程访问名干数据,使用时首先要初始化,其声明一个TRTLCriticalSection类型的变量:

var
CS:TRTLCriticalSection;

初始化:

initializeCriticalSection(cs);

独占

EnterCriticalSection(cs);

解除独占

LeaveCriticalSection(CS);

使用临界区是比较方便而且概念比较清晰的的线程同步机制,应用比较广泛。

2009-10-29 23:08

1-1 多线程的基本概念

多线程带来如下好处:(自己阅读)
1)避免瓶颈;
2)并行操作;
3)提高效率;
在多线程中,通过优先级管理,可以使重要的程序优先操作,提高了任务管理的灵活性。
另一方面,在多CPU 系统中,可以把不同的线程在不同的CPU 中执行,真正做到同时处理多任务(Win 98 只是模拟的,而Win/NT/2000是真正的多CPU同时操作)。

多线程的两个概念:

1)进程:也称任务,程序载入内存,并分配资源,称为“一个进程”。
注意:进程本身并不一定要正在执行。进程由以下几部分组成:
a>一个私有的地址空间,它是进程可以使用的一组虚拟内存地址空间;
b>程序的相关代码、数据源;
c>系统资源,比如操作系统同步对象等;
d>至少包含一个线程(主线程);

2)线程:是程序的执行单位(线程本身并不包括程序代码,真正拥有代码的是进程),每个进程至少包括一个线程,称为主线程,一个进程如果有多个线程,就可以共享同一进程的资源,并可以并发执行。
线程是进程的一个执行单元,是操作系统分配CPU 时间的基本实体,线程主要由如下两部分组成:
a>数据结构;
b>CPU 寄存器和堆栈;
一个进程中的线程,可以独立运行,也可以控制另一个线程的运行。

请注意:
多线程不能滥用,书上提到了多线程的几个缺点(自阅)。

1-2 Tthread 对象

虽然Windows 提供了比较多的多线程设计的API 函数,但是直接使用API 函数一方面极其不方便,而且使用不当还容易出错。为解决这个问题,Borland 公司率先推出了一种Tthread 对象,来解决多线程设计上的困难,简化了多线程问题的处理。
应该注意,Tthread 对象是没有实例的,它和界面的交流,主要依靠主窗体(主VCL线程),这和其他对象使用上有些区别。

一、Tthread 对象的主要方法

构造线程:

constructor Create(CreateSuspended:boolean)

其中:CreateSuspended=true 构造但不唤醒
false 构造的同时即唤醒

也可以用如下方法

inheried Create(CreateSuspended:boolean)


挂起线程:suspend

(把线程挂起的次数加一)

唤醒线程:

resume

(注意:注意这个属性是把线程挂起的次数减一,当次数为0 时,即唤醒。也就是说,线程挂起多少次,唤醒也需要多少次。同时挂起的时候将保持线程的地址指针不变,所以线程挂起后再唤醒,将从挂起的地方开始运行)

析构(清除线程所占用的内存):

destroy

终止线程(后面会具体讨论):

Terminate

二、线程应用的简单例子:

下面通过一个例子说明上述方法的应用。我们知道,循环是独占性最强的运行方式之一,现在希望建立两个线程对象,实现循环的并行运行。具体方法如下:

File---New---Thread Object

这就自动在主Form中建立了一个线程单元(在对话框里写上线程名字),默认的名字是Unit2。同样方法建立第二个线程单元Unit3。

要注意的是:Unit2和Unit3中有一个给定的过程:

procedure Object.Execute;
begin

end;

其中的程序是线程唤醒后自动执行的程序,也可以在里面调用其他自定义的过程和函数。这个过程的结束,意味着线程程序的结束。
为了构造线程,在interface的Type区,定义一个构造过程:

type
Object = class(TThread) //自动给出的,也可以直接改

private

protected

procedure Execute; override;

public
constructor create; //自己写的

并且在implementation区域写上:

constructor Object.create;
begin
inherited create(true);
end

其中Object 为线程对象的名字。所以这么写,是希望在主Form中调用这个构造过程。
Create()的参数用True,表明构造出的线程为挂起状态。
注意一下,在同一个线程对象里,如果两次构造,将产生两个独立的线程,不但运行是独立的,而且使用线程的局部变量也是独立的。但这里为了简化问题,还是建立了两个独立的线程对象,而且两个循环数是不同的,在并行运算时容易判断出是两个不同的程序在运行。

假定我们给两个线程对象起的名字是:

mymath1
mymath2

这样在Unit1,应该作如下声明:

implementation

{$R *.DFM}

uses unit2,unit3;

var thread1:mymath1;
thread2:mymath2;

这样在主线程,将可以通过这两个线程变量调用对应的线程方法。

在主线程区构造线程的方法是:

thread1:=mymath1.create;
thread2:=mymath2.create;

挂起:

thread1.suspend;
thread2.suspend;

唤醒:

thread1.resume;
thread2.resume;

析构:

thread1.destroy;
thread2.destroy;

这里需要说明的是,由于线程单元需要调用Form的Edit控件(对象),可以采用两种方法:

1)在线程单元定义一个TEdit对象,例如

edit4:Tedit;

在Execute过程内直接引用

但在Unit1中一定要在FormCreate过程里作一个赋值:

procedure TForm1.FormCreate(Sender: TObject);
begin
thread1.edit4:=edit1;
end;

这样,就把第一线程的edit4与Form上的edit1联系来。

2)在第二个线程中首先声明调用Unti1,也就是要加上
Uses Unit1;

这样就可以在该线程单元直接调用主Form的控件了,比如在Unit3中可以写:

form1.edit2.text:=inttostr(i)

了解了这些基本规则,就可以写出比较复杂的多线程程序了。
还有一点要说明的,默认生成的线程单元,调用的单元只有一个:

Uses Classes;

这样,往往很多函数和对象在线程单元里不能使用,所以在必要时,应该根据需要User相应的单元,这个例程为了简单,把大部分常用的单元都拷过去了,这并不是推荐的办法,因为这样一来会使程序的垃圾过多,所以,一般要用什么拷什么。

三、常用的API 函数

在处理多线程问题的时候,也经常用到Windows提供的API 函数,需要说明的是,Tthread 对象内部封装的方法,其实主要也是调用API 函数,但是,考虑更全面,更安全。而直接调用API 函数,往往会因为运用不当,出现一些不应有的错误。所以,我个人以为,只要用Tthread 对象的方法能解决的,就不要直接调用API 函数,API 函数只应该在用在Tthread 对象方法解决不了的时候。
例如Tthread 对象方法内部调用API 函数的时候,一般使用推荐的默认值,但需要更精细的控制时,就可以直接使用API 函数。
其实,Tthread 对象方法已经受到了大多数程序设计者的认可,比如,原来VB是不具备直接处理多线程的能力的,但是,现在VB.Net就宣称,它具备了简单处理多线程问题的能力,这就很说明问题。
下面简单介绍几种API 函数,为了清晰方便,这里着重在于说明,函数正确的描述可以自己阅读书上的例子和手册:
构建线程:

CreateThread(参数1,--安全属性(一般=Nil,默认安全属性)

参数2,--线程堆栈尺寸(一般=0,与主线程相同长度,而且可以根据需要自动变化)
参数3,--指向函数名指针,@函数名,这个参数十分重要,不正确将无法调用成功。
参数4,--用户需要向线程传递的参数,是一个指向结构的指针,不需传递参数时,为Nil。
参数5)--传入与线程有关的一些参数,例如:
CREATE_SUSPENDED 创建一个挂起的线程;
0 创建后立即激活。

书上有这个函数应用的十分清晰的例子,可以自己阅读。
一般并不推荐使用 CreateTheard函数,而推荐使用RTL 库里的System单元中定义的 BeginTheard函数,因为这除了能创建一个线程和一个入口函数以外,还增加了几项保护措施,具体的请参阅书上的第10页说明。

对应suspend(挂起)和resume(唤醒)的两个API 函数为:

Function SuspendThread(hThread:Thandle):DWORD;

Function ResumeThread(hThread:Thandle):DWORD;

其中,Thandle被要求控制线程的句柄,函数调用成功,返回挂起的次数,调用不成功。则返回0xFFFFFFFF。

四、线程的终止和退出:

1)自动退出:

一个线程从Execute()过程中退出,即意味着线程的终止,此时将调用Windows的ExitThread()函数来清除线程所占用的堆栈。
如果线程对象的 FreeOnTerminate 属性设为True,则线程对象将自动删除,并释放线程所占用的资源。
这是消除线程对象最简单的办法。

2)受控退出:

利用线程对象的Terminate属性,可以由进程或者由其他线程控制线程的退出。只需要简单的调用该线程的Terminate方法,并设直线程对象的Terminate属性为True。
在线程中,应该不断监视Terminate的值,一旦发现为True,则退出,例如在Execute()过程中可以这样写:

While not Terminate do
begin
........
end;

3)退出的API 函数:

关于线程退出的API 函数声明如下:code

Function TerminateThread(hThread:Thandle;dwExitCode:DWORD);

不过,这个函数会使代码立刻终止,而不管程序中有没有

try....finally

机制,可能会导致错误,不到万不得已,最好不要使用。

4) 利用挂起线程的方法(suspend)

利用挂起线程的suspend方法,后面跟个Free,也可以释放线程,
例如:

thread1.suspend; //挂起
thread2.free; //释放

书上有相应的例子。


五、线程的优先级:

在多线程的情况下,一般要根据线程执行任务的重要性,给线程适当的优先级,一般如果量的线程同时申请CPU 时间,优先级高的线程优先。

在Windows下,给线程的优先级分为30级,而Delphi中Tthread 对象相对简单的把优先级分为七级。也就是在Tthread中声明了一个枚举类型TTthreadPriority:

type

TTthreadPriority(tpidle,tpLowest,tpLower,tpNormal,
tpHight,tpHighest,tpTimecrital)

分别对应的是最低(系统空闲时有效,-15),较低(-2),低(-1),正常(普通0),高(1),较高(2),最高(15)。

其中tpidle和tpTimecrital有些特殊,具体情况请阅读书上有关内容。

设置优先级可使用thread对象的priority属性:

threadObject.priority:=Tthreadpriority(级别);

这里给出了一个演示多线程优先级的实例:


1-3 在数据库中使用多线程

一)使用ADO模式

由于Delphi 6.0的ADO 数据源控件内置了多线程能力,所以,在ADO模式下,使用多线程不需要做更多的工作。用两个ADOTable控件,分别连到两个数据库,并且分别通过DataSource控件,与数据帮定控件联系就可以了,这样就可以实现前后台处理数据库问题。

二)使用BDE模式和Tseeion对象

如果需要使用BDE 模式,那么多线程使用数据库,就要考虑Session的问题。在单线程时,每个数据源的建立就自动生成一个Session,这是这个数据源私有的关于数据库信息的文件。但多线程时,必须统一管理,所以在BDE 中专门提供了一个Tsession对象,它可以同时管理不同的Databas数据源对象。
Databas数据源可以接受来自不同数据平台的数据库。

数据库1---databas(2)----table(Qurey)(3)---datasource
| |
| |
|--------- Tsession(1)
| |
| |
数据库2---databas(2)----table(Qurey)(3)---datasource


方法:
1)Tsession
属性:SessionName=名(自起)
Active=true (激活)
2)Database(可以有多个)
属性:SessionName=Tsession名
Dataname=名(自起,作为Table的标识)
AliasName=数据库别名
Connected=True (激活)
3)Table或Qurey
属性:SessionName=Tsession名(不要用默认值)
DatabaseName=如果前面起了名,这里就会出现Database
的名字。
Tablename=表名
Active=true (激活)
以后比如加入Datasoucre和其他一样,这样就可以构造两个前后台处理的数据库管理系统了。

2-4 多线程的同步机制

同步机制,实际上是事件驱动机制,意思是让线程平时处于“休眠”状态,除非发生某个事件才触发。
例如一个拷贝文件,拷贝线程完成一个程序块后,再唤醒进程条线程做一个格的填充。
研究多线程的同步机制的必要性在于,多线程同步工作时,如果同时调用相同的资源,就可能会出现问题,一般读出是不会有问题的,但是,如果写入(全局变量、数据库),就会发生冲突,甚至产生死
锁和竞争问题。

一、使用Synchronize方法

这个方法用于访问VCL 主线程所管理的资源,其方法的应用是:
第一步:把访问主窗口(或主窗口控件资源)的代码放到线程的一个方法中;
第二步:是在线程对象的Execute方法中,通过Synchronize方法使用该方法。
实例:
procedure Theater.Execute;
begin
Synchronize(update);
end;

procedure Theater.update;
begin
.........
end;

这里通过 Synchronize使线程方法update同步。


二、使用VCL类的Look方法

在Delphi的IDE提供的构件中,有一些对象内部提供了线程的同步机制,工作线程可以直接使用这些控件,比如:Tfont,Tpen,TBitmap,TMetafile,Ticon等。另外,一个很重要的控件对象叫TCanvas,提供了一个Lock方法用于线程的同步,当一个线程使用此控件对象的时候,首先调用这个对象的Lock方法,然后对这个控件进行操作,完毕后再调用Unlock方法,释放对控间的控制权。
例如:
CanversObject.look;
try
画图
finally
CanversObject.unlock;
end;
{使用这个保护机制,保证不论有没有异常,unlock都会被执行否则很可能会发生死锁。在多线程设计的时候,应该很注意发生死锁的问题}

三、Waitfor方法
当一个线程应该等待另一个线程结束时,可以调用Waitfor方法。这个方法属于等待线程对象,Waitfor方法的原型如下:

Function Waitfor(Const Astring:string):string;

比如在前面最基本的线程的例子中,唤醒线程的语句中加上

thread1.resume;
thread1.waitfor;
thread2.resume;

那么所有的线程都必须等待thread1运行完毕后才能运行,其中包括主线程,可以预想,由于thread1调用了主窗体的Edit控件,那么,在thread1运行中间,Edie1也不会显示。
这就告诉我们,这样的代码是不能作为主线程的一部分的,如果与主窗体连接的线程内等待另一个线程结束,而另一个线程又要等待访问用户界面,就可能是程序陷于死锁。
这点在应用的时候要谨慎。


四、利用Windows的API 实现同步

Windows API函数提供了很多同步技术,下面简要介绍。

1)临界区

使用线程的时候,遇到的一个基本的问题,就是多个线程访问同一个对象,比如访问相同的文件、DLL、相同的通讯资源,特别是数据库的访问,当多个线程对同一数据库字段写入的时候,其结果会出
现不确定性。
临界区用于解决这个问题,它可以保证线程使用敏感数据的时候,阻赛其他的线程访问名干数据,使用时首先要初始化,其声明一个TRTLCriticalSection类型的变量:

var
CS:TRTLCriticalSection;

初始化:

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线程被调用。该线程对象也可在该事件中被释放。

posted @ 2009-10-30 01:59 装配中的脑袋 阅读(1697) 评论(0) 编辑
  在C++中使用soap toolkit访问Web Service,一般要用到以下几个接口:
  • ISoapConnector
  • ISoapSerializer
  • ISoapReader
      这三个接口顾名思义。ISoapConnector实现了在对象之间发送和接收SOAP消息的传输协议;ISoapSerializer用于序列化(串行化),创建、封装SOAP消息;ISoapReader用于读取SOAP消息,将SOAP消息解析为一个DOM模型,并提供了一系列操作该DOM模型的方法。
      使用Soap Toolkit的应用程序,需要引入动态库MSSOAP1.dll(当前最新版本是Soap Toolkit3.0:MSSOAP3.dl),同时还需要msxml3.dll用于解析DOM和XML。在程序的开头可以这样来引入相关的文库和名称空间:
      #import "msxml3.dll"
      using namespace MSXML2;
      #import "D:\Program Files\Common Files\MSSoap\Binaries\MSSOAP1.dll" \
      exclude("IStream", "ISequentialStream", "_LARGE_INTEGER", "_ULARGE_INTEGER",
                  "tagSTATSTG", "_FILETIME")
      using namespace MSSOAPLib;
 
      废话少说,觉得还是来分析一段示例代码比较实在。 假设在Web服务器上(如http://localhost/MyTestWebService/),有一个名为Loginout的Web服务,并提供了一个Web Mothod,原型如下:
string LoginTest(string strIP, int nPort);
      下面的代码说明了如何来访问该Web服务。    

 ISoapConnectorPtr Connector; 
 ISoapSerializerPtr Serializer;
 ISoapReaderPtr Reader;
 // 与Web服务连接
 Connector.CreateInstance(__uuidof(HttpConnector));
 Connector->Property[L"EndPointURL"] =
                  _variant_t( "http://localhost/MyTestWebService/Loginout.asmx");
 HRESULT result = Connector->Connect();
 // 创建SoapSerializer对象
 Serializer.CreateInstance(__uuidof(SoapSerializer));
 // 将serializer连接到connector的输入字符串
 Serializer->Init(_variant_t((IUnknown*)Connector->InputStream));

 Connector->Property["SoapAction"] = "http://tempuri.org/LoginTest"
 // 开始soap消息
 Connector>BeginMessage();
 // 创建SOAP消息
 Serializer->startEnvelope("","","");
 Serializer->startBody("");
 Serializer->startElement("LoginTest","http://tempuri.org/","","");
 Serializer->startElement("strIP","http://tempuri.org/","","");
 Serializer->writeString("192.168.1.170");
 Serializer->endElement();
 Serializer->startElement("nPort","http://tempuri.org/","","");
 Serializer->writeString("8000");
 Serializer->endElement(); 
 Serializer->endElement();
 Serializer->endBody();
 Serializer->endEnvelope();
 // 将该soap消息发送给web服务
 Connector->EndMessage();
 // 读取响应soap消息
 Reader.CreateInstance(__uuidof(SoapReader));
 // 将reader联接到connector的输出字符串
 Reader->Load(_variant_t((IUnknown*)Connector->OutputStream), "");
 DWORD dwRt = GetLastError();
 // 显示结果
 printf("Soap return result: %s \n",(const char *)Reader->RPCResult->text);

 CoUninitialize();  

      上面程序将打印出Web服务的返回值。
      几点说明:
  1. http://localhost/MyTestWebService/Loginout.asmx 是提供Web服务的URL。
  2. "http://tempuri.org/"是Web服务所属的名称空间(如果Web服务提供者没有指定名称空间,将会使用该默认名称空间)。
  3. 设置Property时的SoapAction就是要调用的WebMethod“http://tempuri.org/LoginTest"。我觉得称作SoapOperation似乎更好理解些。
  4. Reader->RPCResult->text是调用WebMethod的string类型返回值。如果要读取返回的参数或复杂对象,可以使用ISoapReader的相关方法。
  5. 另外,按上述方法,如果相关参数中含有中文,service服务器接收到的相应内容可能会是乱码!例如我做demo时server端是基于.NET的,字符串肯定是Unicode,以两个字节表示;而soap toolkit默认的xml编码规则是UTF-8(使用该编码,虽然也称为Unicode,但是汉字会被编码为2到3个字节不等),这就产生了冲突。解决的办法是soap调用startEnvelope()时第三个参数指定encoding:UTF-16,这样所有字符均是以2个字节编码。
posted @ 2009-10-25 23:14 装配中的脑袋 阅读(625) 评论(1) 编辑
我想快速搜索所有主机,我已经试过以下方法,注意,我强调的是快速  
1.用ping,当然可以,但是如果要在程序中实现不大合理,而且我觉得速度也快不到哪里去  
2.用winpcap发送arp请求包可以获得,但是获取时间不确定,而且获取速度不算太快  
3.用SendARP函数,对于需要扫描255个主机时简直太慢了不能忍受,感觉程序都无法响应了。于是我尝试着?55个线程分别调用SendARP函数,结果发现大多数SendArp都调用失败,错误值是31。不知道为什么  
4.看到网上有人使用先枚举网络资源,再获取主机信息,然后有主机名获取IP,最后由IP获取MAC的方法,我觉得速度也快不到哪里去(这个没有具体试过)  
 
---------------------------------------------------------------  
 
方式  4,  采用网上邻居的方法,但是只能枚举本工作组内的主机  
 
源码:  
 
       DWORD  dwScope  =  RESOURCE_CONTEXT;  
       NETRESOURCE  *NetResource  =  NULL;  
       HANDLE  hEnum;  
       WNetOpenEnum(  dwScope,  NULL,  NULL,    
                         NULL,  &hEnum  );  
       if  (  hEnum  )  
           {  
       DWORD  Count  =  0xFFFFFFFF;  
       DWORD  BufferSize  =  2048;  
       LPVOID  Buffer  =  new  char[2048];  
       WNetEnumResource(  hEnum,  &Count,    
               Buffer,  &BufferSize  );  
       NetResource  =  (NETRESOURCE*)Buffer;  
 
       char  szHostName[200];  
       unsigned  int  i;  
 
       for  (  i  =  0;i  <  BufferSize/sizeof(NETRESOURCE);i++,  NetResource++  )  
       {  
               if  (  NetResource->dwUsage  ==RESOURCEUSAGE_CONTAINER  &&NetResource->dwType  ==RESOURCETYPE_ANY  )  
               {  
                       if  (  NetResource->lpRemoteName  )  
                       {  
                               CString  strFullName  =NetResource->lpRemoteName;  
                               if  (  0  ==strFullName.Left(2).Compare("\\\\")  )        
                                       strFullName  =strFullName.Right(strFullName.GetLength()-2);  
                               gethostname(  szHostName,strlen(  szHostName  )  );  
                               host  =  gethostbyname(strFullName);  
                               if(host  ==  NULL)  continue;    
                               strTemp.Format("%s",strFullName);  
                               m_List.InsertItem(i,strTemp,0);                  //  获得地址添加到列表  
                       }  
               }  
       }  
       delete  Buffer;  
       WNetCloseEnum(  hEnum  );  
 
---------------------------------------------------------------  
 
第四种方法,网上邻居也是采用netenum  api,codeproject上有个例子  
www.codeproject.com/treectrl/pathpicker.asp  
 
--------------------------------------------------------------  
 
 
这两天看到一个《基于UDP协议的网段扫描器》,大致代码如下,可以得到工作组,机器名,用户名和MAC地址  
BYTE  bs[50]={0x0,0x00,0x0,0x10,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x0,0x20,0x43,0x4b,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x0,0x0,0x21,0x0,0x1};  
HANDLE  wait_handle;  
 
m_UDPSocket.SendTo((void*)bs,50,destPORT,m_strIP,0);//向指定的ip发数据报  
WaitForSingleObject(  
                       wait_handle,                //  等待事件的句柄  
                       nWait      //  超时  
                       );  
ResetEvent(wait_handle);//将事件重新置回非触发状态  
 
void  CNBTSTATDlg::OnReceive()  
{              
           BYTE  Buf[500];  
           CString  str,strIP,strHost,strHex,strMac,Host,Group,User;  
           UINT  dport;  
           m_UDPSocket.ReceiveFrom(Buf,500,strIP,dport,0);//接收数据  
       //如果接收到的ip为空或者与原来接收到的ip相同,则返回  
           if(strIP==(char)NULL  |  |strIP==strOldIP)return;  
           strOldIP=strIP;  
           int  index=m_ListView.InsertItem(0,strIP);//将ip插入ListView  
           strHost="";  //机器名字  
           strHex="";//MAC地址  
           User="?";//  
           Host="\\";  
           int  tem=0,num=0;  
           bool  bAdd=true;  
           //根据数据报规则取出相应的信息  
           for(i=57;i<500;i++)  //57-72  
           {  
                       if(Buf[i]==0xcc)break;    
                       if(Buf[i]==0x20)bAdd=false;  
                       if(bAdd)  
                       {  
                                   str.Format("%c",Buf[i]);  
                                   if(Buf[i]>='  ')strHost+=str;  
 
                                   str.Format("%02x.",Buf[i]);  
                                   strHex+=str;  
                       }  
 
                       if((++tem)%18==0)  
                       {  
                       bAdd=true;    
                                   strHost.TrimRight((char)NULL);  
                                   if(strHost=="")  
                                   {  
                                                     strMac.Delete(17,strMac.GetLength()-17);  
                                               m_ListView.SetItem(index,4,LVIF_TEXT,strMac,  0,  0,  0,0);  
                                               break;  
                                   }  
 
 
                                   if(num==0&&strHost!="")  
                                   {  
                                               m_ListView.SetItem(index,2,LVIF_TEXT,strHost,  0,  0,  0,0);  
                                               Host=strHost;  
                                               num++;  
                                   }  
                                   else  
                                   {  
                                               if(Host!=strHost&&num==1&&strHost!="")  
                                               {  
                                                           m_ListView.SetItem(index,1,LVIF_TEXT,strHost,  0,  0,  0,0);  
                                                           Group=strHost;  
                                                       num++;  
                                               }  
                                               else    
                                               {  
                                                           if(strHost!=Host&&strHost!=Group&&num==2&&strHost!="")  
                                                           {  
                                                                       User=strHost;  
                                                                       if(User!="__MSBROWSE__")  
                                                                       {  
                                                                                   m_ListView.SetItem(index,3,LVIF_TEXT,User,  0,  0,  0,0);  
                                                                                   num++;  
                                                                       }  
                                                           }  
                                               }  
 
                                   }  
                                     
                                   strMac=strHex;  
                                   strHost="";  
                                   strHex="";  
                                     
                       }  
                         
           }  
           //触发事件,导致线程函数的继续执行  
           SetEvent(wait_handle);  
             
 
}  
 
---------------------------------------------------------------  
 
采用多线程调用SendARP的方法不错,效率很高(我的局域网11台机器在线,几秒钟就搞定:))不用每开一个线程就Sleep(),这样速度很慢,可以预先定义一个CString  szIp[255];数组,作为255个线程的参数,下面是程序的大概:  
//全局变量  
typedef  struct  ipmac  
{  
           char  szIp[16];  
           char  szMac[6];  
 
}IPMAC;  
IPMAC  host[255];  
int  k=0;  
CRITICAL_SECTION  cs;  
/*线程函数,用来查询IP对应的MAC地址*/  
DWORD  WINAPI  ArpThread(LPVOID  lParam)  
{  
           char  *  szIp=(char  *)lParam;  
           ULONG  pMac[2];  
           ULONG  pulen=6;  
           int  ret;  
       TRACE(szIp);  
           if  ((ret=SendARP(inet_addr(szIp),0,pMac,&pulen))==0)  
           {  
                       EnterCriticalSection(&cs);  //多线程同步,呵呵:0  
                       strcpy(host[k].szIp,szIp);  
                       PBYTE  pbyte=(PBYTE)pMac;  
                       for  (int  i=0;i<5;i++)  
                       {  
                                   host[k].szMac[i]=pbyte[i];  
                                   TRACE("%02X-",pbyte[i]);  
                       }  
                       TRACE("%02X",pbyte[5]);  
                       host[k].szMac[5]=pbyte[5];  
                       k++;  
                       LeaveCriticalSection(&cs);  
                                     
           }  
           else  
           {  
                       TRACE("SendARP  Error  %d",ret);  
           }  
           return  0;  
}  
 
/*枚举局域网内所有主机,并将IP/MAC对插入ListBox中显示*/  
void  CTestArpDlg::OnButton1()    
{  
           //  TODO:  Add  your  control  notification  handler  code  here  
           HANDLE  hthread[254];  
           CString  IpSuffix="192.168.10.";  
           CString  strIp[254];    
           InitializeCriticalSection(&cs);  
           for  (int  i=0;i<254;i++)  
           {  
                       strIp[i].Format("%d",i+1);  
                       strIp[i]=IpSuffix+strIp[i];  
                       hthread[i]=CreateThread(NULL,0,ArpThread,strIp[i].GetBuffer(0),0,NULL);    
             
           }  
                 /*呵呵,因为一次只能等待  64个内核对象,所以只有分几次了*/  
                 /*当然也可以用循环了*/  
           WaitForMultipleObjects(64,hthread,TRUE,INFINITE);  
           WaitForMultipleObjects(64,&hthread[64],TRUE,INFINITE);  
           WaitForMultipleObjects(64,&hthread[128],TRUE,INFINITE);  
           WaitForMultipleObjects(62,&hthread[192],TRUE,INFINITE);  
       DeleteCriticalSection(&cs);  
       CString  temp;  
           for  (i=0;i           {  
                       PBYTE  pmac=(PBYTE)host[i].szMac;  
                       temp.Format("%s(%02x-%02x-%02x-%02x-%02x-%02x)",host[i].szIp,pmac[0],pmac[1],pmac[2],pmac[3],pmac[4],pmac[5]);  
                       m_list.AddString(temp);  
           }  
             
 
}
posted @ 2009-10-25 01:26 装配中的脑袋 阅读(993) 评论(0) 编辑
摘要: 1.CDumpContext 该类没有基类。这个类支持面向流的诊断输出,以人能够阅读的文本。该类重载了<<操作符。afxDump是一个预声明的CDumpContext对象,可以方便使用。该对象只在MFC的Debug版中有效。可以将调式信息输出到调试输出窗口或调试终端。// example for afxDumpCPerson myPerson = new CPerson;// set ...阅读全文
posted @ 2009-04-29 13:31 装配中的脑袋 阅读(1468) 评论(0) 编辑
摘要: (1) 如何通过代码获得应用程序主窗口的指针?主窗口的 指针保存在CWinThread::m_pMainWnd中,调用AfxGetMainWnd实现。AfxGetMainWnd() ->ShowWindow(SW_SHOWMAXMIZED)//使程序最大化.(2) 确定应用程序的路径Use GetModuleFileName 获得应用程序的路径,然后去掉可执行文件名。Example:TCHA...阅读全文
posted @ 2009-04-29 09:28 装配中的脑袋 阅读(1371) 评论(0) 编辑
摘要: Process p = new Process(); p.StartInfo.FileName = "文件名" p.SratrInfo.Arguments = "运行参数" p.Start(); --------------------------------------------------------------- 或者使用ShellExecute这个API函数,[DllImport("sh...阅读全文
posted @ 2008-03-05 19:57 装配中的脑袋 阅读(1999) 评论(0) 编辑
摘要: 1.创建WebService2.配置 网站 的 WebService文件夹目录安全性 - 身份验证与访问控制3.访问WebService================ 1.创建WebService 2.配置 网站 的 WebService文件夹目录安全性 - 身份验证与访问控制-------------------IIS管理器->网站->WebService文件夹->右键-&g...阅读全文
posted @ 2008-03-01 15:10 装配中的脑袋 阅读(730) 评论(0) 编辑