随笔-90  评论-947  文章-0  trackbacks-0

引言

 

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

 

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

 

建立普通窗口

 

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

 

#include <Windows.h>

#include <tchar.h>

 

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

{

    switch (message)

    {

    case WM_DESTROY:

        PostQuitMessage(0);

        break;

    default:

        return DefWindowProc(hWnd, message, wParam, lParam);

    }

 

    return 0;

}

 

int APIENTRY _tWinMain(_In_ HINSTANCE     hInstance,

                       _In_opt_ HINSTANCE hPrevInstance,

                       _In_ LPTSTR        lpCmdLine,

                       _In_ int           nCmdShow)

{

    UNREFERENCED_PARAMETER(hPrevInstance);

    UNREFERENCED_PARAMETER(lpCmdLine);

 

    const LPCTSTR CLASS_NAME = _T("WebBrowserContainer");

 

    WNDCLASSEX wcex    = { sizeof(WNDCLASSEX) };

    wcex.style         = CS_HREDRAW | CS_VREDRAW;

    wcex.lpfnWndProc   = WndProc;

    wcex.cbClsExtra    = 0;

    wcex.cbWndExtra    = 0;

    wcex.hInstance     = hInstance;

    wcex.hCursor       = LoadCursor(nullptr, IDC_ARROW);

    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);

    wcex.lpszClassName = CLASS_NAME;

 

    RegisterClassEx(&wcex);

 

    HWND hWnd = CreateWindow(CLASS_NAME,

                             _T("WebBrowser Sample"),

                             WS_OVERLAPPEDWINDOW,

                             CW_USEDEFAULT,

                             0,

                             CW_USEDEFAULT,

                             0,

                             nullptr,

                             nullptr,

                             hInstance,

                             nullptr);

 

    if (hWnd == nullptr)

    {

        return 0;

    }

 

    ShowWindow(hWnd, nCmdShow);

    UpdateWindow(hWnd);

 

    MyWebBrowser wb(hWnd);

 

    MSG msg = {};

 

    while (GetMessage(&msg, nullptr, 0, 0))

    {

        if (!TranslateAccelerator(msg.hwnd, nullptr, &msg))

        {

            TranslateMessage(&msg);

            DispatchMessage(&msg);

        }

    }

 

    return (int)msg.wParam;

}

 

加入WebBrowser

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

 

实现OLE容器类

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

 

代码清单如下:

 

#pragma once

 

#include <Windows.h>

#include <OleIdl.h>

 

class OleContainer : public IOleClientSite,

                     public IOleInPlaceSite,

                     public IOleInPlaceFrame

