Board logo

标题: [交流] [原创]十五分钟教你写出TheWorld的COM状态栏插件 [打印本页]

作者: henryouly    时间: 2008-8-31 14:22     标题: [原创]十五分钟教你写出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 编辑 ]
作者: henryouly    时间: 2008-8-31 14:23

把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 编辑 ]
作者: 495127903    时间: 2008-8-31 15:00

板凳做好!
作者: haokeyy    时间: 2008-8-31 15:20

这个一定要学习下了
作者: westone    时间: 2008-8-31 15:23

坐下来认真学习领会。
作者: needed    时间: 2008-8-31 16:55

高亮 & 置顶 & 精华..

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

插件区缺少这样的帖子..
作者: 东暴    时间: 2008-8-31 17:14

精华中的精华!
作者: ad6543210    时间: 2008-8-31 17:48

很少用到不是exe的 看看com怎麼做的..
作者: fghxy    时间: 2008-8-31 18:26

没功底恐怕十五天都学不会
作者: kc1143    时间: 2008-8-31 19:44

头有点大。。。
技术文章要支持
作者: glq10107    时间: 2008-9-1 01:34

在学C#中。。。不过看了还是有点晕 呵呵
作者: sbyguli    时间: 2008-9-1 09:23

强帖~呵呵,不会这个东东,来支持下会的,顶了
作者: yjwgi    时间: 2008-9-1 09:44

精华啊。
可是没时间去学
作者: sky5    时间: 2008-9-1 10:17

好贴啊,支持支持!
作者: mulao    时间: 2008-9-1 12:59

不怎么看得懂,不过还是支持
作者: Coptis    时间: 2008-9-1 13:23

支持原创....
作者: mutalisker    时间: 2008-9-1 17:28

强帖一定要顶
作者: starsoft    时间: 2008-9-1 18:29

好贴子呀,改天也试试
作者: lixupeng    时间: 2008-9-4 12:16

不错!!!
作者: baifengs    时间: 2008-9-5 09:26

不错,虽然没时间研究,但很欣赏这样的帖子!
作者: cyberholic    时间: 2008-9-5 15:51

用了五分钟发现学不会,只能坐享其成了
作者: vlolv    时间: 2008-9-9 14:20

收藏
有空玩一下
作者: 412268499    时间: 2008-9-13 18:16

有空再看看 太长了...
作者: kidsuntown    时间: 2008-9-16 10:21

真是无私呀,知识贡献出来,就是非常值得称赞的!佩服个,大家顶顶,人家也花了力气的
作者: 31505337    时间: 2008-9-21 07:26

请问C#能否实现
作者: henryouly    时间: 2008-10-5 21:58

原帖由 31505337 于 2008-9-21 07:26 发表 http://bbs.ioage.com/cn/images/common/back.gif
请问C#能否实现

理论上COM和语言本身无关,C#可以用interop实现,但是实现起来比c++实现还要复杂,故不提倡
作者: 81635631    时间: 2008-10-7 17:09

在Dllentry把该做的都做了~~patch~~
然后一切尽在掌握中~~~
COM太麻烦了~~
作者: 81635631    时间: 2008-10-7 17:09

支持楼主啊~~!
作者: april    时间: 2008-10-14 10:43

完全看不明白
作者: chentca    时间: 2008-10-17 11:16

下来好好学习!
作者: vanciki    时间: 2008-10-20 22:26

完全看不懂的,呵呵
作者: 人生观    时间: 2008-11-2 17:47

全不明看了满头烟.
作者: sdksunny    时间: 2008-11-11 16:23     标题: 音乐播放插件

能做一个类似于千千静听迷你模式样子的插件吗
作者: xuedl    时间: 2008-11-12 15:56

俺菜鸟一个都看不懂哦!
作者: wangzai117    时间: 2008-11-15 22:45

在例子中那个添加窗口的地方能不能用VC做一个示例啊???

看不懂 WTL的例子??
作者: henryouly    时间: 2008-11-16 23:41

