c# 调用 win32 API的 SendMessage 函数 ,里面的属性用法?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了c# 调用 win32 API的 SendMessage 函数 ,里面的属性用法?相关的知识,希望对你有一定的参考价值。

函数原型:LRESULT SendMessage(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM IParam);

参数:

hWnd:其窗口程序将接收消息的窗口的句柄。如果此参数为HWND_BROADCAST,则消息将被发送到系统中所有顶层窗口,包括无效或不可见的非自身拥有的窗口、被覆盖的窗口和弹出式窗口,但消息不被发送到子窗口。

Msg:指定被发送的消息。

wParam:指定附加的消息指定信息。

IParam:指定附加的消息指定信息。

上面是API上面写的东西,请问:
WPARAM 和 LPARAM 各是什么类型?

wParam 和IParam 都是干什么用的?

SendMessage 这个消息比较特别,因为它的参数返回值会根据不同的消息而不同
首先来解析一下这个函数
第一个参数HWND hWnd ,是要将这个消息发送的对象的句柄,hwnd一般指窗口句柄,但这里的窗口是广义上的,一个按钮,一个文本框,一个列表框等等控件这些都可以看成按钮,所以传它们的句柄也可以。
第二个参数UINT Msg就是要发送的消息是什么, 窗口消息一般都以WM开头,意思是WindowMessage 取了2个字母, 而按钮消息一般以BM开头即ButtonMessage,静态类的是
STM,列表框是LBM 等等。 总之你要发什么消息,这里就写什么消息。
第三个和第四个参数是针对消息的附加参数。 这2个参数会参着所传入的消息值的不同而变经。
WPARAM曾经在16位的WINDOWS当中是一个16位的整数即word parameter
LPARAM曾经在16位的WINDOWS当中是一个32位的整数即long parameter
当时要使用LPARAM一般都要用HIWORD和LOWORD宏来取LPARAM的高16位和低16位,因它们分别代表不同的东西。
而现在对于32位的操作系统来说,它们没啥区别了,都是一个32位的整数,之所以没改名称,大概微软为了兼容性吧。
其实在winuser.h中对它们都有定义,追踪过去一般都可以看到是long类型。
此函数的返回值也会因为所发消息的不同而不同

举个例子,当我们单击了窗口中一个按钮中,程序会将一个WM_COMMAND放入程序的消息队列中,于是窗口可以对WM_COMMAND消息进行处理
LRESULT CALLBACK WindowProc(
HWND hwnd, // handle to window
WM_COMMAND, // the message to send
WPARAM wParam, // notification code and identifier
LPARAM lParam // handle to control (HWND)
);
这是MSDN中的说明,第一个肯定是窗口的句柄,第二个就是消息
第三个参数,指明了按钮的通知码和ID
wParam
The high-order word specifies the notification code if the message is from a control. If the message is from an accelerator, this value is 1. If the message is from a menu, this value is zero.
The low-order word specifies the identifier of the menu item, control, or accelerator
高位的字代表通知码,低位字代表它的ID, 我们一般用HIWORD 和LOWORD来分离它们
比如id = LOWORD(wParam) 这样
lParam
Handle to the control sending the message if the message is from a control. Otherwise, this parameter is NULL.
这个代表了控件的句柄,就是所点击的那个按钮的句柄。
再比如这个:
SendMessage(
(HWND) hWnd, // handle to destination window
BM_GETCHECK, // message to send
(WPARAM) wParam, // not used; must be zero
(LPARAM) lParam // not used; must be zero
);
这个BM_GETCHECK消息可以获得一个复选框是否打勾
第一个参数,很显然该写上那个复选框的句柄
第二个参数,就是那个消息
第三和第四个参数在MSDN中写的很清楚,不使用,必须写0
所以调用时我们很明确要写成
SendMessage(hwndCtrl,BM_GETCHECK,0,0);
而它的返回值可以是下边的几个
BST_CHECKED BST_INDETERMINATE BST_UNCHECKED
这个些在winuser.h中有宏定义的,它们本质就是一些整数,只不过这样宏定义以后更容易理解
所以有时候我们就可以用一个if句判断它是不是选中
if(BST_CHECKED == SendMessage(hwndCtrl,BM_GETCHECK,0,0))

//写上处理代码

这些东西都可以在MSDN中查到,OK 差不多就这些
参考技术A 就是给目标窗口hwnd发送一个消息Msg。消息是什么,就是一个约定好了的数字(UINT无符号整形)。用宏定义换成人容易记住的名字,如WM_SETFOCUS,WM_PAINT,WM_SIZE等等,以及你自己约定的新消息(不要和预定义的重复就好)
WPARAM和LPARAM是你传给处理这个消息的函数的参数.它的使用很灵活,不同的消息有不同的约定.你可以查找MSDN相关的消息就有他两个参数的用法.脱去宏定义WPARAM其实就是一个UINT而LPARAM就是一个LONG当然也可以传指针都没有问题的.
比如下面是WM_SIZE的说明
[wParam]表示改变尺寸的方式类型
Specifies the type of resizing requested. This parameter can be one of the following values.
SIZE_MAXHIDE
Message is sent to all pop-up windows when some other window is maximized.
SIZE_MAXIMIZED
The window has been maximized.
SIZE_MAXSHOW
Message is sent to all pop-up windows when some other window has been restored to its former size.
SIZE_MINIMIZED
The window has been minimized.
SIZE_RESTORED
The window has been resized, but neither the SIZE_MINIMIZED nor SIZE_MAXIMIZED value applies.
[lParam]一个参数放了宽高两个数
The low-order word of lParam specifies the new width of the client area.
The high-order word of lParam specifies the new height of the client area.
还有很大一部分消息是不需要参数的.两个参数传0就行

