返回列表 发帖

[交流] [原创]十五分钟教你写出TheWorld的COM状态栏插件

本文的对象是有基本visual studio开发经验,懂得使用visual studio appwizard来创建各种项目和类,懂得C++和Windows编程的基本概念,但可能没有COM组件开发经验的开发者。

什么是COM?通俗来说,COM是一套在Windows内部广泛使用的接口标准。它的好处很多,例如跨语言,跨平台,二进制兼容等等,但是我们不需要了解过多,在这篇文章里面,我们只需要记住,IE平台其实也是由成百上千个COM组件搭建起来的,各个组件之间通过COM定义了一套标准的通讯语言。TheWorld是基于IE内核的一个浏览器框架实现,所以也遵循着这些接口标准。

TheWorld的状态栏插件其实是一个简单的COM组件,它使用了标准的IDeskBand接口,是一个标准的符合微软规范的Band Object(不知道BandObject的可以参考知识库文章http://msdn.microsoft.com/en-us/library/bb776819(VS.85).aspx,不过这不影响我们后面内容的理解)。TheWorld浏览器通过这个标准的COM接口和插件进行通讯,包括通知插件进行加载,重画等,插件由此可以实现强大的几乎无所不能的功能。(Javascript插件受语言本身的限制很大,基本只能实现一些很简单的网页渲染和数据转换功能。)

我们选择用ATL来实现这个COM组件。ATL是一套封装COM的通用实现,以便开发者进行快速开发的轻量级类库。本文以VC6为例讲解具体的操作流程,VC2005及VC2008的操作大同小异,不再赘述。

COM组件的实现

首先,新建一个项目,选择ATL COM AppWizard,项目名字我们选TWPluginDev,Server类型我们选DLL,保持其他选项为默认值,确定。这样VC自动帮我们创建好了一个ATL项目。

然后,在ClassView当中创建一个新的ATL Class,类名CTWPlugin,接口类型我们选择相对简单的Custom,保持其他选项为默认值,确定。此时,ClassView里面就新增添了一个COM接口ITWPlugin和对应的实现类CTWPlugin。

我们打开CTWPlugin的头文件TWPlugin.h,VC的Wizard已经为我们自动产生了这个类的定义:

  1. class CTWPlugin :
  2.     public ITWPlugin,
  3.     public CComObjectRoot,
  4.     public CComCoClass<CTWPlugin,&CLSID_TWPlugin>
复制代码


一个完整的Band Object需要支持IDispatch、IObjectWithSite和IDeskBand三个接口。前两个我们可以用ATL的默认实现,第三个是从浏览器组件里面来的,ATL并没有带默认实现,所以我们一会儿还需要实现它。修改后如下:

  1. class CTWPlugin :
  2.     public CComObjectRoot,
  3.     public CComCoClass<CTWPlugin,&CLSID_TWPlugin>,
  4.     public IDispatchImpl<ITWPlugin, &IID_ITWPlugin, &LIBID_TWPLUGINDEVLib>,
  5.     public IObjectWithSiteImpl<CTWPlugin>,
  6.     public IDeskBand
复制代码



添加头文件,让编译器能够找到IDeskBand

  1. #include <shlobj.h>
  2. #include <comdef.h>
复制代码



由于我们新增了三个接口,为了实现COM的接口转换,我们还需要添加如下的接口映射,修改后:

  1. BEGIN_COM_MAP(CTWPlugin)
  2.     COM_INTERFACE_ENTRY(ITWPlugin)
  3.     COM_INTERFACE_ENTRY2(IDispatch, ITWPlugin)
  4.     COM_INTERFACE_ENTRY(IObjectWithSite)
  5.     COM_INTERFACE_ENTRY(IDeskBand)
  6.     COM_INTERFACE_ENTRY2(IOleWindow, IDeskBand)
  7.     COM_INTERFACE_ENTRY2(IDockingWindow, IDeskBand)
  8. END_COM_MAP()
复制代码


接下来,我们需要给出IDeskBand的具体实现。把IDeskBand中需要被实现的接口加到TWPlugin.h的public函数当中

  1.     // *** IOleWindow methods ***
  2.     STDMETHOD(GetWindow) (THIS_ HWND * lphwnd);
  3.     STDMETHOD(ContextSensitiveHelp) (THIS_ BOOL fEnterMode);

  4.     // *** IDockingWindow methods ***
  5.     STDMETHOD(ShowDW)         (THIS_ BOOL fShow);
  6.     STDMETHOD(CloseDW)        (THIS_ DWORD dwReserved);
  7.     STDMETHOD(ResizeBorderDW) (THIS_ LPCRECT   prcBorder,
  8.                                      IUnknown* punkToolbarSite,
  9.                                      BOOL      fReserved);
  10.     // *** IDeskBand methods ***
  11.     STDMETHOD(GetBandInfo)    (THIS_ DWORD dwBandID, DWORD dwViewMode,
  12.                                 DESKBANDINFO* pdbi);
复制代码


在TWPlugin.cpp当中,我们给出上面接口的空实现(mock up)。

  1. STDMETHODIMP CTWPlugin::GetWindow(HWND *phWnd)
  2. {
  3.     return E_NOTIMPL;
  4. }

  5. STDMETHODIMP CTWPlugin::ContextSensitiveHelp(BOOL fEnterMode)
  6. {
  7.     return S_OK;
  8. }

  9. STDMETHODIMP CTWPlugin::ShowDW(BOOL fShow)
  10. {
  11.     return E_NOTIMPL;
  12. }

  13. STDMETHODIMP CTWPlugin::CloseDW(DWORD dwReserved)
  14. {
  15.     return S_OK;
  16. }

  17. STDMETHODIMP CTWPlugin::ResizeBorderDW( LPCRECT prcBorder, IUnknown* punkSite, BOOL fReserved)
  18. {
  19.     return E_NOTIMPL;
  20. }

  21. STDMETHODIMP CTWPlugin::GetBandInfo(DWORD dwBandID, DWORD dwViewMode, DESKBANDINFO* pdbi)
  22. {
  23.     return E_NOTIMPL;
  24. }
复制代码


此时,我们插件的COM接口部分代码已经实现完毕。

插件界面的实现

下面让我们来把UI加上去。实现方法有多种,如Win32API、MFC等,本文的例子选择用WTL——一套基于ATL的轻量级窗口运行库。

在TWPlugin.h当中加入头文件<atlwin.h>,并给CTWPlugin再添加一个基类CWindowImpl<CTWPlugin>和一个私有成员DWORD m_dwBandID,在构造函数中初始化为-1,加入窗口消息映射
  1. BEGIN_MSG_MAP(CTWPlugin)
  2.     MESSAGE_HANDLER(WM_PAINT, OnPaint)
  3.     MESSAGE_HANDLER(WM_CREATE, OnCreate)
  4. END_MSG_MAP()
复制代码



另外创建两个消息**函数OnPaintOnCreate
  1. LRESULT OnPaint( UINT, WPARAM, LPARAM, BOOL& );
  2. LRESULT OnCreate( UINT, WPARAM, LPARAM, BOOL& );
复制代码



并在TWPlugin.cc当中实现
  1. LRESULT CTWPlugin::OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bResult) {
  2.     PAINTSTRUCT ps;
  3.     HDC hDC = BeginPaint(&ps);
  4.     TextOut(hDC, 0, 0, _T("Hello"), 5);
  5.     EndPaint(&ps);
  6.     return 0;
  7. }  
  8. LRESULT CTWPlugin::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bResult) {
  9.     // Rebar Info
  10.     if( m_dwBandID != -1 )
  11.     {
  12.         REBARBANDINFO    rbBand;
  13.         rbBand.cbSize        = sizeof(REBARBANDINFO);
  14.         rbBand.fMask        = RBBIM_CHILDSIZE | RBBIM_IDEALSIZE | RBBIM_ID;
  15.         rbBand.wID            = m_dwBandID;


  16.         HWND hWndReBar = GetParent();
  17.         int nIndex = ::SendMessage( hWndReBar, RB_IDTOINDEX, m_dwBandID, NULL );
  18.         ::SendMessage( hWndReBar, RB_GETBANDINFO, nIndex, (LPARAM)&rbBand );

  19.         rbBand.cxIdeal        = 50;
  20.         rbBand.cxMinChild    = 50;
  21.         ::SendMessage( hWndReBar, RB_SETBANDINFO, nIndex, (LPARAM)&rbBand );
  22.     }
  23.     return 0;
  24. }
