CodeProject上有一个名为MessageSender的工具

,这个工具提供了一个轻量的类似Spy++的UI操作方式来获取窗口相关的信息。实际上稍加改造后可以将它改装成一个UI相关的工具套件的框架,仅仅MessageSender本身能发挥的作用是有限的,但是如果把它变成一个支持插件的工具箱那将会是另一番模样。

插件框架:

1.插件接口定义:

为了让MessageSender能够支持插件,我们可以添加一个轻量级的插件框架,让它通过枚举支持插件接口的模块。首先定义插件接口(为了让插件能够支持UI展现,添加了InitializeUI和UnititializeUI作为插件的初始化接口和卸载入口):

#define MAX_UTILINFO_SIZE    200 
// 获取插件描述,同时用于验证插件接口 
typedef void (*PGetPlugginName)(TCHAR *szPlugginName); 
// 初始化插件 UI 
typedef void (*PInitializeUI)(HWND hWndOwner, RECT rcPlugginArea); 
// 销毁插件 UI 
typedef void (*PUninitializeUI)(); 
// 绑定窗口信息 
typedef void (*PAttachHWND)(HWND hWnd ); 
// 取消窗口信息绑定 
typedef void (*PDetachHWND)(void); typedef struct tagPlugginEntry 
{ 
    TCHAR            szPlugginPath[MAX_PATH]; 
    TCHAR            szPlugginName[MAX_UTILINFO_SIZE]; 
    HMODULE            hModule; 
    PGetPlugginName    GetPlugginName; 
    PInitializeUI    InitializeUI; 
    PUninitializeUI    UninitializeUI; 
    PAttachHWND        AttachHWND; 
    PDetachHWND        DetachHWND;     tagPlugginEntry() 
    { 
        Reset(); 
    }     void Reset() 
    { 
        GetPlugginName    = NULL; 
        InitializeUI    = NULL; 
        UninitializeUI    = NULL; 
        AttachHWND        = NULL; 
        DetachHWND        = NULL; 
        szPlugginPath[0]    = _T('/0'); 
        szPlugginName[0]    = _T('/0'); 
        hModule                = NULL; 
    } 
} 
PlugginEntry; 
typedef list 
   
     PlugginList; 
    
2.插件信息扫描 
     第二步是实现插件信息的扫描,使插件框架在之后能够加载这些插件,通过FindFirstFile / FindNextFile 枚举文件和 LoadLibrary / GetProcAddress来验证动态库是否是有效的插件。此处只需要验证GetPlugginName即可,验证插件后将插件模块卸载掉,因为一般情况下我们不需要将所有模块都保留在内存中。由于都是常规操作,这里我就不再贴代码出来。
3.插件的初始化和卸载
插件的初始化和卸载分别使用到 InitializeUI 和 UninitializeUI 功能,由于一个时刻只需要一个插件,所以在初始化一个插件时需要将前一插件进行卸载。同时需要根据MessageSender是否已经锁定了某个窗口句柄调用相应插件的AttachHWND和DetachHWND通知插件执行相应的处理流程。
为了保留MessageSender原有的消息发送功能且简化插件,并没有将原有的消息发送功能独立出来成为插件,这里只用了一点点技巧,仅仅是在当选择其他插件时覆盖了消息发送界面而已,但这样看起来仿佛消息发送也成为插件之一。
在InitializeUI和UnitializeUI的过程中,需要注意在模块间的资源切换,否则在插件中加载插件本身的资源可能会失败,从而造成界面无法正常显示;为了让插件更少关注UI内容,我们将资源切换放到插件框架中,因此插件接口的调用过程看起来就像下面代码所显现的形式:
if ( m_pluggin.InitializeUI ) 
{ 
    HINSTANCE hResourceHandle = AfxGetResourceHandle(); 
    AfxSetResourceHandle(m_pluggin.hModule); 
    { 
        CRect rcClient; 
        GetClientRect( rcClient ); 
        rcClient.DeflateRect(280, 0, 0, 0); 
        m_pluggin.InitializeUI( m_hWnd, rcClient); 
    } 
    AfxSetResourceHandle(hResourceHandle); 
}


插件模板:

为了减小插件开发的工作量,我们可以构建一个插件模板,之后的插件只需要在此基础上针对插件本身功能进行开发即可。模板主要完成界面展现和实现常规的插件接口过程即可,因此我编写了一个支持MFC的规则DLL,并为其添加了一个用于界面展开的对话框子窗口模板,以后所有扩展的模板只需要在子窗口模板上添加自己所需的控件即可。所有插件接口模板的代码看起来也不过如下这些内容:

CWebwndSpyDlg g_wndSpy; 
extern "C" __declspec(dllexport) void GetPlugginName(TCHAR *szPlugginName) 
{ 
    _tcscpy_s(szPlugginName, 200, _T("WEB控件解析器")); 
} extern "C" __declspec(dllexport) void InitializeUI(HWND hwndParent, RECT rcArea) 
{ 
    g_wndSpy.Create(IDD_DIALOG_WEBWNDSPY, CWnd::FromHandle(hwndParent)); 
    g_wndSpy.MoveWindow(&rcArea); 
    g_wndSpy.ShowWindow(SW_SHOW); 
} extern "C" __declspec(dllexport) void UninitializeUI() 
{ 
    if ( g_wndSpy.GetSafeHwnd()) 
        g_wndSpy.PostMessage(WM_DESTROY, 0, 0); 
} extern "C" __declspec(dllexport) void AttachHWND(HWND hWnd ) 
{ 
    if ( g_wndSpy.GetSafeHwnd()) 
        __noop; 
} extern "C" __declspec(dllexport) void DetachHWND(void) 
{ 
    if ( g_wndSpy.GetSafeHwnd()) 
        __noop; 
}

插件示例(WEB控件分析插件):

原有的消息发送界面:

MemoryAnalyzer插件 immersion插件设置_null

插件:WEB控件分析

MemoryAnalyzer插件 immersion插件设置_扩展_02

插件本身功能只增加了数十行,便可以完成这个功能:

void DoWebSpy(HWND hWebWnd) 
{ 
    HINSTANCE hInst = ::LoadLibrary( _T("OLEACC.DLL") ); 
    if ( hInst != NULL ) 
    { 
        CComPtr 
 
   spDocument; 
  
        LRESULT lRes; 
         // 根据 HWND 获取 COM DOM 对象 
        UINT nMsg = ::RegisterWindowMessage( _T("WM_HTML_GETOBJECT") ); 
        ::SendMessageTimeout( hWebWnd, nMsg, 0L, 0L, SMTO_ABORTIFHUNG, 1000, (DWORD*)&lRes );         LPFNOBJECTFROMLRESULT pfnObjectFromLRESULT = (LPFNOBJECTFROMLRESULT)::GetProcAddress( hInst, "ObjectFromLresult" ); 
        if ( pfnObjectFromLRESULT ) 
        { 
            HRESULT hr; 
            // 获取IHTMLDocument2接口 
            hr = pfnObjectFromLRESULT( lRes, IID_IHTMLDocument2, 0, (void**)&spDocument ); 
            if ( SUCCEEDED(hr) ) 
            { 
                BSTR bszURL = NULL; 
                BSTR bszTitle = NULL; 
                BSTR bszContent = NULL; 
                CComPtr 
 
   spBodyElement; 
  
                if ( SUCCEEDED(spDocument->get_URL(&bszURL))) 
  
                { 
  
                    g_wndSpy.m_editURL.SetWindowText(W2T(bszURL)); 
  
                    ::SysFreeString(bszURL); 
  
                } 
  
                if ( SUCCEEDED(spDocument->get_title(&bszTitle))) 
  
                { 
  
                    g_wndSpy.m_editTitle.SetWindowText(W2T(bszTitle)); 
  
                    ::SysFreeString(bszTitle); 
  
                } 
  
                if ( SUCCEEDED(spDocument->get_body(&spBodyElement))) 
  
                { 
  
                    if ( SUCCEEDED(spBodyElement->get_innerHTML(&bszContent))) 
  
                    { 
  
                        g_wndSpy.m_editBody.SetWindowText(W2T(bszContent)); 
  
                        ::SysFreeString(bszContent); 
  
                    } 
  
                } 
  
            } 
  
            else 
  
            { 
  
                spDocument = NULL; 
  
            } 
  
        } 
  
    } 
  
} 
 extern "C" __declspec(dllexport) void AttachHWND(HWND hWnd ) 
{ 
    if ( g_wndSpy.GetSafeHwnd()) 
    { 
        TCHAR    szClassName[MAX_PATH]; 
        ::GetClassName(hWnd, szClassName, MAX_PATH); 
        if ( _tcscmp(szClassName, _T("Internet Explorer_Server")) != 0) 
            return;         DoWebSpy(hWnd); 
    } 
}