偶是兩個QQ群的管理員,平常都是在群里跟其它人交流.當然啦,因為偶是管理員,就要承擔起管理員的責任.在實際中,會碰到兩個問題:
1、我的兩個群都很熱,有很多人加入,這樣,不用很快,群里的人就達到了上限200人了,就不能再讓新的人加入.
2、平常有些人加入只是為了發廣告,整天在這里發一些與群的主題不相關的內容.或者是有的人的QQ中毒了,老是不時發一些有病毒的鏈接.
對于這兩個問題,我是這么的解決的.
1、當人數達到上限時,我就讓群里的人都在群的名字前面加上一些特別的符號,比如:@%#%&^*,總之就是一些一般人不會用在自己群的名字的符號吧,以這些符號作為標志,識別哪些人是長期沒有在群里發言的人.把這個改名的要求發在群的公告里,對于那些長期沒有上線的人,當然看不到群的公告,也就不會改群的名片了.我以這些符號作為標志,清除那些長期不上線的人,留些空間,讓新人能加進來.
2、對于那些亂發信息的人,當然就是立即T出群里啦.
這兩種的做法都是把人給T出群里,但是在實際操作中卻很麻煩了. 對于第一種情況,有些人把那個特別的符號放在群名字中的某個地方,比如,要求把@加在名字前面,有個名字叫天使,本來按照要求,改名后就變為@天使,但這個人卻很有個性,他把名字改為天@使,對于這些人,當然可以不管三七二十一,一律當成是沒有改名,把他T出群外啦.但是考慮到這個人還是有看到公告的,還是讓他留下來吧,但這樣就苦了我這個當管理員的啦,在200個人里面,一個個的看哪個人的名字不符合公告的要求.人這么多,把我都看到眼花聊亂的了.既要把人T走,又不好T錯了.做這樣的事,也真是費功夫的. 對于第二種情況,也是一樣的,因為聊天信息的那個窗口里,只能看到這個人的名字和QQ號,為了把這個人T了,還得在群設置里,一個個人的去對,找那個QQ號,實在是痛苦,都是數字,要很細心一個個的核對,一不小心就把這個號給漏了過去,又得重新找一遍了,有好幾次,我都是找了三次以上才把那個QQ號才找出來.為此,我想做一個工具,只要輸入QQ號,就可以把人T走了.最初,我是想抓取QQ把群里的人T走時的數據包來分析一下.知道了這個數據包消息的格式后,我就可以仿造一個消息,直接的向QQ服務器發過去,就可以把人給T了.我用工具把T人時的數據包抓取一下,全部都是亂碼的.因為QQ的消息格式并沒有公開,把以分析起來真的是頭痛了,都無從下手了,只好把這個想法放棄了.我又想了一下,既然我不能發這樣的數據包,那就直接讓QQ自己發這個包吧.為了要讓QQ把T人的包給發出去,就得從QQ自己的界面入手,輸入QQ號后,能在群設置里直接的定位到要T的QQ號,這樣就不用人工的去找這個QQ號,省卻了去找這個QQ號的痛苦了.
二、問題的分析
我在實現時使用的是TM2006新春版,在群聊天的窗口里點工具務欄上的"群設置",彈出了"群設置"窗口,在這個窗口里,選擇"成員列表"這一項,右邊有一個list,這個list就包含有所有的成員了,當選中了某個人后,就可把它T了.
圖一 群設置窗口
現在的問題是要先把想T的人找出來.怎么樣在list中把想T的人給找出來呢,我的想法是枚舉這個list里所有人的QQ號,然后跟想要T的QQ號作比較,如果有相同的,就把list里的這一項選中,然后我們就可以進一步的操作了.那現在就可以把問題轉化為,枚舉list,獲得list里的項的信息.我用spy++查看了一下那個"群設置"窗口,如圖所示:
圖二 用spy++查看的窗口關系
最頂層的就是那個"群設置"窗口了,那個顯示成員的list原來是一個syslistview32類型的控件,包含在一個類型為"#32770"的dialog中,我們只要順著最頂層的窗口中,一層層的窗口找下去就可以得到我個想要的那個list窗口的名柄了,呵呵,之后,就可以向這個list發出消息,讓它干活了.想到這,偶心時狂喜了一陣子.但是在實現過程中卻是不是那么的順利的.在頂層窗口中找那個list的句柄,并不是什么困難的事.把桌面上的把有窗口都枚舉一遍,就要以找到窗口名為”群設置”的窗口了.
EnumWindows(EnumWindowsProc,0);//枚舉所有的窗口 BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) { TCHAR buff[1000]; int buffsize(100),nPosition(-1); HWND hQQWnd=NULL; ::GetWindowText(hwnd,buff,buffsize); if (strlen(buff)<1) return TRUE; CString str(buff); nPosition=str.Find(_T("群設置")); //這里設置要找的窗口名 if(nPosition!=-1) EnumChildWindows(hwnd,ChildWndProc,0);//繼續找子窗口 return TRUE; } //枚舉包含有syslistview32的類型為#32770的窗口的句柄 BOOL CALLBACK ChildWndProc(HWND hwnd, LPARAM lParam) { LPTSTR lptstr; HGLOBAL hglb=NULL; char className[CLASS_SIZE]; if (GetClassName(hwnd,className,CLASS_SIZE)==0) return TRUE; CString str(className); HWND hChild = GetWindow(hwnd, GW_CHILD); if (GetClassName(hChild,className,CLASS_SIZE)==0) return TRUE; CString strChildName(className); //頂層窗口下有四個類型都為”#32770”的dialog,其中只有其中一個 //才是包括有成員列表的 while (str != _T("#32770") || strChildName != _T("SysListView32")) { HWND h1= GetNextWindow(hwnd, GW_HWNDNEXT); GetClassName(h1,className,CLASS_SIZE); str = className; hwnd = h1; hChild = GetWindow(hwnd, GW_CHILD); if (GetClassName(hChild,className,CLASS_SIZE)==0) return TRUE; strChildName =className; } EnumChildWindows(hwnd,DeleWndProc,0);//在包含syslistview的dialog中繼續找 } |
找到了包含有syslistview的窗口后,就繼續找syslistview了,然后可以向它發送命令消息了.這是整個程序的關鍵部分,先把代碼給出來,我再進行解釋.
#define CLASS_SIZE 4096 BOOL CALLBACK DeleWndProc(HWND hwnd, LPARAM lParam) { LPTSTR lptstr; HGLOBAL hglb=NULL; char className[CLASS_SIZE]; if (GetClassName(hwnd,className,CLASS_SIZE)==0) return TRUE; CString str(className); char sz[254] ="\0"; if (_T("SysListView32") == str) { int iItem=0; int iFoundFlag = 0;//if find the qq number, iFoundFlag = 1;else 0; LVITEM lvitem,lvitem1, *plvitem,*plvitem1; DWORD PID; HANDLE hProcess; char ItemBuf[512],*pItem; GetWindowThreadProcessId(hwnd, &PID); hProcess=OpenProcess(PROCESS_ALL_ACCESS,false,PID); if (!hProcess) { MessageBox(NULL,"獲取進程句柄操作失敗!","錯誤!",NULL); } else { plvitem=(LVITEM*)VirtualAllocEx(hProcess, NULL, sizeof(LVITEM), MEM_COMMIT, PAGE_READWRITE); plvitem1=(LVITEM*)VirtualAllocEx(hProcess, NULL, sizeof(LVITEM), MEM_COMMIT, PAGE_READWRITE); pItem=(char*)VirtualAllocEx(hProcess, NULL, 5120, MEM_COMMIT, PAGE_READWRITE); if (!plvitem) { MessageBox(NULL,"無法分配內存!","錯誤!",NULL); } else { int nItemCount = ::SendMessage(hwnd, LVM_GETITEMCOUNT, 0 ,0); lvitem.mask=LVIF_TEXT; lvitem.cchTextMax=512; lvitem.iSubItem=1; //ProcessName lvitem.pszText=pItem; WriteProcessMemory(hProcess, plvitem, &lvitem, sizeof(LVITEM), NULL); lvitem1.state=LVIS_SELECTED; lvitem1.stateMask=LVIS_SELECTED; WriteProcessMemory(hProcess, plvitem1, &lvitem1, sizeof(LVITEM), NULL); for(; iItem<NITEMCOUNT; p iItem++) { SendMessage(hwnd, LVM_GETITEMTEXT, (WPARAM)iItem, (LPARAM)plvitem); ReadProcessMemory(hProcess, pItem, ItemBuf, 512, NULL); CString strItem(ItemBuf); //strQQNum就是要找的QQ號碼了 if(strQQNum == strItem) { SendMessage(hwnd, LVM_SETITEMSTATE, (WPARAM)iItem, (LPARAM)plvitem1); iFoundFlag = 1; break; } } if(0 == iFoundFlag) { CString str; str = "沒有找到QQ號:\n"; str += strQQNum; MessageBox(NULL, str, "提醒", NULL); } } } CloseHandle(hProcess); VirtualFreeEx(hProcess, pItem, 0, MEM_RELEASE); VirtualFreeEx(hProcess, plvitem1,0, MEM_RELEASE); VirtualFreeEx(hProcess, plvitem, 0, MEM_RELEASE); } return TRUE; } |
DeleWndProc函數主要是把枚舉syslistview32的項,查找出我們想要找的QQ號,并選中. 最初時我是嘗試用以下的代碼去得到list的item的內容的.
TCHAR szText[100]; LV_ITEM lvi; lvi.mask = LVIF_TEXT; lvi.iItem = nIndex; lvi.iSubItem = 0; lvi.pszText = szText; lvi.cchTextMax = 100; ListView_GetItem(hwndLV, &lvi); |
但卻會報錯誤,存取錯誤,也就是說內存方面的問題了.問題定位到了ListView_GetItem(hwndLV, &lvi);這一句了.后來我查找了很多資料才知道為什么會有錯誤.因為我的程序與TM的程序是分別屬于不同的Progress,我在自己的程序的進程中申請了lvi的內存空間,卻希望把TM進程往這個內存空間去寫入數據,當然是會有錯誤啦. Windows用到了虛存這個概念,它讓每個程序都覺得自己占有2G的內存,每個程序都把自己用到的數據放在這2G的內存中去運行.每個程序間的內存空間是互不相干的,這樣,如果某個程序出現了問題,也不會影響到其它程序的運行了.ListView_GetItem要往TM的程序里寫數據,當然這樣的數據只能保存在TM這個程序的內存空間里了.我們可以用VirtualAllocEx這個函數在TM這個程序運行的內存片中申請內存空間,這樣ListView_GetItem就可以向這個新申請的空間中寫入數據了.然后,我們再用ReadProcessMemory函數把新申請的空間中的數據讀到自己程序進程里的緩沖區中去,采用了一個曲折的辦法,實現了不同進程的數據交換.最后當然要把申請的空間用VirtualFreeEx釋放,要不就會有內存泄漏了.
三、問題的解決
至此,再回頭看看文中開頭提到的兩個問題. 1查找到有特殊標志的QQ號的名字,只要修改DeleWndProc中的匹配QQ號的語句就行了.這個純粹是一個字符串的匹配了. 2查找指寫的的QQ號. DeleWndProc已經是查找指定的QQ號了.示例程序給出的就是這種情況. 當已經找到指定的項時,就可以向那個刪除成員Button發出一個BM_Click的消息了,呵呵,人就給T了.這個也不難實現,也照樣的找到這個按鈕的HWND,用SendMessage就可以把消息發送過去了.至此就解決了開頭提出的兩個問題了. 在查找群設置的窗口時,也可以用FindWindow和FindWindowEx來得到syslistview32的句柄.
四、后續工作
我在把程序也給另一個管理員用的時候,他給我提出要實現以下的功能: 輸入被T的QQ號,軟件自動搜索自己是管理員的群,軟件能在自己是管理員的群中搜索到此QQ號,把這個人在每個群里都T走. 呵呵,我的想法模擬人的操作過程,把TM的聯系人面板打開,
圖三 TM的聯系人面板
逐個項展開,如果這個項是群就向這個項發出雙擊消息,讓它出現群聊天窗口,再向群聊天窗口中的群設置發雙擊消息,
圖四 聊天窗口工具欄上的群設置
這樣就會出現群設置窗口了,就把問題歸結到原來已經解決了的問題了.但我發現那個面板的類是Tencent_UserBar_Class_Ver1.0,如圖三所示.不知是從什么派生出的,從而就不知道要發出些什么消息了.還請高人指教.
五、結束語
從vckbase學到了很多東西,很感謝大家的共同分享.自己的水平還很菜,但還是把自己碰到的問題寫出來了,一方面可以給像我同樣的菜鳥吸取經驗教訓,再碰到同樣的問題時,能有一些參考資料.另一方面,是讓各位高人指點一下我的不足. 歡迎用下面的聯系方式與我繼續討論。