复制代码

窗体实现完毕。

[ 本帖最后由 needed 于 2008-9-1 13:47 编辑 ]
1

评分人数

  • mutalisker

把COM组件接口和插件关联起来

为了让TW的主程序能够通过接口调用我们的插件,我们还需要在接口当中实现我们的调用逻辑。为此,我们需要实现SetSite,GetBandInfo,GetWindow三个函数

SetSite是从IObjectWithSite继承下来的,作用是通知插件标签页被切换。我们可以在这里做一些重画工作。

  1. STDMETHODIMP CTWPlugin::SetSite(IUnknown* punkSite)
  2. {
  3.      if(punkSite)
  4.     {
  5.         if( !IsWindow() )
  6.         {
  7.             HWND hWndReBar = NULL;
  8.             {
  9.                 CComQIPtr<IOleWindow> pOleWindow(punkSite);
  10.                 pOleWindow->GetWindow(&hWndReBar);
  11.             }
  12.             if( !::IsWindow( hWndReBar ) ) return E_FAIL;
  13.             if (IsWindow()) DestroyWindow();
  14.             Create(hWndReBar, CWindow::rcDefault);
  15.             if (!IsWindow()) return E_FAIL;
  16.         }
  17.     }

  18.     return S_OK;
  19. }
复制代码



