CnPack Forum


 
Subject: 浅谈Object Pascal的指针[转]
skyjacker
版主
Rank: 7Rank: 7Rank: 7
茶农


UID 2239
Digest Posts 9
Credits 617
Posts 269
点点分 617
Reading Access 100
Registered 2006-6-8
Status Offline
Post at 2007-3-7 10:11  Profile | Blog | P.M.  | QQ
浅谈Object Pascal的指针[转]

浅谈Object Pascal的指针[转]

作者: Nicrosoft
  大家都认为,C语言之所以强大,以及其自由性,很大部分体现在其灵活的指针运用上。因此,说指针是C语言的灵魂,一点都不为过。同时,这种说法也让很多人产生误解,似乎只有C语言的指针才能算指针。Basic不支持指针,在此不论。其实,Pascal语言本身也是支持指针的。从最初的Pascal发展至今的 Object Pascal,可以说在指针运用上,丝毫不会逊色于C语言的指针。

  以下内容分为八个部分,分别是

  一、类型指针的定义
  二、无类型指针的定义
  三、指针的解除引用
  四、取地址(指针赋值)
  五、指针运算
  六、动态内存分配
  七、字符数组的运算
  八、函数指针

  一、类型指针的定义。对于指向特定类型的指针,在C中是这样定义的:
    int *ptr;
    char *ptr;
    与之等价的Object Pascal是如何定义的呢?
    var ptr : ^Integer;
      ptr : ^char;
    其实也就是符号的差别而已。

  二、无类型指针的定义。C中有void *类型,也就是可以指向任何类型数据的指针。Object Pascal为其定义了一个专门的类型:Pointer。于是,
    ptr : Pointer;
    就与C中的
    void *ptr;
    等价了。

  三、指针的解除引用。要解除指针引用(即取出指针所指区域的值),C 的语法是 (*ptr),Object Pascal则是 ptr^。

  四、取地址(指针赋值)。取某对象的地址并将其赋值给指针变量,C 的语法是
    ptr = &Object;
    Object Pascal 则是
    ptr := @Object;
    也只是符号的差别而已。

  五、指针运算。在 C 中,可以对指针进行移动的运算,如:
    char a[20];
    char *ptr=a;
    ptr++;
    ptr+=2;
    当执行ptr++;时,编译器会产生让ptr前进sizeof(char)步长的代码,之后,ptr将指向a[1]。ptr+=2;这句使得ptr前进两个sizeof(char)大小的步长。同样,我们来看一下Object Pascal中如何实现:
    var a : array [1..20] of Char;
      ptr : PChar; //PChar 可以看作 ^Char
    begin
      ptr := @a;
      Inc(ptr); // 这句等价于 C 的 ptr++;
      Inc(ptr, 2); //这句等价于 C 的 ptr+=2;
    end;

  六、动态内存分配。C语言中,使用malloc()库函数分配内存,free()函数释放内存。如这样的代码:
    int *ptr, *ptr2;
    int i;
    ptr = (int*) malloc(sizeof(int) * 20);
    ptr2 = ptr;
    for (i=0; i<20; i++){
      *ptr = i; ptr++;
    }
    free(ptr2);
    Object Pascal中,动态分配内存的函数是GetMem(),与之对应的释放函数为FreeMem()(传统 Pascal中获取内存的函数是New()和 Dispose(),但New()只能获得对象的单个实体的内存大小,无法取得连续的存放多个对象的内存块)。因此,与上面那段C的代码等价的 Object Pascal的代码为:
    var ptr, ptr2 : ^integer;
      i : integer;
    begin
      GetMem(ptr, sizeof(integer) * 20);
        //这句等价于C的 ptr = (int*) malloc(sizeof(int) * 20);
      ptr2 := ptr; //保留原始指针位置
      for i := 0 to 19 do
      begin
        ptr^ := i;
        Inc(ptr);
      end;
      FreeMem(ptr2);
    end;
    对于以上这个例子(无论是C版本的,还是Object Pascal版本的),都要注意一个问题,就是分配内存的单位是字节(BYTE),因此在使用GetMem时,其第二个参数如果想当然的写成 20,那么就会出问题了(内存访问越界)。因为GetMem(ptr, 20);实际只分配了20个字节的内存空间,而一个整形的大小是四个字节,那么访问第五个之后的所有元素都是非法的了(对于malloc()的参数同样)。

  七、字符数组的运算。C语言中,是没有字符串类型的,因此,字符串都是用字符数组来实现,于是也有一套str打头的库函数以进行字符数组的运算,如以下代码:
    char str[15];
    char *pstr;
    strcpy(str, "teststr");
    strcat(str, "_testok");
    pstr = (char*) malloc(sizeof(char) * 15);
    strcpy(pstr, str);
    printf(pstr);
    free(pstr);
    而在Object Pascal中,有了String类型,因此可以很方便的对字符串进行各种运算。但是,有时我们的Pascal代码需要与C的代码交互(比如:用 Object Pascal的代码调用C写的DLL或者用Object Pascal 写的DLL准备允许用C写客户端的代码)的话,就不能使用String类型了,而必须使用两种语言通用的字符数组。其实,Object Pascal提供了完全类似C的一整套字符数组的运算函数,以上那段代码的Object Pascal 版本是这样的:
    var str : array [1..15] of char;
      pstr : PChar; //Pchar 也就是 ^Char
    begin
      StrCopy(@str, 'teststr');
        //在C中,数组的名称可以直接作为数组首地址指针来用
        //但Pascal不是这样的,因此 str前要加上取地址的运算符
      StrCat(@str, '_testok');
      GetMem(pstr, sizeof(char) * 15);
      StrCopy(pstr, @str);
      Write(pstr);
      FreeMem(pstr);
    end;

  八、函数指针。在动态调用DLL中的函数时,就会用到函数指针。假设用C写的一段代码如下:
    typedef int (*PVFN)(int); //定义函数指针类型
    int main()
    {
      HMODULE hModule = LoadLibrary("test.dll");
      PVFN pvfn = NULL;
      pvfn = (PVFN) GetProcAddress(hModule, "Function1");
      pvfn(2);
      FreeLibrary(hModule);
    }
    就我个人感觉来说,C语言中定义函数指针类型的typedef代码的语法有些晦涩,而同样的代码在 Object Pascal中却非常易懂:
    type PVFN = Function (para : Integer) : Integer;
    var fn : PVFN;
      //也可以直接在此处定义,如:fn : function (para:Integer):Integer;
      hm : HMODULE;
    begin
      hm := LoadLibrary('test.dll');
      fn := GetProcAddress(hm, 'Function1');
      fn(2);
      FreeLibrary(hm);
    end;




