一、問題的提出
偶是兩個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] ="";
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號: ";
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學到了很多東西,很感謝大家的共同分享.自己的水平還很菜,但還是把自己碰到的問題寫出來了,一方面可以給像我同樣的菜鳥吸取經驗教訓,再碰到同樣的問題時,能有一些參考資料.另一方面,是讓各位高人指點一下我的不足. 歡迎用下面的聯系方式與我繼續討論。