对 OLE (Object Linking and Embedding) 进行详细深入的解析,包括 UML 建模、项目结构、实例实现。覆盖 OLE 的本质、原理、机制,并实现一个简单的 OLE 容器和服务器示例(或至少演示嵌入/链接、就地激活)。由于 OLE 复杂,可以简化但必须展现关键接口:IOleObject, IOleClientSite, IOleInPlaceFrame, IOleInPlaceObject, IStorage, IPersistStorage 等。

OLE (Object Linking and Embedding) 工作原理与机制:UML 建模与实现方案

1. 项目概述

OLE(Object Linking and Embedding)是基于 COM 的复合文档技术,允许在一个文档(容器)中嵌入或链接由另一个应用程序(服务器)创建的数据。经典例子:在 Word 文档中嵌入 Excel 表格,双击即可就地编辑。

核心概念

  • 嵌入(Embedding):将数据的完整副本保存到容器文档中,独立于源文件。
  • 链接(Linking):容器文档只保存指向源文件的引用,数据动态更新。
  • 就地激活(In-Place Activation):用户双击对象时,服务器程序界面无缝融入容器窗口,实现编辑。

关键接口

  • 容器IOleClientSiteIOleInPlaceSiteIOleInPlaceFrameIAdviseSink
  • 服务器IOleObjectIOleInPlaceObjectIOleInPlaceActiveObjectIPersistStorageIDataObject

本项目实现一个简单的 OLE 容器(类似画板,可插入 OLE 对象)和一个简单的 OLE 服务器(提供圆形图形对象),演示嵌入、链接、就地激活。


2. 项目文件结构

ole_demo/
├── README.md
├── docs/
│   └── uml.md                      # UML 图
├── idl/
│   └── OleShapes.idl               # 定义图形对象接口
├── container/                      # OLE 容器应用程序
│   ├── ContainerWindow.h
│   ├── ContainerWindow.cpp         # 主窗口,管理嵌入对象
│   ├── OleClientSite.h
│   ├── OleClientSite.cpp           # IOleClientSite 实现
│   ├── OleInPlaceSite.h
│   ├── OleInPlaceSite.cpp          # IOleInPlaceSite
│   ├── main.cpp
│   └── Makefile
├── server/                         # OLE 服务器(图形对象)
│   ├── ShapeObject.h
│   ├── ShapeObject.cpp             # 实现 IOleObject, IPersistStorage, IDataObject
│   ├── ShapeInPlaceObj.h
│   ├── ShapeInPlaceObj.cpp         # 就地激活窗口
│   ├── ShapeFactory.h
│   ├── ShapeFactory.cpp
│   ├── dllmain.cpp
│   ├── server.def
│   └── Makefile
└── tools/
    └── register.bat

3. UML 建模(Mermaid)

3.1 类图 – OLE 容器与服务器的接口关系

creates

holds

delegates in-place

creates on activation

«interface»

IOleObject

+...

+SetClientSite()

+GetClientSite()

+DoVerb()

+Close()

«interface»

IOleClientSite

+...

+SaveObject()

+ShowObject()

«interface»

IOleInPlaceSite

+...

+GetWindow()

+OnInPlaceActivate()

«interface»

IOleInPlaceFrame

+...

+SetActiveObject()

+InsertMenus()

«interface»

IOleInPlaceObject

+...

+SetObjectRects()

+InPlaceDeactivate()

«interface»

IPersistStorage

+...

+Load()

+Save()

«interface»

IDataObject

+...

+GetData()

+SetData()

ContainerWindow

+HWND hWnd

+list<OleClientSite> m_objects

+OnInsertObject()

+OnDraw()

OleClientSite

-ContainerWindow* pContainer

-IOleObject* pObject

+IOleClientSite methods

OleInPlaceSite

-ContainerWindow* pContainer

+IOleInPlaceSite methods

ShapeObject

-DWORD m_color

-RECT m_rect

-IOleClientSite* m_pClientSite

+IOleObject methods

+IPersistStorage methods

+IDataObject methods

ShapeInPlaceObj

-ShapeObject* m_pParent

-HWND m_hWnd

+IOleInPlaceObject methods

3.2 序列图 – 嵌入对象并就地激活