一壶清茶煮青春.
Top
kendling (小冬)
高级版主
Rank: 8Rank: 8
MyvNet


Medal No.1  
UID 703
Digest Posts 5
Credits 978
Posts 580
点点分 978
Reading Access 101
Registered 2005-2-18
Location 广东
Status Offline
Post at 2007-3-7 13:00  Profile | Site | Blog | P.M.  | QQ | Yahoo!
哈好文章!!!虽然对指针的应用了解得差不多,但是以前一直都不怎么清楚,看了这文章后清楚多了。。




小冬
http://MyvNet.com
Top
Passion (LiuXiao)
管理员
Rank: 9Rank: 9Rank: 9


UID 359
Digest Posts 19
Credits 6756
Posts 3554
点点分 6756
Reading Access 102
Registered 2004-3-28
Status Offline
Post at 2007-3-7 15:45  Profile | Blog | P.M. 
C指针和Pascal指针的最大不同就是加偏移时一个乘以了类型长度,一个没乘。
Top
skyjacker
版主
Rank: 7Rank: 7Rank: 7
茶农


UID 2239
Digest Posts 9
Credits 617
Posts 269
点点分 617
Reading Access 100
Registered 2006-6-8
Status Offline
Post at 2007-3-7 16:05  Profile | Blog | P.M.  | QQ
第 五 条 用 ptr : PInteger 举例会更好些。
PChar 移动一个字节看不来啥。




一壶清茶煮青春.
Top
kendling (小冬)
高级版主
Rank: 8Rank: 8
MyvNet


Medal No.1  
UID 703
Digest Posts 5
Credits 978
Posts 580
点点分 978
Reading Access 101
Registered 2005-2-18
Location 广东
Status Offline
Post at 2007-3-7 16:07  Profile | Site | Blog | P.M.  | QQ | Yahoo!
怎么个说法?




小冬
http://MyvNet.com
Top
Passion (LiuXiao)
管理员
Rank: 9Rank: 9Rank: 9


UID 359
Digest Posts 19
Credits 6756
Posts 3554
点点分 6756
Reading Access 102
Registered 2004-3-28
Status Offline
Post at 2007-3-7 17:08  Profile | Blog | P.M. 
对,第五条如果用PInteger就可以顺便指出这个步长的差异了。PChar也是1,没区别。
Top
kendling (小冬)
高级版主
Rank: 8Rank: 8
MyvNet


Medal No.1  
UID 703
Digest Posts 5
Credits 978
Posts 580
点点分 978
Reading Access 101
Registered 2005-2-18
Location 广东
Status Offline
Post at 2007-3-8 09:04  Profile | Site | Blog | P.M.  | QQ | Yahoo!
C移动需要加长度?Pascal移动不需要加?