GetBandInfo用于获得Band对象的显示大小,这里我们假设插件的显示大小固定为120×24。代码如下:

  1. STDMETHODIMP CTWPlugin::GetBandInfo(DWORD dwBandID, DWORD dwViewMode, DESKBANDINFO* pdbi)
  2. {
  3.     m_dwBandID = dwBandID;
  4.     int nHeight = 24;
  5.     int nWidth  = 120;

  6.     if( pdbi)
  7.     {
  8.         if( pdbi->dwMask & DBIM_MINSIZE)
  9.         {
  10.             pdbi->ptMinSize.x = nWidth;            //最小显示尺寸
  11.             pdbi->ptMinSize.y = nHeight;
  12.         }

  13.         if( pdbi->dwMask & DBIM_MAXSIZE)
  14.         {
  15.             pdbi->ptMaxSize.x = 0;            //该参数没有用
  16.             pdbi->ptMaxSize.y = nHeight;
  17.         }


  18.         if( pdbi->dwMask & DBIM_MODEFLAGS)
  19.         {
  20.             pdbi->dwModeFlags = DBIMF_NORMAL;
  21.         }

  22.         if( pdbi->dwMask & DBIM_ACTUAL)
  23.         {
  24.             pdbi->ptActual.x = 50;            //超过这个尺寸不显示图标
  25.             pdbi->ptActual.y = nHeight;
  26.         }


  27.         if (pdbi->dwMask & DBIM_TITLE)
  28.         {
  29.             ZeroMemory(pdbi->wszTitle, sizeof(pdbi->wszTitle));
  30.         }
  31.     }
  32.     return S_OK;
  33. }
复制代码


最后,我们需要实现GetWindow,用于获取插件的窗口句柄HWND

  1. STDMETHODIMP CTWPlugin::GetWindow(HWND *phWnd)
  2. {
  3.     if ( NULL == phWnd )
  4.         return E_INVALIDARG;
  5.     *phWnd = m_hWnd;
  6.     return S_OK;
  7. }
复制代码


至此,插件的代码开发工作就全部完成了。发布前记住要把dll编译成Release版本,而不要直接发布debug版。我一般会选择Release MinDependency。


作为最后的收官工作,要写一个plugin.ini放到插件目录下,声明版本号,插件文件名,CLSID等信息,以便TW的插件管理器读取。下面是一个例子

  1. [General]
  2. Name=TWPluginDev
  3. NameCN=开发示例
  4. Author=henryouly
  5. Version=1.0.0.1
  6. Comments=Dev sample
  7. FileName=TWPluginDev.dll
  8. HotIcon=
  9. Type=STATUSBAR
  10. ModuleType=COM
  11. CLSID={43B6A73E-C284-4F98-8328-ABE004C1EF7A}
  12. StartAfterPageDone=
复制代码



最后,享受COM插件给你带来的乐趣吧。

[ 本帖最后由 needed 于 2008-9-1 13:45 编辑 ]

TOP

板凳做好!
用自己的HIPS,让别人中毒去吧!

TOP

这个一定要学习下了
December 21 2012

TOP

坐下来认真学习领会。

TOP

高亮 & 置顶 & 精华..

  希望插件区 多点这样的帖子 . 少些弄exe的快捷方式. ..

插件区缺少这样的帖子..
天下无不散之筵席.

世界之窗浏览器开发计划

TOP

精华中的精华!

TOP

很少用到不是exe的 看看com怎麼做的..

TOP

没功底恐怕十五天都学不会

TOP

头有点大。。。
技术文章要支持

TOP

在学C#中。。。不过看了还是有点晕 呵呵

TOP

强帖~呵呵,不会这个东东,来支持下会的,顶了

TOP

返回列表