DEV Community

海前 王
海前 王

Posted on

gpt

ChatGPT 4o mini

登录

注册
您说:
纯技术讨论com控件创建过程

用一个没有任何意义的例子来看看com控件的创建过程。又是狠狠忙了一阵子。前几天,一个网上的朋友找到我,他想在richedit里面插入一个ax控件,这个倒是很简单,网上也很多。接下来,他想要在richedit里面插入一个窗口,当然无论ax控件是否设置m_bWindowOnly都无法达到需求。我给他的答案是不行,其实我不知道该如何解释这个问题。不管需求如何怪异,也不管是否解决了他的问题,这里我单纯来强行插入一个窗口到richedit,不关心插入进去是否可以正常工作。声明,这是一个没有意义的事情,只是想说明com控件创建过程。

一个支持com控件的窗口必须创建CWnd的COleControlContainer* m_pCtrlCont; // for containing OLE controls,通过BOOL InitControlContainer();函数,它又会调用虚函数virtual BOOL CreateControlContainer(COleControlContainer** ppContainer);你可以重载来创建你自己的Container。而这个冬冬就好比一个宾馆,每个控件好比一个人,有人生自由,可以入住任何一个宾馆,但是一旦寄宿在一家宾馆,这个宾馆必须为之提供一个房间,这是ControlSite的概念,创建调用:
virtual BOOL CreateControlSite(COleControlContainer* pContainer,
COleControlSite** ppSite, UINT nID, REFCLSID clsid);
这个这个宾馆的房间有多豪华就是你宾馆的星级,当然你可以通过重载这个函数来定制你的房间设施。
再通过:
virtual HRESULT CreateControl(CWnd* pWndCtrl, REFCLSID clsid,
LPCTSTR lpszWindowName, DWORD dwStyle, const POINT* ppt,
const SIZE* psize, UINT nID, CFile* pPersist=NULL, BOOL bStorage=FALSE,
创建com控件。

我们有时候看见这样的创建方式:CWnd::CreateControl()来创建com控件。这其中经历了父窗口初始化ControlContainer,准备一个ControlSite,把com控件创建并安排到此房间并做一些初始化操作,然后把com控件窗口句柄(如果存在)依附到CWnd,这里CWnd充当的是一个与控件通信的代理。可以看出是否创建有窗口还是无窗口的,依赖CreateControl内部操作。再控件里面,我们看到控件实现了这样两个接口:
COM_INTERFACE_ENTRY(IOleInPlaceObjectWindowless)
COM_INTERFACE_ENTRY(IOleInPlaceObject)
他们接口大同小异,一个是有窗口方式一个是无窗口方式,为什么这么做是历史原因,因为窗口耗费大量资源。在不深究下去,有兴趣可以找点老古典来瞅瞅。

下面开始我的创建过程:
我想实现在RichEdit里面创建一个房间(ControlSite)来容纳一个web控件,由于COleControlSite不让我new出来,所以我的继承一个CMyOleControlSite:
class CMyOleControlSite : public COleControlSite
{
public:
CMyOleControlSite(COleControlContainer* pCtrlCont) : COleControlSite(pCtrlCont) {}

protected:
// Implementation
LPUNKNOWN GetInterfaceHook(const void *iid);
// IUnknown methods
STDMETHOD(QueryInterface)(REFIID riid, void **ppvObject);
STDMETHOD_(ULONG, AddRef)();
STDMETHOD_(ULONG, Release)();
};

LPUNKNOWN CMyOleControlSite::GetInterfaceHook(const void iid)
{
if (IsEqualIID((REFIID) (
(IID*)iid), IID_IDocHostUIHandler))
return (IDocHostUIHandler *) this;
return NULL;
}

// IUnknown methods
STDMETHODIMP CMyOleControlSite::QueryInterface(REFIID riid, void **ppvObject)
{
return COleControlSite::ExternalQueryInterface(&riid, ppvObject);
}
STDMETHODIMP_(ULONG) CMyOleControlSite::AddRef()
{
return ExternalAddRef();
}

STDMETHODIMP_(ULONG) CMyOleControlSite::Release()
{
return ExternalRelease();
}
我要创建自己的ControlSite,而CreateControlSite是protect的,所以我的派生一下:
class CMyRichEditCtrl : public CRichEditCtrl
{
friend class CTestCtrlInRichEditDlg;
DECLARE_DYNAMIC(CMyRichEditCtrl)
public:
CWnd m_wndCtrl;

BOOL CreateControlSite(COleControlContainer* pContainer, 
    COleControlSite** ppSite, UINT /* nID */, REFCLSID /* clsid */);
Enter fullscreen mode Exit fullscreen mode

};
实现之:
IMPLEMENT_DYNAMIC(CMyRichEditCtrl, CRichEditCtrl)

BOOL CMyRichEditCtrl::CreateControlSite(COleControlContainer* pContainer,
COleControlSite** ppSite, UINT /* nID /, REFCLSID / clsid */)
{
if(ppSite == NULL)
{
ASSERT(FALSE);
return FALSE;
}

CMyOleControlSite *pOleSite = 
    new CMyOleControlSite(pContainer);

*ppSite = pOleSite;
(*ppSite)->m_pCtrlCont = pContainer;
return TRUE;
Enter fullscreen mode Exit fullscreen mode

}

在对话框的OnInitDialog函数里面:
m_wndRichCtrl.InitControlContainer();

COleControlSite* pControlSite = NULL;
m_wndRichCtrl.CreateControlSite(m_wndRichCtrl.GetControlContainer(), &pControlSite, 102, CLSID_NULL/*CLSID_WebBrowser*/);
POINT pt = { 20, 20 };
SIZE sz = { 300, 300 };
pControlSite->CreateControl(&m_wndRichCtrl.m_wndCtrl, CLSID_WebBrowser,
    _T("^_^"), WS_VISIBLE|WS_CHILD|WS_TABSTOP|WS_GROUP, &pt, &sz, 102);
COleControlSiteOrWnd* pSiteOrWnd = new COleControlSiteOrWnd(pControlSite);
m_wndRichCtrl.GetControlContainer()->m_listSitesOrWnds.AddTail(pSiteOrWnd);
IWebBrowser2* pIWebBrowser = NULL;
pControlSite->m_pInPlaceObject->QueryInterface(IID_IWebBrowser2, (void**)&pIWebBrowser);
pIWebBrowser->Navigate2(&CComVariant("www.cppblog.com/wlwlxj"), NULL, NULL, NULL, NULL);
pIWebBrowser->Release();
IOleControl* pIOleCtrl = NULL;
pControlSite->m_pInPlaceObject->QueryInterface(IID_IOleControl, (void**)&pIOleCtrl);
pIOleCtrl->Release();代码的过程
Enter fullscreen mode Exit fullscreen mode

ChatGPT 说:
ChatGPT
在你的示例中,你描述了如何在 CRichEditCtrl 中插入一个 COM 控件,并通过自定义实现管理控件的创建和位置。这个过程涉及几个重要的步骤,包括创建 COleControlSite 和 COleControlContainer,以及如何将控件嵌入到 CRichEditCtrl 中。让我详细解释一下这些步骤,并提供一些额外的背景信息,以帮助理解 COM 控件创建的过程。

COM 控件创建过程详细解释
初始化 Control Container

CRichEditCtrl 需要一个 COleControlContainer 作为控件的宿主。这个容器负责管理控件的生命周期以及与控件的交互。在你的示例中,你通过 InitControlContainer 方法初始化了容器。

cpp
复制代码
m_wndRichCtrl.InitControlContainer();
创建 Control Site

COleControlSite 是控件的 "房间",它负责管理控件在容器中的位置和外观。CreateControlSite 方法负责创建一个 COleControlSite 实例。在你的例子中,你重载了 CreateControlSite 方法,以返回一个自定义的 CMyOleControlSite 对象。

cpp
复制代码
BOOL CMyRichEditCtrl::CreateControlSite(COleControlContainer* pContainer,
COleControlSite** ppSite, UINT /* nID /, REFCLSID / clsid */)
{
if(ppSite == NULL)
{
ASSERT(FALSE);
return FALSE;
}

CMyOleControlSite *pOleSite = new CMyOleControlSite(pContainer);
*ppSite = pOleSite;
(*ppSite)->m_pCtrlCont = pContainer;
return TRUE;
Enter fullscreen mode Exit fullscreen mode

}
创建控件

通过 COleControlSite::CreateControl 方法,你可以创建并初始化实际的 COM 控件。在你的示例中,你创建了一个 Web 浏览器控件,并将其嵌入到 CRichEditCtrl 中。

cpp
复制代码
POINT pt = { 20, 20 };
SIZE sz = { 300, 300 };
pControlSite->CreateControl(&m_wndRichCtrl.m_wndCtrl, CLSID_WebBrowser,
T("^^"), WS_VISIBLE|WS_CHILD|WS_TABSTOP|WS_GROUP, &pt, &sz, 102);
管理控件的接口

你可以通过 COleControlSite 获取控件的接口(例如 IWebBrowser2)。这允许你对控件进行操作,比如导航到一个 URL。

cpp
复制代码
IWebBrowser2* pIWebBrowser = NULL;
pControlSite->m_pInPlaceObject->QueryInterface(IID_IWebBrowser2, (void**)&pIWebBrowser);
pIWebBrowser->Navigate2(&CComVariant("www.cppblog.com/wlwlxj"), NULL, NULL, NULL, NULL);
pIWebBrowser->Release();
你也可以通过 IOleControl 接口对控件进行其他操作,如设置属性或调用方法。

cpp
复制代码
IOleControl* pIOleCtrl = NULL;
pControlSite->m_pInPlaceObject->QueryInterface(IID_IOleControl, (void**)&pIOleCtrl);
pIOleCtrl->Release();
关键点总结
Control Container (COleControlContainer): 控件容器负责管理控件的创建和销毁,提供控件所需的环境。
Control Site (COleControlSite): 控件的宿主,它负责管理控件的位置和交互。
控件创建 (CreateControl): 通过 CreateControl 方法,你可以在指定的位置和大小创建一个控件,并将其嵌入到宿主窗口中。
接口管理: 通过 COM 接口(如 IWebBrowser2 和 IOleControl),你可以与控件进行交互,执行各种操作。
附加说明
虽然你的示例中展示了如何在 CRichEditCtrl 中创建 COM 控件,但在实际应用中,你可能还需要处理更多的细节,如消息处理、控件的生命周期管理等。此外,由于 CRichEditCtrl 的特殊性,它可能不完全支持所有 COM 控件,因此实际效果可能会有所不同。

希望这些说明能够帮助你更好地理解 COM 控件的创建过程。如果你有其他问题或需要进一步的解释,请随时问我!

您说:
完整的示例
ChatGPT 说:
ChatGPT
为了提供一个完整的示例,我将展示如何在 MFC 应用程序中将一个 Web 浏览器控件(或其他 COM 控件)嵌入到 CRichEditCtrl 中。这个示例将涵盖所有必要的步骤,包括创建控件容器、创建控件站点、以及初始化控件。

假设你已经有一个基本的 MFC 应用程序,以下是如何实现将 Web 浏览器控件嵌入到 CRichEditCtrl 中的完整示例:

  1. 添加必要的头文件和库 确保你的项目链接了必要的库,并包含了必要的头文件。在你的项目设置中,确保链接了 ole32.lib 和 oleaut32.lib。在源文件中包含以下头文件:

cpp
复制代码

include

include

include

include

include

  1. 创建自定义控件站点类 创建一个自定义的控件站点类 CMyOleControlSite,它继承自 COleControlSite:

cpp
复制代码
class CMyOleControlSite : public COleControlSite
{
public:
CMyOleControlSite(COleControlContainer* pCtrlCont) : COleControlSite(pCtrlCont) {}

protected:
LPUNKNOWN GetInterfaceHook(const void *iid) override;
STDMETHOD(QueryInterface)(REFIID riid, void **ppvObject) override;
STDMETHOD_(ULONG, AddRef)() override;
STDMETHOD_(ULONG, Release)() override;
};

LPUNKNOWN CMyOleControlSite::GetInterfaceHook(const void iid)
{
if (IsEqualIID((REFIID) (
(IID*)iid), IID_IDocHostUIHandler))
return (IDocHostUIHandler *) this;
return NULL;
}

STDMETHODIMP CMyOleControlSite::QueryInterface(REFIID riid, void **ppvObject)
{
return COleControlSite::ExternalQueryInterface(&riid, ppvObject);
}

STDMETHODIMP_(ULONG) CMyOleControlSite::AddRef()
{
return ExternalAddRef();
}

STDMETHODIMP_(ULONG) CMyOleControlSite::Release()
{
return ExternalRelease();
}

  1. 创建自定义的 CRichEditCtrl 子类 创建一个自定义的 CRichEditCtrl 子类 CMyRichEditCtrl,并重载 CreateControlSite 方法:

cpp
复制代码
class CMyRichEditCtrl : public CRichEditCtrl
{
public:
CWnd m_wndCtrl;

BOOL CreateControlSite(COleControlContainer* pContainer, COleControlSite** ppSite, UINT /* nID */, REFCLSID /* clsid */);
Enter fullscreen mode Exit fullscreen mode

};

BOOL CMyRichEditCtrl::CreateControlSite(COleControlContainer* pContainer, COleControlSite** ppSite, UINT /* nID /, REFCLSID / clsid */)
{
if (ppSite == NULL)
{
ASSERT(FALSE);
return FALSE;
}

CMyOleControlSite* pOleSite = new CMyOleControlSite(pContainer);
*ppSite = pOleSite;
(*ppSite)->m_pCtrlCont = pContainer;
return TRUE;
Enter fullscreen mode Exit fullscreen mode

}

  1. 使用控件 在你的对话框类(例如 CTestCtrlInRichEditDlg)中,使用 CMyRichEditCtrl 来创建和管理 Web 浏览器控件:

cpp
复制代码
class CTestCtrlInRichEditDlg : public CDialogEx
{
// 其他对话框成员...

protected:
CMyRichEditCtrl m_wndRichCtrl;

virtual BOOL OnInitDialog() override;
Enter fullscreen mode Exit fullscreen mode

};

BOOL CTestCtrlInRichEditDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();

// 初始化 RichEdit 控件
m_wndRichCtrl.Create(WS_CHILD | WS_VISIBLE | ES_MULTILINE, CRect(10, 10, 600, 400), this, 1);
m_wndRichCtrl.InitControlContainer();

// 创建控件站点
COleControlSite* pControlSite = NULL;
m_wndRichCtrl.CreateControlSite(m_wndRichCtrl.GetControlContainer(), &pControlSite, 102, CLSID_WebBrowser);

// 创建 Web 浏览器控件
POINT pt = { 20, 20 };
SIZE sz = { 300, 300 };
pControlSite->CreateControl(&m_wndRichCtrl.m_wndCtrl, CLSID_WebBrowser,
    _T("Web Browser"), WS_VISIBLE | WS_CHILD | WS_TABSTOP | WS_GROUP, &pt, &sz, 102);

// 使用 Web 浏览器控件
IWebBrowser2* pIWebBrowser = NULL;
pControlSite->m_pInPlaceObject->QueryInterface(IID_IWebBrowser2, (void**)&pIWebBrowser);
if (pIWebBrowser)
{
    pIWebBrowser->Navigate2(&CComVariant("https://www.example.com"), NULL, NULL, NULL, NULL);
    pIWebBrowser->Release();
}

// 释放资源
IOleControl* pIOleCtrl = NULL;
pControlSite->m_pInPlaceObject->QueryInterface(IID_IOleControl, (void**)&pIOleCtrl);
if (pIOleCtrl)
{
    pIOleCtrl->Release();
}

return TRUE;
Enter fullscreen mode Exit fullscreen mode

}

  1. 运行你的应用程序 确保你的应用程序正确地初始化了 COM 库,通常这可以在 InitInstance 方法中完成:

cpp
复制代码
BOOL CMyApp::InitInstance()
{
AfxOleInit(); // 初始化 OLE 库

CTestCtrlInRichEditDlg dlg;
m_pMainWnd = &dlg;
dlg.DoModal();

return FALSE;
Enter fullscreen mode Exit fullscreen mode

}
备注
确保你的项目设置正确,包含了 ATL 库,且 COM 库初始化正确。
这个示例展示了如何将一个 Web 浏览器控件嵌入到 CRichEditCtrl 中。实际应用中,控件的创建和管理可能会涉及更多细节,例如处理控件的消息和事件等。
这个示例涵盖了创建 COM 控件、将其嵌入到 CRichEditCtrl 中的完整过程。如果你有其他问题或需要进一步的帮助,请告诉我!

您说:
完整代码
ChatGPT 说:
ChatGPT
好的,我将提供一个完整的 MFC 示例代码,演示如何将一个 Web 浏览器控件嵌入到 CRichEditCtrl 中。为了简化,示例将包括创建自定义控件站点、创建和管理控件,以及在对话框中显示这些控件的步骤。

  1. 创建 MFC 应用程序
    首先,创建一个新的 MFC 应用程序项目。如果你使用 Visual Studio,可以选择 MFC 应用程序 模板。

  2. 添加必要的库和头文件
    确保在项目设置中链接了以下库:

ole32.lib
oleaut32.lib
uuid.lib
在源文件中包含以下头文件:

cpp
复制代码

include

include

include

include

include

  1. 创建自定义控件站点类 创建一个新的头文件 MyOleControlSite.h,并实现 CMyOleControlSite 类:

cpp
复制代码
// MyOleControlSite.h

pragma once

include

class CMyOleControlSite : public COleControlSite
{
public:
CMyOleControlSite(COleControlContainer* pCtrlCont) : COleControlSite(pCtrlCont) {}

protected:
LPUNKNOWN GetInterfaceHook(const void *iid) override;
STDMETHOD(QueryInterface)(REFIID riid, void **ppvObject) override;
STDMETHOD_(ULONG, AddRef)() override;
STDMETHOD_(ULONG, Release)() override;
};

LPUNKNOWN CMyOleControlSite::GetInterfaceHook(const void iid)
{
if (IsEqualIID((REFIID) (
(IID*)iid), IID_IDocHostUIHandler))
return (IDocHostUIHandler *) this;
return NULL;
}

STDMETHODIMP CMyOleControlSite::QueryInterface(REFIID riid, void **ppvObject)
{
return COleControlSite::ExternalQueryInterface(&riid, ppvObject);
}

STDMETHODIMP_(ULONG) CMyOleControlSite::AddRef()
{
return ExternalAddRef();
}

