跳转至

Richedit OLE 无注册实现指南

本文介绍了如何在 SOUI 中实现无需注册 COM 的 Richedit OLE 对象支持。这种实现方式特别适用于需要制作绿色版本的应用程序。

问题背景

在 Richedit 中使用 OLE 对象时,通常需要: - 注册 COM 对象 - 管理员权限 - 系统注册表访问

这些要求使得应用程序难以实现完全绿色化,因为: - 注册 COM 需要管理员权限 - 依赖系统注册表 - 不利于便携式部署

技术原理分析

COM 注册的作用

在 Richedit 中,COM 注册主要用于: 1. 通过 GUID 实例化 OLE 对象 2. 在复制粘贴时重建 OLE 对象 3. 维护对象的 ProgID 信息

关键 API 调用链

复制粘贴过程中的关键 API 调用:

  1. 复制阶段
  2. ProgIDFromCLSID:查询对象 ProgID
  3. 写入剪贴板 RTF 格式

  4. 粘贴阶段

  5. CoCreateInstance:创建 OLE 对象
  6. OleLoad:加载对象数据
  7. SetClientSite:设置容器环境

实现方案

1. 复制阶段处理

Hook ProgIDFromCLSID API:

HRESULT WINAPI MyProgIDFromCLSID(
    REFCLSID clsid,
    LPOLESTR *ppszProgID)
{
    // 检查是否为我们的 OLE 对象
    if(IsOurOLEObject(clsid))
    {
        // 返回自定义 ProgID
        *ppszProgID = L"Our.OLE.Object";
        return S_OK;
    }

    // 其他情况走原始流程
    return OriginalProgIDFromCLSID(clsid, ppszProgID);
}

2. 粘贴阶段处理

Hook CoCreateInstance API:

HRESULT WINAPI MyCoCreateInstance(
    REFCLSID rclsid,
    LPUNKNOWN pUnkOuter,
    DWORD dwClsContext,
    REFIID riid,
    LPVOID *ppv)
{
    // 检查是否为我们的 OLE 对象
    if(IsOurOLEObject(rclsid))
    {
        // 创建我们的 OLE 对象实例
        return CreateOurOLEObject(riid, ppv);
    }

    // 其他情况走原始流程
    return OriginalCoCreateInstance(rclsid, pUnkOuter, 
                                  dwClsContext, riid, ppv);
}

3. 对象初始化处理

实现 IOleObject::GetMiscStatus

HRESULT COurOleObject::GetMiscStatus(
    DWORD dwAspect,
    DWORD *pdwStatus)
{
    // 设置 OLEMISC_SETCLIENTSITEFIRST 标志
    // 确保在 Load 前调用 SetClientSite
    *pdwStatus = OLEMISC_SETCLIENTSITEFIRST;
    return S_OK;
}

关键实现细节

1. Hook 机制

使用 Detours 或类似库实现 API Hook:

#include <detours.h>

// 定义原始函数指针
static HRESULT (WINAPI *OriginalProgIDFromCLSID)(
    REFCLSID clsid,
    LPOLESTR *ppszProgID) = ProgIDFromCLSID;

// 安装 Hook
void InstallHooks()
{
    DetourTransactionBegin();
    DetourUpdateThread(GetCurrentThread());

    DetourAttach(&(PVOID&)OriginalProgIDFromCLSID, 
                 MyProgIDFromCLSID);

    DetourTransactionCommit();
}

2. OLE 对象实现

基本接口实现:

class COurOleObject : 
    public IOleObject,
    public IPersistStorage
{
public:
    // IOleObject 接口
    HRESULT STDMETHODCALLTYPE SetClientSite(
        IOleClientSite *pClientSite) override
    {
        m_pClientSite = pClientSite;
        return S_OK;
    }

    HRESULT STDMETHODCALLTYPE GetMiscStatus(
        DWORD dwAspect,
        DWORD *pdwStatus) override
    {
        *pdwStatus = OLEMISC_SETCLIENTSITEFIRST;
        return S_OK;
    }

    // IPersistStorage 接口
    HRESULT STDMETHODCALLTYPE Load(
        IStorage *pStg) override
    {
        // 确保已经设置了 ClientSite
        if(!m_pClientSite)
            return E_FAIL;

        return LoadFromStorage(pStg);
    }

private:
    IOleClientSite *m_pClientSite;
};

最佳实践

1. Hook 安装时机

class CRichEditOleManager
{
public:
    static void Initialize()
    {
        // 安装所需的 API Hook
        InstallHooks();

        // 注册清理函数
        atexit(Cleanup);
    }

private:
    static void Cleanup()
    {
        // 移除 Hook
        RemoveHooks();
    }
};

2. 对象创建管理

class COleObjectFactory
{
public:
    static HRESULT CreateOleObject(
        REFCLSID clsid,
        REFIID riid,
        void **ppv)
    {
        // 对象池管理
        if(IsObjectInPool(clsid))
            return ReuseObject(clsid, riid, ppv);

        // 创建新对象
        return CreateNewObject(clsid, riid, ppv);
    }
};

3. 调试支持

#ifdef _DEBUG
#define TRACE_OLE(fmt, ...) \
    OutputDebugString(SStringT().Format(fmt, __VA_ARGS__))
#else
#define TRACE_OLE(fmt, ...)
#endif

void DebugOleOperation()
{
    TRACE_OLE(_T("OLE Operation: %s"), _T("Copy/Paste"));
}

注意事项

  1. 安全性考虑
  2. 谨慎处理 Hook
  3. 避免干扰其他 COM 对象
  4. 注意线程安全

  5. 性能优化

  6. 使用对象池
  7. 避免频繁创建/销毁
  8. 合理缓存数据

  9. 兼容性

  10. 测试不同 Windows 版本
  11. 处理特殊情况
  12. 提供降级方案

总结

无注册 COM 的 Richedit OLE 实现提供了:

✓ 完全绿色化的解决方案
✓ 无需管理员权限
✓ 更好的便携性
✓ 灵活的定制能力

通过 Hook 系统 API 和精心设计的 OLE 对象实现,我们可以在不注册 COM 的情况下实现完整的 Richedit OLE 功能,使应用程序更加便携和独立。