- 准备工作:搭建开发环境。
- 创建项目:使用 Visual Studio 创建 ATL 项目。
- 添加控件:向项目中添加一个 ATL Simple Control。
- 实现功能:为控件添加属性、方法和事件。
- 编译与注册:编译项目并自动注册控件。
- 测试控件:在容器(如 VB6 或 测试容器)中测试控件。
- 打包与分发:创建安装程序以便在其他机器上部署。
第 1 步:准备工作
你需要安装 Visual Studio,为了开发 COM/ATL 组件,建议安装 “使用 C++ 的桌面开发” 工作负载,它包含了 ATL 库和相关的模板。

第 2 步:创建 ATL 项目
- 打开 Visual Studio。
- 选择“创建新项目”。
- 在模板搜索框中输入 "ATL",然后选择 "ATL 项目"。
- 为你的项目命名,
MyActiveXControl,并选择一个位置。 - 点击“创建”。
在 ATL 项目向导中:
- 应用程序类型:选择 "动态链接库 (DLL)",这是最常见的方式。
- 其他选项:暂时保持默认设置即可。
点击“完成”。
你已经创建了一个空的 ATL DLL 项目,它可以作为一个 COM 服务器运行。
第 3 步:添加 ATL Simple Control
- 在 “解决方案资源管理器” 中,右键点击你的项目名称。
- 选择 “添加” -> “类...”。
- 在“添加类”对话框中,从左侧的模板列表中选择 “ATL”。
- 在右侧的模板列表中,选择 “ATL Simple Control”。
- 点击“添加”。
ATL Control 向导 将会打开,你需要在这里配置你的控件。

- 短名称:输入一个简短的名称,
MyCtrl,向导会自动根据这个名称生成其他相关的 ID,如MyCtrlLib、IMyCtrl等。 - ProgID:这是程序matic ID,如
MyActiveXControl.MyCtrl.1,其他应用程序可以通过这个 ID 来创建你的控件实例,通常保持默认即可。 - 类型:
- 标准控件:创建一个标准的窗口控件。
- 复合控件:可以包含标准 Windows 控件(如按钮、文本框)。
- 微型控件:没有自己的窗口,轻量级。
- 默认选择“标准控件”。
- 线程模型:
- 单线程:不推荐。
- 公寓:适用于有用户界面的控件,是大多数情况下的选择。
- 自由线程:适用于无 UI 的后台服务。
- 中性线程:适用于可重入的组件。
- 默认选择“公寓”。
- 接口:
- 双接口:同时支持 IDispatch(用于脚本语言)和 v-table(用于 C++ 等高性能语言)。强烈推荐选择此项,因为它兼容性最好。
- 仅自定义:只支持 v-table,性能高但脚本无法调用。
- 仅调度:只支持 IDispatch,性能较低但易于脚本调用。
- 聚合:保持默认“否”。
- 支持 ISupportErrorInfo:如果需要通过
ErrorInfo机制返回详细的错误信息,则勾选,可以暂时不选。 - 支持连接点:如果你的控件需要触发事件(例如按钮被点击),则必须勾选此项。强烈推荐勾选。
- 支持异步下载:用于从 URL 加载数据,不相关。
配置完成后,点击“完成”。
向导会自动为你生成大量的代码,包括:
- 一个 C++ 类
CMyCtrl,继承自CAxWindow和IMyCtrl。 - IDL (Interface Definition Language) 文件 (
.idl),定义了控件的接口。 - 资源文件 (
.rc),包含控件的默认图标和位图。 - 注册表脚本文件 (
.rgs),用于在注册表中添加和删除 COM 组件信息。
第 4 步:实现功能
现在我们来为控件添加一个简单的属性、方法和事件。
添加一个属性 (Property)
属性是控件的“数据成员”,一个按钮控件的 Caption 属性。