{

public:

    OleContainer() : m_nRefCount(0),

                     m_pStorage(nullptr),

                     m_pOleObj(nullptr),

                     m_pInPlaceObj(nullptr),

                     m_hWindow(nullptr)

    {

        AddRef();

        OleInitialize(nullptr);

    }

 

    ~OleContainer()

    {

        if (m_pInPlaceObj != nullptr)

        {

            m_pInPlaceObj->Release();

            m_pInPlaceObj = nullptr;

        }

 

        if (m_pOleObj != nullptr)

        {

            m_pOleObj->Release();

            m_pOleObj = nullptr;

        }

 

        if (m_pStorage != nullptr)

        {

            m_pStorage->Release();

            m_pStorage = nullptr;

        }

 

        OleUninitialize();

    }

 

public:

    bool CreateOleObject(const IID &clsid)

    {

        HRESULT hr = StgCreateDocfile(nullptr,

                                        STGM_READWRITE | STGM_SHARE_EXCLUSIVE | STGM_DIRECT | STGM_CREATE,

                                        0,

                                        &m_pStorage);

        if (FAILED(hr))

        {

            return false;

        }

 

        hr = OleCreate(clsid, IID_IOleObject, OLERENDER_DRAW, 0, this, m_pStorage, (LPVOID *)&m_pOleObj);

 

        if (FAILED(hr))

        {

            return false;

        }

 

        hr = m_pOleObj->QueryInterface(IID_IOleInPlaceObject, (LPVOID *)&m_pInPlaceObj);

 

        if (FAILED(hr))

        {

            return false;

        }

 

        return true;

    }

 

    bool InPlaceActive(HWND hWnd, LPCRECT lpRect)

    {

        if (hWnd == nullptr)

        {

            return false;

        }

 

        RECT rect = {};

 

        if (lpRect == nullptr)

        {

            GetClientRect(hWnd, &rect);

            lpRect = &rect;

        }

 

        HRESULT hr = m_pOleObj->DoVerb(OLEIVERB_INPLACEACTIVATE, nullptr, this, 0, hWnd, lpRect);

 

        if (FAILED(hr))

        {

            return false;

        }

 

        m_hWindow = hWnd;

 

        return true;

    }

 

public: // IUnknown Methods

    HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID *ppvObject)

    {

        *ppvObject = nullptr;

 

        if (riid == IID_IUnknown)

        {

            *ppvObject = (IOleClientSite *)this;

        }

        else if (riid == IID_IOleInPlaceSite)

        {

            *ppvObject = (IOleInPlaceSite *)this;

        }

        else if (riid == IID_IOleInPlaceUIWindow)

        {

            *ppvObject = (IOleInPlaceUIWindow *)this;

        }

        else if (riid == IID_IOleInPlaceFrame)

        {  

            *ppvObject = (IOleInPlaceFrame *)this;

        }

 

        if (*ppvObject == nullptr)

        {

            return E_NOINTERFACE;

        }

 

        AddRef();

        return S_OK;

    }

 

    ULONG STDMETHODCALLTYPE AddRef()

    {

        return (ULONG)InterlockedIncrement(&m_nRefCount);

    }

 

    ULONG STDMETHODCALLTYPE Release()

    {

        LONG nRefCount = InterlockedDecrement(&m_nRefCount);

 

        if (nRefCount <= 0)

        {

            delete this;

        }

 

        return (ULONG)nRefCount;

    }

 

public: // IOleClientSite Methods

    STDMETHOD(SaveObject)()

    {

        return E_NOTIMPL;

    }

 

    STDMETHOD(GetMoniker)(DWORD dwAssign, DWORD dwWhichMoniker, IMoniker **ppmk)

    {

        return E_NOTIMPL;

    }

 

    STDMETHOD(GetContainer)(IOleContainer **ppContainer)

    {

        return E_NOTIMPL;

    }

 

    STDMETHOD(ShowObject)()

    {

        return E_NOTIMPL;

    }

 

    STDMETHOD(OnShowWindow)(BOOL fShow)

    {

        return E_NOTIMPL;

    }

 

    STDMETHOD(RequestNewObjectLayout)()

    {

        return E_NOTIMPL;

    }

 

public: // IOleWindow Methods

    STDMETHOD(GetWindow)(HWND *phwnd)

    {

        return E_NOTIMPL;

    }

 

    STDMETHOD(ContextSensitiveHelp)(BOOL fEnterMode)

    {

        return E_NOTIMPL;

    }

 

public: // IOleInPlaceSite Methods

    STDMETHOD(CanInPlaceActivate)()

    {

        return m_hWindow == nullptr ? S_OK : S_FALSE;

    }

 

    STDMETHOD(OnInPlaceActivate)()

    {

        return E_NOTIMPL;

    }

 

    STDMETHOD(OnUIActivate)()

    {

        return E_NOTIMPL;

    }

 

    STDMETHOD(GetWindowContext)(IOleInPlaceFrame **ppFrame,

                                IOleInPlaceUIWindow **ppDoc,

                                LPRECT lprcPosRect,

                                LPRECT lprcClipRect,

                                LPOLEINPLACEFRAMEINFO lpFrameInfo)

    {

        if (m_hWindow == nullptr)

        {

            return E_NOTIMPL;

        }

 

        *ppFrame = (IOleInPlaceFrame*)this;

        *ppDoc = NULL;

        AddRef();

 

        GetClientRect(m_hWindow, lprcPosRect);

        GetClientRect(m_hWindow, lprcClipRect);

 

        lpFrameInfo->cb = sizeof(OLEINPLACEFRAMEINFO);

        lpFrameInfo->fMDIApp = false;

        lpFrameInfo->hwndFrame = GetParent(m_hWindow);

        lpFrameInfo->haccel = nullptr;

        lpFrameInfo->cAccelEntries = 0;

 

        return S_OK;

    }

 

    STDMETHOD(Scroll)(SIZE scrollExtant)

    {

        return E_NOTIMPL;

    }

 

    STDMETHOD(OnUIDeactivate)(BOOL fUndoable)

    {

        return E_NOTIMPL;

    }

 

    STDMETHOD(OnInPlaceDeactivate)()

    {

        return E_NOTIMPL;

    }

 

    STDMETHOD(DiscardUndoState)()

    {

        return E_NOTIMPL;

    }

 

    STDMETHOD(DeactivateAndUndo)()

    {

        return E_NOTIMPL;

    }

 

    STDMETHOD(OnPosRectChange)(LPCRECT lprcPosRect)

    {

        return E_NOTIMPL;

    }

 