ShapeInPlaceObj ShapeObject (服务器) OLE库 OleClientSite 容器主窗口 User ShapeInPlaceObj ShapeObject (服务器) OLE库 OleClientSite 容器主窗口 User 插入对象菜单 OleCreate(CLSID_Shape, ...) CoCreateInstance IOleObject IOleObject::SetClientSite(ClientSite) IPersistStorage::InitNew 返回对象指针 创建 ClientSite,保存对象 双击对象 调用对象 DoVerb(OLEIVERB_INPLACEACTIVATE) IOleObject::DoVerb(...) 创建 ShapeInPlaceObj 创建窗口 (CreateWindow) IOleInPlaceSite::GetWindow 容器窗口句柄 IOleInPlaceFrame::SetActiveObject 合并菜单/工具栏 显示窗口,位置由 SetObjectRects 设置 用户编辑(改变颜色、大小) 点击外部区域 IOleObject::Close InPlaceDeactivate 销毁窗口 IOleClientSite::SaveObject IOleClientSite::ShowObject (刷新显示)

3.3 活动图 – 容器文档的保存(嵌入数据)

用户点击保存

容器打开 IStorage

遍历每个嵌入对象

获取对象的 IOleObject

调用 QueryInterface for IPersistStorage

对象支持?

创建子 IStorage

调用 IPersistStorage::Save

对象保存自身数据到子存储

使用 IDataObject 保存

继续下一个对象

完成

3.4 部署图 – OLE 服务器 DLL 与容器 EXE

渲染错误: Mermaid 渲染失败: No diagram type detected matching given configuration for text: deployment node "User PC" { artifact "OleContainer.exe" as container artifact "OleShape.dll" as server_dll artifact "Windows Registry" as registry } container --> server_dll : CoCreateInstance server_dll --> registry : 查询 CLSID container --> registry : 获取服务器位置

3.5 状态图 – OLE 对象的生命周期

OleCreate / CoCreateInstance

SetClientSite, Load

DoVerb(OLEIVERB_SHOW)

DoVerb(OLEIVERB_INPLACEACTIVATE)

用户获得焦点

失去焦点

Close / Save

Release

未加载

已实例化

无活动

运行中

就地激活

UI激活


4. IDL 定义(图形对象接口)

文件idl/OleShapes.idl

import "unknwn.idl";
import "objidl.idl";
import "oleidl.idl";

[
    object,
    uuid(12345678-1234-1234-1234-123456789ABC),  // IID_IShape
    pointer_default(unique)
]
interface IShape : IUnknown
{
    HRESULT SetColor([in] COLORREF color);
    HRESULT GetColor([out] COLORREF* pColor);
    HRESULT SetRect([in] RECT rc);
    HRESULT GetRect([out] RECT* prc);
};

[
    uuid(87654321-4321-4321-4321-CBA987654321),  // CLSID_Shape
    helpstring("OLE Shape Object")
]
coclass Shape
{
    [default] interface IShape;
    interface IOleObject;
    interface IPersistStorage;
    interface IDataObject;
};

MIDL 编译:midl OleShapes.idl 生成 .h_i.c


5. OLE 容器实现(C++)

5.1 容器主窗口(管理多个嵌入对象)

文件container/ContainerWindow.h

#include <windows.h>
#include <ole2.h>
#include <vector>
#include "OleClientSite.h"

class ContainerWindow
{
private:
    HWND m_hWnd;
    std::vector<OleClientSite*> m_objects;
    RECT m_clientRect;

public:
    ContainerWindow();
    ~ContainerWindow();
    BOOL Create();
    void OnPaint();
    void OnInsertObject();   // 插入新的 OLE 对象
    void OnSaveDocument();
    void OnLoadDocument();

    HWND GetWindow() const { return m_hWnd; }
    void AddObject(OleClientSite* pSite);
    void RemoveObject(OleClientSite* pSite);
    void DrawObject(HDC hdc, OleClientSite* pSite, RECT& rc);
    // IOleInPlaceFrame 相关(简化,实际需要实现接口)
    void OnUIActivate(IOleInPlaceActiveObject* pActiveObj);
    void OnUIDeactivate();
};

文件container/ContainerWindow.cpp(关键部分)

#include "ContainerWindow.h"
#include <commctrl.h>

