首页 / 技术类 / COM / 裸写一个含内嵌IE控件的窗口

裸写一个含内嵌IE控件的窗口

2012-09-01 01:04:00

引言

之前也做过一些含内嵌IE控件的东西,只是一直用MFC/ATL等框架,对于里面的原理其实一知半解,只有脱离它们写一遍,才算能真正懂。前不久在写一个SkyDriveClient的时候正好有一个需求,就练习了一下。技术含量没有,在此记录一笔,供后来人入门,供前辈们批评。

本文中,行文以流水帐、贴代码方式为主,同时为了不带来干扰,代码将尽量以不带或少带封装的方式书写。目标明确,只为“功利性”地实现一个带IE控件的窗口。对于未实现的接口也不进行任何解释。

建立普通窗口

首先,我们建立一个普通的窗口程序。向导生成即可,剔除点代码,整理一下,代码清单如下:

 1#include <Windows.h>
 2#include <tchar.h>
 3
 4LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
 5{
 6    switch (message)
 7    {
 8    case WM_DESTROY:
 9        PostQuitMessage(0);
10        break;
11    default:
12        return DefWindowProc(hWnd, message, wParam, lParam);
13    }
14
15    return 0;
16}
17
18int APIENTRY _tWinMain(_In_ HINSTANCE     hInstance,
19                       _In_opt_ HINSTANCE hPrevInstance,
20                       _In_ LPTSTR        lpCmdLine,
21                       _In_ int           nCmdShow)
22{
23    UNREFERENCED_PARAMETER(hPrevInstance);
24    UNREFERENCED_PARAMETER(lpCmdLine);
25
26    const LPCTSTR CLASS_NAME = _T("WebBrowserContainer");
27
28    WNDCLASSEX wcex    = { sizeof(WNDCLASSEX) };
29    wcex.style         = CS_HREDRAW | CS_VREDRAW;
30    wcex.lpfnWndProc   = WndProc;
31    wcex.cbClsExtra    = 0;
32    wcex.cbWndExtra    = 0;
33    wcex.hInstance     = hInstance;
34    wcex.hCursor       = LoadCursor(nullptr, IDC_ARROW);
35    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
36    wcex.lpszClassName = CLASS_NAME;
37
38    RegisterClassEx(&wcex);
39
40    HWND hWnd = CreateWindow(CLASS_NAME,
41                             _T("WebBrowser Sample"),
42                             WS_OVERLAPPEDWINDOW,
43                             CW_USEDEFAULT,
44                             0,
45                             CW_USEDEFAULT,
46                             0,
47                             nullptr,
48                             nullptr,
49                             hInstance,
50                             nullptr);
51
52    if (hWnd == nullptr)
53    {
54        return 0;
55    }
56
57    ShowWindow(hWnd, nCmdShow);
58    UpdateWindow(hWnd);
59
60    MyWebBrowser wb(hWnd);
61
62    MSG msg = {};
63
64    while (GetMessage(&msg, nullptr, 0, 0))
65    {
66        if (!TranslateAccelerator(msg.hwnd, nullptr, &msg))
67        {
68            TranslateMessage(&msg);
69            DispatchMessage(&msg);
70        }
71    }
72
73    return (int)msg.wParam;
74}

加入WebBrowser

这一部分主要参考了《使用C++实现SDK之WebBrowser容器》(http://blog.csdn.net/norsd/article/details/2921389)。但是这篇文章里没有给出真正可运行的代码,有几处小笔误,然后呢,其实我也不是很喜欢原作者的封装方式。本篇的目的是尽可能简单、原始,让后来者容易上手。

##实现OLE容器类

实现一个WebBrowser,先要实现一个OLE容器类。我不知道OLE容器类的精确定义是什么,但现在我们需要实现三个接口:IOleClientSiteIOleInPlaceSiteIOleInPlaceFrame<OleIdl.h>)。它们的定义在MSDN上都可以查到。