public: // IOleInPlaceUIWindow Methods

    STDMETHOD(GetBorder)(LPRECT lprectBorder)

    {

        return E_NOTIMPL;

    }

 

    STDMETHOD(RequestBorderSpace)(LPCBORDERWIDTHS pborderwidths)

    {

        return E_NOTIMPL;

    }

 

    STDMETHOD(SetBorderSpace)(LPCBORDERWIDTHS pborderwidths)

    {

        return E_NOTIMPL;

    }

 

    STDMETHOD(SetActiveObject)(IOleInPlaceActiveObject *pActiveObject, LPCOLESTR pszObjName)

    {

        return E_NOTIMPL;

    }

 

public: // IOleInPlaceFrame Methods

    STDMETHOD(InsertMenus)(HMENU hmenuShared, LPOLEMENUGROUPWIDTHS lpMenuWidths)

    {

        return E_NOTIMPL;

    }

 

    STDMETHOD(SetMenu)(HMENU hmenuShared, HOLEMENU holemenu, HWND hwndActiveObject)

    {

        return E_NOTIMPL;

    }

 

    STDMETHOD(RemoveMenus)(HMENU hmenuShared)

    {

        return E_NOTIMPL;

    }

 

    STDMETHOD(SetStatusText)(LPCOLESTR pszStatusText)

    {

        return E_NOTIMPL;

    }

 

    STDMETHOD(EnableModeless)(BOOL fEnable)

    {

        return E_NOTIMPL;

    }

 

    STDMETHOD(TranslateAccelerator)(LPMSG lpmsg, WORD wID)

    {

        return E_NOTIMPL;

    }

 

private:

    LONG m_nRefCount;

 

protected:

    IStorage          *m_pStorage;

    IOleObject        *m_pOleObj;

    IOleInPlaceObject *m_pInPlaceObj;

    HWND               m_hWindow;

};

 

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

 

其中CreateOleObjectInPlaceActive是我自己加的,将来要调用的。

 

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

 

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

 

实现WebBrowser容器类

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

 

代码清单:

 

#pragma once

 

#include "OleContainer.h"

#include <ExDisp.h>

 

class WebBrowser : public OleContainer

{

public:

    WebBrowser(HWND hParent) : m_pWebBrowser(nullptr), m_hWnd(hParent)

    {

 

    }

 

    ~WebBrowser()

    {

        if (m_pWebBrowser != nullptr)

        {

            m_pWebBrowser->Release();

            m_pWebBrowser = nullptr;

        }

    }

 

public:

    bool CreateWebBrowser()

    {

        if (!CreateOleObject(CLSID_WebBrowser))

        {

            return false;

        }

 

        RECT rect = {};

        GetClientRect(m_hWnd, &rect);

 

        if (!InPlaceActive(m_hWnd, &rect))

        {

            return false;

        }

 

        HRESULT hr = m_pOleObj->QueryInterface(IID_IWebBrowser2, (LPVOID *)&m_pWebBrowser);

 

        if (FAILED(hr))

        {

            return false;

        }

 

        return true;

    }

   

public:

    void Navigate(LPCTSTR lpUrl)

    {

        BSTR bstrUrl = SysAllocString(lpUrl);

        m_pWebBrowser->Navigate(bstrUrl, nullptr, nullptr, nullptr, nullptr);

        SysFreeString(bstrUrl);

    }

 

public:

    STDMETHOD(GetWindow)(HWND *phwnd)

    {

        *phwnd = m_hWnd;

        return S_OK;

 

    }

 