// 窗口过程
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    ContainerWindow* pThis = (ContainerWindow*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
    switch (msg)
    {
    case WM_CREATE:
        pThis = new ContainerWindow();
        SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)pThis);
        pThis->Create();
        break;
    case WM_PAINT:
        if (pThis) pThis->OnPaint();
        break;
    case WM_COMMAND:
        if (LOWORD(wParam) == 1) pThis->OnInsertObject();
        if (LOWORD(wParam) == 2) pThis->OnSaveDocument();
        break;
    case WM_DESTROY:
        delete pThis;
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, msg, wParam, lParam);
    }
    return 0;
}

void ContainerWindow::OnInsertObject()
{
    // 调用 OLE 插入对象对话框
    IOleObject* pObject = NULL;
    HRESULT hr = OleCreate(CLSID_Shape, IID_IOleObject, OLERENDER_DRAW, NULL, NULL, NULL, NULL, (void**)&pObject);
    if (SUCCEEDED(hr))
    {
        OleClientSite* pSite = new OleClientSite(this, pObject);
        m_objects.push_back(pSite);
        // 设置对象的位置和大小(暂定 100x100)
        RECT rc = { 50, 50, 150, 150 };
        pSite->SetRect(rc);
        InvalidateRect(m_hWnd, NULL, TRUE);
        pObject->Release();
    }
}

void ContainerWindow::OnPaint()
{
    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(m_hWnd, &ps);
    // 绘制所有嵌入对象(通过 IDataObject 或直接调用 IViewObject)
    for (OleClientSite* pSite : m_objects)
    {
        RECT rc = pSite->GetRect();
        DrawObject(hdc, pSite, rc);
    }
    EndPaint(m_hWnd, &ps);
}

void ContainerWindow::DrawObject(HDC hdc, OleClientSite* pSite, RECT& rc)
{
    // 调用对象的 IViewObject::Draw
    IOleObject* pObj = pSite->GetObject();
    IViewObject* pView = NULL;
    if (SUCCEEDED(pObj->QueryInterface(IID_IViewObject, (void**)&pView)))
    {
        pView->Draw(DVASPECT_CONTENT, -1, NULL, NULL, hdc, NULL, (RECTL*)&rc, NULL, NULL, 0);
        pView->Release();
    }
}

5.2 实现 IOleClientSite

文件container/OleClientSite.h

#include <ole2.h>
#include "ContainerWindow.h"

class OleClientSite : public IOleClientSite
{
private:
    LONG m_cRef;
    ContainerWindow* m_pContainer;
    IOleObject* m_pObject;
    RECT m_rect;               // 对象在容器中的位置
    IOleInPlaceSite* m_pInPlaceSite;  // 用于就地激活

public:
    OleClientSite(ContainerWindow* pContainer, IOleObject* pObject);
    ~OleClientSite();

    // IUnknown
    STDMETHODIMP QueryInterface(REFIID riid, void** ppv);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();

    // IOleClientSite
    STDMETHODIMP SaveObject();
    STDMETHODIMP GetMoniker(DWORD dwAssign, DWORD dwWhichMoniker, IMoniker** ppmk);
    STDMETHODIMP GetContainer(LPOLECONTAINER FAR* ppContainer);
    STDMETHODIMP ShowObject();
    STDMETHODIMP OnShowWindow(BOOL fShow);
    STDMETHODIMP RequestNewObjectLayout();

    // Helper
    IOleObject* GetObject() const { return m_pObject; }
    void SetRect(RECT rc) { m_rect = rc; }
    RECT GetRect() const { return m_rect; }
};

实现

#include "OleClientSite.h"
#include "OleInPlaceSite.h"

OleClientSite::OleClientSite(ContainerWindow* pContainer, IOleObject* pObject)
    : m_cRef(1), m_pContainer(pContainer), m_pObject(pObject), m_pInPlaceSite(NULL)
{
    m_pObject->SetClientSite(this);
    // 创建就地站点
    m_pInPlaceSite = new OleInPlaceSite(pContainer, this);
    // 设置对象范围
    m_pObject->SetExtent(DVASPECT_CONTENT, (SIZEL*)&m_rect);
}

OleClientSite::~OleClientSite()
{
    if (m_pInPlaceSite) m_pInPlaceSite->Release();
    if (m_pObject) m_pObject->Release();
}

STDMETHODIMP OleClientSite::QueryInterface(REFIID riid, void** ppv)
{
    if (riid == IID_IUnknown || riid == IID_IOleClientSite)
    {
        *ppv = static_cast<IOleClientSite*>(this);
        AddRef();
        return S_OK;
    }
    *ppv = NULL;
    return E_NOINTERFACE;
}