代码清单如下:

  1#pragma once
  2
  3#include <Windows.h>
  4#include <OleIdl.h>
  5
  6class OleContainer : public IOleClientSite,
  7                     public IOleInPlaceSite,
  8                     public IOleInPlaceFrame
  9{
 10public:
 11    OleContainer() : m_nRefCount(0),
 12                     m_pStorage(nullptr),
 13                     m_pOleObj(nullptr),
 14                     m_pInPlaceObj(nullptr),
 15                     m_hWindow(nullptr)
 16    {
 17        AddRef();
 18        OleInitialize(nullptr);
 19    }
 20
 21    ~OleContainer()
 22    {
 23        if (m_pInPlaceObj != nullptr)
 24        {
 25            m_pInPlaceObj->Release();
 26            m_pInPlaceObj = nullptr;
 27        }
 28
 29        if (m_pOleObj != nullptr)
 30        {
 31            m_pOleObj->Release();
 32            m_pOleObj = nullptr;
 33        }
 34
 35        if (m_pStorage != nullptr)
 36        {
 37            m_pStorage->Release();
 38            m_pStorage = nullptr;
 39        }
 40
 41        OleUninitialize();
 42    }
 43
 44public:
 45    bool CreateOleObject(const IID &clsid)
 46    {
 47        HRESULT hr = StgCreateDocfile(nullptr,
 48                                        STGM_READWRITE | STGM_SHARE_EXCLUSIVE | STGM_DIRECT | STGM_CREATE,
 49                                        0,
 50                                        &m_pStorage);
 51        if (FAILED(hr))
 52        {
 53            return false;
 54        }
 55
 56        hr = OleCreate(clsid, IID_IOleObject, OLERENDER_DRAW, 0, this, m_pStorage, (LPVOID *)&m_pOleObj);
 57
 58        if (FAILED(hr))
 59        {
 60            return false;
 61        }
 62
 63        hr = m_pOleObj->QueryInterface(IID_IOleInPlaceObject, (LPVOID *)&m_pInPlaceObj);
 64
 65        if (FAILED(hr))
 66        {
 67            return false;
 68        }
 69
 70        return true;
 71    }
 72
 73    bool InPlaceActive(HWND hWnd, LPCRECT lpRect)
 74    {
 75        if (hWnd == nullptr)
 76        {
 77            return false;
 78        }
 79
 80        RECT rect = {};
 81
 82        if (lpRect == nullptr)
 83        {
 84            GetClientRect(hWnd, &rect);
 85            lpRect = &rect;
 86        }
 87
 88        HRESULT hr = m_pOleObj->DoVerb(OLEIVERB_INPLACEACTIVATE, nullptr, this, 0, hWnd, lpRect);
 89
 90        if (FAILED(hr))
 91        {
 92            return false;
 93        }
 94
 95        m_hWindow = hWnd;
 96
 97        return true;
 98    }
 99
100public: // IUnknown Methods
101    HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID *ppvObject)
102    {
103        *ppvObject = nullptr;
104
105        if (riid == IID_IUnknown)
106        {
107            *ppvObject = (IOleClientSite *)this;
108        }
109
110        else if (riid == IID_IOleInPlaceSite)
111        {
112            *ppvObject = (IOleInPlaceSite *)this;
113        }
114        else if (riid == IID_IOleInPlaceUIWindow)
115        {
116            *ppvObject = (IOleInPlaceUIWindow *)this;
117        }
118        else if (riid == IID_IOleInPlaceFrame)
119        { 
120            *ppvObject = (IOleInPlaceFrame *)this;
121        }
122
123        if (*ppvObject == nullptr)
124        {
125            return E_NOINTERFACE;
126        }
127
128        AddRef();
129        return S_OK;
130    }
131
132    ULONG STDMETHODCALLTYPE AddRef()
133    {
134        return (ULONG)InterlockedIncrement(&m_nRefCount);
135    }
136
137    ULONG STDMETHODCALLTYPE Release()
138    {
139        LONG nRefCount = InterlockedDecrement(&m_nRefCount);
140
141        if (nRefCount <= 0)
142        {
143            delete this;
144        }
145
146        return (ULONG)nRefCount;
147    }
148
149public: // IOleClientSite Methods
150    STDMETHOD(SaveObject)()
151    {
152        return E_NOTIMPL;
153    }
154
155    STDMETHOD(GetMoniker)(DWORD dwAssign, DWORD dwWhichMoniker, IMoniker **ppmk)
156    {
157        return E_NOTIMPL;
158    }
159
160    STDMETHOD(GetContainer)(IOleContainer **ppContainer)
161    {
162        return E_NOTIMPL;
163    }
164
165    STDMETHOD(ShowObject)()
166    {
167        return E_NOTIMPL;
168    }
169
170    STDMETHOD(OnShowWindow)(BOOL fShow)
171    {
172        return E_NOTIMPL;
173    }
174
175    STDMETHOD(RequestNewObjectLayout)()
176    {
177        return E_NOTIMPL;
178    }
179
180public: // IOleWindow Methods
181    STDMETHOD(GetWindow)(HWND *phwnd)
182    {
183        return E_NOTIMPL;
184    }
185
186    STDMETHOD(ContextSensitiveHelp)(BOOL fEnterMode)
187    {
188        return E_NOTIMPL;
189    }
190
191public: // IOleInPlaceSite Methods
192    STDMETHOD(CanInPlaceActivate)()
193    {
194        return m_hWindow == nullptr ? S_OK : S_FALSE;
195    }
196
197    STDMETHOD(OnInPlaceActivate)()
198    {
199        return E_NOTIMPL;
200    }
201
202    STDMETHOD(OnUIActivate)()
203    {
204        return E_NOTIMPL;
205    }
206
207    STDMETHOD(GetWindowContext)(IOleInPlaceFrame **ppFrame,
208                                IOleInPlaceUIWindow **ppDoc,
209                                LPRECT lprcPosRect,
210                                LPRECT lprcClipRect,
211                                LPOLEINPLACEFRAMEINFO lpFrameInfo)
212    {
213        if (m_hWindow == nullptr)
214        {
215            return E_NOTIMPL;
216        }
217
218        *ppFrame = (IOleInPlaceFrame*)this;
219        *ppDoc = NULL;
220        AddRef();
221
222        GetClientRect(m_hWindow, lprcPosRect);
223        GetClientRect(m_hWindow, lprcClipRect);
224
225        lpFrameInfo->cb = sizeof(OLEINPLACEFRAMEINFO);
226        lpFrameInfo->fMDIApp = false;
227        lpFrameInfo->hwndFrame = GetParent(m_hWindow);
228        lpFrameInfo->haccel = nullptr;
229        lpFrameInfo->cAccelEntries = 0;
230
231        return S_OK;
232    }
233
234    STDMETHOD(Scroll)(SIZE scrollExtant)
235    {
236        return E_NOTIMPL;
237    }
238
239    STDMETHOD(OnUIDeactivate)(BOOL fUndoable)
240    {
241        return E_NOTIMPL;
242    }
243
244    STDMETHOD(OnInPlaceDeactivate)()
245    {
246        return E_NOTIMPL;
247    }
248
249    STDMETHOD(DiscardUndoState)()
250    {
251        return E_NOTIMPL;
252    }
253
254    STDMETHOD(DeactivateAndUndo)()
255    {
256        return E_NOTIMPL;
257    }
258
259    STDMETHOD(OnPosRectChange)(LPCRECT lprcPosRect)
260    {
261        return E_NOTIMPL;
262    }
263
264public: // IOleInPlaceUIWindow Methods
265    STDMETHOD(GetBorder)(LPRECT lprectBorder)
266    {
267        return E_NOTIMPL;
268    }
269
270    STDMETHOD(RequestBorderSpace)(LPCBORDERWIDTHS pborderwidths)
271    {
272        return E_NOTIMPL;
273    }
274
275    STDMETHOD(SetBorderSpace)(LPCBORDERWIDTHS pborderwidths)
276    {
277        return E_NOTIMPL;
278    }
279
280    STDMETHOD(SetActiveObject)(IOleInPlaceActiveObject *pActiveObject, LPCOLESTR pszObjName)
281    {
282        return E_NOTIMPL;
283    }
284
285public: // IOleInPlaceFrame Methods
286    STDMETHOD(InsertMenus)(HMENU hmenuShared, LPOLEMENUGROUPWIDTHS lpMenuWidths)
287    {
288        return E_NOTIMPL;
289    }
290
291    STDMETHOD(SetMenu)(HMENU hmenuShared, HOLEMENU holemenu, HWND hwndActiveObject)
292    {
293        return E_NOTIMPL;
294    }
295
296    STDMETHOD(RemoveMenus)(HMENU hmenuShared)
297    {
298        return E_NOTIMPL;
299    }
300
301    STDMETHOD(SetStatusText)(LPCOLESTR pszStatusText)
302    {
303        return E_NOTIMPL;
304    }
305
306    STDMETHOD(EnableModeless)(BOOL fEnable)
307    {
308        return E_NOTIMPL;
309    }
310
311    STDMETHOD(TranslateAccelerator)(LPMSG lpmsg, WORD wID)
312    {
313        return E_NOTIMPL;
314    }
315
316private:
317    LONG m_nRefCount;
318
319protected:
320    IStorage          *m_pStorage;
321    IOleObject        *m_pOleObj;
322    IOleInPlaceObject *m_pInPlaceObj;
323    HWND               m_hWindow;
324};

