现在的位置: 首页 > 综合 > 正文

[.NET] C#中SendMessage通过LPARAM传递Structure

2013年10月31日 ⁄ 综合 ⁄ 共 2818字 ⁄ 字号 评论关闭
 

这两天一直被一个问题烦着:SendMessage在C#始终不正常。因为SendMessage是标准的WinAPI,在C/C++中,Structure可以很方便通过取地址传递给SendMessage,比如Richedit中常用的EM_GETCHARFORMAT消息:
SendMessage
(
hWnd, EM_GETCHARFORMAT, ( WPARAM)SCF_SELECTION, (LPARAM)&cfm );

 

在C#乃至CLR中,并没有对应的CHARFORMAT2,所以必须自己依照MSDN中CHARFORMAT2的C++定义:
[StructLayout(LayoutKind.Sequential,
CharSet = CharSet.Auto)]
private struct CHARFORMAT2
{
    public int
cbSize;
    public uint dwMask;
    public uint dwEffects;
    public
int yHeight;
    public int yOffset;
    public int crTextColor;
   
public byte bCharSet;
    public byte bPitchAndFamily;
   
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
    public char[]
szFaceName;
    public short wWeight;
    public short sSpacing;
   
public int crBackColor;
    public int LCID;
    public uint
dwReserved;
    public short sStyle;
    public short wKerning;
   
public byte bUnderlineType;
    public byte bAnimation;
    public byte
bRevAuthor;
    public byte bReserved1;
}

LayoutKind和UnmanagedType.ByValArray是基本的Marshal常识,偶想强调的是CharSet这个Attribute,尽管CLR默认是CharSet.Auto,但部分语言会override这个属性,C#中就会默认改为CharSet.Ansi

,这也是磕绊偶两天的罪魁祸首。

Structure定义好了,DllImport就比较方便,同样,必须制定相同的CharSet
。因为Microsot在Windows
NT开始已经在内核全部Unicode化,使用Ansi的CharSet会使得操作系统额外多出两部CharSet之间的转换,所以推荐Unicode或者Auto,Auto模式只在Windows
98和Windows ME中是Ansi。

 

对于SendMessage这个定义在USER32.DLL中的导出函数来说,可以定义很多不同的DllImport,常用的有如:
[DllImport("user32",
CharSet = CharSet.Auto, SetLastError = true)]
private static extern int
SendMessage(HandleRef hWnd, int msg, int wParam, int lParam);

[DllImport("user32", CharSet = CharSet.Auto)]
private static extern int
SendMessage(HandleRef hWnd, int msg, int wParam, ref CHARFORMAT2 lParam);

[DllImport("user32", CharSet = CharSet.Auto, SetLastError = true)]
private
static extern IntPtr SendMessage(HandleRef hWnd, int msg, Int32 wParam, ref
IntPtr lParam);

[DllImport("USER32", EntryPoint = "SendMessage", ExactSpelling = true,
CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr
SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);

最简单的方法莫过于第二种定义,它直接在LPARAM参数中使用了CHARFORMAT2的ref。C#编译器会直接生成内部GCHandle以完成Unmanaged和Managed
Heap之间的数据转换。对于别的Structure,可以定义其自己的DllImport,并为LPARAM使用自身的Structure。

对于其他三种定义,需要自己转换IntPtr了。其方法无外乎以下两种:
GCHandle gch =
GCHandle.Alloc(fmt);
IntPtr lParam =
GCHandle.ToIntPtr(gch);
SendMessage(new HandleRef(this, Handle),
EM_GETCHARFORMAT, SCF_SELECTION, ref lParam);
fmt =
(CHARFORMAT2)Marshal.PtrToStructure(lParam, typeof(CHARFORMAT2));
gch.Free();

或:
IntPtr lparam = IntPtr.Zero;
lparam =
Marshal.AllocCoTaskMem(Marshal.SizeOf(fmtRange));
Marshal.StructureToPtr(fmtRange,
lparam, false);
SendMessage(Handle, EM_FORMATRANGE, wparam, lparam);

Marshal.FreeCoTaskMem(lparam);

 

特别的,是对于输出为String的WinAPI,可以使用StringBuilder,而不需要制定ref或者out。如:
[DllImport("user32.dll",
EntryPoint="SendMessage")]
private static extern Int32 SendMessage (IntPtr
hwnd, Int32 wMsg, Int32 wParam, StringBuilder lParam);
调用时:
const Int32
MAX_SIZE = 1024;
StringBuilder buffer = new
StringBuilder(MAX_SIZE);
SendMessage(tb_Input.Handle, WM_GETTEXT, MAX_SIZE,
buffer);

 

Issued by Alva Chien
2009.11.3

抱歉!评论已关闭.