STDMETHODIMP_(ULONG) OleClientSite::AddRef() { return InterlockedIncrement(&m_cRef); }
STDMETHODIMP_(ULONG) OleClientSite::Release()
{
    ULONG ref = InterlockedDecrement(&m_cRef);
    if (ref == 0) delete this;
    return ref;
}

STDMETHODIMP OleClientSite::SaveObject()
{
    // 通知对象保存到持久化存储(容器实现 IStorage 链)
    // 简化:调用 IPersistStorage::Save
    return S_OK;
}

STDMETHODIMP OleClientSite::ShowObject()
{
    // 重绘容器中该对象的区域
    InvalidateRect(m_pContainer->GetWindow(), &m_rect, TRUE);
    return S_OK;
}

// 其他方法简化返回 E_NOTIMPL

5.3 实现 IOleInPlaceSite

文件container/OleInPlaceSite.cpp

class OleInPlaceSite : public IOleInPlaceSite
{
private:
    LONG m_cRef;
    ContainerWindow* m_pContainer;
    OleClientSite* m_pClientSite;
public:
    OleInPlaceSite(ContainerWindow* pContainer, OleClientSite* pSite)
        : m_cRef(1), m_pContainer(pContainer), m_pClientSite(pSite) {}
    // IUnknown 标准实现
    STDMETHODIMP GetWindow(HWND* phwnd) { *phwnd = m_pContainer->GetWindow(); return S_OK; }
    STDMETHODIMP CanInPlaceActivate() { return S_OK; }
    STDMETHODIMP OnInPlaceActivate() { return S_OK; }
    STDMETHODIMP GetWindowContext(IOleInPlaceFrame** ppFrame, IOleInPlaceUIWindow** ppDoc, LPRECT lprcPosRect, LPRECT lprcClipRect, LPOLEINPLACEFRAMEINFO lpFrameInfo)
    {
        // 返回容器框架(简化:容器本身实现 IOleInPlaceFrame)
        // 这里返回 NULL 表示就地激活无菜单合并等高级功能
        *ppFrame = NULL;
        *ppDoc = NULL;
        // 设置位置矩形
        *lprcPosRect = m_pClientSite->GetRect();
        GetClientRect(m_pContainer->GetWindow(), lprcClipRect);
        // 填充框架信息
        lpFrameInfo->fMDIApp = FALSE;
        lpFrameInfo->hwndFrame = m_pContainer->GetWindow();
        lpFrameInfo->haccel = NULL;
        lpFrameInfo->cAccelEntries = 0;
        return S_OK;
    }
    // 其他方法...
};

6. OLE 服务器实现(图形对象)

6.1 ShapeObject 类(核心)

文件server/ShapeObject.h

#include "OleShapes.h"  // MIDL generated
#include <ole2.h>

class ShapeObject : public IShape, public IOleObject, public IPersistStorage, public IDataObject, public IViewObject
{
private:
    LONG m_cRef;
    COLORREF m_color;
    RECT m_rect;
    IOleClientSite* m_pClientSite;
    IStorage* m_pStorage;               // 持久化存储
    BOOL m_bDirty;
    // 就地激活相关
    ShapeInPlaceObj* m_pInPlaceObj;

public:
    ShapeObject();
    ~ShapeObject();

    // IUnknown
    STDMETHODIMP QueryInterface(REFIID riid, void** ppv);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();

    // IShape
    STDMETHODIMP SetColor(COLORREF color);
    STDMETHODIMP GetColor(COLORREF* pColor);
    STDMETHODIMP SetRect(RECT rc);
    STDMETHODIMP GetRect(RECT* prc);