这三个接口,连带他们的父类,总共需要实现31个方法!不过还好,除了篇幅稍微长一点,基本上都是E_NOTIMPL。

其中CreateOleObject和InPlaceActive是我自己加的,将来要调用的。

OleCreate期间,除了若干次QueryInterface之外,GetContainer会被调到一次。OLEIVERB_INPLACEACTIVATE期间,除了若干次QueryInterface之外,CanInPlaceActivate、OnInPlaceActivate、ShowObject会被调到。

那些不返回E_NOTIMPL的接口,我也没有一一查询是干嘛的,都是抄的~

实现WebBrowser容器类

上一节可视为(通用的)OLE容器类(只是没有使用除WebBrowser之外的其他玩意儿测试过,共性不是很明确,不敢保证通用性),这一节来实现WebBrowser。作为最简单的WebBrowser,不用再额外实现别的借口了,直接使用上面的OleContainer,创建WebBrowser对象即可。

代码清单:

 1#pragma once
 2
 3#include "OleContainer.h"
 4#include <ExDisp.h>
 5
 6class WebBrowser : public OleContainer
 7{
 8public:
 9    WebBrowser(HWND hParent) : m_pWebBrowser(nullptr), m_hWnd(hParent)
10    {
11
12    }
13
14    ~WebBrowser()
15    {
16        if (m_pWebBrowser != nullptr)
17        {
18            m_pWebBrowser->Release();
19            m_pWebBrowser = nullptr;
20        }
21    }
22
23public:
24    bool CreateWebBrowser()
25    {
26        if (!CreateOleObject(CLSID_WebBrowser))
27        {
28            return false;
29        }
30
31        RECT rect = {};
32        GetClientRect(m_hWnd, &rect);
33
34        if (!InPlaceActive(m_hWnd, &rect))
35        {
36            return false;
37        }
38
39        HRESULT hr = m_pOleObj->QueryInterface(IID_IWebBrowser2, (LPVOID *)&m_pWebBrowser);
40
41        if (FAILED(hr))
42        {
43            return false;
44        }
45
46        return true;
47    }
48  
49public:
50    void Navigate(LPCTSTR lpUrl)
51    {
52        BSTR bstrUrl = SysAllocString(lpUrl);
53        m_pWebBrowser->Navigate(bstrUrl, nullptr, nullptr, nullptr, nullptr);
54        SysFreeString(bstrUrl);
55    }
56
57public:
58    STDMETHOD(GetWindow)(HWND *phwnd)
59    {
60        *phwnd = m_hWnd;
61        return S_OK;
62
63    }
64
65    STDMETHOD(GetWindowContext)(IOleInPlaceFrame **ppFrame,
66                                IOleInPlaceUIWindow **ppDoc,
67                                LPRECT lprcPosRect,
68                                LPRECT lprcClipRect,
69                                LPOLEINPLACEFRAMEINFO lpFrameInfo)
70    {
71        *ppFrame = (IOleInPlaceFrame*)this;
72        *ppDoc = NULL;
73        AddRef();
74
75        GetClientRect(m_hWnd, lprcPosRect);
76        GetClientRect(m_hWnd, lprcClipRect);
77
78        lpFrameInfo->cb = sizeof(OLEINPLACEFRAMEINFO);
79        lpFrameInfo->fMDIApp = false;
80        lpFrameInfo->hwndFrame = GetParent(m_hWnd);
81        lpFrameInfo->haccel = nullptr;
82        lpFrameInfo->cAccelEntries = 0;
83
84        return S_OK;
85    }
86
87protected:
88    IWebBrowser2 *m_pWebBrowser;
89    HWND m_hWnd;
90};