STDMETHODIMP_(ULONG) CMyOleControlSite::Release()
{
return ExternalRelease();
}

  1. 创建自定义 CRichEditCtrl 子类 创建一个新的头文件 MyRichEditCtrl.h,并实现 CMyRichEditCtrl 类:

cpp
复制代码
// MyRichEditCtrl.h

pragma once

include

include "MyOleControlSite.h"

class CMyRichEditCtrl : public CRichEditCtrl
{
public:
CWnd m_wndCtrl;

BOOL CreateControlSite(COleControlContainer* pContainer, COleControlSite** ppSite, UINT /* nID */, REFCLSID /* clsid */);
Enter fullscreen mode Exit fullscreen mode

};
实现 CMyRichEditCtrl 的源文件 MyRichEditCtrl.cpp:

cpp
复制代码
// MyRichEditCtrl.cpp

include "stdafx.h"

include "MyRichEditCtrl.h"

BOOL CMyRichEditCtrl::CreateControlSite(COleControlContainer* pContainer, COleControlSite** ppSite, UINT /* nID /, REFCLSID / clsid */)
{
if (ppSite == NULL)
{
ASSERT(FALSE);
return FALSE;
}

CMyOleControlSite* pOleSite = new CMyOleControlSite(pContainer);
*ppSite = pOleSite;
(*ppSite)->m_pCtrlCont = pContainer;
return TRUE;
Enter fullscreen mode Exit fullscreen mode

}

  1. 使用控件 在你的对话框类(例如 CTestCtrlInRichEditDlg)中,使用 CMyRichEditCtrl 来创建和管理 Web 浏览器控件。修改对话框类头文件 TestCtrlInRichEditDlg.h 和实现文件 TestCtrlInRichEditDlg.cpp 如下:

TestCtrlInRichEditDlg.h

cpp
复制代码
// TestCtrlInRichEditDlg.h

pragma once

include "MyRichEditCtrl.h"

class CTestCtrlInRichEditDlg : public CDialogEx
{
public:
CTestCtrlInRichEditDlg(CWnd* pParent = nullptr);

ifdef AFX_DESIGN_TIME

enum { IDD = IDD_TESTCTRLINRICHEDIT_DIALOG };
Enter fullscreen mode Exit fullscreen mode

endif

protected:
virtual void DoDataExchange(CDataExchange* pDX);
virtual BOOL OnInitDialog();
DECLARE_MESSAGE_MAP()

private:
CMyRichEditCtrl m_wndRichCtrl;
};
TestCtrlInRichEditDlg.cpp

cpp
复制代码
// TestCtrlInRichEditDlg.cpp

include "stdafx.h"

include "TestCtrlInRichEditDlg.h"

include "afxdialogex.h"

ifdef _DEBUG

define new DEBUG_NEW

endif

CTestCtrlInRichEditDlg::CTestCtrlInRichEditDlg(CWnd* pParent /=nullptr/)
: CDialogEx(IDD_TESTCTRLINRICHEDIT_DIALOG, pParent)
{
}

void CTestCtrlInRichEditDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CTestCtrlInRichEditDlg, CDialogEx)
END_MESSAGE_MAP()

BOOL CTestCtrlInRichEditDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();

// 初始化 RichEdit 控件
m_wndRichCtrl.Create(WS_CHILD | WS_VISIBLE | ES_MULTILINE, CRect(10, 10, 600, 400), this, 1);
m_wndRichCtrl.InitControlContainer();

// 创建控件站点
COleControlSite* pControlSite = NULL;
m_wndRichCtrl.CreateControlSite(m_wndRichCtrl.GetControlContainer(), &pControlSite, 102, CLSID_WebBrowser);

// 创建 Web 浏览器控件
POINT pt = { 20, 20 };
SIZE sz = { 300, 300 };
pControlSite->CreateControl(&m_wndRichCtrl.m_wndCtrl, CLSID_WebBrowser,
    _T("Web Browser"), WS_VISIBLE | WS_CHILD | WS_TABSTOP | WS_GROUP, &pt, &sz, 102);

// 使用 Web 浏览器控件
IWebBrowser2* pIWebBrowser = NULL;
pControlSite->m_pInPlaceObject->QueryInterface(IID_IWebBrowser2, (void**)&pIWebBrowser);
if (pIWebBrowser)
{
    pIWebBrowser->Navigate2(&CComVariant("https://www.example.com"), NULL, NULL, NULL, NULL);
    pIWebBrowser->Release();
}

// 释放资源
IOleControl* pIOleCtrl = NULL;
pControlSite->m_pInPlaceObject->QueryInterface(IID_IOleControl, (void**)&pIOleCtrl);
if (pIOleCtrl)
{
    pIOleCtrl->Release();
}

return TRUE;
Enter fullscreen mode Exit fullscreen mode

}

  1. 初始化 COM 库 确保在 InitInstance 方法中初始化 COM 库:

cpp
复制代码
// YourApp.cpp

include "stdafx.h"

include "YourApp.h"

BEGIN_MESSAGE_MAP(CYourApp, CWinApp)
END_MESSAGE_MAP()

CYourApp::CYourApp()
{
// TODO: add construction code here,
}

CYourApp theApp;

BOOL CYourApp::InitInstance()
{
CWinApp::InitInstance();

AfxOleInit();  // 初始化 OLE 库

CTestCtrlInRichEditDlg dlg;
m_pMainWnd = &dlg;
dlg.DoModal();

return FALSE;
Enter fullscreen mode Exit fullscreen mode

}

  1. 编译和运行 确保你的项目正确配置,包含所有必要的库和头文件。然后编译并运行你的应用程序。对话框中应该会显示一个包含 Web 浏览器控件的 CRichEditCtrl。

总结
这个完整的示例展示了如何在 MFC 应用程序中将 COM 控件(如 Web 浏览器控件)嵌入到 CRichEditCtrl 中。这个示例涵盖了自定义控件站点类、创建和管理控件的步骤。你可以根据实际需求进一步扩展和修改这个示例。如果有任何问题,请随时问我!

ChatGPT 也可能会犯错。请核查重要信息。
?

Top comments (0)