    // IOleObject
    STDMETHODIMP SetClientSite(IOleClientSite* pClientSite);
    STDMETHODIMP GetClientSite(IOleClientSite** ppClientSite);
    STDMETHODIMP SetHostNames(LPCOLESTR szContainerApp, LPCOLESTR szContainerObj);
    STDMETHODIMP Close(DWORD dwSaveOption);
    STDMETHODIMP SetMoniker(DWORD dwWhichMoniker, IMoniker* pmk);
    STDMETHODIMP GetMoniker(DWORD dwAssign, DWORD dwWhichMoniker, IMoniker** ppmk);
    STDMETHODIMP InitFromData(IDataObject* pDataObject, BOOL fCreation, DWORD dwReserved);
    STDMETHODIMP GetClipboardData(DWORD dwReserved, IDataObject** ppDataObject);
    STDMETHODIMP DoVerb(LONG iVerb, LPMSG lpmsg, IOleClientSite* pActiveSite, LONG lindex, HWND hwndParent, LPCRECT lprcPosRect);
    STDMETHODIMP EnumVerbs(IEnumOLEVERB** ppEnumOleVerb);
    STDMETHODIMP Update();
    STDMETHODIMP IsUpToDate();
    STDMETHODIMP GetUserClassID(CLSID* pClsid);
    STDMETHODIMP GetUserType(DWORD dwFormOfType, LPOLESTR* pszUserType);
    STDMETHODIMP SetExtent(DWORD dwDrawAspect, SIZEL* psizel);
    STDMETHODIMP GetExtent(DWORD dwDrawAspect, SIZEL* psizel);
    STDMETHODIMP Advise(IAdviseSink* pAdvSink, DWORD* pdwConnection);
    STDMETHODIMP Unadvise(DWORD dwConnection);
    STDMETHODIMP EnumAdvise(IEnumSTATDATA** ppenumAdvise);
    STDMETHODIMP GetMiscStatus(DWORD dwAspect, DWORD* pdwStatus);
    STDMETHODIMP SetColorScheme(LOGPALETTE* pLogpal);

    // IPersistStorage
    STDMETHODIMP GetClassID(CLSID* pClassID);
    STDMETHODIMP IsDirty();
    STDMETHODIMP Load(IStorage* pStg);
    STDMETHODIMP Save(IStorage* pStg, BOOL fSameAsLoad);
    STDMETHODIMP SaveCompleted(IStorage* pStgNew);
    STDMETHODIMP HandsOffStorage();

    // IDataObject
    STDMETHODIMP GetData(FORMATETC* pFormatEtc, STGMEDIUM* pMedium);
    STDMETHODIMP GetDataHere(FORMATETC* pFormatEtc, STGMEDIUM* pMedium);
    STDMETHODIMP QueryGetData(FORMATETC* pFormatEtc);
    STDMETHODIMP GetCanonicalFormatEtc(FORMATETC* pFormatEtcIn, FORMATETC* pFormatEtcOut);
    STDMETHODIMP SetData(FORMATETC* pFormatEtc, STGMEDIUM* pMedium, BOOL fRelease);
    STDMETHODIMP EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC** ppEnumFormatEtc);
    STDMETHODIMP DAdvise(FORMATETC* pFormatEtc, DWORD advf, IAdviseSink* pAdvSink, DWORD* pdwConnection);
    STDMETHODIMP DUnadvise(DWORD dwConnection);
    STDMETHODIMP EnumDAdvise(IEnumSTATDATA** ppenumAdvise);

    // IViewObject
    STDMETHODIMP Draw(DWORD dwDrawAspect, LONG lindex, void* pvAspect, DVTARGETDEVICE* ptd, HDC hdcTargetDev, HDC hdcDraw, LPCRECTL lprcBounds, LPCRECTL lprcWBounds, BOOL (CALLBACK* pfnContinue)(ULONG_PTR), ULONG_PTR dwContinue);
    STDMETHODIMP GetColorSet(DWORD dwDrawAspect, LONG lindex, void* pvAspect, DVTARGETDEVICE* ptd, HDC hicTargetDev, LOGPALETTE** ppColorSet);
    STDMETHODIMP Freeze(DWORD dwDrawAspect, LONG lindex, void* pvAspect, DWORD* pdwFreeze);
    STDMETHODIMP Unfreeze(DWORD dwFreeze);
    STDMETHODIMP SetAdvise(DWORD dwAspects, DWORD dwAdvf, IAdviseSink* pAdvSink);
    STDMETHODIMP GetAdvise(DWORD* pAspects, DWORD* pAdvf, IAdviseSink** ppAdvSink);
};

6.2 关键实现片段

// 构造函数
ShapeObject::ShapeObject() : m_cRef(1), m_color(RGB(0,0,255)), m_pClientSite(NULL), m_pStorage(NULL), m_bDirty(FALSE), m_pInPlaceObj(NULL)
{
    SetRect(&m_rect, 0, 0, 100, 100);
}