其中CreateWebBrowser 只是简单的调用了一下CreateOleObject,传入CLSID_WebBrowser。然后InPlaceActive就可以了。最后拿的m_pWebBrowser指针是备用的,用于实现一个示例功能:Navigate。

后面的GetWindow和GetWindowContext在OleContainer中已经有了,之所以再写一遍,是因为WebBrowser的创建过程中,就会调用GetWindow,需要给出一个窗口句柄;InPlaceActive过程中会调用GetWindowContext,也要做事情,不能return E_NOTIMPL了事。而OleContainer中,暂时我是在InPlaceActive的时候传入窗口句柄的。可能是OleContianer根本应该更早的知道窗口的存在,但这一点我还没弄清楚,所以暂时写成那样。但到WebBrowser层面,创建一个WebBrowser之前就需要先创建好窗口。

使用WebBrowser容器类

我们回到Main,创建好主窗口后插入几行代码:

 1int APIENTRY _tWinMain(_In_ HINSTANCE     hInstance,
 2                       _In_opt_ HINSTANCE hPrevInstance,
 3                       _In_ LPTSTR        lpCmdLine,
 4                       _In_ int           nCmdShow)
 5{
 6    UNREFERENCED_PARAMETER(hPrevInstance);
 7    UNREFERENCED_PARAMETER(lpCmdLine);
 8
 9    const LPCTSTR CLASS_NAME = _T("WebBrowserContainer");
10
11    WNDCLASSEX wcex    = { sizeof(WNDCLASSEX) };
12    wcex.style         = CS_HREDRAW | CS_VREDRAW;
13    wcex.lpfnWndProc   = WndProc;
14    wcex.cbClsExtra    = 0;
15    wcex.cbWndExtra    = 0;
16    wcex.hInstance     = hInstance;
17    wcex.hCursor       = LoadCursor(nullptr, IDC_ARROW);
18    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
19    wcex.lpszClassName = CLASS_NAME;
20
21    RegisterClassEx(&wcex);
22
23    HWND hWnd = CreateWindow(CLASS_NAME,
24                             _T("WebBrowser Sample"),
25                             WS_OVERLAPPEDWINDOW,
26                             CW_USEDEFAULT,
27                             0,
28                             CW_USEDEFAULT,
29                             0,
30                             nullptr,
31                             nullptr,
32                             hInstance,
33                             nullptr);
34
35    if (hWnd == nullptr)
36    {
37        return 0;
38    }
39
40    ShowWindow(hWnd, nCmdShow);
41    UpdateWindow(hWnd);
42
43    WebBrowser wb(hWnd);
44
45    if (!wb.CreateWebBrowser())
46    {
47        return 0;
48    }
49
50    wb.Navigate(_T("http://www.baidu.com/"));
51
52    MSG msg = {};
53
54    while (GetMessage(&msg, nullptr, 0, 0))
55    {
56        if (!TranslateAccelerator(msg.hwnd, nullptr, &msg))
57        {
58            TranslateMessage(&msg);
59            DispatchMessage(&msg);
60        }
61    }
62
63    return (int)msg.wParam;
64}

然后运行,截图如下:

小结

上文演示了一个最简单的在正常窗口嵌入WebBrowser对象的例子。例子代码可在 http://pan.baidu.com/s/18PtQu(WebBrowserSample.rar)下载。

处于直观性考虑,没有做过多的封装和梳理。后面再来梳理这些。


首发:http://www.cppblog.com/Streamlet/archive/2012/09/01/188962.html



NoteIsSite/0.4