-
打开
MyCtrl.h文件。 -
在
CMyCtrl类的定义中,添加一个私有成员变量来存储属性的值:// MyCtrl.h : Header file for the CMyCtrl ActiveX Control class. // ... class ATL_NO_VTABLE CMyCtrl : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CMyCtrl, &CLSID_MyCtrl>, public IConnectionPointContainerImpl<CMyCtrl>, public IConnectionPointImpl<CMyCtrl, &IID__IMyCtrlEvents>, public IPersistStreamInitImpl<CMyCtrl>, public IOleControlImpl<CMyCtrl>, public IOleObjectImpl<CMyCtrl>, public IOleInPlaceActiveObjectImpl<CMyCtrl>, public IViewObjectExImpl<CMyCtrl>, public IViewObject2Impl<CMyCtrl>, public OLECMNDEXECUTE_IMPL, public IProvideClassInfo2Impl<&CLSID_MyCtrl, &DIID__IMyCtrlEvents, &LIBID_MyActiveXControlLib>, public ISupportErrorInfo, public IObjectSafetyImpl<CMyCtrl, INTERFACESAFE_FOR_UNTRUSTED_CALLER | INTERFACESAFE_FOR_UNTRUSTED_DATA>, public CStockPropImpl<CMyCtrl, long, &CLSID_MyCtrl, &dispidBackColor>, public CStockPropImpl<CMyCtrl, OLE_COLOR, &CLSID_MyCtrl, &dispidForeColor>, public CStockPropImpl<CMyCtrl, BSTR, &CLSID_MyCtrl, &dispidFont>, public CStockPropImpl<CMyCtrl, CString, &CLSID_MyCtrl, &dispidText>, public CComControl<CMyCtrl>, public IMyCtrl, // <-- 这是我们自定义的接口 public IDispatchImpl<IMyCtrl, &IID_IMyCtrl, &LIBID_MyActiveXControlLib> { // ... (其他代码) private: long m_MyProperty; // 添加一个私有变量来存储属性值 public: // ... (其他代码) }; -
在
IMyCtrl接口定义中(通常在.idl文件中)添加属性的get和set方法,向导通常会为你创建一个示例属性,我们可以修改它或添加新的。 打开MyCtrl.idl文件,找到IMyCtrl接口定义:// MyCtrl.idl : IDL source for MyCtrl.dll // ... [ object, uuid(....), // 一个唯一的 GUID dual, helpstring("IMyCtrl Interface"), pointer_default(unique) ] interface IMyCtrl : IDispatch { [id(1), helpstring("method AboutBox")] void AboutBox(); [id(2), propget, helpstring("property MyProperty")] HRESULT MyProperty([out, retval] long *pVal); [id(2), propput, helpstring("property MyProperty")] HRESULT MyProperty([in] long newVal); };这里的
[id(2)]表示get和set共享同一个 dispatch ID。 -
在
MyCtrl.cpp文件中实现这些方法。// MyCtrl.cpp : Implementation of the CMyCtrl class // ... STDMETHODIMP CMyCtrl::get_MyProperty(long* pVal) { *pVal = m_MyProperty; return S_OK; } STDMETHODIMP CMyCtrl::put_MyProperty(long newVal) { m_MyProperty = newVal; // 如果属性改变需要触发重绘,可以在这里调用 // FireViewChange(); return S_OK; }
添加一个方法 (Method)
方法是可以被调用的“函数”。
-
在
MyCtrl.idl文件的IMyCtrl接口中添加一个方法:interface IMyCtrl : IDispatch { // ... [id(3), helpstring("method SayHello")] HRESULT SayHello([in] BSTR name, [out, retval] BSTR* greeting); }; -
在
MyCtrl.cpp中实现它:STDMETHODIMP CMyCtrl::SayHello(BSTR name, BSTR* greeting) { if (!greeting) return E_POINTER; // 使用 ATL 的字符串转换宏 CString strGreeting; strGreeting.Format(L"Hello, %s! Welcome to my ActiveX Control.", name); // 将结果复制到输出参数 *greeting = strGreeting.AllocSysString(); // 分配 COM 内存 return S_OK; }
添加一个事件 (Event)
事件是控件向容器“通知”某些事情发生的方式,这需要连接点。
-
定义事件接口:在
MyCtrl.idl文件中,找到_IMyCtrlEvents接口定义,这是容器需要实现的接口,用于接收事件。[ uuid(....), // 另一个唯一的 GUID helpstring("_IMyCtrlEvents Interface"), dispinterface ] _IMyCtrlEvents { properties: methods: [id(1), helpstring("Event OnClick")] void OnClick(); };我们添加了一个
OnClick事件。 -
触发事件:在
MyCtrl.cpp中,你需要一个宏来触发事件,通常在控件的某个行为(如鼠标点击)中调用。 在MyCtrl.cpp的开头,确保包含了atldef.h。 在需要触发事件的地方(如果你处理了 WM_LBUTTONDOWN 消息),添加以下代码:// 在 MyCtrl.cpp 中 #include <atldef.h> // ... 假设这是某个消息处理函数 LRESULT CMyCtrl::OnLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { // ... 你的处理逻辑 // 触发 OnClick 事件 Fire_Click(); // 注意:Fire_ + 事件名称 return 0; }Fire_Click宏是由 ATL 根据连接点接口自动生成的。
第 5 步:编译与注册
- 生成解决方案:按
F7或点击菜单“生成” -> “生成解决方案”。 - 自动注册:默认情况下,Visual Studio 会在编译后自动调用
regsvr32.exe来注册你的 DLL。- 如果成功,你会在“输出”窗口看到
DllRegisterServer in C:\...\MyActiveXControl.dll succeeded.的消息。 - 如果失败,会显示
failed,检查“错误列表”窗口,通常是代码中有语法错误或 IDL 文件有误。
- 如果成功,你会在“输出”窗口看到
注册过程会将你的控件信息写入到 Windows 注册表中,这样其他程序(如 IE, VB6)才能找到它。
第 6 步:测试控件
使用 ActiveX Control Test Container
这是 ATL 开发自带的、最方便的测试工具。
- 在 Visual Studio 中,点击菜单 “工具” -> “ActiveX Control Test Container”。
- 在 Test Container 窗口中,点击 “编辑” -> “插入新控件...”。
- 在列表中,你应该能找到你的控件,名称通常是你在 ProgID 中指定的部分(如
MyCtrl)。 - 选中它并点击“确定”,你的控件就会出现在容器窗口中。
- 你可以:
- 右键点击控件,选择“属性”来测试你添加的属性。
- 使用容器菜单的“调用方法”来测试你的方法。
- 点击控件,看是否能触发你定义的
OnClick事件(在 Test Container 中,你需要点击“查看” -> “连接点”,然后选择_IMyCtrlEvents并点击“连接”,才能在事件发生时看到消息)。
在 Visual Basic 6.0 中测试
VB6 是一个经典的 ActiveX 容器。
-
打开 VB6,创建一个新的“标准 EXE”工程。
-
在菜单中选择 “工程” -> “部件...”。
-
在“控件”选项卡中,浏览并找到你的控件(可能需要点击“浏览”并导航到你的 DLL 文件)。
-
勾选你的控件,然后点击“确定”。
-
你的控件会出现在工具箱中,你可以像使用其他标准控件一样把它拖到窗体上。
-
在 VB6 的属性窗口中,你可以设置和读取你定义的属性。
-
在代码窗口中,你可以调用方法和响应事件:
Private Sub Form_Load() ' 调用方法 Dim greeting As String greeting = MyCtrl1.SayHello("VB6 User") MsgBox greeting ' 设置属性 MyCtrl1.MyProperty = 123 End Sub Private Sub MyCtrl1_Click() ' 响应事件 MsgBox "You clicked the ActiveX control!" End Sub
第 7 步:打包与分发
你编译出的 DLL 文件不能直接复制到其他电脑上使用,因为它依赖于 Visual C++ Redistributable,并且需要注册。
正确的分发方式是创建一个 安装程序 (MSI)。
- 在 Visual Studio 中,右键点击你的解决方案,选择 “添加” -> “新建项目...”。
- 搜索并选择 “安装项目” 或 “Visual Studio Installer 项目”(根据你的 VS 版本,名称可能略有不同)。
- 为安装项目命名,
MyActiveXControlSetup。 - 在解决方案资源管理器中,右键点击新的安装项目,选择 “添加” -> “项目输出...”。
- 选择你的 ActiveX 控件项目,并勾选“主输出”,点击“确定”。
- 右键点击“主输出”,选择 “创建快捷方式”,并将其拖放到用户的“[程序集]文件夹”中(可选)。
- 右键点击安装项目,选择 “视图” -> “注册表”。
- 展开
HKEY_CLASSES_ROOT。 - 找到你的控件的 CLSID(在
MyCtrl_i.c/h文件中定义),右键点击它,选择 “新建” -> “项”**,命名为InprocServer32。 - 右键点击新建的
InprocServer32项,选择 “新建” -> “字符串值”**,命名为ThreadingModel,并将其值设置为Apartment。 - 双击
(默认)值,将其值设置为你的 DLL 文件的完整路径([TARGETDIR]MyActiveXControl.dll)。
- 展开
- 设置自定义操作:为了让安装程序在安装时注册控件,卸载时注销控件。
- 在安装项目上右键,选择 “视图” -> “自定义操作”**。
- 在“安装”节点上右键,选择 “添加自定义操作...”。
- 选择“应用程序文件夹”,然后选择你的“主输出”。
- 对“卸载”节点重复此操作。
- 生成安装包:右键点击安装项目,选择 “生成”。
你得到了一个 .msi 文件,用户只需双击这个文件,按照向导安装,你的控件就会被正确地复制到系统目录并注册,然后在任何兼容的容器中都可以使用了。
总结与注意事项
- GUID:每个 CLSID 和 IID 都必须是全局唯一的,向导会自动生成,不要手动修改。
- 内存管理:在 COM 中,字符串等数据需要使用特定的内存分配函数(如
SysAllocString),ATL 提供了方便的宏(如CComBSTR)和包装类(如CStringT的AllocSysString方法)来简化这个过程。 - 错误处理:使用
HRESULT作为返回值,如果出错,返回HRESULT_FROM_WIN32(GetLastError())或自定义的错误码,如果勾选了ISupportErrorInfo,可以通过SetErrorInfo提供更详细的错误信息。 - 线程安全:如果你的控件可能被多线程访问,需要仔细处理同步问题,ATL 的
CComCritSecLock等工具可以帮助你。 - 64位 vs 32位:确保你的项目平台(x86, x64, Any CPU)和目标应用程序的平台一致,在 64 位系统上,32 位控件注册在
Wow6432Node下。
开发 ActiveX 控件是一个复杂但功能强大的过程,遵循以上步骤,你就可以成功地创建、注册和分发自己的 C++ ActiveX 控件了。
