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

 

上一篇我们简单学习了下ATL 的继承链处理。可是,如果要裸写一个含内嵌IE控件的窗口,还是要写一个很长的QueryInterface,以及AddRefRelease,确保引用计数的正确性。于是我们不得不参考ATLCOM_TNTERFACE的处理技巧,来达到一定程度上的易用性。

 

首先,除了IUnknown以外,其余所有涉及到的接口,均按上一篇的形式,弄成相应的IXXXImpl,这部分代码见:

http://xllib.codeplex.com/SourceControl/changeset/view/19617#315460

细节不再赘述。现在关键是IUnknown的处理:

1.         首先,如果对象继承自两个或以上的COM接口,要保证所有的查询IUnknown的地方都会返回同一个IUnknown*

2.         其次,在同一对象中,无论哪个接口调用AddRefRelease,都修改同一个引用计数。

 

基于以上两点,各个IXXXImpl不能自己去实现IUnknown的这三个函数,就是return E_NOTIMPL也不行。对于COM接口来说,IUnknown必须进行有意义的实现。

 

就拿WebBrowser的例子来说,WebBrowser容器至少实现IOleClientSiteIOleInPlaceSiteIOleInPlaceFrame,通常还会实现IDocHostUIHandlerDWebBrowserEvents2,它们的继承关系是:

IOleClientSite : IUnknown

IOleInPlaceSite : IOleWindow : IUnknown

IOleInPlaceFrame : IOleInPlaceUIWindow : IOleWindow : IUnknown

IDocHostUIHandler : IUnknown

DWebBrowserEvents2 : IDispatch : IUnknown

 

如果看虚函数表,将会是这样:

 

IUnknown

IOleClientSite

IOleClientSite

IUnknown

IOleWindow

IOleInPlaceSite

IOleWindow

IOleInPlaceSite

 

IUnknown

IOleWindow

IOleInPlaceUIWindow

IOleInPlaceFrame

IOleWindow

IOleInPlaceUIWindow

 

IOleInPlaceFrame

 

 

IUnknown

IDocHostUIHandler

IDocHostUIHandler

IUnknown

IDispatch (DWebBrowserEvents2)

IDispatch (DWebBrowserEvents2)

 

其中出现了5IUnknown,我们要在最底层对象上一次性的实现了,而不是在中间层实现。

如果我们裸写QueryInterface,就像是上次一样,会是这个样子:

 

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;

    }

    else if (riid == IID_IDocHostUIHandler)

    {  

        *ppvObject = (IDocHostUIHandler *)this;

    }

    else if (riid == IID_IDispatch)

    {  

        *ppvObject = (IDispatch *)this;

    }

    else if (riid == DIID_DWebBrowserEvents2)

    {  

        *ppvObject = (DWebBrowserEvents2 *)this;

    }

 

    if (*ppvObject == nullptr)

    {

        return E_NOINTERFACE;

    }

 

    AddRef();

    return S_OK;

}

 

注意高亮的那一句,查询IUnknown接口时,我们指定了IOleClientSite对应的IUnknown,也就是表中最前面的那个IUnknown

 

很容易想到的一个做法是:

 

#define COM_INTERFACE(i)            \

                                    \

        if (riid == __uuidof(i))    \

        {                           \

            *ppvObject = (i *)this; \

            AddRef();               \

            return S_OK;            \

        }

 

然后前后用 BEGINEND宏定义,拼凑成完整的QueryInterface。可是这没有解决IUnknown的问题,如果查询IUnknown,“(i *)this”会有歧义。这种定义下,代码上无法知道“第一个接口”是啥,无法写出一个可行的thisIUnknown*的转换。

 

不会了,于是就抄ATLATL中是列了一张表,我简化一下,每一个项的结构定义是:

 

struct InterfaceEntry

{

    const IID *piid;

    DWORD_PTR dwOffset;

};

 

然后定义如下函数:

 

typedef c ComClass;

 

static const InterfaceEntry *GetEntries()

{

    static const InterfaceEntry entries[] =

    {

        { &__uuidof(i), (DWORD_PTR)((i *)(ComClass *)8) - 8 },

        { &__uuidof(i), (DWORD_PTR)((i *)(ComClass *)8) - 8 },

        // ...

        { nullptr, 0 }

    };

 

    return entries;

}

 

其中c是最终那个对象的类,i是每一个要对外暴露的接口。每一项的第二列存了该接口到类的首地址的偏移量。至于那个8,为什么要用8,是不是他们拍脑袋的?用别常数有没有问题呢……谁来告诉我?(在下面的实际实现中我用了sizeof(nullptr),不知道有木有问题。)

 

为此,我们定义一个额外的辅助类:

 

template <typename T>

class ComClass

{

public:

    ComClass() : m_nRefCount(0)

    {

        InternalAddRef();

    }

 