小冬
http://MyvNet.com
Top
Passion (LiuXiao)
管理员
Rank: 9Rank: 9Rank: 9


UID 359
Digest Posts 19
Credits 6756
Posts 3554
点点分 6756
Reading Access 102
Registered 2004-3-28
Status Offline
Post at 2007-3-8 12:09  Profile | Blog | P.M. 
C移动1,实际移动的是一个指针所指类型的大小,
Pascal移动1,则就是移动一个字节。
Top
kendling (小冬)
高级版主
Rank: 8Rank: 8
MyvNet


Medal No.1  
UID 703
Digest Posts 5
Credits 978
Posts 580
点点分 978
Reading Access 101
Registered 2005-2-18
Location 广东
Status Offline
Post at 2007-3-8 16:37  Profile | Site | Blog | P.M.  | QQ | Yahoo!
哦,倒过来了。




小冬
http://MyvNet.com
Top
skyjacker
版主
Rank: 7Rank: 7Rank: 7
茶农


UID 2239
Digest Posts 9
Credits 617
Posts 269
点点分 617
Reading Access 100
Registered 2006-6-8
Status Offline
Post at 2007-11-26 15:04  Profile | Blog | P.M.  | QQ
[转]Delphi下使用指针的简单总结[fxh7622]

Delphi下使用指针的简单总结
2007-05-27 16:21:08

版权声明:原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://fxh7622.blog.51cto.com/63841/28207

由于最近公司太忙,好久没有更新我的BLOG了。原来想着写写关于HOOK驱动的文章,可是最后想想好久已经没有做驱动的东西了,怕写出来有错误,于是作罢。开发游戏也有一段时间了,发现使用DELPHI来开发网络游戏不了解DELPHI下指针的使用是完全不行的。所以今天我简单总结以下我使用DELPHI指针的心得。希望对大家有所帮助。
记得在大学学习C语言的时候在谭浩强编写的书中,关于指针一章的开始就说“指针是C语言的精华”,可见指针对于C语言的重要性。其实在Pascal语言中指针也占据着重要的位置。

1:指针的赋值。
type
RTestInfo = record
Age:Integer;
end;
PtestInfo = ^ RtestInfo;
var
Test1,Test2:PtestInfo;
Begin
     New(Test1);
     New(Test2);
     Test1^.Age:=12;
     Test2:=Test1;
     Application.MessageBox(Pchar(IntToStr(Test2^.Age)),’测试’,MB_OK);
     Test1^.Age:=13;
Application.MessageBox(Pchar(IntToStr(Test2^.Age)),’测试’,MB_OK);
DisPose(Test1);
DisPose(Test2);
End;
上面的代码中使用了Test2:=Test1;进行指针的赋值,也就是说进行赋值以后两个变量指向的相同的地址,所以当Test1的Age发送变化以后Test2的Age也随之发生了变化。反过来也是一样。那如果我们要将Test1中的内容放在Test2中并且当Test1中的内容发生变化的时候Test2的内容不会发生变化有如何来做呢?其实很简单,使用Test2^:=Test1^;就可以了,这个时候变量Test1和变量Test2指向的是两个不同的地址,当一方的内容发生变化的时候另外一方不会受到影响。

2:数组和指针的转换。
曾使用过API函数来编写网络通信的都知道,网络传输过程中传输的都是char类型的数组。而我们经常需要将自己定义的一个结构通过网络传输出去,并且当对方接收到这个数据以后又能将其转换为相应的结构来处理。以前我是使用添加标记位来解决这个问题。其实使用数组和指针转换是很简单的。
type
         RtestInfo = record
                   Age:Integer;
         End;
Var
         Test: RtestInfo;
         Data:array[0..1024] of Char;
