用Visual C++設(shè)計QQ群管理工具

2010-08-28 10:49:52來源:西部e網(wǎng)作者:

    一、問題的提出

  偶是兩個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ù)討論。

關(guān)鍵詞:VisualC++QQ

贊助商鏈接: