置顶随笔
摘要: http://www.cnblogs.com/lizarb/archive/2006/07/26/460026.htmlhttp://blog.csdn.net/hjf1223/archive/2006/08/27/1129283.aspx阅读全文
posted @ 2007-07-05 23:04 左左右右 阅读(74) 评论(0) 编辑
  2007年7月6日

http://www.cnblogs.com/ugoer/category/32456.html
 http://www.z6688.com/info/47936-1.htm  
Home - Forum - Articles - Books - About
Presented in HDTV, where available.
 E-mail:

Password:

Save login? Login

Register - Forgot password?
  Search | Recent | Register

Forum => General ASP.NET => Password hashing and salting


Jump to:  Announcements Article discussions CliqueSite® NewsBlog CliqueSite®.Ads Feedback and Feature Requests General ASP.NET Known Issues Maximizing ASP.NET: Real World, Object-Oriented Development POP Forums v5.1 Installation Help POP Forums v6.0 Installation Help POP Forums v8 Running Your Forum Suggestions and Feedback Testing... Testing... v7.x Installation Help

Next Oldest | Next Newest
11/17/2005 7:32:20 PM  Link | Reply | Edit | Quote 
petela     
Location: | Joined: 1/11/2005 | Posts: 53 | Offline 
Hi,

I wrote an application which takes a username and password from text boxes, salts and hashes the password, and stores the username, password, and salt values in a database table. So far, so good.

The problem I'm having is that I cannot login using a valid username and password. Here are the methods I've created:


public bool CheckCredentials(string suppliedUsername, string suppliedPassword) // returns checkPasswordMatch

{

bool checkPasswordMatch = false;

// get database connection from config file

SqlConnection DbConnection = new SqlConnection(ConfigurationSettings.AppSettings["bridgeportConnectionString"]);;

DbConnection.Open();

// from stored procedure

SqlCommand loginCommand = new SqlCommand("GetLogin", DbConnection);

// SELECT passwordhash, passwordsalt FROM LoginInfo WHERE loginname = @loginname (from sp GetLogin)

loginCommand.CommandType = CommandType.StoredProcedure;

// create parameters

SqlParameter loginnameParam = loginCommand.Parameters.Add("@loginname", SqlDbType.VarChar, 30);

// assign values to parameters

loginnameParam.Value = suppliedUsername;

// create a DataReader

SqlDataReader loginReader = loginCommand.ExecuteReader();

if (!loginReader.Read()) // if username not found in database, return false

return false;

// get passwordhash and passwordsalt values from database corresponding to loginusername

string passwordHashValue = loginReader.GetString(0);

string passwordSaltValue = loginReader.GetString(1);

string userSuppliedPassword = suppliedPassword;

// take the loginpassword and passwordsalt values and concatenate them together

string passwordAndSaltValue = String.Concat(userSuppliedPassword, passwordSaltValue);

// hash the loginpassword and passwordsalt values

string hashedPasswordAndSaltValue = FormsAuthentication.HashPasswordForStoringInConfigFile(passwordAndSaltValue, "SHA1");

// check to see if the hashedPasswordAndSaltValue matches the passwordhash value in the database

checkPasswordMatch = hashedPasswordAndSaltValue.Equals(passwordHashValue); // returns true or false

// close the DataReader and the Database connection

loginReader.Close();

DbConnection.Close();

// return true or false

return checkPasswordMatch;

}

/////////////

public void Login(object sender, System.EventArgs e)

{

bool passwordVerified = false;

// using the supplied username and password, determine if valid combination

passwordVerified = CheckCredentials(txtUsername.Text, txtPassword.Text); // return true or false

if (passwordVerified == true) // username and password combination matches database

{

// create session and redirect valid user

Session["ValidLogin"] = true;

Response.Redirect("sitemanager.aspx");

}

else // username and password combination does not match database record

{

// write out error message

message.Text = "Invalid login. Please try again.";

}

}


The code compiles without errors, but it keeps returning the error message and never validates the user. Is there a problem with the methods? What should the web.config file look like? If i'm storing the login info in a database, do I still need forms authentication?

Thanks,

petela
 
11/19/2005 3:41:39 PM  Link | Reply | Edit | Quote 
Jeff         
Location: Cleveland, OH, USA | Joined: 8/15/2000 | Posts: 794 | Offline 
Well you'll have to fire up the debugger and see what kind of values everything is returning one line at a time.


--------------------------------------------------------------------------------

Jeff 'Jones' Putz
POP World Media, LLC
Maximizing ASP.NET
 
11/21/2005 6:25:43 PM  Link | Reply | Edit | Quote 
petela     
Location: | Joined: 1/11/2005 | Posts: 53 | Offline 
Thanks for the advice. That's exactly what I did. I hadn't used the Debugger much because I've never had this much trouble getting something to work properly. Anyway, I did finally get it to work. The problem was not in my login function, but in the code I wrote to create the salt values, and in the corresponding database table.

Thanks again for your help.

petela
 

Next Oldest | Next Newest
Forum => General ASP.NET => Password hashing and salting
Please login or register to post.
CliqueSite® POP Forums Feature UI v7.5.0
©2004, POP World Media, LLC

©2006, POP World Media, LLC. All rights reserved
Legal, privacy, terms of service


(SELECT PasswordSalt FROM aspnet_Membership WHERE UserID IN (SELECT UserID FROM aspnet_Users WHERE UserName=@UserName))