    STDMETHOD(GetWindowContext)(IOleInPlaceFrame **ppFrame,

                                IOleInPlaceUIWindow **ppDoc,

                                LPRECT lprcPosRect,

                                LPRECT lprcClipRect,

                                LPOLEINPLACEFRAMEINFO lpFrameInfo)

    {

        *ppFrame = (IOleInPlaceFrame*)this;

        *ppDoc = NULL;

        AddRef();

 

        GetClientRect(m_hWnd, lprcPosRect);

        GetClientRect(m_hWnd, lprcClipRect);

 

        lpFrameInfo->cb = sizeof(OLEINPLACEFRAMEINFO);

        lpFrameInfo->fMDIApp = false;

        lpFrameInfo->hwndFrame = GetParent(m_hWnd);

        lpFrameInfo->haccel = nullptr;

        lpFrameInfo->cAccelEntries = 0;

 

        return S_OK;

    }

 

protected:

    IWebBrowser2 *m_pWebBrowser;

    HWND m_hWnd;

};

 

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

 

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

 

使用WebBrowser容器类

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

 

int APIENTRY _tWinMain(_In_ HINSTANCE     hInstance,

                       _In_opt_ HINSTANCE hPrevInstance,

                       _In_ LPTSTR        lpCmdLine,

                       _In_ int           nCmdShow)

{

    UNREFERENCED_PARAMETER(hPrevInstance);

    UNREFERENCED_PARAMETER(lpCmdLine);

 

    const LPCTSTR CLASS_NAME = _T("WebBrowserContainer");

 

    WNDCLASSEX wcex    = { sizeof(WNDCLASSEX) };

    wcex.style         = CS_HREDRAW | CS_VREDRAW;

    wcex.lpfnWndProc   = WndProc;

    wcex.cbClsExtra    = 0;

    wcex.cbWndExtra    = 0;

    wcex.hInstance     = hInstance;

    wcex.hCursor       = LoadCursor(nullptr, IDC_ARROW);

    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);

    wcex.lpszClassName = CLASS_NAME;

 

    RegisterClassEx(&wcex);

 

    HWND hWnd = CreateWindow(CLASS_NAME,

                             _T("WebBrowser Sample"),

                             WS_OVERLAPPEDWINDOW,

                             CW_USEDEFAULT,

                             0,

                             CW_USEDEFAULT,

                             0,

                             nullptr,

                             nullptr,

                             hInstance,

                             nullptr);

 

    if (hWnd == nullptr)

    {

        return 0;

    }

 

    ShowWindow(hWnd, nCmdShow);

    UpdateWindow(hWnd);

 

    WebBrowser wb(hWnd);

 

    if (!wb.CreateWebBrowser())

    {

        return 0;

    }

 

    wb.Navigate(_T("http://www.baidu.com/"));

 

    MSG msg = {};

 

    while (GetMessage(&msg, nullptr, 0, 0))

    {

        if (!TranslateAccelerator(msg.hwnd, nullptr, &msg))

        {

            TranslateMessage(&msg);

            DispatchMessage(&msg);

        }

    }

 

    return (int)msg.wParam;

}

 

 

然后运行,截图如下:

 

clip_image002

 

小结

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

 

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

 

 


评论:
# re: 裸写一个含内嵌IE控件的窗口 2012-09-01 11:56 | 毕达哥拉斯半圆
好文,收了!  回复  更多评论
  
# re: 裸写一个含内嵌IE控件的窗口 2012-09-01 21:22 | OwnWaterloo
啊,先跑个题。。。这文章是原创吧?有你的风格。。。
但你发现没有。。。这文章在cppblog里转载了好几个地方。。。
而且那几爷子的blog最近都这样,统一发同一篇文章。。。
之前还在想是不是有个是主号、其他是马甲。。。 看来全都是马甲吗。。。


嗯,我关注的是这里: >> 这三个接口,连带他们的父类,总共需要实现31个接口!不过还好,除了篇幅稍微长一点,基本上都是E_NOTIMPL。
想了解这种情况会不会经常发生?

就是说,父类定义了N个虚函数(是纯虚吗?其实是不是都无所谓)。子类override之,然后传递一个子类到某个地方。但其实传递进去的子类,它们的那N个虚函数中只有少部分被调用。

于是只能E_NOTIMPL啥啥的。。。  回复  更多评论
  
