ATL下载
ATL
作为微软官方库,源码随Visual Studio
安装包 默认集成
1 |
C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC\14.29.30133\atlmfc\include\atl |
正确代码结构
接口定义(使用 IDL)
1 2 3 4 5 6 7 8 9 |
// Example.idl [ object, uuid(A4F4B3E0-1234-5678-9ABC-DEF123456789), pointer_default(unique) ] interface IExample : IUnknown { HRESULT ExampleMethod(); }; |
实现类(使用 ATL)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// CExample.h class ATL_NO_VTABLE CExample : public CComObjectRootEx<CComMultiThreadModel>, public CComCoClass<CExample, &CLSID_CExample>, public IExample { public: DECLARE_REGISTRY_RESOURCEID(IDR_CEXAMPLE) BEGIN_COM_MAP(CExample) COM_INTERFACE_ENTRY(IExample) END_COM_MAP() // IExample STDMETHODIMP ExampleMethod() override { // 具体实现 return S_OK; } }; |
对象创建宏
1 2 |
// 在 .cpp 文件中添加对象创建宏 OBJECT_ENTRY_AUTO(__uuidof(CExample), CExample) |
客户端调用示例
1 2 3 4 5 6 7 8 9 10 11 12 |
CComPtr<IExample> spExample; HRESULT hr = CoCreateInstance( CLSID_CExample, nullptr, CLSCTX_INPROC_SERVER, IID_IExample, reinterpret_cast<void**>(&spExample) ); if (SUCCEEDED(hr)) { spExample->ExampleMethod(); } |
完整示例
项目结构
1 2 3 4 5 |
ExampleComponent/ ├── ExampleComponent.idl // 接口定义文件 ├── ExampleComponent.h // 实现类头文件 ├── ExampleComponent.cpp // 实现类CPP文件 └── Client.cpp // 客户端调用代码 |
ExampleComponent.idl
- 在
Visual Studio
中创建ATL Project
- 添加
.idl
文件并编译,MIDL
会自动生成: ExampleComponent_i.h
(接口定义)ExampleComponent.tlb
(类型库)
- 添加
import "oaidl.idl";
- 见细节学习
import "ocidl.idl";
- 见细节学习
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
// 导入必要的 IDL 文件 import "oaidl.idl"; import "ocidl.idl"; // 定义接口 IExample [ object, uuid(6B29FC40-CA47-1067-B31D-00DD010662DA), // 用 uuidgen 生成唯一 GUID pointer_default(unique) ] interface IExample : IUnknown { [helpstring("示例方法")] HRESULT Add([in] int a, [in] int b, [out, retval] int* result); }; // 定义组件类库 [ uuid(12345678-ABCD-EF12-3456-7890ABCDEF01), version(1.0) ] library ExampleComponentLib { importlib("stdole32.tlb"); importlib("stdole2.tlb"); // 组件类 CLSID_CExample [ uuid(23456789-ABCD-EF12-3456-7890ABCDEF02) ] coclass CExample { [default] interface IExample; }; }; |
实现类 ExampleComponent.h
ATL_NO_VTABLE
- 见实现类细节学习
CComObjectRootEx
- 见实现类细节学习
CComMultiThreadModel
- 见实现类细节学习
CComCoClass<CCExample, &CLSID_CExample>
- 见实现类细节学习
DECLARE_REGISTRY_RESOURCEID(IDR_CCEXAMPLE)
- 见实现类细节学习
BEGIN_COM_MAP(CCExample)
- 见实现类细节学习
COM_INTERFACE_ENTRY
- 见实现类细节学习
STDMETHODIMP
- 见实现类细节学习
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#pragma once #include "ExampleComponent_i.h" // MIDL 生成的头文件 // ATL 实现类 class ATL_NO_VTABLE CCExample : public CComObjectRootEx<CComMultiThreadModel>, // 线程模型 public CComCoClass<CCExample, &CLSID_CExample>, // 组件 CLSID public IExample { public: DECLARE_REGISTRY_RESOURCEID(IDR_CCEXAMPLE) // 注册资源ID // COM 接口映射表 BEGIN_COM_MAP(CCExample) COM_INTERFACE_ENTRY(IExample) END_COM_MAP() // IExample 方法实现 STDMETHODIMP Add(int a, int b, int* result) override { *result = a + b; return S_OK; } }; |
实现类注册 ExampleComponent.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#include "pch.h" #include "ExampleComponent.h" // 将组件注册到 ATL 模块 OBJECT_ENTRY_AUTO(__uuidof(CExample), CCExample) // ATL 模块定义 CComModule _Module; // DLL 入口点 extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) { return _Module.DllMain(hInstance, dwReason, lpReserved); } // 导出函数 STDAPI DllCanUnloadNow() { return _Module.DllCanUnloadNow(); } STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv) { return _Module.DllGetClassObject(rclsid, riid, ppv); } |
编译组件
- 编译生成
ExampleComponent.dll
注册组件
1 |
regsvr32 ExampleComponent.dll |
客户端调用 Client.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
#include <windows.h> #include <iostream> #include "ExampleComponent_i.h" // 包含生成的接口头文件 int main() { CoInitialize(nullptr); // 初始化 COM // 创建组件实例 CComPtr<IExample> spExample; HRESULT hr = CoCreateInstance( CLSID_CExample, nullptr, CLSCTX_INPROC_SERVER, IID_IExample, (void**)&spExample ); if (SUCCEEDED(hr)) { int result = 0; spExample->Add(3, 5, &result); std::cout << "3 + 5 = " << result << std::endl; } else { std::cerr << "创建组件失败! HRESULT = 0x" << std::hex << hr << std::endl; } CoUninitialize(); return 0; } |
IDL文件细节学习
核心作用
IDL
文件用于 标准化定义COM
接口、组件和类型库,通过MIDL
编译器生成以下内容C/C++
头文件(如ExampleComponent_i.h
):包含接口的C++
定义- 类型库(
.tlb
文件):二进制文件,供高级语言(如C#
、VB
)调用COM
组件 - 代理/存根代码:支持跨进程或跨机器调用(
DCOM
)
import "oaidl.idl";
- 定义自动化(
Automation
)相关接口和数据类型:- 基础接口:
IUnknown
、IDispatch
(支持自动化调用的核心接口) - 数据类型:
BSTR
(宽字符串)、VARIANT
(泛型数据类型)、SAFEARRAY
(安全数组)等 - 错误处理:
HRESULT
的定义
- 基础接口:
- 用途:
- 若接口需要支持自动化(如被脚本语言调用)或使用自动化兼容的数据类型,必须导入此文件
import "ocidl.idl";
- 定义
OLE
控件(ActiveX
控件)相关接口:- 控件接口:
IOleControl
、IOleObject
- 可视化特性:
IViewObject
(渲染)、IQuickActivate
(快速激活) - 连接点(
Connection Points
):IConnectionPoint
(事件机制)
- 控件接口:
- 用途:
- 若组件是
ActiveX
控件或需要事件机制,必须导入此文件
- 若组件是
oaidl和ocidl的场景
场景 | 是否需要 oaidl.idl |
是否需要 ocidl.idl |
定义纯 COM 接口(仅 IUnknown ) |
是(包含 IUnknown ) |
否 |
支持自动化(如脚本调用) | 是 | 否 |
创建 ActiveX 控件 | 是 | 是 |
使用 BSTR 或 VARIANT |
是 | 否 |
oaidl和ocidl实际开发中的常见用法
- 基础
COM
组件(非自动化)
1 2 3 4 5 6 7 8 9 10 |
import "oaidl.idl"; // 必需(IUnknown 在此定义) // import "ocidl.idl"; // 可选(未使用 OLE 控件特性时可省略) [ object, uuid(...) ] interface IMath : IUnknown { HRESULT Add([in] int a, [in] int b, [out, retval] int* result); }; |
ActiveX
控件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import "oaidl.idl"; // 必需 import "ocidl.idl"; // 必需(控件相关接口在此定义) [ object, uuid(...) ] interface IMyControl : IDispatch { // 支持自动化方法 [id(1)] HRESULT ShowDialog(); }; [ uuid(...) ] coclass MyControl { [default] interface IMyControl; [default, source] interface IConnectionPointContainer; // 需要 ocidl.idl }; |
接口定义interface
块
1 2 3 4 5 6 7 8 9 10 11 12 13 |
[ object, // 标记为 COM 接口(非 DCOM) uuid(6B29FC40-CA47-1067-B31D-00DD010662DA), // 接口的 GUID pointer_default(unique) // 默认指针类型为 "unique" ] interface IExample : IUnknown { // 继承自 IUnknown [helpstring("示例方法")] // 方法描述(可选) HRESULT Add( [in] int a, // 输入参数 [in] int b, [out, retval] int* result // 输出参数(返回值) ); }; |
属性/语法 | 说明 |
[object] |
声明这是一个 COM 接口(非 RPC 接口)。 |
uuid(...) |
接口的全局唯一标识符(GUID),可用 guidgen.exe 生成。 |
pointer_default(unique) |
定义未明确标记的指针的默认行为: - unique :指针不可为 NULL 。 |
[helpstring("...")] |
提供接口或方法的描述信息,供开发工具显示。 |
[in] / [out] |
参数方向:输入、输出或双向([in, out] )。 |
[retval] |
标记返回值参数,某些语言(如 VB)可将其映射为函数返回值。 |
类型库定义library
块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
[ uuid(12345678-ABCD-EF12-3456-7890ABCDEF01), // 类型库的 GUID version(1.0) // 类型库版本号 ] library ExampleComponentLib { // 类型库名称 importlib("stdole32.tlb"); // 导入标准类型库 importlib("stdole2.tlb"); // 定义组件类 [ uuid(23456789-ABCD-EF12-3456-7890ABCDEF02) // 组件类的 CLSID ] coclass CExample { // COM 类名 [default] interface IExample; // 默认接口 }; }; |
元素/语法 | 说明 |
library |
定义一个 类型库,包含组件类、接口等信息。 |
uuid(...) |
类型库的全局唯一标识符(GUID)。 |
version(1.0) |
类型库版本号,用于版本控制。 |
importlib("stdole32.tlb") |
导入标准类型库,确保当前库可引用其定义(如 IUnknown )。 |
coclass |
定义一个 COM 组件类(CoClass),对应实现类的 CLSID。 |
[default] interface |
指定组件的默认接口,客户端可通过 CoCreateInstance 直接获取此接口。 |
语法规则
- 接口定义
1 2 3 4 5 6 7 8 9 10 |
[ // 属性列表 object, uuid(...), ... ] interface InterfaceName : BaseInterface { // 方法定义 [attributes] ReturnType MethodName([参数属性] 类型 参数名, ...); }; |
- 组件类定义
1 2 3 4 5 6 7 8 |
[ uuid(...) // CLSID ] coclass ClassName { [default] interface Interface1; // 默认接口 interface Interface2; // 其他接口 [source] interface Interface3; // 事件源接口 }; |
- 类型库定义
- 可以定义多个组件类
1 2 3 4 5 6 7 8 9 10 11 12 13 |
[ uuid(...), version(...), ... ] library LibraryName { // 导入其他类型库 importlib("xxx.tlb"); // 声明接口、组件类等 interface IExample; coclass CExample; }; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
// 类型库定义 [ uuid(12345678-ABCD-EF12-3456-7890ABCDEF01), version(1.0) ] library ExampleComponentLib { importlib("stdole32.tlb"); importlib("stdole2.tlb"); // 第一个组件类:CExample1 [ uuid(23456789-ABCD-EF12-3456-7890ABCDEF02) ] coclass CExample1 { [default] interface IExample1; // 默认接口 }; // 第二个组件类:CExample2 [ uuid(3456789A-BCDE-F123-4567-890ABCDEF03) ] coclass CExample2 { [default] interface IExample2; [source] interface IExampleEvents; // 事件源接口 }; }; |
实现类细节学习
ATL_NO_VTABLE
- 是
ATL
提供的一个宏,其定义为:
1 |
#define ATL_NO_VTABLE __declspec(novtable) |
- 作用:
- 告诉编译器 不要为该类生成虚函数表(
vtable
) - 虚函数表是
C++
实现多态的核心机制,但对于某些中间基类(如ATL
实现类的基类),虚函数表可能未被完全使用
禁用虚函数表可减少代码体积和初始化开销
- 告诉编译器 不要为该类生成虚函数表(
- 适用场景
- 当类被用作中间基类(如
CCExample
继承自CComObjectRootEx
和CComCoClass
),且不会直接实例化时 - 就是说:
当类标记为ATL_NO_VTABLE
时,编译器不会生成完整的虚函数表。
如果直接通过new
创建该类的实例
由于虚函数表未正确初始化,调用虚函数(如QueryInterface
、AddRef
)会导致 未定义行为(如崩溃) - 所以说:
它的核心作用是 阻止编译器为类生成虚函数表(vtable
)。但这并不直接决定类的实例化方式,而是优化手段 - 在
ATL
中,所有实现COM
接口的类都应使用此宏
- 当类被用作中间基类(如
CComObjectRootEx
- 功能
- 提供
COM
对象的 引用计数管理 和 线程安全支持 - 实现
IUnknown
的核心方法(QueryInterface
、AddRef
、Release
)的默认逻辑
- 提供
- 模板参数
- 接受一个线程模型类(如
CComMultiThreadModel
),用于定义线程安全策略
- 接受一个线程模型类(如
CComMultiThreadModel
- 功能
- 定义 多线程环境 下的线程安全操作
- 使用
InterlockedIncrement
和InterlockedDecrement
保证引用计数的原子性 - 通过临界区(
Critical Section
)保护内部数据
CComObjectRootEx<CComMultiThreadModel>
- 将
CComMultiThreadModel
的线程安全机制注入到CComObjectRootEx
中 - 使
CCExample
类在多线程环境下安全地管理引用计数和接口查询
CComCoClass
- 功能
- 提供
COM
对象的 类工厂支持,用于创建对象实例 - 管理组件的
CLSID
(Class Identifier
)和 注册信息 - 包含
CreateInstance
静态方法,用于创建对象实例
- 提供
CComCoClass<CCExample, &CLSID_CExample>
CCExample
- 当前实现类的类型
&CLSID_CExample
- 组件的
CLSID
(在IDL
文件中定义)
- 组件的
- 作用
- 将组件类
CCExample
与特定的CLSID
绑定 - 自动生成类工厂代码,使得客户端可通过
CoCreateInstance
创建该组件
- 将组件类
DECLARE_REGISTRY_RESOURCEID(IDR_CCEXAMPLE)
DECLARE_REGISTRY_RESOURCEID
- 是
ATL
中的一个宏,用于 将COM
组件的注册信息与资源文件中的注册脚本(.rgs
文件)绑定 - 它的核心作用是:
- 声明组件注册时需要使用的资源
ID
,该资源ID
对应嵌入在二进制文件(DLL
或EXE
)中的注册脚本 - 当组件被注册(如通过
regsvr32
)时,ATL
会自动解析该资源脚本,将组件的CLSID
、ProgID
、线程模型等信息写入注册表
- 是
- 宏定义
- 参数
x
:资源 ID(如IDR_CCEXAMPLE
),对应嵌入的.rgs
注册脚本 - 功能:声明一个静态方法
UpdateRegistry
,该方法调用ATL
模块的UpdateRegistryFromResource
,加载并执行资源中的注册脚本
- 参数
1 2 3 4 |
#define DECLARE_REGISTRY_RESOURCEID(x) \ static HRESULT WINAPI UpdateRegistry(BOOL bRegister) { \ return _Module.UpdateRegistryFromResource(x, bRegister); \ } |
- 资源文件(
.rc
文件)- 假设资源文件中存在名为
IDR_CCEXAMPLE
的注册脚本(通常为.rgs
文件)
- 假设资源文件中存在名为
1 |
IDR_CCEXAMPLE REGISTRY "ExampleComponent.rgs" |
- 资源脚本(
.rgs
文件),内容示例如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
HKCR { Example.Component.1 = s 'CCExample Class' { CLSID = s '{23456789-ABCD-EF12-3456-7890ABCDEF02}' } Example.Component = s 'CCExample Class' { CLSID = s '{23456789-ABCD-EF12-3456-7890ABCDEF02}' CurVer = s 'Example.Component.1' } NoRemove CLSID { ForceRemove '{23456789-ABCD-EF12-3456-7890ABCDEF02}' = s 'CCExample Class' { ProgID = s 'Example.Component.1' VersionIndependentProgID = s 'Example.Component' InprocServer32 = s '%MODULE%' { val ThreadingModel = s 'Apartment' } } } } |
- 具体注册流程:当调用
DllRegisterServer
注册组件时:ATL
模块调用CComCoClass
的UpdateRegistry
方法- 通过
DECLARE_REGISTRY_RESOURCEID
绑定的资源ID
,找到.rgs
文件 - 解析脚本并将注册表项写入系统注册表
- 对比其他注册方式一:
DECLARE_REGISTRY_RESOURCE
- 与
DECLARE_REGISTRY_RESOURCEID
类似,但通过资源名称(而非ID
)定位脚本
- 与
1 |
DECLARE_REGISTRY_RESOURCE("ExampleComponent.rgs") |
- 对比其他注册方式二:动态注册(手动编写注册代码)
- 代码冗余,维护困难
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
static HRESULT WINAPI UpdateRegistry(BOOL bRegister) { if (bRegister) { // 手动写入注册表 CRegKey key; key.Create(HKEY_CLASSES_ROOT, L"Example.Component"); key.SetStringValue(L"CLSID", L"{23456789-ABCD-EF12-3456-7890ABCDEF02}"); } else { // 删除注册表项 CRegKey key; key.Open(HKEY_CLASSES_ROOT, L""); key.DeleteSubKey(L"Example.Component"); } return S_OK; } |
BEGIN_COM_MAP(CCExample)
- 实现
BEGIN_COM_MAP
是一个宏,展开后生成以下代码:- 它定义了
QueryInterface
方法的框架,并通过后续的COM_INTERFACE_ENTRY
宏填充接口映射表
1 2 3 4 5 6 7 |
public: typedef CCExample _ComMapClass; static HRESULT CALLMAP_FUNC QueryInterface(void* pThis, REFIID iid, void** ppv) { _ComMapClass* p = reinterpret_cast<_ComMapClass*>(pThis); // 接口查询逻辑 } // 其他支持代码... |
- 作用
- 声明一个
COM
接口映射表,用于告诉ATL
当前对象支持哪些接口 - 在
QueryInterface
被调用时,ATL
根据此映射表返回正确的接口指针
- 声明一个
COM_INTERFACE_ENTRY(IExample)
COM_INTERFACE_ENTRY
- 是一个宏,展开后生成:
- 它会在接口映射表中插入一条记录,包含接口的 IID 和偏移量信息
1 2 3 |
{ &IID_IExample, offsetofclass(IExample, _ComMapClass), _ATL_SIMPLEMAPENTRY }, |
- 作用
- 将接口
IExample
添加到CCExample
的接口映射表中 - 当客户端调用
QueryInterface(IID_IExample, ...)
时,ATL
会根据此条目返回IExample
接口的指针
- 将接口
STDMETHODIMP
- 定义
1 |
#define STDMETHODIMP HRESULT STDMETHODCALLTYPE |
- 作用
- 声明一个
COM
接口方法的返回类型和调用约定 HRESULT
是COM
方法的标准返回类型(用于错误码)STDMETHODCALLTYPE
定义为__stdcall
,这是COM
方法的默认调用约定
- 声明一个
- 示例:
- 等效写法
1 2 3 4 |
STDMETHODIMP Add(int a, int b, int* result) override { *result = a + b; return S_OK; } |
1 |
virtual HRESULT __stdcall Add(int a, int b, int* result) override; |
tlb
文件细节学习
概述
C#
、VB
或其他支持COM
互操作的语言(如Python
、Delphi
)可以通过 类型库(.tlb
文件) 直接调用C++
编写的COM
组件
作用
- 类型库是一个 二进制元数据文件,由
IDL
文件通过MIDL
编译器生成,包含以下信息:- 接口定义(方法签名、参数类型、返回类型)
- 组件类(
CoClass
)的CLSID
和其支持的接口 - 类型信息(如枚举、结构体、
GUID
等)
- 它的核心作用是 充当跨语言的“桥梁”,让非
C++
语言(如C#
)能理解COM
组件的接口规范,并生成对应的代理代码
C# 调用 C++ COM 组件的具体步骤
- 生成 COM 组件并注册
- 编译
C++
COM
项目,生成.dll
文件 - 使用
regsvr32
注册组件,将CLSID
和接口信息写入注册表
- 编译
1 |
regsvr32 ExampleComponent.dll |
- 在
C#
中引用类型库- 方法 1:直接引用
.tlb
文件
在Visual Studio
中右键项目 → 添加引用 →COM
→ 选择类型库(.tlb
文件) - 方法 2:通过
tlbimp.exe
生成互操作程序集
使用.NET Framework SDK
工具tlbimp.exe
生成托管代理类(Interop Assembly
)
然后在C#
项目中引用生成的Interop.ExampleComponent.dll
- 方法 1:直接引用
1 |
tlbimp ExampleComponent.tlb /out:Interop.ExampleComponent.dll |
- 代码调用
COM
组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
using System; using ExampleComponentLib; // 导入类型库命名空间 class Program { static void Main() { // 创建 COM 组件实例(通过 CLSID 或 ProgID) IExample example = (IExample)new CExample(); // 调用方法 int result; example.Add(3, 5, out result); Console.WriteLine($"3 + 5 = {result}"); } } |
C# 调用 C++ COM底层机制解析
- 运行时可调用包装器(
RCW
,Runtime Callable Wrapper
).NET
CLR
通过RCW
将COM
对象封装为托管对象,处理以下任务:- 封送处理(
Marshaling
):转换COM
数据类型(如BSTR
、VARIANT
)为.NET
类型(如string
、object
) - 生命周期管理:自动调用
AddRef
/Release
,避免内存泄漏 - 错误转换:将
COM
的HRESULT
转换为.NET
异常(如COMException
)
- 调用过程
- 实例化组件:
C#
的new CExample()
实际调用CoCreateInstance(CLSID_CExample, ...)
- 方法调用:
example.Add(...)
通过RCW
将参数封送为COM
兼容格式,调用IExample::Add
- 结果返回:
COM
返回的结果通过RCW
转换回.NET
类型
- 实例化组件:
VB调用 C++ COM 组件的具体步骤
VB
的调用方式与C#
类似,语法更简洁:
1 2 3 4 5 6 7 8 9 10 |
Imports ExampleComponentLib Module Program Sub Main() Dim example As IExample = New CExample() Dim result As Integer example.Add(3, 5, result) Console.WriteLine($"3 + 5 = {result}") End Sub End Module |
本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ 51CTO:Linux C++网络编程五08/20
- ♥ 深入理解C++11:C++11新特性解析与应用 一12/21
- ♥ Windows 核心编程 _ 创建&&终止线程07/02
- ♥ C++_volatile10/08
- ♥ Windows 核心编程 _ 线程调度07/07
- ♥ C++14_第二篇06/21