    ~ComClass()

    {

       

    }

 

public:

    STDMETHODIMP InternalQueryInterface(const InterfaceEntry *pEntries, REFIID riid, LPVOID *ppvObject)

    {

        *ppvObject = nullptr;

        T *pThis = (T *)this;

 

        IUnknown *pUnknown = (IUnknown *)((INT_PTR)pThis + pEntries->dwOffset);

 

        if (riid == __uuidof(IUnknown))

        {

            *ppvObject = pUnknown;

            pUnknown->AddRef();

            return S_OK;

        }

 

        for (const InterfaceEntry *pEntry = pEntries; pEntry->piid != nullptr; ++pEntry)

        {

            if (riid == *pEntry->piid)

            {

                *ppvObject = (IUnknown *)((INT_PTR)pThis + pEntry->dwOffset);

                pUnknown->AddRef();

                return S_OK;

            }

        }

 

        return E_NOINTERFACE;

    }

 

    ULONG STDMETHODCALLTYPE InternalAddRef()

    {

        return (ULONG)InterlockedIncrement(&m_nRefCount);

    }

 

    ULONG STDMETHODCALLTYPE InternalRelease()

    {

        LONG nRefCount = InterlockedDecrement(&m_nRefCount);

 

        if (nRefCount <= 0)

        {

            delete this;

        }

           

        return (ULONG)nRefCount;

    }

 

protected:

    LONG m_nRefCount;

};

 

它来对三个IUnknown的接口作实际的实现,其中InternalQueryInterface完成从Entries里面找到偏移量,然后算出接口地址返回给外界的过程。然后……规定:所有COM类必须继承刚才这个ComClass

 

现在可以来定义COM_INTERFACE以及它的BEGINEND宏了:

 

#define XL_COM_INTERFACE_BEGIN(c)                                                                   \

                                                                                                    \

        typedef c ComClass;                                                                         \

                                                                                                    \

        static const InterfaceEntry *GetEntries()                                                   \

        {                                                                                           \

            static const InterfaceEntry entries[] =                                                 \

            {                                                                                       \

 

#define XL_COM_INTERFACE(i)                                                                         \

                                                                                                    \

                { &__uuidof(i), (DWORD_PTR)((i *)(ComClass *)sizeof(nullptr)) - sizeof(nullptr) },  \

 

#define XL_COM_INTERFACE_END()                                                                      \

                                                                                                    \

                { nullptr, 0 }                                                                      \

            };                                                                                      \

                                                                                                    \

            return entries;                                                                         \

        }                                                                                           \

                                                                                                    \

        STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppvObject)                                 \

        {                                                                                           \

            return InternalQueryInterface(GetEntries(), riid, ppvObject);                           \

        }                                                                                           \

                                                                                                    \

        ULONG STDMETHODCALLTYPE AddRef()                                                            \

        {                                                                                           \

            return InternalAddRef();                                                                \

        }                                                                                           \

                                                                                                    \

        ULONG STDMETHODCALLTYPE Release()                                                           \

        {                                                                                           \

            return InternalRelease();                                                               \

        }                                                                                           \

 

 

其实主要就是在拼凑那张表,其余都是直接带上的。

上面的代码位于:

http://xllib.codeplex.com/SourceControl/changeset/view/19617#315452

http://xllib.codeplex.com/SourceControl/changeset/view/19617#315458

 

现在来做OleContainerhttp://xllib.codeplex.com/SourceControl/changeset/view/19617#315455)。

 

class OleContainerImpl : public IOleClientSiteImpl<>,

                            public IOleInPlaceSiteImpl<>,

                            public IOleInPlaceFrameImpl<>

{

public:

    OleContainerImpl() : m_hOleParent(nullptr),

                            m_pStorage(nullptr),

                            m_pOleObj(nullptr),

                            m_pInPlaceObj(nullptr),

                            m_bInPlaceActived(false)

    {

        ZeroMemory(&m_rect, sizeof(RECT));

 

        OleInitialize(nullptr);

    }

 

    virtual ~OleContainerImpl()

    {

        DestroyOleObject();

 

        OleUninitialize();

    }

 

public:

    bool CreateOleObject(const IID &clsid)

    {

        DestroyOleObject();

 

        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;

    }

 

    void DestroyOleObject()

    {

        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;

        }

    }

 

    bool InPlaceActive(HWND hWnd, LPCRECT lpRect = nullptr)

    {

        if (hWnd == nullptr || m_pOleObj == nullptr)

        {

            return false;

        }

 

        m_hOleParent = hWnd;

 

        if (lpRect != nullptr)

        {

            CopyMemory(&m_rect, lpRect, sizeof(RECT));

        }

        else

        {

            GetClientRect(m_hOleParent, &m_rect);

        }

 

        HRESULT hr = m_pOleObj->DoVerb(OLEIVERB_INPLACEACTIVATE, nullptr, this, 0, m_hOleParent, &m_rect);

 

        if (FAILED(hr))

        {

            return false;

        }

 

        return true;

    }

 

