偶是兩個QQ群的管理員,平常都是在群里跟其它人交流.當然啦,因為偶是管理員,就要承擔起管理員的責(zé)任.在實際中,會碰到兩個問題:
1、我的兩個群都很熱,有很多人加入,這樣,不用很快,群里的人就達到了上限200人了,就不能再讓新的人加入.
2、平常有些人加入只是為了發(fā)廣告,整天在這里發(fā)一些與群的主題不相關(guān)的內(nèi)容.或者是有的人的QQ中毒了,老是不時發(fā)一些有病毒的鏈接.
對于這兩個問題,我是這么的解決的.
1、當人數(shù)達到上限時,我就讓群里的人都在群的名字前面加上一些特別的符號,比如:@%#%&^*,總之就是一些一般人不會用在自己群的名字的符號吧,以這些符號作為標志,識別哪些人是長期沒有在群里發(fā)言的人.把這個改名的要求發(fā)在群的公告里,對于那些長期沒有上線的人,當然看不到群的公告,也就不會改群的名片了.我以這些符號作為標志,清除那些長期不上線的人,留些空間,讓新人能加進來.
2、對于那些亂發(fā)信息的人,當然就是立即T出群里啦.
這兩種的做法都是把人給T出群里,但是在實際操作中卻很麻煩了. 對于第一種情況,有些人把那個特別的符號放在群名字中的某個地方,比如,要求把@加在名字前面,有個名字叫天使,本來按照要求,改名后就變?yōu)锧天使,但這個人卻很有個性,他把名字改為天@使,對于這些人,當然可以不管三七二十一,一律當成是沒有改名,把他T出群外啦.但是考慮到這個人還是有看到公告的,還是讓他留下來吧,但這樣就苦了我這個當管理員的啦,在200個人里面,一個個的看哪個人的名字不符合公告的要求.人這么多,把我都看到眼花聊亂的了.既要把人T走,又不好T錯了.做這樣的事,也真是費功夫的. 對于第二種情況,也是一樣的,因為聊天信息的那個窗口里,只能看到這個人的名字和QQ號,為了把這個人T了,還得在群設(shè)置里,一個個人的去對,找那個QQ號,實在是痛苦,都是數(shù)字,要很細心一個個的核對,一不小心就把這個號給漏了過去,又得重新找一遍了,有好幾次,我都是找了三次以上才把那個QQ號才找出來.為此,我想做一個工具,只要輸入QQ號,就可以把人T走了.最初,我是想抓取QQ把群里的人T走時的數(shù)據(jù)包來分析一下.知道了這個數(shù)據(jù)包消息的格式后,我就可以仿造一個消息,直接的向QQ服務(wù)器發(fā)過去,就可以把人給T了.我用工具把T人時的數(shù)據(jù)包抓取一下,全部都是亂碼的.因為QQ的消息格式并沒有公開,把以分析起來真的是頭痛了,都無從下手了,只好把這個想法放棄了.我又想了一下,既然我不能發(fā)這樣的數(shù)據(jù)包,那就直接讓QQ自己發(fā)這個包吧.為了要讓QQ把T人的包給發(fā)出去,就得從QQ自己的界面入手,輸入QQ號后,能在群設(shè)置里直接的定位到要T的QQ號,這樣就不用人工的去找這個QQ號,省卻了去找這個QQ號的痛苦了.
二、問題的分析
我在實現(xiàn)時使用的是TM2006新春版,在群聊天的窗口里點工具務(wù)欄上的"群設(shè)置",彈出了"群設(shè)置"窗口,在這個窗口里,選擇"成員列表"這一項,右邊有一個list,這個list就包含有所有的成員了,當選中了某個人后,就可把它T了.
圖一 群設(shè)置窗口
現(xiàn)在的問題是要先把想T的人找出來.怎么樣在list中把想T的人給找出來呢,我的想法是枚舉這個list里所有人的QQ號,然后跟想要T的QQ號作比較,如果有相同的,就把list里的這一項選中,然后我們就可以進一步的操作了.那現(xiàn)在就可以把問題轉(zhuǎn)化為,枚舉list,獲得list里的項的信息.我用spy++查看了一下那個"群設(shè)置"窗口,如圖所示:
圖二 用spy++查看的窗口關(guān)系
最頂層的就是那個"群設(shè)置"窗口了,那個顯示成員的list原來是一個syslistview32類型的控件,包含在一個類型為"#32770"的dialog中,我們只要順著最頂層的窗口中,一層層的窗口找下去就可以得到我個想要的那個list窗口的名柄了,呵呵,之后,就可以向這個list發(fā)出消息,讓它干活了.想到這,偶心時狂喜了一陣子.但是在實現(xiàn)過程中卻是不是那么的順利的.在頂層窗口中找那個list的句柄,并不是什么困難的事.把桌面上的把有窗口都枚舉一遍,就要以找到窗口名為”群設(shè)置”的窗口了.
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("群設(shè)置")); //這里設(shè)置要找的窗口名 if(nPosition!=-1) EnumChildWindows(hwnd,ChildWndProc,0);//繼續(xù)找子窗口 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中繼續(xù)找 } |
找到了包含有syslistview的窗口后,就繼續(xù)找syslistview了,然后可以向它發(fā)送命令消息了.這是整個程序的關(guān)鍵部分,先把代碼給出來,我再進行解釋.
#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,"無法分配內(nèi)存!","錯誤!",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函數(shù)主要是把枚舉syslistview32的項,查找出我們想要找的QQ號,并選中. 最初時我是嘗試用以下的代碼去得到list的item的內(nèi)容的.
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); |
但卻會報錯誤,存取錯誤,也就是說內(nèi)存方面的問題了.問題定位到了ListView_GetItem(hwndLV, &lvi);這一句了.后來我查找了很多資料才知道為什么會有錯誤.因為我的程序與TM的程序是分別屬于不同的Progress,我在自己的程序的進程中申請了lvi的內(nèi)存空間,卻希望把TM進程往這個內(nèi)存空間去寫入數(shù)據(jù),當然是會有錯誤啦. Windows用到了虛存這個概念,它讓每個程序都覺得自己占有2G的內(nèi)存,每個程序都把自己用到的數(shù)據(jù)放在這2G的內(nèi)存中去運行.每個程序間的內(nèi)存空間是互不相干的,這樣,如果某個程序出現(xiàn)了問題,也不會影響到其它程序的運行了.ListView_GetItem要往TM的程序里寫數(shù)據(jù),當然這樣的數(shù)據(jù)只能保存在TM這個程序的內(nèi)存空間里了.我們可以用VirtualAllocEx這個函數(shù)在TM這個程序運行的內(nèi)存片中申請內(nèi)存空間,這樣ListView_GetItem就可以向這個新申請的空間中寫入數(shù)據(jù)了.然后,我們再用ReadProcessMemory函數(shù)把新申請的空間中的數(shù)據(jù)讀到自己程序進程里的緩沖區(qū)中去,采用了一個曲折的辦法,實現(xiàn)了不同進程的數(shù)據(jù)交換.最后當然要把申請的空間用VirtualFreeEx釋放,要不就會有內(nèi)存泄漏了.
三、問題的解決
至此,再回頭看看文中開頭提到的兩個問題. 1查找到有特殊標志的QQ號的名字,只要修改DeleWndProc中的匹配QQ號的語句就行了.這個純粹是一個字符串的匹配了. 2查找指寫的的QQ號. DeleWndProc已經(jīng)是查找指定的QQ號了.示例程序給出的就是這種情況. 當已經(jīng)找到指定的項時,就可以向那個刪除成員Button發(fā)出一個BM_Click的消息了,呵呵,人就給T了.這個也不難實現(xiàn),也照樣的找到這個按鈕的HWND,用SendMessage就可以把消息發(fā)送過去了.至此就解決了開頭提出的兩個問題了. 在查找群設(shè)置的窗口時,也可以用FindWindow和FindWindowEx來得到syslistview32的句柄.
四、后續(xù)工作
我在把程序也給另一個管理員用的時候,他給我提出要實現(xiàn)以下的功能: 輸入被T的QQ號,軟件自動搜索自己是管理員的群,軟件能在自己是管理員的群中搜索到此QQ號,把這個人在每個群里都T走. 呵呵,我的想法模擬人的操作過程,把TM的聯(lián)系人面板打開,
圖三 TM的聯(lián)系人面板
逐個項展開,如果這個項是群就向這個項發(fā)出雙擊消息,讓它出現(xiàn)群聊天窗口,再向群聊天窗口中的群設(shè)置發(fā)雙擊消息,
圖四 聊天窗口工具欄上的群設(shè)置
這樣就會出現(xiàn)群設(shè)置窗口了,就把問題歸結(jié)到原來已經(jīng)解決了的問題了.但我發(fā)現(xiàn)那個面板的類是Tencent_UserBar_Class_Ver1.0,如圖三所示.不知是從什么派生出的,從而就不知道要發(fā)出些什么消息了.還請高人指教.
五、結(jié)束語
從vckbase學(xué)到了很多東西,很感謝大家的共同分享.自己的水平還很菜,但還是把自己碰到的問題寫出來了,一方面可以給像我同樣的菜鳥吸取經(jīng)驗教訓(xùn),再碰到同樣的問題時,能有一些參考資料.另一方面,是讓各位高人指點一下我的不足. 歡迎用下面的聯(lián)系方式與我繼續(xù)討論。