select PasswordSalt from aspnet_Membership  where UserId IN (SELECT UserID FROM aspnet_Users WHERE UserName = '123'

kiEkXjmXCeIHeYFp+e07fQ==
98AFEE4E5ED96EFA0ABE882160B732D79DC33D44
fwJOdSvrP6oE20fDt7/hx01DaD8=

目的是在后台管理员可以修改用户的密码。
已知用户名UserName,用一个Textbox得到一个新的密码newpassword。我查到一种方法说是可以实现加密 System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(newpassword, "SHA1");
我把得到的加密后的密码直接写到数据库中,得到的密码是类似:98AFEE4E5ED96EFA0ABE882160B732D79DC33D44形式的,而用vs2005自带的登陆控件CreateUser创建的用户密码格式是:fwJOdSvrP6oE20fDt7/hx01DaD8= 形式的,之后用vs2005自带的登陆控件login登录时,发现修改后的密码不能登陆。我查了很多资料上说:MembershipUser提供了GetPassword 方法,但是这是只有在加密形式设置成Clear,即密码在数据库中以明码的形式存在,才能得到密码。而ChangePassword必须要提供旧密码或者密码提示答案才可以修改。默认状态下,membership是采用SHA1的方法进行加密,然后采取一种机制,与passwordsalt进行再次加密,最后形成数据库中显示的密码。
我找了很久也不知道“采取一种机制,与passwordsalt进行再次加密”是什么机制,我怎么才能实现管理员在后台修改密码呢?
我打算登录和创建新用户还是用vs2005自带的登陆控件。
在我的web.config里面
<membership>
...
passwordFormat = "Hashed"
</membership>

public override MembershipUser CreateUser(string username,
         string password,
         string email,
         string passwordQuestion,
         string passwordAnswer,
         bool isApproved,
         object providerUserKey,
         out MembershipCreateStatus status)
{
  ValidatePasswordEventArgs args =
    new ValidatePasswordEventArgs(username, password, true);

  OnValidatingPassword(args);

  if (args.Cancel)
  {
    status = MembershipCreateStatus.InvalidPassword;
    return null;
  }


  if (RequiresUniqueEmail && GetUserNameByEmail(email) != "")
  {
    status = MembershipCreateStatus.DuplicateEmail;
    return null;
  }

  MembershipUser u = GetUser(username, false);

  if (u == null)
  {
    DateTime createDate = DateTime.Now;

    if (providerUserKey == null)
    {
      providerUserKey = Guid.NewGuid();
    }
    else
    {
      if (!(providerUserKey is Guid))
      {
        status = MembershipCreateStatus.InvalidProviderUserKey;
        return null;
      }
    }

    OdbcConnection conn = new OdbcConnection(ConnectionString);
    OdbcCommand cmd = new OdbcCommand("INSERT INTO [" + tableName + "]" +
          " (PKID, Username, Password, Email, PasswordQuestion, " +
          " PasswordAnswer, IsApproved," +
          " Comment, CreationDate, LastPasswordChangedDate, LastActivityDate," +
          " ApplicationName, IsLockedOut, LastLockedOutDate," +
          " FailedPasswordAttemptCount, FailedPasswordAttemptWindowStart, " +
          " FailedPasswordAnswerAttemptCount, FailedPasswordAnswerAttemptWindowStart)" +
          " Values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", conn);

    cmd.Parameters.Add("@PKID", OdbcType.UniqueIdentifier).Value = providerUserKey;
    cmd.Parameters.Add("@Username", OdbcType.VarChar, 255).Value = username;
    cmd.Parameters.Add("@Password", OdbcType.VarChar, 255).Value = EncodePassword(password);
    cmd.Parameters.Add("@Email", OdbcType.VarChar, 128).Value = email;
    cmd.Parameters.Add("@PasswordQuestion", OdbcType.VarChar, 255).Value = passwordQuestion;
    cmd.Parameters.Add("@PasswordAnswer", OdbcType.VarChar, 255).Value = EncodePassword(passwordAnswer);
    cmd.Parameters.Add("@IsApproved", OdbcType.Bit).Value = isApproved;
    cmd.Parameters.Add("@Comment", OdbcType.VarChar, 255).Value = "";
    cmd.Parameters.Add("@CreationDate", OdbcType.DateTime).Value = createDate;
    cmd.Parameters.Add("@LastPasswordChangedDate", OdbcType.DateTime).Value = createDate;
    cmd.Parameters.Add("@LastActivityDate", OdbcType.DateTime).Value = createDate;
    cmd.Parameters.Add("@ApplicationName", OdbcType.VarChar, 255).Value = pApplicationName;
    cmd.Parameters.Add("@IsLockedOut", OdbcType.Bit).Value = false;
    cmd.Parameters.Add("@LastLockedOutDate", OdbcType.DateTime).Value = createDate;
    cmd.Parameters.Add("@FailedPasswordAttemptCount", OdbcType.Int).Value = 0;
    cmd.Parameters.Add("@FailedPasswordAttemptWindowStart", OdbcType.DateTime).Value = createDate;
    cmd.Parameters.Add("@FailedPasswordAnswerAttemptCount", OdbcType.Int).Value = 0;
    cmd.Parameters.Add("@FailedPasswordAnswerAttemptWindowStart", OdbcType.DateTime).Value = createDate;

    try
    {
      conn.Open();

      int recAdded = cmd.ExecuteNonQuery();

      if (recAdded > 0)
      {
        status = MembershipCreateStatus.Success;
      }
      else
      {
        status = MembershipCreateStatus.UserRejected;
      }
    }
    catch (OdbcException)
    {
      // Handle exception.

      status = MembershipCreateStatus.ProviderError;
    }
    finally
    {
      conn.Close();
    }


    return GetUser(username, false);
  }
  else
  {
    status = MembershipCreateStatus.DuplicateUserName;
  }


  return null;
}
internal string encodepassword(string pass, int passwordformat, string salt);

posted @ 2007-07-06 18:55 左左右右 阅读(108) 评论(0) 编辑
  2007年7月5日
http://www.cnblogs.com/lizarb/archive/2006/07/26/460026.html

http://blog.csdn.net/hjf1223/archive/2006/08/27/1129283.aspx
posted @ 2007-07-05 23:04 左左右右 阅读(74) 评论(0) 编辑
  2007年5月17日
Treeview控件中一个树形图由节点(TreeNode)和连接线组成。TtreeNodeTTreeview的基本组成单元。一个树的节点又包含文本(Text)和数据(Data)TextString类,Data则为无定形指针(Untyped Pointer),可以指向一个与节点相联系的数据结构。
每一个节点下子节点形成这一节点的Items属性,当前节点有一个唯一的Index(TreeNodeIndex属性),用于说明子节点在Items中的位置,每一个节点下的子节点是顺序编号的,第一个是0,第二个是1,依次类推。用IndexOf方法获得子节点的顺序,绝对顺序(AbsoluteIndex)则是指从Treeview第一个项开始的顺序值,第一个是0,如此推下去。Item属性则根据Index的值返回当前节点的第Index个子节点。Count则表明属于此项的所有子节点的数量。用MoveTo方法将Item由一个位置移到另一个位置。
1.添加、删除、修改节点:
静态的方法可以在设计时通过Items的编辑器设置各节点的内容。
在添加和删除前必须保证有节点被选中(Treeview.Selected = nil)
AddFirst, AddFirstChild, AddChild等先添加根节点,如Treeview.Items.AddFirst( nil, 'Root')
然后以此为基础,添加此项的子节点。

删除节点
Treeview.Selected.Delete

编辑节点内容
Treeview.Selected.EditText

注意:由于根节点没有父节点 (TTreeNode.Parent= nil)
此外,在大批量添加数据到Treeview中时最好使用
  TreeView.Items.BeginUpdate;
  添加节点
  TreeView.Items.EndUpdate
这样能加快显示速度。
http://users.iafrica.com/d/da/dart/zen/Articles/TTreeView/TTreeView_eg01.html

posted @ 2007-05-17 23:08 左左右右 阅读(563) 评论(0) 编辑
  2007年5月9日
http://community.csdn.net/Expert/TopicView3.asp?id=5479325
出自:http://www.mylinux.com.cn/bbsShowDetail.do?id=432
 MOV(MOVe)   传送指令
PUSH     入栈指令
POP     出栈指令
XCHG(eXCHanG)   交换指令
XLAT(TRANSLATE)   换码指令
LEA (Load Effective Address) 有效地址送寄存器指令
LDS(Load DS with pointer) 指针送寄存器和DS指令
LES(Load ES with pointer) 指针送寄存器和ES指令
LAHF(Load AH with Flags) 标志位送AH指令
SAHF(Store AH into Flgs) AH送标志寄存器指令
PUSHF(PUSH the Flags)   标志进栈指令
POPF(POP the Flags)   标志出栈指令
ADD     加法指令
ADC     带进位加法指令
INC     加1指令
SUB(SUBtract)   不带借位的减法指令
SBB(SuVtrach with borrow) 带借位的减法指令
DEC(DECrement)   减1指领
NEG(NEGate)   求补指令
CMP(CoMPare)   比较指令
MUL(unsinged MULtiple) 无符号数乘法指令
IMUL(sIgned MUL tiple) 有符号数乘法指令
DIV(unsigned DIVide)   无符号数除法指令
IDIV(sIgned DIVide)   有符号数除法指令
CBW(Count Byte to Word) 字节转换为字指令
CWD(Count Word to Doble word) 字转换为双字指令
DAA   压缩的BCD码加法十进制调整指令
DAS   压缩的BCD码减法十进制调整指令
AAA   非压缩的BCD码加法十进制调整指令
AAS   非压缩的BCD码加法十进制调整指令
AND     逻辑与指令
OR     逻辑或指令
XOR     逻辑异或指令
NOT     逻辑非指令
TEST     测试指令
SHL(SHift logical Letf)   逻辑左移指令
SHR(SHift logical Right)   逻辑右移指令
ROL(Rotate Left )   循环左移指令P58
ROR(Rotate Right)   循环右移指令P58
RCL(Rotate Left through Carry) 带进位循环左移
RCR(Rotate Right through Carry) 带进位循环左移
MOVS(MOVe String)   串传送指令
STOS(STOre into String) 存入串指令
LODS(LOad from string) 从串取指令
REP(REPeat)   重复操作前
CLD(CLear Direction flag) 清除方向标志指令
STD(SeT Direction flag)   设置方向标志指令
CMPS(CoMPare String)   串比较指令
SCAS(SCAn String)   串扫描指令
REPE/REPZ(REPeat while Equal/Zero)相等/为零时重复操作前缀
REPNE/REPNZ(REPeat while Not Equal/Zero)不相等/不为零进重复前缀
IN(INput)   输入指令
OUT(OUTput)   输出指令
JMP(JuMP)   无条件转移指令
JZ,JNZ,JS,JNS,JO,JNO,JP,JNP,JB,JNB,JBE,JNBE,JL,JNL,JLE,JNLE,JCXZ   条件转移指令
LOOP     循环指令P70
LOOPZ/LOOPE   为零/相等时循环指令
LOOPNZ/LOOPNE   不为零/不相等时循环指令
CALL     子程序调用指令
RET(RETun)   子程序返回指令
CLC(CLear Carry)   进位位置0指令
CMC(CoMplement Carry) 进位位求反指令
SRC(SeT Carry)   进位位置1指令
NOP(No OPeretion)   无操作指令
HLT(HaLT)   停机指令
OFFSET   返回偏移地址
SEG     返回段地址
EQU(=)   等值语句
PURGE   解除语句
DUP     操作数字段用复制操作符
SEGMENT,ENDS   段定义指令
ASSUME   段地址分配指令
ORG     起始偏移地址设置指令
$     地址计数器的当前值
PROC,ENDP   过程定义语句
NAME,TITLE,END   程序开始结束语句
MACRO,ENDM   宏定义指令

JZ   OPR //结果为零转移 Jump Zero
JNZ   OPR //结果不为零转移  Jump No Zero
JS   OPR //结果为负转移
JNS   OPR //结果为正转移
JO   OPR //溢出转移  Jump Overflow
JNO   OPR //不溢出转移  Jump No Overflow
JP   OPR //结果为偶转移
JNP   OPR //结果为奇转移
JC   OPR //有进位转移   Jump Carry
JNC   OPR //无进位转移 Jump No Carry
posted @ 2007-05-09 23:04 左左右右 阅读(336) 评论(0) 编辑
转自:http://blog.csdn.net/hugoon/archive/2007/02/08/1504966.aspx


 

我一直认为Delphi功能与C++相比毫不逊色,提供了丰富的控件和类、全部API以及嵌入的汇编。最近小弟在把C版的Huffman压缩改用Delphi写时,顺便研究了一下Delphi的位操作和嵌入式汇编,利用嵌入汇编我们可以得到高效的程序代码,完成一些Delphi没有提供的底层功能。借贵报一方宝地与大家分享我的研究

  Delphi的位操作

  每个学习C的朋友都会被告之C中级语言,其位操作非常方便,而Pascal之流只适用于教学。但是Delphi中提供了一组位操作,可别以过去对Pascal的态度看Delphi

  * 按位的逻辑操作:

  Delphi中的ANDORNOT可不仅仅只对逻辑表达式有作用,它们还可以操作数;

  AND:按位与,如:1 AND 2其结果为0

  OR:按位或,如:1 OR 2其结果为3

  Not:按位取反:如Not 1其结果对于有符号数是-2,对于无符号数是65534

  另外,还有按位异或XOR:如:1 XOR 2结果为3

  * 移位操作

  Delphi提供了SHLSHR进行移位左移和右移:

  例如:2 SHR1表示2按位右移一位结果为1

  * Delphi中的数

  既然有位的操作就一定涉及到数的类型:是有符号数(头一位用01表示正负)还是无符号数。

  Delphi中:Shortint8位)、Smallint16位)、Longint32位)、Integer32位)、Int6464位)是有符号数;而Byte8位)、Word16位)、Longword32位)是无符号数。它们之间可以像C一样强制转换。例如:Smallint类型的-1转换成Word类型就是65535。转换方法是Word(-1)。

  怎样,够全吧^_^!什么还不够……!?Delphi还有一招,接招吧……

  Delphi的嵌入式汇编

  Delphi中提供了几乎全部常用汇编指令的支持:MOVJEJMPCMPSHLSHRSALSARPOPPUSHHLT……自己去查吧。至于INT也能识别,不过非法操作或死机可别找我(在最早的Windows95中用Delphi 3似乎可以正确运行中断,但Windows 95 OEMWindows 98就不对了,大概是16位模块的问题,还搞不清楚)。

  * 嵌入式汇编的格式

  Delphi是使用ASM……END来标志汇编语句

   如:ASM

   mov al,1

   mov bl,al

   END;

  * 可操作的寄存器

  Delphi可用汇编管理以下寄存器:

  32位寄存器EAX EBX ECX EDX ESP EBP ESI EDI

  16位寄存器AX BX CX DX SP BP SI DI

  8位寄存器AL BL CL DL AH BH CH DH

   16位段寄存器CS DS SS ES

  以及协处理器寄存器堆栈 ST

  * 使用汇编前的工作

  教汇编的老师一再强调使用汇编要保存寄存器现场(保存使用前的寄存器状态,使用Push压栈和Pop从栈中弹出),不过这一切对于Delphi的嵌入式汇编是没有必要的(除非你自己要使用PushPop),因为Delphi已经帮你做了,不必担心会使数据丢掉。

  * Delphi嵌入式汇编的使用方式

  1.在一般函数过程中使用汇编

  汇编程序段可以嵌套于其它过程中:如:

  procedure TForm1.Button1Click(Sender: TObject);

  var i:smallint;

  begin

   i:=1;

   asm

   mov ax,i

   sal ax,1

   mov i,ax

   end;

   showmessage(inttostr(i));

  end;

  这个程序段是把16位的变量I进行左移,然后把结果用Mov Iax语句放入I变量所在地址返回值。最后显示I 的值是2

  2.独立的汇编程序段

  汇编程序段也可以单独写成函数或过程。这就涉及到参数的传递与结果的返回。首先Delphi对于函数的返回有一个约定:

  即:整型数据:8位的用AL返回,16位的用AX返回,32位的用EAX返回;

  实型:用ST0)返回

  指针:用EAX返回

  长字符串:用EAX返回其所在地址

  变量:可用@Result返回

  例如:一个用汇编的求和函数

  function _Sum(X, Y: Integer): Integer;

   asm

  MOV EAX,X //32位的数放入EAX

  ADD EAX,Y //进行加法运算

  MOV @Result,EAX //返回XY

   end;

  一个把字符转化为大写的函数例子

  function _UpCase( ch : Char ) : Char;

  asm

   CMP AL,`a'

   JB @@exit

   CMP AL,`z'

   JA @@exit

   SUB AL,`a' `A'

  @@exit:

  end;

  值得注意的是第二个例子中,没有象第一个那样把参数用语句放到寄存器中,这是由于Delphi中默认的把Byte(Char)类型放在AL中,不需要用Mov语句,但是这种函数不能是类的成员,否则结果会出错。

  3.在汇编中调用其它过程

  汇编语句中的Call语句,可以用于调用其它过程,既可以是其它汇编程序段也可以是Delphi中的标准过程:

  例如:假设新建一个窗体并在上面加了一个按钮,在Click事件中写入以下代码

  procedure TForm1.Button1Click(Sender: TObject);

  begin

   showmessage(`ok');

  end;

  再写一个过程_X

  function TForm1._x(var i:smallint):integer;

  asm

   call button1click

  end;

  执行_x的结果就可以显示消息框。

  * 汇编的调试

  编好了程序,没错,还好,如果有错,就得用到调试工具:如变量的跟踪、断点、堆栈查看……对于汇编还可以用View菜单的Debug WindowsCPU窗口跟踪。

  OK!就谈到这
-----------------------------------
Delphi嵌入式汇编一例

来源:ether's Blog 

delphi写的程序,把x指针指向的4个字节次序颠倒过来:

function toulong(x: pchar): longword;

begin

  result := (longword(x^) shl 24) or

    (longword((x + 1)^) shl 16) or

    (longword((x + 2)^) shl 8) or

    (longword((x + 3)^));

end;


以下是用delphi的嵌入式汇编写法:

function toulong(x: pchar): longword;

asm

  mov esi,eax

  mov ax,[esi]

  xchg ah,al

  shl eax,16

  mov ax,[esi+2]

  xchg ah,al

end;


说明:默认情况下,delphi使用“register”方式,若参数在3个已内,
将分别使用eaxedxecx,超过3个参数部分将使用堆栈。返回参数的
存放视长度而定,例如8位用al返回,16位用ax32位用eax64位用用两个
32
位寄存器edx:eax,其中eax是低位。


效率:本例asm大约比delphic50%


posted @ 2007-05-09 22:46 左左右右 阅读(398) 评论(0) 编辑
由于TObject.Dispatch几乎被大量执行,为了提高效率,使用汇编语言撰写。在Dispatch方法中首先检查触发的消息ID是否在0~c000H之中,如果不在此范围调用DefaultHandler;如果是的话就调用GetDynaMethod从VCL组件中根据消息ID在动态方法表格中搜寻。如果找到能够处理消息的VCL事件处理函数就跳到此事件处理函数执行,否则就调用DefaultHandler虚拟方法来处理尚未分派的消息。
posted @ 2007-05-09 17:39 左左右右 阅读(196) 评论(0) 编辑
摘要: Windows应用程序的每一个窗口都有一个大的消息循环以及一个窗口函数(WndProc)用以分发和处理消息。VCL作为一个Framework,当然会将这些东西隐藏起来,而重新提供一种易用的、易理解的虚拟机制给程序员。那么VCL是如何做到的呢?本节就来解答这个问题。只要代码单元中包含了Forms.pas,就会得到一个对象——Application。利用它可以帮助我们完成许多工作。例如要退出应用程序,...阅读全文
posted @ 2007-05-09 14:34 左左右右 阅读(663) 评论(1) 编辑
  2007年5月8日

1.TGraphicControl/TcustomControl 与画布(Canvas)
    VCL中,TCotnrol之下的组件分两条路各行其道。一条为图形组件,这类组件并非窗口,职责只在于显示图形、图像,其基类是TGraphicControl;另一条为窗口组件,这类组件本身是一个Windows窗口(有窗口句柄),其基类是TWinControl。
    TGraphicControl作为显示图形、图像的组件分支,从其开始就提供了一个TCanvas类型的Canvas属性,以便在组件上绘制图形、显示图像。
    对于窗口组件的分支,TWinControl并没有提供Canvas属性,而在其派生类TCustomControl才开始提供Canvas属性。

                       控件类分支
    TGraphicControl与TCustomControl的实现都在Controls.pas单元中,它们的声明看上去也是如此相似:

  TGraphicControl = class(TControl)
  
private
    FCanvas: TCanvas;
    procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
  protected
    procedure Paint; virtual;
    
property Canvas: TCanvas read FCanvas;
  
public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  
end;

  TCustomControl 
= class(TWinControl)
  
private
    FCanvas: TCanvas;
    procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
  protected
    procedure Paint; virtual;
    procedure PaintWindow(DC: HDC); override;
    
property Canvas: TCanvas read FCanvas;
  
public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  
end;

       它们提供了Canvas属性,只不过此时Canvas属性被隐藏在protected节中,它们的派生类可以选择性地将其publish。
由于TGraphicControl与TCustomControl在有关Canvas熟悉的实现上也非常相似,在此只以TGraphicControl的实现来讲解“画布”属性。
由TGraphicControl的声明中的
property Canvas: TCanvas read FCanvas;
可知Canvas是一个只读属性,其载体是private的成员对象FCanvas。FCanvas在TGraphicControl的构造函数中被创建:

{ TGraphicControl }

constructor TGraphicControl.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FCanvas :
= TControlCanvas.Create;
  TControlCanvas(FCanvas).Control :
= Self;
end;

destructor TGraphicControl.Destroy;
begin
  
if CaptureControl = Self then SetCaptureControl(nil);
  FCanvas.Free;
  inherited Destroy;
end;

procedure TGraphicControl.WMPaint(var Message: TWMPaint);
begin
  
if Message.DC <> 0 then
  begin
    Canvas.Lock;
    try
      Canvas.Handle :
= Message.DC;
      try
        Paint;
      finally
        Canvas.Handle :
= 0;
      
end;
    finally
      Canvas.Unlock;
    
end;
  
end;
end;

procedure TGraphicControl.Paint;
begin
end;

       在此需要注意的是,FCanvas在声明时,是被声明为TCanvas类型的,而在创建时,却创建了TControlCanvas的示例。其实,TControlCanvas是TCanvas的派生类,它提供了一些额外的属性和事件来辅助在Control(控件)上提供“画布”属性。
这里暂停一下,先来看一下TcontrolCanvas:
  TControlCanvas = class(TCanvas)
  
private
    FControl: TControl;
    FDeviceContext: HDC;
    FWindowHandle: HWnd;
    procedure SetControl(AControl: TControl);
  protected
    procedure CreateHandle; override;
  
public
    destructor Destroy; override;
    procedure FreeHandle;
    procedure UpdateTextFlags;
    
property Control: TControl read FControl write SetControl;
  
end;
       TControlCanvas将Canvas绑定到一个TControl实例上,其内部的FControl指针即指向Canvas所属的TControl实例。
      TCanvas提供了一个空的虚方法CreateHandle()。这个虚方法在TControlCanvas中被覆盖重新实现:
procedure TControlCanvas.CreateHandle;
begin
  
if FControl = nil then inherited CreateHandle else
  begin
    
if FDeviceContext = 0 then
    begin
      
with CanvasList.LockList do
      try
        
if Count >= CanvasListCacheSize then FreeDeviceContext;
        FDeviceContext :
= FControl.GetDeviceContext(FWindowHandle);
        Add(Self);
      finally
        CanvasList.UnlockList;
      
end;
    
end;
    Handle :
= FDeviceContext;
    UpdateTextFlags;
  
end;
end;

在CreateHandle()方法中,如果FControl是TWinControl或其派生类的实例,即控件本身是窗口,则取得该窗口的设备上下文句柄赋给Handle属性;如果FControl非TWinControl或其派生类的实例,即控件本身并非窗口,则将其父窗口的设备上下文句柄赋给Handle。这些都是通过TControl声明的虚函数GetDeviceContext()实现的,因为TWinControl覆盖重新实现了GetDeviceContext()。
说完TControlCanvas,下面继续刚才的话题。TGraphicControl的构造函数中创建了TControlCanvas实例并赋给FCanvas。构造函数的最后一行代码
TControlCanvas(FCanvas).Control := Self;
将Canvas属性绑定到了控件本身。
然后,TGraphicControl定义了一个处理WM_PAINT Windows消息的消息处理函数:
procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
在WMPaint()方法中,根据接受到的消息的参数所给出的窗口的设备上下文句柄,给Canvas属性的Handle重新赋值,并且调用虚方法Paint():
procedure TGraphicControl.WMPaint(var Message: TWMPaint);
begin
  
if Message.DC <> 0 then
  begin
    Canvas.Lock;
    try
      Canvas.Handle :
= Message.DC;
      try
        Paint;
      finally
        Canvas.Handle :
= 0;
      
end;
    finally
      Canvas.Unlock;
    
end;
  
end;
end;

虚方法Paint()可以被TGraphicCotnrol的派生类所覆盖,重新定义并实现绘制图形、图像的方法,并且TGraphicControl的派生的实例总是可以放心使用其Canvas属性,而不必自行获得窗口的设备上下文句柄。而虚方法Paint()在TGraphicControl中的实现也只是一个空方法而已。

2.TCustomPanel与窗口重绘
TCustomPanel派生自TCustomControl,是所有Panel类组件的基类。TCustomPanel与4.8节中所述的TGraphicControl非常类似,只是TCustomControl派生自TWinControl,所以它的实例是一个窗口。
TCustomControl与TGraphicControl一样,拥有一个空的虚方法Paint(),以便让派生类决定如何重绘窗口。
现在就来看一下TcustomPanel。它从TCustomControl派生,并且覆盖重新实现了Paint()方法。在此,我们不关心TCustomPanel所实现的其他特性,而只关注其实现的Paint()方法。TCustomPanel实现的Paint()方法负责将组件窗口绘制出一个Panel效果(边框、背景和标题)。先来看一下Paint()方法:
procedure TCustomPanel.Paint;
const
  Alignments: 
array[TAlignment] of Longint = (DT_LEFT, DT_RIGHT, DT_CENTER);
var
  Rect: TRect;
  TopColor, BottomColor: TColor;
  FontHeight: 
Integer;
  Flags: Longint;

  procedure AdjustColors(Bevel: TPanelBevel);
  begin
    TopColor :
= clBtnHighlight;
    
if Bevel = bvLowered then TopColor := clBtnShadow;
    BottomColor :
= clBtnShadow;
    
if Bevel = bvLowered then BottomColor := clBtnHighlight;
  
end;

begin
  Rect :
= GetClientRect;
      // 画边框
  
if BevelOuter <> bvNone then
  begin
    AdjustColors(BevelOuter);
    Frame3D(Canvas, Rect, TopColor, BottomColor, BevelWidth);
  
end;
  Frame3D(Canvas, Rect, Color, Color, BorderWidth);
  
if BevelInner <> bvNone then
  begin
    AdjustColors(BevelInner);
    Frame3D(Canvas, Rect, TopColor, BottomColor, BevelWidth);
  
end;
  
with Canvas do
  begin
        // 画背景
    Brush.Color :
= Color;
    FillRect(Rect);
    Brush.Style :
= bsClear;
        // 写标题
    Font :
= Self.Font;
    FontHeight :
= TextHeight('W');
    with Rect do
    begin
      Top :
= ((Bottom + Top) - FontHeight) div 2;
      Bottom :
= Top + FontHeight;
    
end;
    Flags :
= DT_EXPANDTABS or DT_VCENTER or Alignments[FAlignment];
    Flags :
= DrawTextBiDiModeFlags(Flags);
    DrawText(Handle, PChar(Caption), 
-1, Rect, Flags);
  
end;
end;

Paint()方法含有一个内嵌函数AdjustColors(),其作用是确定边框的上下线条颜色(一条边框由两个像素宽度的直线构成,形成立体效果)。
TCustomPanel使用其基类(TCustomControl)提供的Canvas属性,覆盖其基类定义的虚方法Paint(),完成了窗口重绘过程。
在自己编写组件时,如果需要在组件表面绘制图形、图像的话,就可以如同TCustomPanel一样,覆盖重新实现Paint()方法。同时,使用基类提供的Canvas属性,对于绘图过程来说,也是非常简单的。
posted @ 2007-05-08 16:56 左左右右 阅读(932) 评论(2) 编辑

TControl作为控件类的根类提供的服务:
1)TControl控件基本信息
TControl开始加入控件的基本信息并使用持久化机制保存信息。TControl声明的Left、Top等控件信息并使用Published关键字输出以便让客户端存取。这些控件信息会自动被持久化。

 TControl = class(TComponent)
  
private
    FParent: TWinControl;
    FWindowProc: TWndMethod;
    FLeft: 
Integer;
    FTop: 
Integer;
    FWidth: 
Integer;
    FHeight: 
Integer;
    FControlStyle: TControlStyle;
    FControlState: TControlState;
    
 published
    
property LeftInteger read FLeft write SetLeft;
    
property Top: Integer read FTop write SetTop;
    
property Width: Integer read FWidth write SetWidth;
    
property Height: Integer read FHeight write SetHeight;
    
property Cursor: TCursor read FCursor write SetCursor default crDefault;
    
property Hint: string read FHint write FHint stored IsHintStored;
    
property HelpType: THelpType read FHelpType write FHelpType default htContext;
    
property HelpKeyword: String read FHelpKeyword write SetHelpKeyword stored IsHelpContextStored;
    
property HelpContext: THelpContext read FHelpContext write SetHelpContext stored IsHelpContextStored default 0;
end;

FParent: TWinControl代表TControl和TWinControl有紧耦合。
2)基础资源服务
  控件需要使用光标、文字、颜色、字体以及其他的资源,TControl必须具备这些资源的支持,相关属性:

    FParentFont: Boolean;
    FParentColor: 
Boolean;
    FAlign: TAlign;
    FDragMode: TDragMode;
    FText: PChar;
    FFont: TFont;
    FColor: TColor;
    FCursor: TCursor;

除了资源属性,当外界改变控件使用的资源时,TControl提供响应资源事件的方法,CM-XXXChanged方法是和资源改变相关的方法。

    procedure CMFontChanged(var Message: TMessage); message CM_FONTCHANGED;
    procedure CMColorChanged(var Message: TMessage); message CM_COLORCHANGED;

 

procedure TControl.CMFontChanged(var Message: TMessage);
begin
  Invalidate;
end;

procedure TControl.CMColorChanged(var Message: TMessage);
begin
  Invalidate;
end;

TControl.Invalidate调用了TControl.InvalidateControl来重绘控件区域,TControl.InvalidateControl最后调用了Windows API的InvalidateRect莱进行重绘工作。

procedure TControl.Invalidate;
begin
  InvalidateControl(Visible, csOpaque in ControlStyle);
end;

procedure TControl.InvalidateControl(IsVisible, IsOpaque: 
Boolean);
var
  Rect: TRect;

  
function BackgroundClipped: Boolean;
  var
    R: TRect;
    List: TList;
    I: 
Integer;
    C: TControl;
  begin
    Result :
= True;
    List :
= FParent.FControls;
    I :
= List.IndexOf(Self);
    
while I > 0 do
    begin
      Dec(I);
      C :
= List[I];
      
with C do
        
if C.Visible and (csOpaque in ControlStyle) then
        begin
          IntersectRect(R, Rect, BoundsRect);
          
if EqualRect(R, Rect) then Exit;
        
end;
    
end;
    Result :
= False;
  
end;

begin
  
if (IsVisible or (csDesigning in ComponentState) and
    
not (csNoDesignVisible in ControlStyle)) and (Parent <> nil) and
    Parent.HandleAllocated 
then
  begin
    Rect :
= BoundsRect;
    InvalidateRect(Parent.Handle, @Rect, 
not (IsOpaque or
      (csOpaque in Parent.ControlStyle) 
or BackgroundClipped));
  
end;
end;

注意:Invalidate被声明为虚拟方法。procedure Invalidate; virtual;
3)处理鼠标的服务
控件需要处理鼠标事件,WMXXButtonXXXX等方法是TControl提供的基础鼠标服务,

  procedure WMLButtonDown(var Message: TWMLButtonDown); message WM_LBUTTONDOWN;
    procedure WMNCLButtonDown(var Message: TWMNCLButtonDown); message WM_NCLBUTTONDOWN;
    procedure WMRButtonDown(var Message: TWMRButtonDown); message WM_RBUTTONDOWN;
    procedure WMMButtonDown(var Message: TWMMButtonDown); message WM_MBUTTONDOWN;
    procedure WMLButtonDblClk(var Message: TWMLButtonDblClk); message WM_LBUTTONDBLCLK;
    procedure WMRButtonDblClk(var Message: TWMRButtonDblClk); message WM_RBUTTONDBLCLK;
    procedure WMMButtonDblClk(var Message: TWMMButtonDblClk); message WM_MBUTTONDBLCLK;
    procedure WMMouseMove(var Message: TWMMouseMove); message WM_MOUSEMOVE;
    procedure WMLButtonUp(var Message: TWMLButtonUp); message WM_LBUTTONUP;
    procedure WMRButtonUp(var Message: TWMRButtonUp); message WM_RBUTTONUP;
    procedure WMMButtonUp(var Message: TWMMButtonUp); message WM_MBUTTONUP;
    procedure WMMouseWheel(var Message: TWMMouseWheel); message WM_MOUSEWHEEL;
    procedure WMCancelMode(var Message: TWMCancelMode); message WM_CANCELMODE;


如果TControl的派生类没有定义处理鼠标的方法,那么TControl便会负责处理鼠标事件。

procedure TControl.WMLButtonDown(var Message: TWMLButtonDown);
begin
  SendCancelMode(Self);
  inherited;
  
if csCaptureMouse in ControlStyle then MouseCapture := True;
  
if csClickEvents in ControlStyle then Include(FControlState, csClicked);
  DoMouseDown(Message, mbLeft, []);
end;


4)处理消息和事件的服务
控件要处理事件和消息,要加入响应外界事件的处理机制,这就是

    procedure WndProc(var Message: TMessage); virtual;
    procedure DefaultHandler(var Message); override;


5)控件重绘服务
控件重绘事控件类最需要的核心服务,因为控件可以移动,改变字体、颜色、大小等,当这些事件发生时控件都需要进行重绘工作。采用虚拟方法。
TControl 提供了三个相关的虚拟方法来提供控件重绘的功能,分别是

    procedure Repaint; virtual;
    procedure Invalidate; virtual;
    procedure Update; virtual;


1.TControl与Windows消息的封装
TObject提供了最基本的消息分发和处理的机制,而VCL真正对Windows系统消息的封装则是在TControl中完成的。
TControl将消息转换成VCL的事件,以将系统消息融入VCL框架中。
消息分发机制在4.2节已经介绍过,那么系统消息是如何变成事件的呢?
现在,通过观察TControl的一个代码片段来解答这个问题。在此只以鼠标消息变成鼠标事件的过程来解释,其余的消息封装基本类似。
先摘取TControl声明中的一个片段:
TControl = class(TComponent)
Private
  ……
  FOnMouseDown: TMouseEvent;
  ……
  procedure DoMouseDown(var Message: TWMMouse; Button: TMouseButton;
  Shift: TShiftState);
  ……
  procedure MouseDown(Button: TMouseButton; Shift: TShiftState;
  X, Y: Integer); dynamic;
  ……
  procedure WMLButtonDown(var Message: TWMLButtonDown); message
WM_LBUTTONDOWN;
  procedure WMRButtonDown(var Message: TWMRButtonDown); message
WM_RBUTTONDOWN;
  procedure WMMButtonDown(var Message: TWMMButtonDown); message
WM_MBUTTONDOWN;
  ……
protected
  ……
  property OnMouseDown: TMouseEvent read FOnMouseDown write FOnMouseDown;
  ……
end;
这段代码是TControl组件类的声明。
TControl声明了一个OnMouseDown属性,该属性读写一个称为FOnMouseDown的事件指针。因此,FOnMouseDown会指向OnMouseDown事件的用户代码。
TControl声明了WMLButtonDown、WMRButtonDown、WMMButtonDown 3个消息 处理函数,它们分别处理WM_LBUTTONDOWN、WM_RBUTTONDOWN、WM _MBUTTONDOWN 3个Windows消息,对应于鼠标的左键按下、右键按下、中键按下3个硬件事件。
另外,还有一个DoMouseDown()方法和一个MouseDown()的dynamic方法,它们与消息处理函数之间2是什么样的关系呢?
现在,就来具体看一下这些函数的实现。
这里是3个消息的处理函数:
procedure TControl.WMLButtonDown(var Message: TWMLButtonDown);
begin
  SendCancelMode(Self);
  inherited;
  if csCaptureMouse in ControlStyle then
    MouseCapture := True;
  if csClickEvents in ControlStyle then
    Include(FControlState, csClicked);
  DoMouseDown(Message, mbLeft, []);
end;
procedure TControl.WMRButtonDown(var Message: TWMRButtonDown);
begin
  inherited;
  DoMouseDown(Message, mbRight, []);
end;

procedure TControl.WMMButtonDown(var Message: TWMMButtonDown);
begin
  inherited;
  DoMouseDown(Message, mbMiddle, []);
end;
当TObject.Dispatch()将WM_LBUTTONDOWN消息、WM_RBUTTONDOWN消息或WM_MBUTTONDOWN消息分发给TControl的派生类的实例后,WMLButtonDown()、WMRButtonDown()或WMMButtonDown()被执行,然后它们都有类似这样
DoMouseDown(Message, mbRight, []);的代码来调用DoMouseDown():
procedure TControl.DoMouseDown(var Message: TWMMouse; Button:
TMouseButton; Shift: TShiftState);
begin
  if not (csNoStdEvents in ControlStyle) then
  with Message do
    if (Width > 32768) or (Height > 32768) then
  with CalcCursorPos do
    MouseDown(Button, KeysToShiftState(Keys) + Shift, X, Y)
  else
   MouseDown(Button,KeysToShiftState(Keys) + Shift,Message.XPos,Message.Ypos);
end;
在DoMouseDown()中进行一些必要的处理工作后(特殊情况下重新获取鼠标位置),就会调
MouseDown():
procedure TControl.MouseDown(Button: TMouseButton;Shift: TShiftState; X, Y: Integer);
begin
  if Assigned(FOnMouseDown) then
    FOnMouseDown(Self, Button, Shift, X, Y);
end;

在MouseDown()中,才会通过FOnMouseDown事件指针真正去执行用户定义的OnMouseDown事件的代码。
由此,完成了Windows系统消息到VCL事件的转换过程。
因此,从TControl派生的类都可以拥有OnMouseDown事件,只不过该事件属性在TControl中被定义成protected,只有其派生类可见,并且在派生类中可以自由选择是否公布这个属性。要公布该属性只需要简单地将其声明为published即可。如:
TMyControl = class(TControl)
published
  property OnMouseDown;
end;
这些函数过程的调用关系: Dispatch(WM_LBUTTONDOWN)-〉 WMMouseDown() -〉DoMouseDown() -〉MouseDown() -〉程序员的OnMouseDown事件代码;
说明了WM_LBUTTONDOWN消息到OnMouseDown事件的转换过程
在此,只是以OnMouseDown事件为例。其实,VCL对Windows各个消息的封装大同小异,以此一例足以说明事件模型的原理。
另外,值得注意的是,在上例中的MouseDown()函数是一个dynamic方法,因此可以通过在TControl派生类中覆盖MouseDown()来处理自己所编写组件的鼠标按下事件,然后通过inherited;语句调用TControl的MouseDown()来执行使用组件的程序员所编写的OnMouseDown的代码。

posted @ 2007-05-08 16:24 左左右右 阅读(341) 评论(0) 编辑
 1TObject = class
 2    constructor Create;
 3    procedure Free;
 4    class function InitInstance(Instance: Pointer): TObject;
 5    procedure CleanupInstance;
 6    function ClassType: TClass;
 7    class function ClassName: ShortString;
 8    class function ClassNameIs(const Name: string): Boolean;
 9    class function ClassParent: TClass;
10    class function ClassInfo: Pointer;
11    class function InstanceSize: Longint;
12    class function InheritsFrom(AClass: TClass): Boolean;
13    class function MethodAddress(const Name: ShortString): Pointer;
14    class function MethodName(Address: Pointer): ShortString;
15    function FieldAddress(const Name: ShortString): Pointer;
16    function GetInterface(const IID: TGUID; out Obj): Boolean;
17    class function GetInterfaceEntry(const IID: TGUID): PInterfaceEntry;
18    class function GetInterfaceTable: PInterfaceTable;
19    function SafeCallException(ExceptObject: TObject;
20      ExceptAddr: Pointer): HResult; virtual;
21    procedure AfterConstruction; virtual;
22    procedure BeforeDestruction; virtual;
23    procedure Dispatch(var Message); virtual;
24    procedure DefaultHandler(var Message); virtual;
25    class function NewInstance: TObject; virtual;
26    procedure FreeInstance; virtual;
27    destructor Destroy; virtual;
28  end;
29
提供的对象服务:
对象创建服务、对象释放服务、对象识别服务、对象信息服务、对象消息分派服务。
1.对象创建服务
   除了TObjiect的构造函数之外的其他相关函数如下:
TObject = class
    constructor Create;
    class 
function InitInstance(Instance: Pointer): TObject;
    class 
function InstanceSize: Longint;
    procedure AfterConstruction; virtual;
    class 
function NewInstance: TObject; virtual;
    destructor Destroy; virtual;
  
end;
TObject的NewInstance是虚拟方法,代表派生类可以改写(override)它。NewInstance的功能即是为对象分配内存,并调用InitInstance方法为对象设定对象支持的接口。NewInstance的返回值是TObject,代表了调用了NewInstance之后Object Pascal的对象模型已经在内存中形成了TObject的实体(instance),不过此时内存中的TObject仍然无法使用,因为接着需要设定对象的执行框架。

2.对象识别服务
  TObject提供的基础识别对象方法:
TObjiect = class
       class 
function ClassName: ShortString;
    class 
function ClassNameIs(const Name: string): Boolean;
    class 
function ClassParent: TClass;
    class 
function InstanceSize: Longint;
    class 
function InheritsFrom(AClass: TClass): Boolean;
end;
用来得知应用程序执行时期对象的信息。这些对象识别服务方法都是声明成类方法。识别服务方法说明:
 ClassName 返回类名称
 ClassNameIs  返回特定类的名称
 ClassParent     返回类的父类
  InstanceSize  返回对象占据内存的大小
  InheritsFrom 判断类是否从特定类继承下来
 
3 .对象信息服务
  相关函数:
TObject = class
    class 
function ClassInfo: Pointer;
    class 
function MethodAddress(const Name: ShortString): Pointer;
    class 
function MethodName(Address: Pointer): ShortString;
    
function FieldAddress(const Name: ShortString): Pointer;
    
function GetInterface(const IID: TGUID; out Obj): Boolean;
    class 
function GetInterfaceEntry(const IID: TGUID): PInterfaceEntry;
    class 
function GetInterfaceTable: PInterfaceTable;
    
function SafeCallException(ExceptObject: TObject;
      ExceptAddr: Pointer): HResult; virtual;
end;

4.对象释放服务
  相关函数:
TObject = class
    procedure Free;
    procedure CleanupInstance;
    procedure FreeInstance; virtual;
    destructor Destroy; virtual;
end;

destructor TObject.Destroy;
begin
end;

procedure TObject.Free;
begin
  if Self <> nil then
    Destroy;
end;
procedure TObject.FreeInstance;
begin
  CleanupInstance;
  _FreeMem(Self);
end;
procedure TObject.CleanupInstance;
{$IFDEF PUREPASCAL}
var
  ClassPtr: TClass;
  InitTable: Pointer;
begin
  ClassPtr := ClassType;
  InitTable := PPointer(Integer(ClassPtr) + vmtInitTable)^;
  while (ClassPtr <> nil) and (InitTable <> nil) do
  begin
    _FinalizeRecord(Self, InitTable);
    ClassPtr := ClassPtr.ClassParent;
    if ClassPtr <> nil then
      InitTable := PPointer(Integer(ClassPtr) + vmtInitTable)^;
  end;
end;

4.分派消息服务
   在TObject类中,有一个Dispatch()方法和一个DefaultHandler()方法,它们都是与消息分发机制相关的。

TObject = class
    procedure Dispatch(var Message); virtual;
    procedure DefaultHandler(var Message); virtual;
end;

Dispatch和DefaultHandler使用了未指明类型的参数Message,他们可以接受任何数据类型的参数,唯一要求这个数据类型的前两个字节必须是消息ID值,Dispatch和DefaultHandler会根据这个消息ID搜寻拥有相同消息ID的方法,然后把这个参数分配给搜寻到的方法。
    Dispatch()负责将特定的消息分发给合适的消息处理函数。首先它会在对象本身类型的类中寻找该消息的处理函数,如果找到,则调用它;如果没有找到而该类覆盖了TObject的DefaultHandler(),则调用该类的DefaultHandler();如果两者都不存在,则继续在其基类中寻找,直至寻找到TObject这一层,而TObject已经提供了默认的DefaultHandler()方法。
procedure TObject.Dispatch(var Message);
asm
    PUSH    ESI
    MOV     SI,[EDX]
    OR      SI,SI
   //比对ID处理的范围,如果不在0~c000h之间就跳到 @@default处调
   //用 DefaultHandler函数处理
    JE      @@default
    CMP     SI,0C000H
    JAE     @@default
    PUSH    EAX
    MOV     EAX,[EAX]
    //在VCL组件的动态方法表格中搜寻可处理的事件函数
    CALL    GetDynaMethod
    POP     EAX
    JE      @@default
    MOV     ECX,ESI
    POP     ESI
    //找到能够处理消息的VCL事件处理函数,直接跳到此事件处理函数执行
    JMP     ECX

@@default:
    POP     ESI
    MOV     ECX,[EAX]
    JMP     dword ptr [ECX].vmtDefaultHandler
end;
procedure TObject.DefaultHandler(var Message);
begin
end;

posted @ 2007-05-08 12:13 左左右右 阅读(498) 评论(0) 编辑