原帖由 wangzai117 于 2008-11-15 22:45 发表 http://bbs.ioage.com/cn/images/common/back.gif在例子中那个添加窗口的地方能不能用VC做一个示例啊???看不懂 WTL的例子??
WTL和VC并无矛盾,我就是用VC做的。估计你说的是MFC。
作者: hillar    时间: 2008-11-17 21:24


作者: won1112    时间: 2008-12-6 21:36


能否看图说话。。。。。。 我学VB的实在看不明白
作者: FreeZ    时间: 2008-12-9 19:19

这个十五分确实很难学会~
作者: niusmth    时间: 2008-12-21 21:56

认真学习
作者: lsl001270    时间: 2008-12-29 18:03

顶下
作者: john2008    时间: 2009-1-21 17:14

等吃完晚饭,我用一个小时的时间来看看。。。
作者: 天竺葵    时间: 2009-1-22 21:52

有些晕,

但绝对是很好的文章.

希望多多.
作者: xiangyu666    时间: 2009-2-20 19:13

非常 好的帖子
作者: wwwcccyyy    时间: 2009-3-26 17:44

虽然不懂,但是还是支持了
作者: 摩登闪客    时间: 2009-3-27 12:26

不懂编程语言的看这个帖子会一头雾水,不过还是谢谢Lz
作者: sieben    时间: 2009-4-1 17:08

shena深奥了点 看不懂
作者: skyformat99    时间: 2009-4-6 17:52

请教,
如何获取当前窗口的浏览地址。
就是那个地址栏的内容。
作者: 溪风    时间: 2009-4-24 12:36

看不懂……
作者: 汉字    时间: 2009-7-7 21:37

我去试试看!这个不错的!
作者: 情天娃娃    时间: 2009-7-10 04:05

有机会学习一下
作者: 拼命    时间: 2009-7-22 16:57

看不懂..............
作者: i.maomao    时间: 2009-7-30 20:56

theword的开发人员 给出那个twplugin.h的托管代码吧 这样也方便c# vb.net的人开发使用
作者: airplane    时间: 2009-8-4 15:54


作者: 无声雨    时间: 2009-8-29 09:41

好专业,顶一下
作者: huxinan20    时间: 2009-11-9 16:34

真是有技术含量的好帖啊!!!
作者: qhaiming    时间: 2009-11-28 08:03

强力支持!学习哈。
作者: dcrenl    时间: 2009-12-15 09:05

唉,插件是用VC++写的手里没有IDE
作者: hhzxedu    时间: 2009-12-22 08:22

楼主,我是学delphi的,能不能教教用delphi如何制作呢。
作者: apoy    时间: 2009-12-27 15:37

插件用com组件 似乎有些不好,增大了开发入门的门槛,希望考虑一下
作者: 51scott    时间: 2009-12-30 16:41

厉害 啊 看不懂!
作者: 博客大联盟    时间: 2010-1-8 17:06

zhichi

支持
作者: pzhangqi    时间: 2010-1-8 19:24

支持一下
作者: wu8d    时间: 2010-2-2 12:01

怎麼沒有 sample project下載, 不完善.... 樓主加個吧........
作者: Menoetius    时间: 2010-5-18 23:32

api编程还是比较有难度的,主要是没注释这点让初学者非常头疼....
作者: dyydyy    时间: 2010-6-20 17:37

没个demo吗
作者: lishimin321    时间: 2010-7-2 12:17

试一下 看好用不
作者: blueyestar    时间: 2010-9-8 04:17

就是呀, 给你sample呀, 按楼主的这种写法, 根本没法操作呀, 我用VS2010了
作者: lifestyle2009    时间: 2011-4-5 23:39

不懂这个有什么用处
作者: jieke    时间: 2011-7-19 19:05

hjfgjhfghc
作者: husiwe    时间: 2013-10-22 23:27

提示: 作者被禁止或删除 内容自动屏蔽
作者: liuliih    时间: 2014-5-4 01:46

对我而言,是对牛弹琴
作者: 關雲長    时间: 2017-3-24 11:06

C++好久没用了




欢迎光临 世界之窗论坛 (http://bbs.theworld.cn/) Powered by Discuz! 7.2