最近看核心编程,看到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;
}
好了,现在得到桌面ListView控件的句柄了,下面就需要编写注入到Explorer.exe中的DLL文件了。遇到的问题主要有两个:
第一个就是注册了鼠标消息的钩子函数后,如何判断鼠标点击的是图标的哪一项,经过各方面查找资料并实验后发现以下一种可行方法:
 
代码:
 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); 
    }
遇到的第二个问题主要是音符播放问题,由于以前没接触过这方面的知识,所以刚开始没有什么头绪,后来发现应该使用MIDI方面的知识。
主要用到的函数有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
上传的附件 Music MouseHook.rar