附件资料*指针的使用(代码)示例:简单的指针应用 代码: procedure TForm1.Button2Click(Sender: TObject); var a: Integer; p: ^Integer; begin with self.Memo1.Lines do begin a := 100; Add('数据内容:' + inttostr(a)); //--- p := @a; Add('数据地址:' + inttostr(Integer(p))); //--- Add('数据地址指向的内容:' + inttostr(p^)); end; end;
procedure TForm1.Button2Click(Sender: TObject); type PInteger = ^Integer; PPInteger = ^PInteger; var a: Integer; p: PInteger; pp: PPInteger; begin with self.Memo1.Lines do begin a := 100; Add('Integer数据内容:' + inttostr(a)); //--- p := @a; Add('Integer指针内容:' + inttostr(Integer(p))); Add('Integer指针指向的数据:' + inttostr(p^)); //--- pp := @p; Add('PInteger指针内容:' + inttostr(Integer(pp))); Add('PInteger指针指向的数据:' + inttostr(Integer(pp^))); end; end;
*指针的使用(汇编)示例:简单的指针应用 说明: 对于编译器来说,指针的类型可以用来标明地址所指向区域的大小、所指向的类型(整型、对象、方法),以及进行指针运算时指针偏移的长度。对于有类型指针的操作最终反映到编译器的可执行代码中,如果不参考可执行代码,单凭一个内存地址,我们并无法判断地址所指向的数据类型。 代码: procedure TForm1.Button2Click(Sender: TObject); var a:array[0..1] of Integer; b: Integer; p: ^Integer; begin a[0] := 1; a[1] := 2; //--- p := @a[0]; b := p^; //--- Inc(p); b := p^; end; 汇编: 编译器根据源代码中声明的变量类型分配内存如下: [ebp - $04]存储为self,大小为4字节,因为self声明为对象指针 [ebp - $08]标识为a[1],大小为4字节,因为a[0]声明为Integer [ebp - $0c]标识为a[0] [ebp - $10]标识为b,大小为4字节,因为b声明为Integer [ebp - $14]标识为p,大小为4字节,因为p声明为Integer指针 [ebp - $18]存储为Sender,大小为4字节,因为Sender声明为对象指针 其中“ebp - $04”为内存地址,“[ebp - $04]”为地址所指向的数据,ebp、eax为32位寄存器可以用于存取一个4字节数据。
代码: procedure TForm1.Button2Click(Sender: TObject); var a:array[0..1] of Byte; b: Byte; p: ^Byte; begin a[0] := 1; a[1] := 2; //--- p := @a[0]; b := p^; //--- Inc(p); b := p^; end; 汇编: 编译器根据源代码中声明的变量类型分配内存如下: [ebp - $04]存储为self,大小为4字节,因为self声明为对象指针 [ebp - $05]标识为a[1],大小为4字节,因为a[0]声明为Integer [ebp - $06]标识为a[0] [ebp - $07]标识为b,大小为4字节,因为b声明为Integer [ebp - $0c]标识为p,大小为4字节,因为p声明为Integer指针 [ebp - $10]存储为Sender,大小为4字节,因为Sender声明为对象指针 其中编译器为了实现数据对齐,所以申请了大小为$10的空间。
*复杂数据类型指针(代码)示例:字符串指针 代码: procedure TForm1.Button2Click(Sender: TObject); type buffer = string[255]; ptr = ^buffer; var b1: buffer; b2: ptr; begin with self.Memo1.Lines do begin b1 := 'zhang'; Add('数据内存大小:' + IntToStr(SizeOf(b1))); Add('数据内容大小:' + IntToStr(PByte(@b1)^)); //--- b2 := @b1; Add('指针大小:' + IntToStr(SizeOf(b2))); Add('指针指向数据内存大小:' + IntToStr(SizeOf(b2^))); end; end; 说明: 首先,ptr是一个指针类型,而b2是这个指针类型的变量。 其次,ptr是一个指向255长度的短字符串类型的指针,加上一个看不见的计数字节,共256B。当ptr没有分配内存空间,也没初始化时指向的地址是随机的,随意引用可能会出问题。但是只要它指向了某个地址,就代表它指向了256B的空间,它可以指向任何有效的字符串变量,但是无论被指向的字符串有多长,b2^的长度始终是256。
*记录指针(代码)示例:记录指针的应用 代码: procedure TForm1.Button2Click(Sender: TObject); type PTMyRecord = ^TMyRecord; TMyRecord = record Data1:Integer; Data2:Integer; end; var ARecord: TMyRecord; pRecord: PTMyRecord; begin pRecord := @ARecord; ARecord.Data1 := 11; pRecord^.Data2 := 22; pRecord.Data2 := 33; end;
*过程指针(代码)示例:方法指针的结构 说明: (1)、过程/函数指针是一个32位的指针。 代码: procedure TForm1.Button2Click(Sender: TObject); type TMyMethod = function(X: Integer): Integer; var F: TMyMethod; begin Self.Memo1.Lines.Add('函数指针大小:' + IntToStr(SizeOf(TMyMethod))); end;
示例:过程指针的应用 说明: (1)、通过赋值语句“过程变量 := 过程”,使得过程变量指向过程或者函数入口地址。 (2)、在赋值语句“过程变量 := 过程”中,左边变量的类型决定了右边的过程或者方法指针解释。 (3)、可以使用过程变量引用声明的过程或者函数。 (4)、无论何时一个过程变量(procedural variable)出现在一个表达式中,它表示调用所指向的函数或者过程。 (5)、对于过程变量P, @P把P转换成一个包含地址的无类型的指针变量(即@P等价于无类型指针P),此时可以把一个无类型的指针值赋给过程变量P。 代码: function SomeFunction(X: Integer): Integer; begin ShowMessage(IntToStr(X)); end;
procedure TForm1.Button2Click(Sender: TObject); var F: function(X: Integer): Integer; I: Integer; begin F := SomeFunction; //--给f赋值 I := F(4); //--调用所指向的函数 end;
procedure TForm1.Button2Click(Sender: TObject); var F: function(X: Integer): Integer; I: Integer; begin F := SomeFunction; //--给f赋值 I := F(4); //--调用所指向的函数 //--- Self.Memo1.Lines.Add('函数指针大小' + IntToStr(SizeOf(@SomeFunction))); end;
示例:过程指针的应用 代码: function SomeFunction: Integer; begin Result := 0; ShowMessage('0'); end;
procedure TForm1.Button2Click(Sender: TObject); var F: function: Integer; P:Pointer; begin P := @SomeFunction; //--给P赋值 @F := P; //--给f赋值 F; end; 说明: 可以使用@操作符,把一个无类型的指针值赋给一个过程变量,如调用“@过程变量 := 无类型指针值”,此时过程变量指向这个值。
示例:过程指针的应用 代码: function SomeFunction: Integer; begin Result := 0; ShowMessage('0'); end;
procedure TForm1.Button2Click(Sender: TObject); var F,G: function: Integer; I: Integer; begin F := SomeFunction; //--给F赋值 G := F; //--把F的值拷贝给G I := G; //--调用函数 end; 说明: 第一句获得函数的入口,第二句将指针复制,第三句获得函数的返回值。
示例:过程指针的函数调用 代码: function SomeFunction: Integer; begin Result := 0; end;
function MyFunction: Integer; begin Result := 0; end;
procedure TForm1.Button2Click(Sender: TObject); var F: function: Integer; I: Integer; begin F := SomeFunction; //--给f赋值 if F = MyFunction then ShowMessage('比较的函数返回值得结果'); end; 说明: 在if语句中,F的出现导致一个函数调用;编译器调用F指向的函数,然后调用Myfunction,比较结果。这个规则是无论何时一个过程变量出现在一个表达式中,它表示调用所指向的函数或者过程。有时F指向一个过程(没有返回值),或者f指向一个需要参数的函数,则前面的语句会产生一个编译错误。
示例:比较过程入口地址 代码: function SomeFunction: Integer; begin Result := 0; end;
function MyFunction: Integer; begin Result := 0; ShowMessage('123'); end;
procedure TForm1.Button2Click(Sender: TObject); var F: function: Integer; begin F := MyFunction; //--给f赋值 if @F = @MyFunction then ShowMessage('比较函数地址'); end;
procedure TForm1.Button2Click(Sender: TObject); var F: function: Integer; P:Pointer; begin F := MyFunction; //--给f赋值 P := @F; if P = @MyFunction then ShowMessage('比较函数地址'); end; 说明: @F把F转换成一个包含地址的无类型的指针变量,@myfunction返回myfunction的地址。
*方法指针(代码)示例:方法指针的结构 说明: (1)、方法指针指向一个结构,其结构如下 TMethod = record Code: Pointer;//指向过程/函数的指针 Data: Pointer;//指向对象的指针 end; 代码: type TMyObject = class(TObject) public procedure Method1(Sender: TObject); end;
procedure TMyObject.Method1(Sender: TObject); begin ShowMessage('123'); end;
procedure TForm1.Button2Click(Sender: TObject); type TMyMethod = procedure(Sender: TObject) of object; var AMyMethod: TMyMethod; AMyObject: TMyObject; begin with self.Memo1.Lines do begin Add('方法指针大小:' + IntToStr(SizeOf(TMyMethod))); //--- AMyObject := TMyObject.Create; try Add('对象指针:' + IntToHex(Integer(AMyObject),2)); Add('对象方法指针:' + IntToHex(Integer(@TMyObject.Method1),2)); //--- AMyMethod := AMyObject.Method1; //--- with TMethod(AMyMethod) do begin Add('对象指针:' + IntToHex(Integer(Data),2)); Add('对象方法指针:' + IntToHex(Integer(Code),2)); end; //--- AMyMethod(AMyObject); finally AMyObject.Free; end; end; end;
示例:方法指针的结构 代码: type TMyObject = class(TObject) public procedure TestMethod(Value: Integer); virtual; end; TMyObject1 = class(TMyObject) public procedure TestMethod(Value: Integer); override; end;
procedure TMyObject.TestMethod(Value: Integer); begin ShowMessage('TMyObject:' + inttostr(Value)); end;
procedure TMyObject1.TestMethod(Value: Integer); begin inherited; ShowMessage('TMyObject1:' + inttostr(Value)); end;
procedure TForm1.Button2Click(Sender: TObject); type TWndMethod = procedure(Value: Integer) of object; var SomeMethod: TWndMethod; AMyObject: TMyObject1; Msg: TMessage; begin AMyObject := TMyObject1.Create; try SomeMethod := AMyObject.TestMethod; //--赋值后SomeMethod包含TestMethod和 AMyObject的指针 //SomeMethod := TMyObject1.TestMethod; //--错误!不能用类引用。 SomeMethod(1); //--执行方法 finally AMyObject.Free; end; end; 说明: TWndMethod 是一种对象方法类型,它指向一个接收Integer) 类型参数的过程,但它不是一般的静态过程,它是对象相关(object related)的。TWndMethod 在内存中存储为一个指向过程的指针和一个对象的指针,所以占用8个字节。TWndMethod类型的变量必须使用已实例化的对象来赋值。 如果把 TWndMethod变量赋值给虚方法,这时,编译器实现为 SomeMethod 指向AMyObject对象虚方法表中的TestMethod 过程的地址和AMyObject对象的地址。也就是说编译器正确地处理了虚方法的赋值。调用 SomeMethod(1) 就等于调用AMyObject.TestMethod (Message)。 在可能被赋值的情况下,对象方法最好不要设计为有返回值的函数(function),而要设计为过程(procedure)。原因很简单,把一个有返回值的对象方法赋值给TWndMethod 变量,会造成编译时的二义性。
示例:方法指针指向对象方法 代码: type TMyObject = class(TObject) public procedure Method1(Sender: TObject); end;
procedure TMyObject.Method1(Sender: TObject); begin ShowMessage('123'); end;
procedure TForm1.Button2Click(Sender: TObject); type TMyMethod = procedure(Sender: TObject) of object; var AMyMethod: TMyMethod; AMyObject: TMyObject; begin AMyObject := TMyObject.Create; try AMyMethod := AMyObject.Method1; AMyMethod(AMyObject); finally AMyObject.Free; end; end;
示例:方法指针指向过程/函数 说明: (1)、不能使用赋值语句“对象方法指针 := @过程/函数 ”,因为对象方法指针和过程指针(“@过程/函数”返回一个过程指针)类型不同,它们各自的空间大小也不同,普通的过程指针不包含对象指针,所以不能用于给对象方法指针类型赋值。 (2)、因为在Delphi中给对象方法指针赋值必须加上对象实例,如TestObj.Hello才行,一种投机的方法是可以通过TMethod将对象方法指针重定向到一个全局函数。 (3)、由于对象方法包括一个隐含对象指针参数Self,所以定义过程/函数时必须加入一个假参数const pSelf: Pointer以保证对象指针self能够被传入。 代码: type TMyObject = class(TObject) private FTestEvent: TNotifyEvent; public procedure ExecTestEvent(Sender: TObject); //--- published property TestEvent: TNotifyEvent read FTestEvent write FTestEvent; end;
//--类方法隐藏了第一个参数为对象的 Self (放在EAX中传递) //--故第一个参数为 Self: TObject,第二个参数对应 TNotifyEvent 的参数 procedure Gproc(Self: TObject; Sender: TObject); begin ShowMessage('Test'); end;
procedure TMyObject.ExecTestEvent(Sender: TObject); begin if Assigned(FTestEvent) then FTestEvent(Sender); end;
procedure TForm1.Button2Click(Sender: TObject); var M: TMethod; //--方法记录 AMyObject: TMyObject; begin with M do begin Code := @Gproc; //--指向方法(这里是全局过程)地址 Data := nil; //--指向类实例(该值对应着 Gproc 调用时的 Self 参数值,可为任意值,这里传nil) end; //--- AMyObject := TMyObject.Create; try AMyObject.TestEvent := TNotifyEvent(M); AMyObject.ExecTestEvent(nil); finally AMyObject.Free; end; end; 说明: (1)、第一个参数Self: TObject;绝对不是多余的,虽然你不用它编译也能通过,还可能执行正确,但如果你想在过程中调用对象之真的话,没有它可不行。 Delphi默认使用寄存器加堆栈的方式传递过程参数,三个参数以内使用EAX、EDX、ECX的顺序保存参数,三个以后用堆栈。对类的方法来说,EAX恒定是类实例指针Self,这样Sender参数实际上放在EDX中做为第二个参数传递,如下面的代码: procedure MyMouseDown(Self: TObject; Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin ShowMessage(Format('Self: %d, Sender: %d, X: %d, Y: %d', [Integer(Self), Integer(Sender), X, Y])); end; procedure TForm1.FormCreate(Sender: TObject); var M: TMethod; begin M.Code := @MyMouseDown; M.Data := nil; Self.OnMouseDown := TMouseEvent(M); end; 上面的代码: Self --> EAX Sender --> EDX Button --> CL // TMouseButton枚举类型其实内部作Byte用 其它三个放堆栈。 如果把Self: TObject去掉,则MyMouseDown认为只有最后两个参数放在堆栈,返回时POP EIP得到的是错误的指针,你将看到一个非法访问内存错。 (2)、M.Data := nil;我指的是语法上、运行时可为任何值而没有错误。 M.Data保存了方法所对应的对象指针,用于在调用该方法时提供Self这个隐藏参数,在普通过程中其实是没有用的。难道你还想在过程中用Self来访问一个按钮或窗体吗?M.Data并不是Sender,Sender是由方法(这里是过程)调用方指定的。例如VCL源码: procedure TControl.DblClick; begin if Assigned(FOnDblClick) then FOnDblClick(Self); end; 你修改M.Data来作为Sender其实是没用的。
(3)、定义M: TMethod只需要赋值时做一次强制类型转换。Delphi的类型强制转换要求类型相容或长度一致,TMethod与对象方法指针是相容的(8字节),事实上VCL在调用事件时,也是按TMethod的结构来调用的,而TNofityEvent等类型在本质上与TMethod结构相同,但在Delphi这种强类型语言中使用它们可以避免很多错误。
示例:方法指针指向过程/函数 代码: procedure TestMethod(const pSelf: Pointer; AData: Integer); begin ShowMessage(IntToStr(AData)); end;
procedure TForm1.Button2Click(Sender: TObject); type TMyMethod = procedure(AData: Integer) of object; var AMyMethod: TMyMethod; begin with TMethod(AMyMethod) do begin Code := @TestMethod; Data := nil; end; //--- AMyMethod(1); end;
示例:方法指针指向过程/函数 代码: type TMyObject = class(TObject) procedure ExecMethod(const AMsg: string); end;
procedure TMyObject.ExecMethod(const AMsg: string); begin ShowMessage(AMsg); end;
procedure TestMethod(pSelf: TObject; const AMsg: string); begin ShowMessage(AMsg); if pSelf is TMyObject then TMyObject(pSelf).ExecMethod(AMsg); end; procedure TForm1.Button2Click(Sender: TObject); var AMyObject: TMyObject; begin AMyObject := TMyObject.Create; try TestMethod(AMyObject,'123'); finally AMyObject.Free; end; end; 代码: type TFakeEvent = procedure(const AMsg: string) of object;
TMyObject = class(TObject) private FTestEvent: TFakeEvent; public constructor Create; //--- procedure ExecTestEvent(const AMsg: string); procedure ExecMethod(const AMsg: string); end;
procedure TestMethod(pSelf: TObject; const AMsg: string); begin ShowMessage(AMsg); if pSelf is TMyObject then TMyObject(pSelf).ExecMethod(AMsg); end;
constructor TMyObject.Create; begin with TMethod(FTestEvent) do begin Code := @TestMethod; Data := self; end; end;
procedure TMyObject.ExecTestEvent(const AMsg: string); begin if Assigned(FTestEvent) then FTestEvent(AMsg); end;
procedure TMyObject.ExecMethod(const AMsg: string); begin ShowMessage(AMsg); end;
procedure TForm1.Button2Click(Sender: TObject); var AMyObject: TMyObject; begin AMyObject := TMyObject.Create; try AMyObject.ExecTestEvent('123'); finally AMyObject.Free; end; end;
示例:方法指针调用过程/函数 代码: type TFakeEvent = procedure(const AMsg: string) of object;
TMyObject = class(TObject) private FTestEvent: TFakeEvent; procedure TestEvent(const AMsg: string); public constructor Create; //--- procedure ExecTestEvent(const AMsg: string); end;
procedure TestMethod(const AMsg: string); begin ShowMessage(AMsg); end;
constructor TMyObject.Create; begin FTestEvent := self.TestEvent; end;
procedure TMyObject.ExecTestEvent(const AMsg: string); begin if Assigned(FTestEvent) then FTestEvent(AMsg); end;
procedure TMyObject.TestEvent(const AMsg: string); begin TestMethod(AMsg); end;
procedure TForm1.Button2Click(Sender: TObject); var AMyObject: TMyObject; begin AMyObject := TMyObject.Create; try AMyObject.ExecTestEvent('123'); finally AMyObject.Free; end; end;
*对象指针(代码)示例:方法指针的结构 说明:DELPHI中的对象是是一个32位的指针。 代码: procedure TForm1.Button2Click(Sender: TObject); var AObject: TObject; begin AObject := TObject.Create; try Self.Memo1.Lines.Add('对象指针大小:' + IntToStr(SizeOf(AObject))); finally AObject.Free; end; end;
示例:对象指针的地址 代码: procedure TForm1.Button2Click(Sender: TObject); var pEdit: ^TEdit; begin pEdit := @Edit1; //--- with Self.Memo1.Lines do begin Add('对象内容:' + IntToStr(Integer(Edit1))); Add('对象指针的内容:' + IntToStr(Integer(pEdit))); Add('对象指针指向的内容:' + IntToStr(Integer(pEdit^))); end; //--- pEdit^.Text := '123'; pEdit.Text := '123' end; 说明: 在这里还有一个有趣的现象,我们不但可以用“Edit1^.Text”还可以用“Edit1.Text”方式访问对象的属性和方法,其结果是一样的,所以在Delphi中只要是对象都是按指针实现的。
|
|