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}
这一部分主要参考了《使用C++实现SDK之WebBrowser容器》(http://blog.csdn.net/norsd/article/details/2921389)。但是这篇文章里没有给出真正可运行的代码,有几处小笔误,然后呢,其实我也不是很喜欢原作者的封装方式。本篇的目的是尽可能简单、原始,让后来者容易上手。
##实现OLE容器类
实现一个WebBrowser,先要实现一个OLE容器类。我不知道OLE容器类的精确定义是什么,但现在我们需要实现三个接口:IOleClientSite、IOleInPlaceSite、IOleInPlaceFrame(<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 = ▭
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的接口,我也没有一一查询是干嘛的,都是抄的~
上一节可视为(通用的)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之前就需要先创建好窗口。
我们回到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