5.函数中的结构

在上一篇说了在函数中出现的常量 这一篇来说说函数中的结构 以GetWindowRect函数为例 函数说明如下:

函数功能:
    该函数返回指定窗口的边框矩形的尺寸。该尺寸以相对于屏幕坐标左上角的屏幕坐标给出。
函数原型:
    BOOL GetWindowRect(HWND hWnd,LPRECT lpRect);
参数:
    hWnd:窗口句柄。
    lpRect:指向一个RECT结构的指针,该结构接收窗口的左上角和右下角的屏幕坐标。
返回值:
    如果函数成功,返回值为非零:如果函数失败,返回值为零
    若想获得更多错误信息,请调用GetLastError函数。
速查:
    Windows NT:3.1以上版本:Windows:95以上版本;Windows CE:1.0以上版本
    头文件:Winuser.h;库文件:User32.lib。
这个函数的功能和前两篇介绍的功能有点相反 是获取一个窗口的位置信息 第一个参数不用解释了 是一个目标窗体的句柄 第二个参数估计就看不懂了"指向一个结构"?是的指向一个RECT结构 简单来说 也就是传一个对象进去 然后函数会对这个对象赋值 然后调用完函数 就可以直接用这个对象获取值 是一个输出参数 重点是这个对象是什么样子的呢 在C++中他是这样被定义的:
typedef struct tagRECT
{
    LONG    left;
    LONG    top;
    LONG    right;
    LONG    bottom;
} RECT, *PRECT, NEAR *NPRECT, FAR *LPRECT;

这个就是RECT的结构 上面的是C++源代码里面拷贝出来的 在MSDN中它是这样的

其实里面就是四个变量用来接收窗口的坐标的 可是这个是C++的啊?在C#中没有这个类型 那怎么办?第二个参数要怎么处理?- -!自己定义一个就可以了 换成C#的写法就变成了:

public struct RECT {
    public int Left;
    public int Top;
    public int Right;
    public int Bottom;
}

可能你觉得有些奇怪函数签名写的是LPRECT为什么我查询的却是RECT 其实本来是RECT的加了一个LP表示Long Pointer也就是长指针的意思 表示的是一个指向RECT结构的地址

不过要注意的是虽然上面看到的是LONG不过注意在C#中要写成int不然就悲剧了C#中long貌似8字节来着 而在C++中LONG就不一定是8字节 看编译环境(其实在64位系统中有两个版本的dll 在system32文件夹下有一堆dll 在sysWOW64文件夹下也有一堆dll) 如果进去的字节数不对 到时候会错位的、、、、、、、这个不是重点 重点是我怎么知道这个结构就是这个样子的?查询工具还是比较多的 MSDN也可以 好吧 其实只要知道结构的名字 你直接百度百科基本都能查到 Api也一样

来看看是如何调声明和调用的:

[DllImport("user32.dll")]
public static extern bool GetWindowRect(IntPtr hWnd, ref RECT rect);

public struct RECT {
    public int Left;
    public int Top;
    public int Right;
    public int Bottom;
}
//------------------------------------------
RECT rect = new RECT();
GetWindowRect(this.Handle, ref rect);
MessageBox.Show(rect.Left.ToString() + " " + rect.Top.ToString()
    + "\r\n" + rect.Right.ToString() + " " + rect.Bottom.ToString());
然后运行就能看到效果了  不过这里注意的是 后两个不是宽度和高度 而是坐标位置 所以如果想要得到宽度和高度得自己做一个减法运算

还有 重点是 函数签名里面看到 第二个是ref传参的 因为struct是值类型的 所以传进函数将会是一个值的拷贝 所以要加上ref直接传地址 而函数接收到这个地址会在这个地址的前16个字节写入数据(也就是那四个int变量) 然后通过结构 就可以获取到值了当然你不用ref也可以用out修饰

在.NET中有一个自带的Rectangle结构 当然你也可以将它作为函数的第二个参数 反正函数会在收到的这个地址之后16个字节写入数据 而在Rectangle结构中前16个字节分别是变量【X,Y,Width,Height】所以如果你想直接用Rectangle作为参数类型 那么返回的值里面Width黑Height并不是窗口的宽度和 高度而是右下角的坐标Right和Bottom、、看看如下代码:

[DllImport("user32.dll")]
public static extern bool GetWindowRect(IntPtr hWnd, ref RECT rect);
[DllImport("user32.dll")]
public static extern bool GetWindowRect(IntPtr hWnd, ref Rectangle rect);