# re: 裸写一个含内嵌IE控件的窗口 2012-09-02 09:58 | 溪流
@OwnWaterloo
然后你是要说ATL的XXXImpl吗?
======
不知道转载了,搜一下先^_^~  回复  更多评论
  
# re: 裸写一个含内嵌IE控件的窗口 2012-09-02 10:31 | 溪流
@OwnWaterloo
不对,然后你不是要说那个。。。你是说有什么技巧躲开吗?  回复  更多评论
  
# re: 裸写一个含内嵌IE控件的窗口 2012-09-02 11:19 |
你好,请问能给出在richedit里插入IE控件的代码吗?我怎么搞都显示不出来,amuro00@qq.com 谢谢  回复  更多评论
  
# re: 裸写一个含内嵌IE控件的窗口 2012-09-02 11:27 | 溪流
@罗
这么淫荡的需求……我不会……  回复  更多评论
  
# re: 裸写一个含内嵌IE控件的窗口 2012-09-02 11:37 | OwnWaterloo
@溪流
>> 不知道转载了,搜一下先^_^~
我是订阅了cppblog的所有文章、首页原创、还有有几爷子的单独的。。。
所以不用搜就知道这个事。。。
但最近这种同一文章在所有文章里出现4-5次,首页原创里出现4-5次,那几爷子的单独的rss里再出现4-5次。。。 吃不消啊。。。  回复  更多评论
  
# re: 裸写一个含内嵌IE控件的窗口 2012-09-02 12:07 | OwnWaterloo
@溪流
至于躲开的技巧。。。 其实事情起因是这样。。。
大概08-10年我就在cppblog或者CU(不记得是哪个地方了,又或者都有说)上说interface存在的问题。


一个函数f,它对它的参数有一些要求,例如你的代码中不是E_NOTIMPL那些。 而不同的函数对它的参数有不同的需求。

但interface的问题就是,它不能直接描述某函数Fi的需求, 它一定需要一个 **打包** 的过程。

那这个打包的粒度是否恰当,以相信interface是解决问题的方案的人自身的说法, 就看 *设计师(架构师)* 的本事了。
他们还会传授一些经验技巧, 比如"每个interface包含3-5个method比较合适"什么的, 笑死人了。


以我这种不相信interface是解决问题的方案的人来看, 那些展现漂亮的设计的demonstration,也只能在demonstration里维持它的漂亮。
随着系统滚大, interface就不能恰如其分的描述出需求。

例如, 就会出现某个函数Fi, 它接受到某个interface Ij, 但它需要interface Ik, 于是就只能 *运行时查询* , 丢失了静态类型检测。

不是 *架构师* 的设计失误, 而是他要在 *让每个interface Ij都能尽量恰到好处的描述出它使用者Fk 的需求* 与 *让总的interface数目维持在一个可接受的范围内* 之间作一个 *权衡* 。

对实际稍微大一些的系统, 如果想让demonstration展示出的漂亮设计延续下去, 需要的interface数目就会超出人的接受能力。demonstration演示不出这一点。

这是interface本身的缺陷 —— 需要将一组需求打包(还有其他很多, 先不说了。。。)。 架构师只能缓解, 没法解决。



对E_NOTIMPL,其实在C++里还好。。。 因为C++支持多继承。。。
让父类提供默认实现, 子类只override需要的便是。。。 而Java那种单继承就完蛋了。
只是不知道为什么IOleXXX没有这样设计。

对那种需要动态查询,从interface Ij转换到Ik的情况,QueryInterface什么的,真是无能为力。。。



反观duck typing —— 记得你也用Python的吧 —— 就没有这样的限制。
只要实现一个class, 让它支持那些不是返回E_NOTIMPL的method就完了。
甚至都不需要IOleXXX或者QueryInterface这些概念。
每个函数Fi对它的参数们的要求虽然是 *隐式的*, 但却是 *精确的*, 不多不少。

脱离Python,更一般的情况来说,要达到"Fi对它的参数的要求*不多不少*, 参数能 *恰如其分* 的实现这些要求"的目标, 并不一定需要 *隐式*, 也不一定需要Python那样的动态类型。
可以是显式的、 静态类型的, 例如Haskell。

