闲着没事干就做了这么个东西.
第一次在C#里用了这么多跨进程的窗体操作.
还是学到不少东西.
らぶデス3的手动去码流程如下:
先去网上找到ODFConvertor.
然后进入游戏目录的ODF下,将所有的*_base_HI.odf找出来,
用ODFConvertor打开它,将*_moz的勾选去掉后再导出得到一
个!*_base_HI.odf然后用这个新文件替换掉旧文件即可.
ODFConvertor有个地方很讨厌:他只能用文件拖拽的方式打开odf
文件.于是我事先设想的模拟几个按钮单击就搞定的方法是没戏了.
于是我开始去MSDN啃那提到就让我头痛的OLE...结果看了一晚没啥结果.
到处都找不到得到对方窗体的IDropTarget的方法.倒是在Shell编程的资料
里查到一条WM_DROPFILES的消息...可是看到那个HDROP是个形似句柄
的东东,马上就放弃去睡觉了.
第二天上网问了下清洁工...结果他帮我在csdn google出了一篇用WM_DROPFILES
模拟文件拖拽的文章(囧...为啥我google半天没找到?)原来那个所谓的HDROP其实
就是指向一个DROPFILES结构的指针...囧.要是早知道就好了.
接下来就是选语言了.csdn那篇文章是用c++写的.按说这种涉及大量api调用的东西
还是C++比较顺手,但是一想到C++那繁琐的UI编写我马上就放弃了.最后还是选择了
最常用的C#.
接下来就是考虑在C#中实现跨进程的操做了.
思路如下:
Shell文件的拖拽处理事实上是在窗体过程中处理了WM_DROPFILES消息.
接受到的参数HDROP是一个指向DROPFILES的指针.DROPFILES的C++声明如下:
typedef struct _DROPFILES { DWORD pFiles; POINT pt; BOOL fNC; BOOL fWide; } DROPFILES, *LPDROPFILES;
我在C#里声明的时候把它改了一下:
struct DROPFILES { public int pFiles; public int x; public int y; public int fNC; public int fWide; };
这样我可以少声明一个不用的POINT结构,只要保证大小不变就好:)
然后就是这个指针的问题了.我自己的进程里分配的DROPFILES拿给目标进程是肯定没用的.
自然要涉及到跨进程的内存读/写;于是需要以下API:
[DllImport ("kernel32.dll")] public static extern bool CloseHandle (IntPtr hobject); [DllImport ("kernel32.dll")] public static extern IntPtr VirtualAllocEx( IntPtr hProcess, IntPtr lpAddress, int dwSize, uint flAllocationType, uint flProtect ); [DllImport("kernel32.dll")] public static extern bool VirtualFreeEx( IntPtr hProcess, IntPtr lpAddress, int dwSize, uint dwFreeType ); [DllImport("kernel32.dll")] public static extern bool WriteProcessMemory( IntPtr hProcess, IntPtr lpBaseAddress, IntPtr lpBuffer, int nSize, out int lpNumberOfBytesWritten ); [DllImport("kernel32.dll")] public static extern bool ReadProcessMemory( IntPtr hProcess, IntPtr lpBaseAddress, IntPtr lpBuffer, int nSize, out int lpNumberOfBytesRead );
拖动的执行流程为:
先在自己的进程里初始化一个DROPFILES结构d,然后在目标进程中分配一块同样大的空间p.
将d写过去.接着将p作为WM_DROPFILES的HDROP发送过去.
public static bool DropFile (IntPtr hWnd,string filePath) { bool ret =false; int pid; GetWindowThreadProcessId (hWnd,out pid); IntPtr hproc; //打开进程 hproc = OpenProcess (PROCESS_VM_OPERATION|PROCESS_VM_WRITE,false,pid); if (hproc == IntPtr.Zero) return false; char[] strbuf = filePath.ToCharArray (); int cbstrSize = filePath.Length*2+2;//*2因每个字符占用两字节,+2为最后的/0所占的2字节 int sizedropfiles = Marshal.SizeOf(typeof(DROPFILES)); //字符串缓存 int bufSize = sizedropfiles + cbstrSize; //申请缓存 IntPtr pdbuf = Marshal.AllocCoTaskMem (bufSize); DROPFILES* dropfiles = (DROPFILES*)pdbuf; //计算字符串的地址 IntPtr pstrbuf = new IntPtr(pdbuf.ToInt32() + sizedropfiles); //清零初始化 ZeroMemory ((IntPtr)dropfiles,bufSize); //构造本地的DROPFILES dropfiles->pFiles = sizedropfiles; dropfiles->x = 0; dropfiles->y = 0; dropfiles->fNC = 0; dropfiles->fWide = 1;//使用Unicode字符 //复制填充字符串 Marshal.Copy (strbuf,0,pstrbuf,strbuf.Length); //申请远程内存 IntPtr ptrRemote = VirtualAllocEx (hproc,IntPtr.Zero,bufSize,MEM_COMMIT,PAGE_READWRITE); if (ptrRemote==IntPtr.Zero) goto clear; int writecount; //写入目标进程 if (WriteProcessMemory (hproc,ptrRemote,(IntPtr)dropfiles,bufSize,out writecount)) { /*MessageBox.Show(string.Format( "缓存写入对方内存:{0:X}/n应写入{1}字节,成功写入{2}字节", ptrRemote.ToInt32() , bufSize, writecount));*/ //发送消息 SendMessage (hWnd,WM_DROPFILES,ptrRemote.ToInt32(),0); ret = true; } clear://收尾工作 Marshal.FreeCoTaskMem((IntPtr)(dropfiles));//释放Com里分配的缓存 if (ptrRemote!=IntPtr.Zero) { //释放远程内存 if (!VirtualFreeEx(hproc, ptrRemote,0, MEM_RELEASE)) { throw new Win32Exception(); } } //关闭句柄 CloseHandle (hproc); return ret; }
这样只要对着ODFConvertor的hwnd运行这条函数就能实现把文件拖上去的效果了:)
接下来是对ODFConvertor的控制了.ODFConvertor使用一个SystemListView32来显示内容:
在查了Win32SDK的ListView控件的文档后对搞到Name列文本的方法第一反应是C++里的用Dll注入进程后操作.
最好是用C++/CLI的Dll这样可以结合.net便利的Remote功能.但想到为这么个东西还特地用C++写个dll啥的太TM
蛋疼后决定寻找完全用C#的方法.
先是解决取消勾选的问题:
根据commctrl.h里的ListView_SetCheckState宏我先写出了这样一条函数:
static void ListView_SetItemState(IntPtr hwndLV, int i, uint state, uint mask) { LVITEM _macro_lvi = new LVITEM(); _macro_lvi.stateMask = mask; _macro_lvi.state = state; SendMessage(hwndLV, LVM_SETITEMSTATE, i,ref _macro_lvi); } public static void ListView_SetCheckState(IntPtr hwndLV, int i, bool fCheck) { ListView_SetItemState(hwndLV, i, INDEXTOSTATEIMAGEMASK((fCheck) ? 2 : 1), LVIS_STATEIMAGEMASK); }
结果测试执行后ODFConvertor直接崩掉了囧...刚开始的十几分钟一直百思不得其解.
后面才反应过来:SendMessage直接传个int之类的没问题.问题是我的_macro_lvi是
一个结构体.ref _macro_lvi在平台调用中传递的是一个指针.而这个指针对于在另一个
进程中的ListView窗体处理函数来说毫无意义.当然导致非法内存访问崩掉拉~明白这点
后修改了下得到了一条可以成功运行的函数:
static void RemoteListView_SetItemState(int pid,IntPtr hwndLV, int i, uint state, uint mask) { LVITEM _macro_lvi = new LVITEM(); _macro_lvi.stateMask = mask; _macro_lvi.state = state; LVITEM* plvi = &_macro_lvi; { int bufsize = Marshal.SizeOf(_macro_lvi); RemoteProcessMemory rpm = new RemoteProcessMemory (pid); IntPtr ptrRemote = rpm.VirtualAlloc(bufsize); rpm.Write(ptrRemote, new IntPtr(plvi), bufsize); SendMessage(hwndLV, LVM_SETITEMSTATE, i, ptrRemote); rpm.VirtualFree(ptrRemote);//释放掉内存 rpm.Close(); } } public static void RemoteListView_SetCheckState(int pid,IntPtr hwndLV, int i, bool fCheck) { RemoteListView_SetItemState(pid,hwndLV, i, INDEXTOSTATEIMAGEMASK((fCheck) ? 2 : 1), LVIS_STATEIMAGEMASK); }
顺便把远程进程内存的管理封装成了一个RemoteProcessMemory类.嗯...看起来比之前那个实现文件拖拽的直接调用API好看多了:P.
有了解决CheckBox问题的教训后做获取Name列的字符就顺利多了:
unsafe string GetChunkName(int index)//读取远程ListView Name列的字符 { string ret = null; const int cbstrbuf = 250; WinAPI.LVITEM lvitem = new WinAPI.LVITEM(); int cblvitem = Marshal.SizeOf(lvitem); WinAPI.RemoteProcessMemory rpm = new WinAPI.RemoteProcessMemory(pid); lvitem.pszText = rpm.VirtualAlloc(cbstrbuf); lvitem.iItem = index; lvitem.cchTextMax = 250; lvitem.iSubItem = 1;//NAME在第二列 lvitem.mask = WinAPI.LVIF_TEXT; var rplvitem = rpm.VirtualAlloc(cblvitem); rpm.Write(rplvitem, new IntPtr(&lvitem), cblvitem); WinAPI.SendMessage(odflistwnd, WinAPI.LVM_GETITEM, 0, rplvitem); byte* localstrbuf = stackalloc byte[cbstrbuf]; rpm.Read(lvitem.pszText, new IntPtr(localstrbuf), cbstrbuf); ret = Marshal.PtrToStringAnsi(new IntPtr(localstrbuf)); //释放掉远程内存 rpm.VirtualFree(lvitem.pszText); rpm.VirtualFree(rplvitem); rpm.Close(); return ret; }
解决了这几个问题后剩下的就简单了.也就是用SendMessage发个BM_CLICK之类的东西.于是乎最后得到了这么个BT东西:
补丁下载