public struct RECT {
    public int Left;
    public int Top;
    public int Right;
    public int Bottom;
}
//------------------------------------------
RECT rect = new RECT();
GetWindowRect(this.Handle, ref rect);
MessageBox.Show(rect.Left.ToString() + " " + rect.Top.ToString()
    + "\r\n" + rect.Right.ToString() + " " + rect.Bottom.ToString());
Rectangle r = new Rectangle();
GetWindowRect(this.Handle, ref r);
MessageBox.Show(r.ToSctring());
然后下面分别是弹出来的对话框:

- -!、、或许你比较郁闷 都是同一个函数为嘛声明不一样?难道有重载不成?难道那个函数也有Rectangle的类型? 为什么RECT和Rectangle都接收到了这四个数(Rectangle确实也收到了同样的四个数据 可是后面两个并不是高度和宽度一定注意)

通常情况下 函数的参数只要不是值类型 传给函数的都是一个地址 当函数得到这个地址之后 就会把这个地址作为一个目标对象在内存中的开始位置来处理 然后将这个位置开始之后的数据填充进入目标对象中(这里的对象是RECT结构) 所以在这里函数会把这个地址的数据当成是一个RECT结果来处理 而RECT在C++里面是四个变量每个四个字节 所以从这个地址开始之后的16个自己会被依次填入RECT中

rect = new RECT();
假设rect的在内存中的地址是 0x0000 0010
那么:
0x0000 0010:Left    (4Byte)
0x0000 0014:Top     (4Byte)
0x0000 0018:Right   (4Byte)
0x0000 001C:Bottom  (4Byte)

所以只要你给函数进去的是一个有效的地址 那么这个地址开始后的16个字节都会被函数当作是一个RECT结构来写入数据 而然后函数调用完 再用的时候比如rect.Left的时候 程序会找到0x0000 0010这个地址 然后Left在第1个数据上面 然后0x0000 0010之后四个字节的数据就会被当作是Left的值 其他同理

总之函数只会动你结构前面16个字节 所以你Bottom之后再来几个变量也没有关系 而且你还可以在后面加其他的代码也没有关系比如:

public struct RECT
{
    public int Left;
    public int Top;
    public int Right;
    public int Bottom;

    public System.Drawing.Rectangle ToRectangle() {
        return new System.Drawing.Rectangle(
                Left, Top,
                Right - Left, Bottom - Top
            );
    }

    public override string ToString() {
        return string.Format("Left={0},Top={1},Right={2},Bottom={3}", 
            Left, Top, Right, Bottom);
    }
}

这样也是可以的 所以不要觉得感觉好像是函数的第二个参数什么样的类型都可以接受似得 虽然这个函数在声明的时候 需要的是一个LPRECT 但是说白了 就是一个RECT指针 一个地址作为函数参数 而函数把这个地址的数据套用RECT来处理 所以要怎么声明都是可以的 只要符合他的本质就可以了甚至你在RECT里面用16个byte类型都可以 只要到时候 你不嫌取数据麻烦、、因为四个byte才是一个数的数据、、或者这样玩也可以

[DllImport("user32.dll")]
public static extern bool GetWindowRect(IntPtr hWnd, byte[] rect);
//---------
byte[] byRect = new byte[16];
GetWindowRect(this.Handle, byRect);
int Left = BitConverter.ToInt32(byRect,0);
int Top = BitConverter.ToInt32(byRect,4);
int Right = BitConverter.ToInt32(byRect,8);
int Bottom = BitConverter.ToInt32(byRect,12);
这里byte[]就不需要ref了 因为数组默认是传递引用的 玩法很多就是了 所以说要知其然还要知其所以然

对于结构这里也就说这么多 一般情况下 一个WindowApi用的比较多的就是基本类型和结构还有常量作为参数、、当然有些需要回调 需要一个函数作为参数的 比如写钩子程序的时候 需要钩子过程、、

下一篇通过调用Api来写一个恶搞程序娱乐一下、、顺便把GetWindowRect和前面的SetWindowPos一起练习一下、下一篇会用到、、


添加时间:2014-05-12 02:31:41 编辑时间:2016-11-20 17:11:13 阅读:2082 
C#Windows编程 C#Win32
还没有人留言 要不你来抢一个沙发?
  • 编写评论

      我觉得区分大小写是一个码农的基本素质
[访问统计] 今天:20 总数:262831 提示:未成年人 请在大人陪同下浏览本站内容 还有:世界上最帅的码农 -> 石头 RSS:http://st233.com/rss Powered by -> Crystal_lz