public:

    STDMETHOD(GetWindow)(HWND *phwnd)

    {

        *phwnd = m_hOleParent;

        return S_OK;

    }

 

    STDMETHOD(CanInPlaceActivate)()

    {

        return m_bInPlaceActived ? S_FALSE : S_OK;

    }

 

    STDMETHOD(GetWindowContext)(IOleInPlaceFrame **ppFrame,

                                IOleInPlaceUIWindow **ppDoc,

                                LPRECT lprcPosRect,

                                LPRECT lprcClipRect,

                                LPOLEINPLACEFRAMEINFO lpFrameInfo)

    {

        if (m_hOleParent == nullptr)

        {

            return E_NOTIMPL;

        }

 

        *ppFrame = (IOleInPlaceFrame*)this;

        (*ppFrame)->AddRef();

 

        *ppDoc = NULL;

 

        CopyMemory(lprcPosRect, &m_rect, sizeof(RECT));

        CopyMemory(lprcClipRect, &m_rect, sizeof(RECT));

 

        lpFrameInfo->cb = sizeof(OLEINPLACEFRAMEINFO);

        lpFrameInfo->fMDIApp = false;

        lpFrameInfo->hwndFrame = GetParent(m_hOleParent);

        lpFrameInfo->haccel = nullptr;

        lpFrameInfo->cAccelEntries = 0;

 

        return S_OK;

    }

       

protected:

    HWND               m_hOleParent;

    IStorage          *m_pStorage;

    IOleObject        *m_pOleObj;

    IOleInPlaceObject *m_pInPlaceObj;

    bool               m_bInPlaceActived;

    RECT               m_rect;

};

 

class OleContainer : public ComClass<OleContainer>,

                        public OleContainerImpl

{

public:

    OleContainer()

    {

       

    }

 

    ~OleContainer()

    {

        DestroyOleObject();

    }

 

public:

    XL_COM_INTERFACE_BEGIN(OleContainer)

        XL_COM_INTERFACE(IOleClientSite)

        XL_COM_INTERFACE(IOleInPlaceSite)

        XL_COM_INTERFACE(IOleInPlaceFrame)

    XL_COM_INTERFACE_END()

};

 

对于InPlaceActive,之前理解有误,OleCreate的时候,并不需要给出窗口,到InPlaceActive之前的瞬间给出即可。而CanInPlaceActive,之前是根据窗口句柄是否为空来确定S_OK还是S_FALSE,这有错误。快要InPlaceActive了,于是去把m_hWnd设上,InPlaceActive去调用CanInPlaceActiveCanInPlaceActive发现窗口句柄有了,于是拒绝……所以形成了我在《裸写一个含内嵌IE控件的窗口》中的尴尬局面。按照上面的实现,逻辑上就没问题了。

 

另外,这里拆成两个,是为了让后面的WebBrowser继承OleContainerImpl,而OleContainer自己又可以独立使用。

 

还有需要注意的是,OleContainer的析构函数里最好主动调用一下父类的资源释放函数,不然,等子类完全析构后再自动调用到父类的析构函数,子类(子类部分)已经完全消亡了,但是QueryInterface之类的本来是定义在子类的,这时候可能会出现纯虚函数调用的错误。下同。

 

再来做WebBrowserhttp://xllib.codeplex.com/SourceControl/changeset/view/19615#315453):

 

class WebBrowserImpl : public OleContainerImpl,

                        public IDocHostUIHandlerImpl<>,

                        public DWebBrowserEvents2Impl<>

{

public:

    WebBrowserImpl() : m_pWebBrowser(nullptr),

                        m_pCPC(nullptr),

                        m_pCP(nullptr)

    {

 

    }

 

    ~WebBrowserImpl()

    {

        DestroyWebBrowser();

    }

 

public:

    bool CreateWebBrowser(HWND hWnd, LPCRECT lpRect = nullptr)

    {

        DestroyWebBrowser();

 

        if (!CreateOleObject(__uuidof(::WebBrowser)))

        {

            return false;

        }

 

        if (!InPlaceActive(hWnd, lpRect))

        {

            return false;

        }

 

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

 

        if (FAILED(hr))

        {

            return false;

        }

 

        hr = m_pWebBrowser->QueryInterface(__uuidof(IConnectionPointContainer), (LPVOID *)&m_pCPC);

 

        if (FAILED(hr))

        {

            return false;

        }

 

        hr = m_pCPC->FindConnectionPoint(__uuidof(DWebBrowserEvents2), &m_pCP);

 

        if (FAILED(hr))

        {

            return false;

        }

 

        DWORD dwCookie = 0;

        hr = m_pCP->Advise((DWebBrowserEvents2 *)this, &dwCookie);

 

        if (FAILED(hr))

        {

            return false;

        }

 

        return true;

    }

 

    void DestroyWebBrowser()

    {

        if (m_pCP != nullptr)

        {

            m_pCP->Release();

            m_pCP = nullptr;

        }

 

        if (m_pCPC != nullptr)

        {

            m_pCPC->Release();

            m_pCPC = nullptr;

        }

 

        if (m_pWebBrowser != nullptr)

        {

            m_pWebBrowser->Release();

            m_pWebBrowser = nullptr;

        }

 

        DestroyOleObject();

    }

 

protected:

    IWebBrowser2              *m_pWebBrowser;

    IConnectionPointContainer *m_pCPC;

    IConnectionPoint          *m_pCP;

};

 