Begin
         Test.Age:=13;
         Fillchar(Data,SizeOf(Data),#0);
         StrMove(Data,@ Test,sizeof(Test));
         //数据发送
End;
在上面的例子中首先我们将我们定义数组Data清空,然后使用函数StrMove将结构Test的内容复制到Data中去。这个时候就可以将数据发送出去。当对方接受到数据以后,可以用以下的代码进行还原。
type
         RtestInfo = record
                   Age:Integer;
         End;
Var
         Test: RtestInfo;
Begin
         StrMove(@Test,Data,sizeof(Test));
         //处理数据
End;
这个时候就可以对发送过来的数据进行相应的处理了。

3:函数指针的使用。
在分模块开发的过程中,DLL占据着重要的位置。在我开发游戏的服务端也是使用DLL的方式。在开发的时候遇见这样的一个问题,例如我在一个EXE中编写了一个功能非常复杂的函数,在DLL中我想使用到它,如何做呢?其实使用函数的指针就可以很方便的实现。
我们知道DLL的运行空间是和调用它的EXE在一起的。也就是说在这个空间中的资源理论上DLL是都可以使用。所以只要将exe中的函数指针传给DLL,那么DLL就可以使用这个函数了。
例如在DLL中有函数ModuleSendData作用是让EXE中传入函数的指针链表,这个链表中的函数都是DLL中可能用到的。

SendDataFun: procedure(Casetype: Byte; UserSocket: RUserSocket; Data: array of char; DataLen: Integer);

function ModuleSendData(FunPList: TList): Boolean; stdcall; export;
begin
  SendDataFun := FunPList.Items[0];
end;

在EXE中的代码是:
Linstance:=LoadLibrary(Pchar(Temp));
     if Linstance>0 then
     begin
        //将发送数据的指针传入DLL插件中
        @GiveModuleFun:=GetProcAddress(Linstance,'ModuleSendData');
        if @GiveModuleFun<>NIl then
        begin
          m_FunList:=TList.Create;
            //发送数据
            t_Pointer:=@DllSendData;
            m_FunList.Add(t_Pointer);
                  GiveModuleFun(m_FunList);
                   End;
         End;
其中DllSendData就是我们想传入给DLL的函数。

这个时候在DLL中使用SendDataFun就和一般的函数一样了。
这里注意的一点是Exe中的函数DllSendData我定义的是一个全局函数。原因是这样取得函数的指针的时候比较简单(关于类里面的函数指针如何取得我不了解,希望有了解的朋友不吝赐教,感激不尽)。



本文出自 “狗窝” 博客,请务必保留此出处http://fxh7622.blog.51cto.com/63841/28207



文章评论

      [匿名]花朵
2007-05-27 20:10:37
  学习啦,总结的真够全面的,辛苦啦

      [匿名]seewind
2007-05-29 16:35:01
获取类里面的函数指针。类或者对象的函数指针,delphi是特殊处理的。可以定义函数类型,如:
TObjProc = procedure of Object;
那TObjProc就是对象的函数类型,其实际是TMethod记录类型,看看TMethod定义,可以知道有两个内容:Data, Code: Pointer;
你可以用强制类型转换将TObjProc 的类型转换成TMethod,其中Data是Self的引用,Code就是函数的指针。

      fxh7622
2007-06-04 11:51:39
seewind说的方法我倒是没有试过,改天我试试看。谢谢seewind。

      [匿名]路过
2007-10-23 14:22:45
忘记在那个网站上看到,原理也差不多。不知道有没有用
Callbacks and objects
Undertitle: 如何 call back an instance of a class

Category: Win API
Uploader: Peter Morris

Question: Windows uses callbacks in quite a few places (EnumWindows, EnumFonts, EnumSystemLocales etc). The problem is that a callback is always an address of a standard procedure or function.

The reason for this is that windows does not pass back any reference to SELF (ie, the instance of the class), which is used by classes when deciding which instance to work with.

Well, the good news is that SysUtils.pas has an example of how to get a callback to work with an instance of a class, here is how they do it.
Answer: //First they make a record structure, which mimmicks some ASM code.

type
TCallbackThunk = packed record
  POPEDX: Byte;
  MOVEAX: Byte;
  SelfPtr: Pointer;
  PUSHEAX: Byte;
  PUSHEDX: Byte;
  JMP: Byte;
  JmpOffset: Integer;
end;

//A variable is declared of this type. Instead of calling back
//a procedure, we are going to pass this variable as the
//address to call back. This acts as a kind of "pretend"
//procedure, which is aware of SELF.

var
Callback: TCallbackThunk;

begin
//Next, the record is populated with the actual values
//needed to create the actual ASM code. For example
//In the following line $5A is the binary value for POPEDX

Callback.POPEDX := $5A;
Callback.MOVEAX := $B8;

//Here is the reference to self
Callback.SelfPtr := Self;
Callback.PUSHEAX := $50;
Callback.PUSHEDX := $52;
Callback.JMP   := $E9;

//Here is how to declare where to jump to (which procedure)
//All you do is change "LocalesCallBack" with the name of
//the method in your class that you wish to be used as the
//callback

Callback.JmpOffset := Integer(@TLanguages.LocalesCallback) - Integer(@Callback.JMP) - 5;

//Here is an example of how to use it, you could call
//EnumWindows are whatever you like instead
EnumSystemLocales(TFNLocaleEnumProc(@Callback), LCID_SUPPORTED);




一壶清茶煮青春.
Top
 




All times are GMT++8, the time now is 2024-4-18 16:05

    本论坛支付平台由支付宝提供
携手打造安全诚信的交易社区 Powered by Discuz! 5.0.0  © 2001-2006 Comsenz Inc.
Processed in 0.014183 second(s), 7 queries , Gzip enabled

Clear Cookies - Contact Us - CnPack Website - Archiver - WAP