序1撷取自WinObject.cs,针对部份输入文字型的控件,可使用WM_SETTEXT指定文字内容
//由类别名称识别是否为特定的文字输入控件对象
public bool IsEditControl{ get { string cn=ClassName.ToUpper(); return (cn=="EDIT" || cn=="THUNDERRT5TEXTBOX" || cn=="RICHCNTL" || cn=="RICHEDIT"); }} const uint WM_GETTEXTLENGTH=0x0e;const uint WM_GETTEXT=0x0d;const uint WM_SETTEXT=0x0c; //窗口对象的标题或控件的文字内容
public string Text{ get { if (IsEditControl) { int l=(int) SendMessage(hWnd,WM_GETTEXTLENGTH,0,0); StringBuilder sb=new StringBuilder(l+1); l=(int) SendMessage(hWnd,WM_GETTEXT,sb.Capacity,sb); return sb.ToString(); } else { int length = GetWindowTextLength(hWnd); StringBuilder sb = new StringBuilder(length + 1); GetWindowText(hWnd, sb, sb.Capacity); return sb.ToString(); } } set { if (IsEditControl) SendMessage(hWnd,WM_SETTEXT,0,value); else SetWindowText(hWnd, value); }}-----end----- -----box-----#程序2 与下拉选单互动的范例
[DllImport("USER32.DLL", SetLastError=true)]public static extern long SendMessage(IntPtr hWnd, uint msg, int wparam, StringBuilder sb);private void button2_Click(object sender, System.EventArgs e){//由Spy++观察该下拉选单控件的从属关系,以FindChild方式锁定之。
//由于同一层有多个TadvCityCbBox控件,因此使用FindChildren再由数组中取出第2个(户籍地)。
//原理及细节说明请参见前期文章。
WinObject cb = (WinObject)WinObject.FindWindow("TBasicForm","财政部九十二年度综合所得税二维条形码结算申报系统")
.FindChild("TNotebook","").FindChild("TPage","Basic").FindChild("TPanel","Panel2") .FindChild("TPanel","").FindChildren("TadvCityCbBox",null)[1]; int itemCount = (int) cb.SendMessage(0x0146,0,0); //CB_GETCOUNT=0x146string[] itemValues=new string[itemCount];Debug.Write("共有"+itemCount.ToString()+"个选项,包含有:");
int idxTaipei=-1;for (int i=0; i<itemCount; i++) {//先取得字符串长度 CB_GETLBTEXTLEN 0x0149
int len=(int) cb.SendMessage(0x0149,(uint) i,0);//取回字符串 CB_GETLBTEXT 0x0148
StringBuilder sb=new StringBuilder(len); SendMessage(cb.Handle,0x0148, i, sb); itemValues[i]=sb.ToString();if (itemValues[i]=="台北市") idxTaipei=i;
if (i % 10==0) Debug.WriteLine(""); Debug.Write(itemValues[i]+" ");}Debug.WriteLine("");//取得目前的选取索引值 CB_GETCURSEL 0x0147
int idxNow=(int) cb.SendMessage(0x0147,0,0);Debug.WriteLine("目前的选取值="+idxNow.ToString()+"."+itemValues[idxNow]);
//改变选取索引值至台北市的选项,CB_SETCURSEL 0x014E
cb.SendMessage(0x014E,(uint) idxTaipei,0);Debug.WriteLine("已设定为"+idxTaipei.ToString()+"."+itemValues[idxTaipei]);
}-----end----- -----box-----#程序3 跨程序远程记忆体操作 RemoteMemory.cs
public class RemoteMemory{ private uint lpMem=0; private int size=0; private IntPtr hProcess; [DllImport("kernel32.dll")] public static extern IntPtr OpenProcess(uint dwDesiredAccess, int bInheritHandle, uint dwProcessId); uint PROCESS_VM_OPERATION = (0x0008); uint PROCESS_VM_READ = (0x0010); uint PROCESS_VM_WRITE = (0x0020); uint MEM_COMMIT = 0x1000; uint MEM_RESERVE = 0x2000; uint MEM_RELEASE = 0x8000; uint PAGE_READWRITE = 0x04; [DllImport("kernel32.dll")] public static extern uint VirtualAllocEx(IntPtr hProcess, uint lpAddress, uint dwSize, uint flAllocationType, uint flProtect); [DllImport("kernel32.dll")] public static extern int WriteProcessMemory(IntPtr hProcess, uint lpBaseAddress, uint lpObject, uint nSize, ref uint lpNumberOfBytesWritten); [DllImport("kernel32.dll")] public static extern int ReadProcessMemory(IntPtr hProccess, uint lpBaseAddress, uint lpObject, uint nSize, ref uint lpNumberOfBytesRead); [DllImport("kernel32.dll")] public static extern int ReadProcessMemory(IntPtr hProcess, uint lpBaseAddress,[In, Out] byte[] buffer, uint nSize, ref uint lpNumberOfBytesRead); [DllImport("kernel32.dll")] public static extern int VirtualFreeEx(IntPtr hProcess, uint lpAddress, int dwSize, uint dwFreeType); [DllImport("USER32.DLL", SetLastError=true)] public static extern uint SendMessage(IntPtr hWnd, uint Msg, uint wParam, uint lParam); [DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr hWnd, ref uint processId);//在指定的Process中,取得一段指定大小的内存
public RemoteMemory(IntPtr hWnd, int size) { uint dwProcessId=0; GetWindowThreadProcessId(hWnd,ref dwProcessId); hProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, 0, dwProcessId); lpMem=VirtualAllocEx(hProcess, 0, (uint) size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); this.size=size; }//释放原先取得的内存
public void Unallocate() { VirtualFreeEx(hProcess, lpMem, 0, MEM_RELEASE); }//将对象的内容抄写到先前取得的内存中
public void Write(object o) { if (Marshal.SizeOf(o)>size) throw new ApplicationException("RemoteMemory.Write method error: allocated memory size is not enough!"); GCHandle gc=GCHandle.Alloc(o,GCHandleType.Pinned); uint bytesWritten=0; int ret=WriteProcessMemory(hProcess, lpMem, (uint) gc.AddrOfPinnedObject(), (uint) Marshal.SizeOf(o), ref bytesWritten); }//将内存中的内容还原至对象
public void Read(ref object o) { GCHandle gc=GCHandle.Alloc(o,GCHandleType.Pinned); uint bytesRead=0; int ret=ReadProcessMemory(hProcess, lpMem, (uint) gc.AddrOfPinnedObject(), (uint) Marshal.SizeOf(o), ref bytesRead); }//将内存中的某一段解析成为字符串
public string ReadCString(uint offset, int stringLen) { uint bytesRead=0; byte[] buffer=new byte[stringLen]; int ret=ReadProcessMemory(hProcess, lpMem+offset, buffer, (uint) stringLen, ref bytesRead); return szToString(buffer); }//将整块内存解析成为字符串
public string ReadCString() { return ReadCString(0,size); }//将字节转成字符串
private string szToString(byte[] byteArray) { int length=0; for (int i=0; i<byteArray.Length; i++) { if (byteArray[i]==0) { length=i; break; } } return System.Text.Encoding.GetEncoding("big5").GetString(byteArray,0,length); }//传回内存地址
public uint MemoryAddress { get { return lpMem; } }}-----end----- -----box-----#程序4 将List View的Grid内容,转为DataTable: ListViewSpy.cs
public class ListViewSpy{ [StructLayout(LayoutKind.Sequential)]public struct LVITEM //List View的Item数据结构
{ public uint mask; public int iItem; public int iSubItem; public uint state; public uint stateMask;public IntPtr pszText; //struct中不可使用StringBuilder
public int cchTextMax; public int iImage; public IntPtr lParam; } [StructLayout(LayoutKind.Sequential)]public struct HDITEM //List View的Header数据结构
{ public uint mask; public int cxy; public IntPtr pszText; public uint hbm; public int cchTextMax; public int fmt; public IntPtr lParam; } [DllImport("USER32.DLL", SetLastError=true)] public static extern uint SendMessage(IntPtr hWnd, uint msg, uint wparam, uint lparam); private IntPtr hWnd; private int columnCount,rowCount; private const int MAX_STR_SIZE=256; private DataTable table=new DataTable(); public ListViewSpy(IntPtr hWnd) { this.hWnd=hWnd;//取得Header(SysHeader32)
uint LVM_GETHEADER=0x1000+31; IntPtr header=new IntPtr(SendMessage(hWnd,LVM_GETHEADER,0,0)); if (header==IntPtr.Zero) return;//取得Column数
uint HDM_GETITEMCOUNT=0x1200+0; columnCount=(int) SendMessage(header,HDM_GETITEMCOUNT,0,0);//取得Column Header Text
HDITEM hdi=new HDITEM(); uint HDI_TEXT=0x0002; hdi.mask=HDI_TEXT; hdi.cchTextMax=MAX_STR_SIZE; int iStructSize=Marshal.SizeOf(hdi);//在目的地Process取得一块用来存放HDITEM的内存空间
//并额外多要一些用来放字符串
RemoteMemory rm=new RemoteMemory(hWnd,iStructSize+MAX_STR_SIZE);//指向额外的空间
hdi.pszText=new IntPtr(rm.MemoryAddress+iStructSize);//将hdi的内容复制到目的地Process的内存上
rm.Write(hdi); //SendMessage uint HDM_GETITEMA=0x1200+3; byte[] buffer=new byte[MAX_STR_SIZE]; table=new DataTable(); for (int i=0; i<columnCount; i++) { int r=(int) SendMessage(header,HDM_GETITEMA, (uint) i,rm.MemoryAddress);//以字段名称建立DataTable的字段
table.Columns.Add(rm.ReadCString((uint) iStructSize,MAX_STR_SIZE),Type.GetType("System.String")); } rm.Unallocate(); //取得ListView资料笔数
uint LVM_GETITEMCOUNT=0x1000+4; rowCount=(int) SendMessage(hWnd,LVM_GETITEMCOUNT,0,0); LVITEM lvi=new LVITEM(); uint LVIF_TEXT = 0x0001; lvi.mask=LVIF_TEXT; lvi.cchTextMax=MAX_STR_SIZE;//在目的地Process建立LVITEM,原理同HDITEM
iStructSize=Marshal.SizeOf(lvi); rm=new RemoteMemory(hWnd,(int) iStructSize+MAX_STR_SIZE); lvi.pszText=new IntPtr(rm.MemoryAddress+iStructSize); //SendMessage uint LVM_GETITEMTEXTA=0x1000+45; for (int i=0; i<rowCount; i++) { lvi.iItem=i; DataRow row=table.NewRow(); for (int j=0; j<columnCount; j++) { lvi.iSubItem=j; rm.Write(lvi); int ret=(int) SendMessage(hWnd,LVM_GETITEMTEXTA, (uint) i,rm.MemoryAddress); row[j]=rm.ReadCString((uint) iStructSize,MAX_STR_SIZE); } table.Rows.Add(row); } rm.Unallocate(); } public DataTable Data { get { return table;} }}-----end----- -----box-----程序5 将Task Manager Processes List View的数据在DataGrid还原, 结果如图3
private void button1_Click(object sender, System.EventArgs e){//使用SPY++时,看到Task Manager的Class Name是#32770 (Dialog)
//寻找时记得只要指定#32770即可,加上(Dialog)后会找不到
WinObject lvProcesses=WinObject.FindWindow("#32770","Windows Task Manager") .FindChild("#32770","").FindChild("SysListView32","Processes"); ListViewSpy lvs=new ListViewSpy(lvProcesses.Handle); dataGrid1.DataSource=lvs.Data; dataGrid1.Refresh(); }
