深入解析ActiveX 工作原理与机制
aliases:
- |-
深入解析
ActiveX 工作原理与机制
ActiveX 工作原理与机制:UML 建模与实现方案
1. 项目概述
ActiveX 是基于 COM 的可重用组件技术,主要用于在 Web 浏览器(如 Internet Explorer)或 OLE 容器中嵌入交互式控件。一个典型的 ActiveX 控件:
- 实现一组标准接口:
IUnknown,IOleObject,IOleControl,IDispatch,IPersistStreamInit等 - 支持属性(Properties)、方法(Methods)、事件(Events)
- 可通过
<object>标签嵌入 HTML 页面 - 需要自注册(
DllRegisterServer写入注册表控件标记) - 支持持久化(保存/加载属性状态)
- 可选可视化(在容器中绘制自身)
本项目实现一个 MathCtrl ActiveX 控件,提供加减运算,具有 Value 属性,支持 Click 事件,并在网页中演示。
2. 项目文件结构
activex_demo/
├── README.md
├── docs/
│ └── uml.md # UML 图
├── idl/
│ └── MathCtrl.idl # 控件接口定义
├── src/
│ ├── MathCtrl.h # 控件类声明
│ ├── MathCtrl.cpp # 控件实现
│ ├── MathCtrlFactory.h
│ ├── MathCtrlFactory.cpp
│ ├── MathCtrlPropPage.h # 属性页(可选)
│ ├── MathCtrlPropPage.cpp
│ ├── dllmain.cpp
│ ├── MathCtrl.def
│ └── MathCtrl.rgs # 注册表脚本
├── web/
│ ├── test.html # 测试页面
│ └── script.js
├── bin/ # 输出 DLL / OCX
└── tools/
└── register.bat
3. UML 建模(Mermaid)
3.1 类图 – ActiveX 控件的接口实现
3.2 序列图 – 浏览器加载 ActiveX 控件
3.3 活动图 – ActiveX 控件持久化流程
3.4 部署图 – ActiveX 控件在客户端的分布
4. IDL 定义(包括控件、事件)
文件:idl/MathCtrl.idl
import "oaidl.idl";
import "ocidl.idl";
// 事件接口(源接口)
[
uuid(0A1B2C3D-4E5F-6A7B-8C9D-0E1F2A3B4C5D),
hidden
]
dispinterface _IMathCtrlEvents
{
properties:
methods:
[id(1)] void Click();
};
// 控件主接口
[
object,
uuid(1B2C3D4E-5F6A-7B8C-9D0E-1F2A3B4C5D6E),
dual,
oleautomation,
pointer_default(unique)
]
interface IMathCtrl : IDispatch
{
[propget, id(1)] HRESULT Value([out, retval] LONG* pVal);
[propput, id(1)] HRESULT Value([in] LONG newVal);
[id(2)] HRESULT Add([in] LONG a, [in] LONG b, [out, retval] LONG* result);
[id(3)] HRESULT Sub([in] LONG a, [in] LONG b, [out, retval] LONG* result);
};
// 控件 coclass
[
uuid(2C3D4E5F-6A7B-8C9D-0E1F-2A3B4C5D6E7F),
control, // 标记为控件
helpstring("MathCtrl Control")
]
coclass MathCtrl
{
[default] interface IMathCtrl;
[default, source] dispinterface _IMathCtrlEvents;
};
MIDL 编译:midl MathCtrl.idl 生成 .h, .tlb, _i.c, _p.c。
5. 控件实现(C++)
5.1 头文件 MathCtrl.h
#include "MathCtrl.h" // MIDL generated
#include <ocidl.h> // IOleControl, IOleObject etc.
class CMathCtrl : public IMathCtrl,
public IOleObject,
public IOleControl,
public IPersistStreamInit,
public IViewObject,
public IProvideClassInfo,
public IConnectionPointContainer
{
private:
LONG m_cRef;
LONG m_value; // Value 属性
DWORD m_dwCookie; // 连接点 cookie
IConnectionPointContainer* m_pCPC; // 连接点容器
IConnectionPoint* m_pConnPt; // 事件连接点
public:
CMathCtrl();
~CMathCtrl();
// IUnknown
STDMETHODIMP QueryInterface(REFIID riid, void** ppv);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
// IMathCtrl
STDMETHODIMP get_Value(LONG* pVal);
STDMETHODIMP put_Value(LONG newVal);
STDMETHODIMP Add(LONG a, LONG b, LONG* result);
STDMETHODIMP Sub(LONG a, LONG b, LONG* result);
// IDispatch (from IMathCtrl) – use typeinfo
STDMETHODIMP GetTypeInfoCount(UINT* pctinfo);
STDMETHODIMP GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo** ppTInfo);
STDMETHODIMP GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, UINT cNames, LCID lcid, DISPID* rgDispId);
STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pDispParams,
VARIANT* pVarResult, EXCEPINFO* pExcepInfo, UINT* puArgErr);
// 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);
// IOleControl
STDMETHODIMP GetControlInfo(CONTROLINFO* pCI);
STDMETHODIMP OnMnemonic(LPMSG pMsg);
STDMETHODIMP OnAmbientPropertyChange(DISPID dispID);
STDMETHODIMP FreezeEvents(BOOL bFreeze);
// IPersistStreamInit
STDMETHODIMP GetClassID(CLSID* pClassID);
STDMETHODIMP IsDirty();
STDMETHODIMP Load(IStream* pStm);
STDMETHODIMP Save(IStream* pStm, BOOL fClearDirty);
STDMETHODIMP GetSizeMax(ULARGE_INTEGER* pcbSize);
STDMETHODIMP InitNew();
// 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);
// IProvideClassInfo
STDMETHODIMP GetClassInfo(ITypeInfo** ppTI);
// IConnectionPointContainer
STDMETHODIMP EnumConnectionPoints(IEnumConnectionPoints** ppEnum);
STDMETHODIMP FindConnectionPoint(REFIID riid, IConnectionPoint** ppCP);
// Helper: Fire event
HRESULT Fire_Click();
};
5.2 实现文件 MathCtrl.cpp(关键部分)
#include "MathCtrl.h"
#include "MathCtrl_i.c" // IID/CLSID
static ITypeLib* g_pTypeLib = NULL;
// 连接点容器实现(简化,使用标准 ATL 模式,这里手工实现)
class CProxy_IMathCtrlEvents : public IConnectionPoint
{
// ... 标准连接点实现,用于管理事件 sink
};
CMathCtrl::CMathCtrl() : m_cRef(1), m_value(0), m_pConnPt(NULL)
{
// 创建连接点对象
// 实际可用 ATL 简化,这里略
}
CMathCtrl::~CMathCtrl() { }
STDMETHODIMP CMathCtrl::QueryInterface(REFIID riid, void** ppv)
{
if (riid == IID_IUnknown) *ppv = static_cast<IMathCtrl*>(this);
else if (riid == IID_IMathCtrl) *ppv = static_cast<IMathCtrl*>(this);
else if (riid == IID_IDispatch) *ppv = static_cast<IMathCtrl*>(this);
else if (riid == IID_IOleObject) *ppv = static_cast<IOleObject*>(this);
else if (riid == IID_IOleControl) *ppv = static_cast<IOleControl*>(this);
else if (riid == IID_IPersistStreamInit) *ppv = static_cast<IPersistStreamInit*>(this);
else if (riid == IID_IViewObject) *ppv = static_cast<IViewObject*>(this);
else if (riid == IID_IProvideClassInfo) *ppv = static_cast<IProvideClassInfo*>(this);
else if (riid == IID_IConnectionPointContainer) *ppv = static_cast<IConnectionPointContainer*>(this);
else return E_NOINTERFACE;
AddRef();
return S_OK;
}
STDMETHODIMP_(ULONG) CMathCtrl::AddRef() { return InterlockedIncrement(&m_cRef); }
STDMETHODIMP_(ULONG) CMathCtrl::Release()
{
ULONG ref = InterlockedDecrement(&m_cRef);
if (ref == 0) delete this;
return ref;
}
// 属性 Value 的实现
STDMETHODIMP CMathCtrl::get_Value(LONG* pVal)
{
*pVal = m_value;
return S_OK;
}
STDMETHODIMP CMathCtrl::put_Value(LONG newVal)
{
m_value = newVal;
// 通知容器属性变化(可选)
return S_OK;
}
STDMETHODIMP CMathCtrl::Add(LONG a, LONG b, LONG* result)
{
*result = a + b;
// 触发 Click 事件示例
Fire_Click();
return S_OK;
}
STDMETHODIMP CMathCtrl::Sub(LONG a, LONG b, LONG* result)
{
*result = a - b;
return S_OK;
}
// 绘制:在矩形内显示当前 Value
STDMETHODIMP CMathCtrl::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 (hdcDraw == NULL) return E_INVALIDARG;
// 简单绘制一个带边框的矩形,显示 "Value: xxx"
RECT rc = { lprcBounds->left, lprcBounds->top, lprcBounds->right, lprcBounds->bottom };
FillRect(hdcDraw, &rc, (HBRUSH)GetStockObject(WHITE_BRUSH));
FrameRect(hdcDraw, &rc, (HBRUSH)GetStockObject(BLACK_BRUSH));
char text[32];
sprintf_s(text, "Value: %ld", m_value);
DrawTextA(hdcDraw, text, -1, &rc, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
return S_OK;
}
// 持久化:保存 m_value
STDMETHODIMP CMathCtrl::Load(IStream* pStm)
{
ULONG cbRead;
return pStm->Read(&m_value, sizeof(m_value), &cbRead);
}
STDMETHODIMP CMathCtrl::Save(IStream* pStm, BOOL fClearDirty)
{
ULONG cbWritten;
return pStm->Write(&m_value, sizeof(m_value), &cbWritten);
}
STDMETHODIMP CMathCtrl::InitNew()
{
m_value = 0;
return S_OK;
}
// 事件触发
HRESULT CMathCtrl::Fire_Click()
{
if (m_pConnPt == NULL) return S_OK;
IUnknown** ppSinks;
// 枚举连接点,调用每个 sink 的 Invoke (DISPID 1)
// 简化:仅调用第一个 sink
// 实际需使用 IEnumConnections
return S_OK;
}
// 其他接口方法(如 DoVerb 创建窗口等)必须实现,此处省略细节
5.3 类工厂与 DLL 导出
// dllmain.cpp
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void** ppv)
{
if (rclsid != CLSID_MathCtrl) return CLASS_E_CLASSNOTAVAILABLE;
static CClassFactory factory; // 实现 IClassFactory
return factory.QueryInterface(riid, ppv);
}
STDAPI DllCanUnloadNow() { /* 返回 S_OK 如果无活动对象 */ }
STDAPI DllRegisterServer()
{
// 注册 COM 类,添加 Control 子键,注册类型库
// 使用 .rgs 脚本或直接写注册表
return S_OK;
}
6. 注册表脚本(.rgs)
文件:src/MathCtrl.rgs
HKCR
{
CLSID\{2C3D4E5F-6A7B-8C9D-0E1F-2A3B4C5D6E7F} = s 'MathCtrl Control'
{
InprocServer32 = s '%MODULE%'
{
val ThreadingModel = s 'Apartment'
}
Control = s '' // ActiveX 控件标记
TypeLib = s '{...}' // 类型库 IID
MiscStatus = s '131456' // OLEMISC 标志
ProgID = s 'MathCtrl.MathCtrl.1'
VersionIndependentProgID = s 'MathCtrl.MathCtrl'
}
// 类型库注册(通常由 regsvr32 自动处理)
}
7. HTML 测试页面
文件:web/test.html
<!DOCTYPE html>
<html>
<head>
<title>ActiveX MathCtrl Demo</title>
<script type="text/javascript">
function doAdd() {
var ctrl = document.getElementById("mathCtrl");
var a = parseInt(document.getElementById("a").value);
var b = parseInt(document.getElementById("b").value);
var result = ctrl.Add(a, b);
document.getElementById("result").innerHTML = result;
}
function doSub() {
var ctrl = document.getElementById("mathCtrl");
var a = parseInt(document.getElementById("a").value);
var b = parseInt(document.getElementById("b").value);
var result = ctrl.Sub(a, b);
document.getElementById("result").innerHTML = result;
}
function getValue() {
var ctrl = document.getElementById("mathCtrl");
alert("Current Value = " + ctrl.Value);
}
function setValue() {
var ctrl = document.getElementById("mathCtrl");
var newVal = parseInt(document.getElementById("newVal").value);
ctrl.Value = newVal;
}
</script>
</head>
<body>
<h1>ActiveX MathCtrl 测试</h1>
<object id="mathCtrl" classid="clsid:2C3D4E5F-6A7B-8C9D-0E1F-2A3B4C5D6E7F" width="200" height="80">
<param name="Value" value="42">
</object>
<br/>
<label>A: <input type="text" id="a" value="10"/></label>
<label>B: <input type="text" id="b" value="5"/></label>
<button onclick="doAdd()">Add</button>
<button onclick="doSub()">Sub</button>
<p>Result: <span id="result"></span></p>
<hr/>
<label>Set Value: <input type="text" id="newVal" value="100"/></label>
<button onclick="setValue()">Set</button>
<button onclick="getValue()">Get</button>
<p>控件内部会显示当前 Value。</p>
</body>
</html>
8. 构建与部署
8.1 编译
使用 Visual Studio 或 MinGW。需要链接 ole32.lib, oleaut32.lib, uuid.lib。
# 编译 IDL
midl MathCtrl.idl
# 编译 DLL
cl /LD /I. MathCtrl.cpp dllmain.cpp MathCtrl_i.c /FeMathCtrl.dll /link /DEF:MathCtrl.def
# 注册控件
regsvr32 MathCtrl.dll
8.2 部署到浏览器
由于 IE 对 ActiveX 有安全限制,需要:
- 将站点加入受信任的站点。
- 设置 IE 安全级别允许 ActiveX 控件。
- 签名控件(代码签名证书)以避免提示。
也可以将控件打包为 .cab 文件,并在 <object> 中指定 codebase。
<object classid="..." codebase="MathCtrl.cab#version=1,0,0,0" ...>
9. 深入机制解析
9.1 ActiveX 与普通 COM 组件的区别
- 必须实现特定接口:
IOleObject(与容器通信)、IOleControl(接收键盘消息等)、IPersistStreamInit(持久化)、IViewObject(绘制)。 - 注册表标记:
Control子键表示这是一个控件,容器可据此加载设计器。 - 事件支持:通过连接点(
IConnectionPointContainer)暴露事件接口。 - 属性页(可选):通过
ISpecifyPropertyPages允许用户在容器中编辑属性。
9.2 ActiveX 的安全机制
- 初始化安全:实现
IObjectSafety接口,声明控件是脚本安全的(INTERFACESAFE_FOR_UNTRUSTED_CALLER)和初始化安全的(INTERFACESAFE_FOR_UNTRUSTED_DATA)。 - 签名与验证:控件应使用 Authenticode 签名,防止篡改。
9.3 与 MFC/ATL 的关系
开发 ActiveX 控件通常使用 ATL(Active Template Library),它提供了大量模板类和宏,简化了接口实现。上述手工实现是为了展示原理,实际项目应使用 ATL。
9.4 ActiveX 的衰落
由于安全问题和浏览器标准化(NPAPI → PPAPI → 无插件),现代浏览器(Chrome, Firefox, Edge 新版)不再支持 ActiveX。仅在 IE 和某些遗留系统中使用。
10. 总结
本项目通过一个完整的 ActiveX 控件示例,展示了:
- IDL 定义控件接口和事件。
- C++ 实现必须支持
IOleObject,IOleControl,IPersistStreamInit,IViewObject,IConnectionPointContainer等接口。 - 注册时添加
Control子键标记为控件。 - HTML 嵌入通过
<object>标签,JavaScript 调用方法、读写属性、响应事件。 - 安全需要实现
IObjectSafety并签名。
所有 UML 图使用 Mermaid 绘制,代码结构清晰,可作为学习 ActiveX 原理的参考。虽然 ActiveX 已属历史,但其设计思想(组件化、容器交互、持久化、事件)深刻影响了后来的 Web 组件标准(如 Web Components)。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐




所有评论(0)