最近看核心编程,看到DLL注入这一章,有一个Desktop Item Positon Saver(DIPS)的例子,这个例子是使用窗口挂钩来将一个DLL注入到Explorer.exe的地址空间中,来保存和恢复图标位置。
于是便想根据这个例子自己改造一下,正好前段时间看到在 Google 的首页上,谷歌为了纪念电吉他之父莱斯保罗 96 周年诞辰,特意做了一个很有意思的Doodle,这个 谷歌电吉他Logo可以让用户在其上面弹奏吉他,于是便想利用桌面图标来编一个类似的程序,点击不同的图标,发出不同的吉他音色的音符。
下面把编程的大概思路说一下:
首先就是找到桌面ListView控件的窗口句柄,刚开始在这里就遇到了问题。
核心编程这本书上讲到Windows外壳有一个类别为ProgMan的窗口,这个ProgMan窗口有且只有一个类别为SHELLDLL_DefView的子窗口。这个子窗口同样有且只有一个子窗口,子窗口的类别为SysListView32,这个SysListView32窗口就是桌面的ListView控件窗口。
于是我便利用了它的代码:
HWND hWndLV =GetFirstChild(GetFirstChild(
FindWindow(TEXT("ProgMan"), NULL)));
但发现运行不正确,于是便使用spy++查看,发现我的SysListView32窗口是位于类为WorkerW的窗口下。难道这是win7系统更改的?我也不清楚。因此,如果大家下载下来我的源代码后,如果运行不成功,最好用spy++查看一下窗口信息,看一看SysListView32控件到底是谁的子窗口,如果是ProgMan的子窗口,那么大家需要把MouseHookApp.cpp文件中的
HWND hWndLV;
EnumWindows(EnumWindowsProc,(LPARAM)&hWndLV);
和
EnumWindowsProc函数注释掉,
取消HWND hWndLV =GetFirstChild(GetFirstChild(
FindWindow(TEXT("ProgMan"), NULL)));这一代码的注释。
我发现类名为WorkerW的窗口不是唯一的,因此不能使用FindWindows了,改用了EnumWindows函数。
HWND hWndLV;
EnumWindows(EnumWindowsProc,(LPARAM)&hWndLV);
其中的EnumWindowsProc函数实现如下:
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam){ if(hwnd) { TCHAR classname[128]; GetClassName(hwnd, classname, 128); if(!_tcscmp(classname, _T("WorkerW") ) ) { HWND &hWndLV = *(HWND *)lParam; TCHAR childClassName[128]; HWND hChildWnd = GetWindow(hwnd, GW_CHILD); while(hChildWnd) { GetClassName(hChildWnd, childClassName, 128); if(!_tcscmp(childClassName, _T("SHELLDLL_DefView"))) hWndLV = GetFirstChild(hChildWnd); chASSERT(IsWindow(hWndLV)); return false; } } } return true; }
第一个就是注册了鼠标消息的钩子函数后,如何判断鼠标点击的是图标的哪一项,经过各方面查找资料并实验后发现以下一种可行方法:
LVITEM lvi; LVHITTESTINFO lvh={0}; int iIndexItem; long lx = pMouseHook->pt.x;//pMouseHook是指向MOUSEHOOKSTRUCT结构体的指针 long ly = pMouseHook->pt.y; lvh.pt.x = lx; lvh.pt.y = ly; ScreenToClient(pMouseHook->hwnd, &(lvh.pt)); ListView_HitTest(pMouseHook->hwnd, &lvh); iIndexItem = lvh.iItem; if(iIndexItem !=-1) { ZeroMemory(&lvi, sizeof(lvi)); lvi.mask = LVIF_TEXT; lvi.iItem = iIndexItem; lvi.pszText = szBuff; lvi.cchTextMax = _countof(szBuff); if(ListView_GetItem(pMouseHook->hwnd, &lvi)) { wsprintf(szDebug, TEXT("item text = '%s'"), szBuff); OutputDebugString (szDebug); }
主要用到的函数有midiOutOpen和midiOutShortMsg函数,具体该函数的用法,大家可以参考一下MSDN。
函数GetSelectedItem的主要代码如下:
LVHITTESTINFO lvh={0}; int iIndexItem; int keyNo, iOctave, iNote ; long lx = lmhs->pt.x; long ly = lmhs->pt.y; lvh.pt.x = lx; lvh.pt.y = ly; ScreenToClient(lmhs->hwnd, &(lvh.pt)); ListView_HitTest(lmhs->hwnd, &lvh); iIndexItem = lvh.iItem; if(iIndexItem !=-1) { POINT ItemPt; ListView_GetItemPosition(lmhs->hwnd, iIndexItem, &ItemPt); /*wsprintf(szDebug, TEXT("item coordinate = '%d, %d'"), ItemPt.x, ItemPt.y); OutputDebugString (szDebug); */ int lineNo = ItemPt.x/75; int colNo = ItemPt.y/86; keyNo = lineNo*10+colNo; if (keyNo >= NUMSCANS) // No scan codes over 53 return ; if ((iOctave = notes[keyNo].iOctave) == -1) // Non-music key return ; iNote = notes[keyNo].iNote ; if(keyNo <30) MidiNoteOn (hMidiOut, 0, iOctave, iNote, iVelocity) ; // Note on else { MidiSetPatch (hMidiOut, 9, 0) ; MidiNoteOn (hMidiOut, 9, iOctave, iNote, iVelocity) ; // Note on }
大家在运行源代码是,还有一个问题,就是屏幕分辨率问题,因为图标在桌面上的位置与间隔随着屏幕分辨率的不同会有差别,而我程序中判断图标位置的代码,
int lineNo = ItemPt.x/75;
int colNo = ItemPt.y/86;
其中的75和86分别是我自己屏幕分辨率的情况下(1440x900)一个图标与上下图标和左右图标之间上下和左右的距离。我这里为了省事,就直接写死了,没有对其进行程序上的判段,嘿嘿,今后为了通用性,肯定还是会改进的,现在先凑合着用吧。
如果大家想知道自己屏幕分辨率下的图标之间的距离,可以把上述GetSelectedItem函数代码中的
/*wsprintf(szDebug, TEXT("item coordinate = '%d, %d'"), ItemPt.x, ItemPt.y);
OutputDebugString (szDebug); */
注释取消掉,然后运行程序,点击图标,使用debugview软件查看输出地坐标信息,自己计算出相邻图标的距离,把75 和86替换掉即可。
程序运行后点击MusicHook按钮就可以了,这是点击桌面图标就可以发出音符声音了。取消hook点击UnHook按钮。
大致情况就是这样。最后把收集到的一些吉他的曲谱附上供大家参考玩耍,嘿嘿。
小星星:1155665 4433221 5544332 5544332 1155665 4433221;
卖报歌:555 555 35653 235 53532 132 332 612 665 365 53235 5323 5323 61231
月亮之上:3686 3675 53686565326553 3686 89865 36865326656
团子大家族:325 56 67 52 325 565 325 56 67 52 325 565 8753 5635 235 8753 5635 235 251
两只老虎: 1231|1231|345|345|565431|565431|151|151|| (最爱,哈哈)
童年:35553 6676 66588886865 355536676 66658888668 9~
上海滩:356666,352222,356865132