Richedit OLE 无注册实现指南¶
本文介绍了如何在 SOUI 中实现无需注册 COM 的 Richedit OLE 对象支持。这种实现方式特别适用于需要制作绿色版本的应用程序。
问题背景¶
在 Richedit 中使用 OLE 对象时,通常需要: - 注册 COM 对象 - 管理员权限 - 系统注册表访问
这些要求使得应用程序难以实现完全绿色化,因为: - 注册 COM 需要管理员权限 - 依赖系统注册表 - 不利于便携式部署
技术原理分析¶
COM 注册的作用¶
在 Richedit 中,COM 注册主要用于: 1. 通过 GUID 实例化 OLE 对象 2. 在复制粘贴时重建 OLE 对象 3. 维护对象的 ProgID 信息
关键 API 调用链¶
复制粘贴过程中的关键 API 调用:
- 复制阶段
- ProgIDFromCLSID:查询对象 ProgID
-
写入剪贴板 RTF 格式
-
粘贴阶段
- CoCreateInstance:创建 OLE 对象
- OleLoad:加载对象数据
- 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"));
}
注意事项¶
- 安全性考虑
- 谨慎处理 Hook
- 避免干扰其他 COM 对象
-
注意线程安全
-
性能优化
- 使用对象池
- 避免频繁创建/销毁
-
合理缓存数据
-
兼容性
- 测试不同 Windows 版本
- 处理特殊情况
- 提供降级方案
总结¶
无注册 COM 的 Richedit OLE 实现提供了:
✓ 完全绿色化的解决方案
✓ 无需管理员权限
✓ 更好的便携性
✓ 灵活的定制能力
通过 Hook 系统 API 和精心设计的 OLE 对象实现,我们可以在不注册 COM 的情况下实现完整的 Richedit OLE 功能,使应用程序更加便携和独立。