// IOleObject::SetClientSite
STDMETHODIMP ShapeObject::SetClientSite(IOleClientSite* pClientSite)
{
    if (m_pClientSite) m_pClientSite->Release();
    m_pClientSite = pClientSite;
    if (m_pClientSite) m_pClientSite->AddRef();
    return S_OK;
}

// IOleObject::DoVerb – 处理就地激活
STDMETHODIMP ShapeObject::DoVerb(LONG iVerb, LPMSG lpmsg, IOleClientSite* pActiveSite, LONG lindex, HWND hwndParent, LPCRECT lprcPosRect)
{
    if (iVerb == OLEIVERB_INPLACEACTIVATE || iVerb == OLEIVERB_SHOW)
    {
        if (m_pInPlaceObj == NULL)
        {
            // 创建就地激活对象
            m_pInPlaceObj = new ShapeInPlaceObj(this);
            // 获取就地站点
            IOleInPlaceSite* pIPS = NULL;
            if (m_pClientSite && SUCCEEDED(m_pClientSite->QueryInterface(IID_IOleInPlaceSite, (void**)&pIPS)))
            {
                m_pInPlaceObj->SetInPlaceSite(pIPS);
                pIPS->Release();
            }
        }
        // 显示窗口
        m_pInPlaceObj->Show();
        // 通知容器已激活
        if (m_pClientSite)
            m_pClientSite->ShowObject();
        return S_OK;
    }
    return E_NOTIMPL;
}

// IPersistStorage::Save
STDMETHODIMP ShapeObject::Save(IStorage* pStg, BOOL fSameAsLoad)
{
    // 创建流,写入 m_color 和 m_rect
    IStream* pStream = NULL;
    HRESULT hr = pStg->CreateStream(L"ShapeData", STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &pStream);
    if (SUCCEEDED(hr))
    {
        hr = pStream->Write(&m_color, sizeof(m_color), NULL);
        if (SUCCEEDED(hr))
            hr = pStream->Write(&m_rect, sizeof(m_rect), NULL);
        pStream->Release();
    }
    m_bDirty = FALSE;
    return hr;
}

// IViewObject::Draw – 绘制图形
STDMETHODIMP ShapeObject::Draw(DWORD dwDrawAspect, LONG lindex, void* pvAspect, DVTARGETDEVICE* ptd, HDC hdcTargetDev, HDC hdcDraw, LPCRECTL lprcBounds, LPCRECTL lprcWBounds, BOOL (CALLBACK* pfnContinue)(ULONG_PTR), ULONG_PTR dwContinue)
{
    if (dwDrawAspect != DVASPECT_CONTENT) return E_INVALIDARG;
    RECT rc = { lprcBounds->left, lprcBounds->top, lprcBounds->right, lprcBounds->bottom };
    HBRUSH hBrush = CreateSolidBrush(m_color);
    FillRect(hdcDraw, &rc, hBrush);
    DeleteObject(hBrush);
    FrameRect(hdcDraw, &rc, (HBRUSH)GetStockObject(BLACK_BRUSH));
    return S_OK;
}

6.3 就地激活窗口 ShapeInPlaceObj

文件server/ShapeInPlaceObj.h

class ShapeInPlaceObj : public IOleInPlaceObject
{
private:
    LONG m_cRef;
    ShapeObject* m_pParent;
    HWND m_hWnd;
    IOleInPlaceSite* m_pIPS;
public:
    ShapeInPlaceObj(ShapeObject* pParent);
    ~ShapeInPlaceObj();
    // IUnknown
    STDMETHODIMP QueryInterface(REFIID riid, void** ppv);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();
    // IOleInPlaceObject
    STDMETHODIMP GetWindow(HWND* phwnd);
    STDMETHODIMP ContextSensitiveHelp(BOOL fEnterMode);
    STDMETHODIMP InPlaceDeactivate();
    STDMETHODIMP UIDeactivate();
    STDMETHODIMP SetObjectRects(LPCRECT lprcPosRect, LPCRECT lprcClipRect);
    STDMETHODIMP ReactivateAndUndo();
    // Helper
    void SetInPlaceSite(IOleInPlaceSite* pIPS);
    void Show();
};

实现

ShapeInPlaceObj::ShapeInPlaceObj(ShapeObject* pParent) : m_cRef(1), m_pParent(pParent), m_hWnd(NULL), m_pIPS(NULL) {}