使用interface的语言里,一个类型要实现什么interface在类型定义的时候就必须决定, 是另一个设计缺陷。 至少在C++/Java/C#里是这样。
至于其他的静态类型的OO语言。。。我学语言有个标准。。。 就是如果这语言强调自己是支持OO的, 那就不学。 所以其他静态类型的OO语言也不知道。


扯了一大通。。。 其实是想说。。。
我以前只是 *预感* interface的设计在稍微大一点的系统里就会出问题。
但对使用interface的大的系统都没有研究的兴趣。。。 看着头就痛。。。 因此说明这问题的 *实际例子* 比较匮乏。。。
于是看到你的文章就有兴趣了。。。 "看吧,我果然是对的!" <- 心声大致是这样。  回复  更多评论
  
# re: 裸写一个含内嵌IE控件的窗口 2012-09-02 12:45 | 溪流
@OwnWaterloo
可是我 google 标题 site:cppblog.com 没搜到诶  回复  更多评论
  
# re: 裸写一个含内嵌IE控件的窗口 2012-09-02 12:54 | 溪流
@OwnWaterloo
“我学语言有个标准。。。 就是如果这语言强调自己是支持OO的, 那就不学。”哈哈!先吃饭去,回头来读。
前两年(可能受你影响)一直在学GP,没在意OO部分,但是,工作中发现,“架构师”们只关心OO,并且根本不 care GP,以至于我现在不得不回过头来学习OO,学习COM,不然他们永远也不会认为我牛逼……^_^  回复  更多评论
  
# re: 裸写一个含内嵌IE控件的窗口 2012-09-02 19:13 | 溪流
@OwnWaterloo
(void)(1, 2, 3);
这是什么语法?怎么解?真实场景是:
(void)
(
(!!( _GetEntries()[0].pFunc == ((ATL::_ATL_CREATORARGFUNC*)1) )) ||
(1 != _CrtDbgReportW(2, L"SampleClass.h", 34, 0, L"%s", L"_GetEntries()[0].pFunc == ((ATL::_ATL_CREATORARGFUNC*)1)")) ||
(__debugbreak(), 0)
);
(两个场景一样吗?)  回复  更多评论
  
# re: 裸写一个含内嵌IE控件的窗口 2012-09-02 19:50 | OwnWaterloo
@溪流
1, 2, 3逗号运算符,最后转型为void。

从后面的例子来看。。。 可能是为了让那一整托东西是一个表达式。
你试试实现assert就能体会了。。。  回复  更多评论
  
# re: 裸写一个含内嵌IE控件的窗口 2012-09-02 19:58 | OwnWaterloo
@溪流
不一定是不care,有可能是不能胜任。虽然结果都一样,但不能与不愿的原因可是有区别的。

OO确实更简单、更natural,对看不出person.learn(language)与learn(person, language) 是一回事的人来说的话。
  回复  更多评论
  
# re: 裸写一个含内嵌IE控件的窗口 2012-09-02 20:46 | 溪流
@OwnWaterloo
转为void是个什么概念?貌似我不能 auto x = (void)(1, 2, 3); ?  回复  更多评论
  
# re: 裸写一个含内嵌IE控件的窗口 2012-09-02 20:50 | OwnWaterloo
@溪流
嗯,如果表达式的值被设计为没有意义的话,就可以(void),避免被使用。
  回复  更多评论
  
# re: 裸写一个含内嵌IE控件的窗口 2012-09-04 14:26 | 万连文
@溪流
不对,然后你不是要说那个。。。你是说有什么技巧躲开吗?
thunk技术可以躲开,微软早期的ie内核中的dom对象的接口就是采取这种办法实现。

@罗
参见http://www.cppblog.com/wlwlxj/archive/2012/05/20/175489.html
即使嵌入了也无法正常工作,因为richedit对OLE的支持阉割了,不支持大部分OLE接口,即使支持的接口也并非完全遵守OLE规范。  回复  更多评论
  
# re: 裸写一个含内嵌IE控件的窗口 2014-08-14 15:50 | meng85440835
大神,为什么代码下载不了  回复  更多评论
  
# re: 裸写一个含内嵌IE控件的窗口 2015-01-15 22:14 | 3维
谢了  回复  更多评论
  
# re: 裸写一个含内嵌IE控件的窗口[未登录] 2016-07-20 19:23 | James
亲测好用,谢谢LZ  回复  更多评论
  

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理