C#调用Win32 api时的内存操作

一般情况下,C#与Win 32 Api的互操作都表现的很一致:值类型传递结构体,一维、二维指针传递IntPtr。在Win32 分配内存时,可以通过IntPtr以类似移动指针的方式读取内存。通过IntPtr移动时,需要考虑指针的计算。规则总体上来说显得一致,但Win32 Api庞杂,总有一些令人困惑的函数。比如GetIpForwardTable。该函数的功能是返回Ip(v4)的路由表。在win32 的结构体定义如下:

DWORD GetIpForwardTable(
  _Out_   PMIB_IPFORWARDTABLE pIpForwardTable,
  _Inout_ PULONG              pdwSize,
  _In_    BOOL                bOrder
);

typedef struct _MIB_IPFORWARDTABLE {
  DWORD            dwNumEntries;
  MIB_IPFORWARDROW table[ANY_SIZE];
} MIB_IPFORWARDTABLE, *PMIB_IPFORWARDTABLE;

MIB_IPFORWARDROW table[ANY_SIZE]这个定义就很容易使人误解。初一看是一个数组,但搜索之下,却并没有轻易发现ANY_SIZE到底是多大。如果互操作需要定义数组,则一定要指定其大小。再看一下该参数的说明:指向路由实体的指针。很显然,并不是一个数组,而是一个指针。则定义为IntPtr。(确实没有搜索大ANY_SIZE定义)

函数就更有迷惑了,先看定义:

DWORD GetIpForwardTable(
  _Out_   PMIB_IPFORWARDTABLE pIpForwardTable,
  _Inout_ PULONG              pdwSize,
  _In_    BOOL                bOrder
);

Win32 Api的一般套路是空参数返回全部从参数的大小;传递内存空间返回全部内容的数组。按照常理,返回的预期缓存大小是数组的大小,很容易让人以为是MIB_IPFORWARDROW table[ANY_SIZE]; 所需要的大小(基于字节)。我这么做了,并且网上还有示例也这么做了。我按照自己的要么内存错误,要么读取不到数据。不知道网络上是怎么编译通过的。

网络错误示例:

var buffSize = TableLength();
var size = 1 + buffSize / (ulong)Marshal.SizeOf<MibIpForwardRow>();
var table = new MibIpForwardTable()
{
    dwNumEntries = (uint)size,
     table = Marshal.AllocHGlobal((int)buffSize)
};

/** 思路大概是这样,还对结构体数量加了1 (C语言经常这么干)**/