class WebBrowser : public ComClass<WebBrowser>,

                    public WebBrowserImpl

{

public:

    WebBrowser()

    {

 

    }

 

    ~WebBrowser()

    {

        DestroyWebBrowser();

    }

 

public:

    XL_COM_INTERFACE_BEGIN(WebBrowser)

        XL_COM_INTERFACE(IOleClientSite)

        XL_COM_INTERFACE(IOleInPlaceSite)

        XL_COM_INTERFACE(IOleInPlaceFrame)

        XL_COM_INTERFACE(IDocHostUIHandler)

        XL_COM_INTERFACE(DWebBrowserEvents2)

    XL_COM_INTERFACE_END()

};

 

这里很普通,没什么要说的。现在就可以跟原来一样使用了:

 

Main.cpp

class WebBrowser : public xl::WebBrowser

{

public:

    void Navigate(LPCTSTR lpUrl)

    {

        BSTR bstrUrl = SysAllocString(lpUrl);

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

        SysFreeString(bstrUrl);

    }

};

 

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_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,

                             CW_USEDEFAULT,

                             0,

                             CW_USEDEFAULT,

                             0,

                             nullptr,

                             nullptr,

                             hInstance,

                             nullptr);

 

    if (hWnd == nullptr)

    {

        return 0;

    }

 

    ShowWindow(hWnd, nCmdShow);

    UpdateWindow(hWnd);

 

    WebBrowser wb;

 

    if (!wb.CreateWebBrowser(hWnd))

    {

        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_image001

 

和原来一样。例子代码见WebBrowserSample2.rarhttp://pan.baidu.com/s/1c0q4skK

 

posted on 2012-09-03 23:17 溪流 阅读(3541) 评论(4)  编辑 收藏 引用 所属分类: C++WindowsCOM

评论:
# re: 山寨一下 ATL 的 COM_INTERFACE[未登录] 2012-09-04 00:32 | 春秋十二月
有点意思,你好像有较多时间来发明一些轮子,精神可嘉  回复  更多评论
  
# re: 山寨一下 ATL 的 COM_INTERFACE 2012-09-04 00:46 | 毕达哥拉斯半圆
收下来学习以下,这样的工作很好玩。  回复  更多评论
  
# re: 山寨一下 ATL 的 COM_INTERFACE 2012-09-04 10:51 | 溪流
@春秋十二月
都是挤时间的,不是为了发明什么,是为了学习,学习过程中自然要自己写一下,写了一下保存下来就成了所谓的轮子……  回复  更多评论
  
# re: 山寨一下 ATL 的 COM_INTERFACE[未登录] 2014-01-22 15:41 | 123
The non-zero constant is there because the macro does not work with null pointers. We know that the value of a null pointer is a null pointer constant which evaluates to 0:


C++ Standard 4.10/1 Pointer conversions [conv.ptr]:

A null pointer constant is an integral constant expression (5.19) rvalue of integer type that evaluates to zero. A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type and is distinguishable from every other value of pointer to object or pointer to function type....

This is the relevant clause with respect to converting from a derived class to a base class pointer type:


C++ Standard 4.10/3 Pointer conversions [conv.ptr]:

An rvalue of type “pointer to cv D,” where D is a class type, can be converted to an rvalue of type “pointer to cv B,” where B is a base class (clause 10) of D. If B is an inaccessible (clause 11) or ambiguous (10.2) base class of D, a program that necessitates this conversion is ill-formed. The result of the conversion is a pointer to the base class sub-object of the derived class object. The null pointer value is converted to the null pointer value of the destination type.

Basically null pointers prevent pointer arithmetic from kicking in during a derived-to-base conversion; you will just get another null pointer. The arithmetic is used to "fix" non-null pointers during such conversions so that it points to the proper subobject. The offsetofclass macro depends on this arithmetic to determine the offset.

The number 8 used is arbitrary. You could've used any number there like 1 or 4, as long as it wasn't zero.

来自stack overflow  回复  更多评论
  

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