void ShapeInPlaceObj::Show()
{
    if (m_hWnd == NULL)
    {
        // 创建无边框窗口,作为就地编辑界面
        HINSTANCE hInst = GetModuleHandle(NULL);
        WNDCLASS wc = {0};
        wc.lpfnWndProc = DefWindowProc;
        wc.hInstance = hInst;
        wc.lpszClassName = L"ShapeInPlaceWnd";
        RegisterClass(&wc);
        m_hWnd = CreateWindow(L"ShapeInPlaceWnd", NULL, WS_CHILD, 0, 0, 100, 100, GetActiveWindow(), NULL, hInst, NULL);
    }
    // 获取容器窗口位置并调整
    RECT rcPos;
    m_pParent->GetRect(&rcPos);
    SetWindowPos(m_hWnd, NULL, rcPos.left, rcPos.top, rcPos.right-rcPos.left, rcPos.bottom-rcPos.top, SWP_SHOWWINDOW);
    // 告知容器 UI 激活
    IOleInPlaceFrame* pFrame = NULL;
    if (m_pIPS && SUCCEEDED(m_pIPS->GetWindowContext(&pFrame, NULL, NULL, NULL, NULL)))
    {
        pFrame->SetActiveObject(this, NULL);
        pFrame->Release();
    }
}

STDMETHODIMP ShapeInPlaceObj::InPlaceDeactivate()
{
    if (m_hWnd)
    {
        DestroyWindow(m_hWnd);
        m_hWnd = NULL;
    }
    if (m_pIPS)
        m_pIPS->OnInPlaceDeactivate();
    return S_OK;
}

6.4 类工厂与 DLL 导出

标准 COM DLL 导出 DllGetClassObject, DllCanUnloadNow, DllRegisterServer, DllUnregisterServer。在 DllRegisterServer 中注册 CLSID_Shape,并添加 Insertable 键(使 OLE 插入对象对话框能识别)。


7. 运行与测试

  1. 编译服务器 DLLcl /LD shapeobj.cpp ... /FeOleShape.dll /link /DEF:server.def
  2. 注册服务器regsvr32 OleShape.dll
  3. 编译容器 EXEcl container.cpp ... /FeOleContainer.exe
  4. 运行容器:点击“插入对象”菜单,选择“Shape Object”,会在窗口中显示一个蓝色矩形。双击矩形,弹出可编辑窗口(可改变颜色等,本例简化)。

8. 深入机制解析

8.1 嵌入 vs 链接

  • 嵌入:容器调用 OleCreate,服务器将数据保存到容器的 IStorage 中。IPersistStorage::Save 写入子存储。
  • 链接:容器调用 OleCreateLink,服务器返回 IOleLink 接口,数据源为文件或 URL。容器保存链接源信息。

8.2 就地激活的细节

  • 就地站点(IOleInPlaceSite):由容器实现,提供容器窗口、位置、框架信息。
  • 就地对象(IOleInPlaceObject):由服务器实现,负责创建子窗口、响应 SetObjectRects
  • UI 激活:服务器接管容器的菜单、工具栏(通过 IOleInPlaceFrame 插入菜单)。

8.3 持久化

  • 容器为每个嵌入对象创建一个 IStorage(结构化存储)。
  • 对象通过 IPersistStorage::Save 将数据写入该存储的子流或子存储。
  • 容器保存整个 IStorage 到文件(复合文档格式)。

8.4 OLE 自动化

OLE 自动化允许脚本语言(如 VBA)控制 OLE 对象。服务器需要实现 IDispatch,将属性和方法暴露给自动化控制器。


9. 总结

本项目通过实现一个简单的 OLE 容器和图形服务器,完整展示了 OLE 的核心机制:

  • 嵌入:容器创建对象,对象保存到容器存储。
  • 就地激活:服务器窗口嵌入容器,实现无缝编辑。
  • 接口协作IOleClientSite / IOleObject 实现容器-服务器通信,IOleInPlaceSite / IOleInPlaceObject 实现就地激活。

所有 UML 图使用 Mermaid 渲染,代码展示了关键接口的实现,可作为学习 OLE 技术的参考。尽管 OLE 已被现代技术替代,但其设计模式(如结构化存储、就地激活)对理解复合文档和组件化架构仍有重要价值。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