依然失败。我尝试了多种方法,没有成功。初步怀疑自己理解错了,看了MSDN上,关于该函数的C++示例,果然是自己理解错了。(MSDN关于此函数的链接

 if (GetIpForwardTable(pIpForwardTable, &dwSize, 0) ==
        ERROR_INSUFFICIENT_BUFFER) {
        FREE(pIpForwardTable);
        pIpForwardTable = (MIB_IPFORWARDTABLE *) MALLOC(dwSize);
        if (pIpForwardTable == NULL) {
            printf("Error allocating memory\n");
            return 1;
        }
    }

    /* Note that the IPv4 addresses returned in 
     * GetIpForwardTable entries are in network byte order 
     */
    if ((dwRetVal = GetIpForwardTable(pIpForwardTable, &dwSize, 0)) == NO_ERROR) {
        printf("\tNumber of entries: %d\n",
               (int) pIpForwardTable->dwNumEntries);
        for (i = 0; i < (int) pIpForwardTable->dwNumEntries; i++) {
            /* Convert IPv4 addresses to strings */
            IpAddr.S_un.S_addr =
                (u_long) pIpForwardTable->table[i].dwForwardDest;

看黑色加粗的两个部分。当第一次call此函数时,返回的预期空间大小(基于字节)是指针指向的内存空间大小。是 dwNumEntries +  table[ANY_SIZE]的总字节大小。需要自行操作内存,以获取table的首地址。也就是说当通过IntPtr获取到所有的路由表后,需要转换、操作赋值,即:

            var tablePtr = Marshal.AllocHGlobal((int)buffSize);
            GetIpForwardTable(tablePtr, ref buffSize, false);

            var table = Marshal.PtrToStructure<MibIpForwardTable>(tablePtr);
            var list = new List<MibIpForwardRow>();
            var begin = table.table = tablePtr + sizeof(uint);
            for (var i = 0; i < table.dwNumEntries; i++)
            {
                list.Add(Marshal.PtrToStructure<MibIpForwardRow>(begin));
                begin += Marshal.SizeOf<MibIpForwardRow>();
            }


            Marshal.FreeHGlobal(tablePtr);
            return list;

黑体部分:

现将填充后的IntPtr转换为MibIpForwardTable结构体;这时候会得到dwNumEntries。但第二个参数IntPtr需要手动再赋值。

var table = Marshal.PtrToStructure<MibIpForwardTable>(tablePtr);

在赋值语句为:
table.table = tablePtr + sizeof(uint);
需要手动向前移动4个字节。
这样内存布局就正确了。

完整代码如下:

技术分享
  1  #region struct
  2         [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
  3         public struct MibIpForwardRow
  4         {
  5             public uint dwForwardDest;
  6             public uint dwForwardMask;
  7             public uint dwForwardPolicy;
  8             public uint dwForwardNextHop;
  9             public uint dwForwardIfIndex;
 10             public uint dwForwardType;
 11             public uint dwForwardProto;
 12             public uint dwForwardAge;
 13             public uint dwForwardNextHopAS;
 14             public uint dwForwardMetric1;
 15             public uint dwForwardMetric2;
 16             public uint dwForwardMetric3;
 17             public uint dwForwardMetric4;
 18             public uint dwForwardMetric5;
 19         }
 20 
 21         [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
 22         public struct MibIpForwardTable
 23         {
 24             public uint dwNumEntries;
 25             public IntPtr table;
 26         }
 27 
 28         [StructLayout(LayoutKind.Sequential)]
 29         public struct S_un_B
 30         {
 31             public byte s_b1;
 32             public byte s_b2;
 33             public byte s_b3;
 34             public byte s_b4;
 35         }
 36 
 37         [StructLayout(LayoutKind.Sequential)]
 38         public struct S_un_w
 39         {
 40             public ushort s_w1;
 41             public ushort s_w2;
 42         }
 43 
 44         [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode)]
 45         public struct IN_ADDR
 46         {
 47             [FieldOffset(0)]
 48             public S_un_B B_addr;
 49 
 50             [FieldOffset(0)]
 51             public S_un_w St_Addr;
 52 
 53             [FieldOffset(0)]
 54             public uint S_addr;
 55         }
 56         #endregion
 57 
 58 [DllImport("iphlpapi.dll", SetLastError = true)]
 59 public static extern int GetIpForwardTable(IntPtr pIpForwardTable, ref ulong pdwSize, bool bOrder);
 60 
 61 public IEnumerable<Win32.MibIpForwardRow> ForwardTable()
 62         {
 63             ulong TableLength()
 64             {
 65                 ulong _len = 0;
 66                 var zero = new MibIpForwardTable();
 67                 GetIpForwardTable(ref zero, ref _len, false);
 68                 return _len;
 69             }
 70 
 71             var buffSize = TableLength();
 72             var tablePtr = Marshal.AllocHGlobal((int)buffSize);
 73             GetIpForwardTable(tablePtr, ref buffSize, false);
 74 
 75             var table = Marshal.PtrToStructure<MibIpForwardTable>(tablePtr);
 76             var list = new List<MibIpForwardRow>((int)table.dwNumEntries);
 77             var begin = table.table = tablePtr + sizeof(uint);
 78             for (var i = 0; i < table.dwNumEntries; i++)
 79             {
 80                 list.Add(Marshal.PtrToStructure<MibIpForwardRow>(begin));
 81                 begin += Marshal.SizeOf<MibIpForwardRow>();
 82             }
 83 
 84             Marshal.FreeHGlobal(tablePtr);
 85             return list;
 86         }
 87 
 88 public static string ToInAddrString(this uint value)
 89         {
 90             var addr = new IN_ADDR() { S_addr = value };
 91             var ptr = Marshal.AllocHGlobal(64);
 92             RtlIpv4AddressToString(ref addr, ptr);
 93             var ip = Marshal.PtrToStringAnsi(ptr);
 94             Marshal.FreeHGlobal(ptr);
 95             return ip;
 96         }
 97 
 98 static void Main(string[] args)
 99         {
100             var route = new Router();
101             foreach (var r in route.ForwardTable())
102                 Console.WriteLine(r.dwForwardDest.ToInAddrString());
103         }
View Code

输出:

0.0.0.0
127.0.0.0
127.0.0.1
127.255.255.255
192.168.199.0
192.168.199.245
192.168.199.255
224.0.0.0
224.0.0.0
255.255.255.255
255.255.255.255
请按任意键继续. . .

 

以上是关于c# 调用 win32 API的 SendMessage 函数 ,里面的属性用法?的主要内容,如果未能解决你的问题,请参考以下文章

C#调用Win32 api时的内存操作

C#可以直接调用的Win32API(和VCL做的整理工作非常类似)

C#调用user32.dll Win32的API函数

如何使用 C# 拦截 Win32 API 调用?

C#调用win32 api 操作其它窗口

c# 调用 win32 API的 SendMessage 函数 ,里面的属性用法?