概述
Windows
的组件对象模型(Component Object Model
,COM
)是一种用于构建可重用软件组件的标准和技术- 它允许软件组件以二进制形式进行互操作,独立于编程语言、开发工具和平台
COM
技术广泛应用于各种Windows
应用程序和系统服务- 包括
OLE
、ActiveX
和DCOM
- 包括
基本概念
接口
- 接口定义
COM
中的接口是一个抽象类型,定义了一组相关的方法,不包含具体的实现- 每个
COM
接口都有一个唯一的接口标识符(Interface Identifier
,IID
)
IUnknown
接口- 所有
COM
接口都继承自IUnknown
接口 IUnknown
定义了三个方法:QueryInterface
、AddRef
和Release
- 所有
1 2 3 4 5 6 |
class IUnknown { public: virtual HRESULT QueryInterface(REFIID riid, void** ppvObject) = 0; virtual ULONG AddRef() = 0; virtual ULONG Release() = 0; }; |
- 疑问:
- 这里所谓的
COM
接口,就是COM
组件里面某个派生类实现的函数 - 每个函数都对应一个唯一
ID
- 这里所谓的
1 2 3 4 5 6 |
// COM 接口通常是以纯虚函数(抽象方法)的形式定义的 class IExample : public IUnknown { public: virtual void ExampleMethod() = 0; }; |
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 36 37 |
// 实现类提供了这些接口函数的具体实现 class Example : public IExample { public: Example() : refCount(1) {} // IUnknown 方法实现 HRESULT QueryInterface(REFIID riid, void** ppv) override { if (riid == IID_IUnknown || riid == IID_IExample) { *ppv = static_cast<IExample*>(this); AddRef(); return S_OK; } *ppv = nullptr; return E_NOINTERFACE; } ULONG AddRef() override { return InterlockedIncrement(&refCount); } ULONG Release() override { ULONG newRefCount = InterlockedDecrement(&refCount); if (newRefCount == 0) { delete this; } return newRefCount; } // IExample 方法实现 void ExampleMethod() override { // 方法实现 } private: LONG refCount; }; |
1 2 3 4 5 6 7 |
// 每个 COM 接口都有一个唯一的接口标识符(IID),用于标识该接口 const IID IID_IExample = { /* 具体的 GUID 值 */ }; // 每个 COM 类都有一个唯一的类标识符(CLSID),用于标识该类 const CLSID CLSID_Example = { /* 具体的 GUID 值 */ }; |
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
#include <Windows.h> #include <iostream> // {00000000-0000-0000-0000-000000000001} const IID IID_IExample = { 0x00000000, 0x0000, 0x0000, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } }; // {00000000-0000-0000-0000-000000000002} const CLSID CLSID_Example = { 0x00000000, 0x0000, 0x0000, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 } }; class IExample : public IUnknown { public: virtual void ExampleMethod() = 0; }; class Example : public IExample { public: Example() : refCount(1) {} // IUnknown 方法实现 HRESULT QueryInterface(REFIID riid, void** ppv) override { if (riid == IID_IUnknown || riid == IID_IExample) { *ppv = static_cast<IExample*>(this); AddRef(); return S_OK; } *ppv = nullptr; return E_NOINTERFACE; } ULONG AddRef() override { return InterlockedIncrement(&refCount); } ULONG Release() override { ULONG newRefCount = InterlockedDecrement(&refCount); if (newRefCount == 0) { delete this; } return newRefCount; } // IExample 方法实现 void ExampleMethod() override { std::cout << "ExampleMethod called!" << std::endl; } private: LONG refCount; }; // 类工厂实现 class ExampleClassFactory : public IClassFactory { public: ExampleClassFactory() : refCount(1) {} HRESULT QueryInterface(REFIID riid, void** ppv) override { if (riid == IID_IUnknown || riid == IID_IClassFactory) { *ppv = static_cast<IClassFactory*>(this); AddRef(); return S_OK; } *ppv = nullptr; return E_NOINTERFACE; } ULONG AddRef() override { return InterlockedIncrement(&refCount); } ULONG Release() override { ULONG newRefCount = InterlockedDecrement(&refCount); if (newRefCount == 0) { delete this; } return newRefCount; } HRESULT CreateInstance(IUnknown* pUnkOuter, REFIID riid, void** ppv) override { if (pUnkOuter != nullptr) { return CLASS_E_NOAGGREGATION; } Example* pExample = new Example(); HRESULT hr = pExample->QueryInterface(riid, ppv); pExample->Release(); return hr; } HRESULT LockServer(BOOL fLock) override { if (fLock) { AddRef(); } else { Release(); } return S_OK; } private: LONG refCount; }; // DLL 导出函数 extern "C" HRESULT __stdcall DllGetClassObject(REFCLSID rclsid, REFIID riid, void** ppv) { if (rclsid == CLSID_Example) { ExampleClassFactory* pFactory = new ExampleClassFactory(); HRESULT hr = pFactory->QueryInterface(riid, ppv); pFactory->Release(); return hr; } return CLASS_E_CLASSNOTAVAILABLE; } extern "C" HRESULT __stdcall DllCanUnloadNow() { return S_OK; } // 客户端代码 int main() { CoInitialize(NULL); IExample* pExample = nullptr; HRESULT hr = CoCreateInstance(CLSID_Example, NULL, CLSCTX_INPROC_SERVER, IID_IExample, (void**)&pExample); if (SUCCEEDED(hr)) { pExample->ExampleMethod(); pExample->Release(); } else { std::cerr << "CoCreateInstance failed: " << hr << std::endl; } CoUninitialize(); return 0; } |
关于接口和实现类
- 接口(
Interface
):- 纯虚类(如
IExample
),仅定义方法签名,不包含实现
- 纯虚类(如
- 实现类(
Implementation Class
):- 具体实现接口的类(如
Example
),不是接口,而是接口的具体实现
- 具体实现接口的类(如
上述示例代码的一些问题
COM
接口方法 必须返回HRESULT
以支持错误处理机制
1 2 3 4 5 |
// 错误写法 ❌ virtual void ExampleMethod() override; // 正确写法 ✅ virtual HRESULT STDMETHODCALLTYPE ExampleMethod() override; |
- 未正确处理
IUnknown
方法- 虽然手动实现了
QueryInterface
、AddRef
、Release
,但以下问题需注意: - 线程安全性:
InterlockedIncrement
/Decrement
是线程安全的,但若组件被标记为单线程模型(如CComSingleThreadModel
),需调整实现 - 对象生命周期:
delete this
在Release
中是合法的,但需确保对象仅通过new
创建
- 虽然手动实现了
- 未使用
ATL
辅助类ATL
提供CComObjectRootEx
和CComObject
等模板类,可自动处理引用计数和IUnknown
方法,避免手动实现错误
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// 推荐使用 ATL 的实现方式 ✅ class ATL_NO_VTABLE CExample : public CComObjectRootEx<CComMultiThreadModel>, public IExample { public: DECLARE_NO_REGISTRY() BEGIN_COM_MAP(CExample) COM_INTERFACE_ENTRY(IExample) END_COM_MAP() // 无需手动实现 QueryInterface/AddRef/Release // ATL 自动处理 IUnknown 方法 // IExample 方法 STDMETHODIMP ExampleMethod() override { /* 实现 */ } }; |
类
- 类定义:
COM
类是实现一个或多个COM
接口的具体实现- 每个
COM
类都有一个唯一的类标识符(Class Identifier
,CLSID
)
- 实例化:
- 通过类工厂(
Class Factory
)实例化COM
类 - 类工厂实现了
IClassFactory
接口,提供CreateInstance
方法用于创建COM
对象实例
- 通过类工厂(
组件
- 组件定义:
COM
组件是一个包含一个或多个COM
类的二进制文件,通常是DLL
或EXE
文件
关于类工厂
- 在
COM
(组件对象模型)中,通常情况下每个COM
类都需要实现一个类工厂(Class Factory
),以便实例化COM
对象 - 类工厂负责创建
COM
对象的实例,并且它本身是一个COM
对象,实现了IClassFactory
接口- 类工厂可以在创建对象时执行一些初始化操作或进行资源分配
- 通过类工厂,客户端代码无需知道
COM
对象的具体实现,只需要通过类工厂请求实例化对象
- 类工厂实现
CreateInstance
:创建COM
对象的实例LockServer
:用于控制类工厂的生命周期(通常用于保持服务器进程的活跃状态)
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
#include <Windows.h> #include <iostream> #include <thread> class IExample : public IUnknown { public: virtual void ExampleMethod() = 0; }; class Example : public IExample { public: Example() : refCount(1) {} HRESULT QueryInterface(REFIID riid, void** ppv) override { if (riid == IID_IUnknown || riid == __uuidof(IExample)) { *ppv = static_cast<IExample*>(this); AddRef(); return S_OK; } *ppv = nullptr; return E_NOINTERFACE; } ULONG AddRef() override { return InterlockedIncrement(&refCount); } ULONG Release() override { ULONG newRefCount = InterlockedDecrement(&refCount); if (newRefCount == 0) { delete this; } return newRefCount; } void ExampleMethod() override { std::cout << "ExampleMethod called in thread " << GetCurrentThreadId() << std::endl; } private: LONG refCount; }; class ExampleClassFactory : public IClassFactory { public: ExampleClassFactory() : refCount(1) {} HRESULT QueryInterface(REFIID riid, void** ppv) override { if (riid == IID_IUnknown || riid == IID_IClassFactory) { *ppv = static_cast<IClassFactory*>(this); AddRef(); return S_OK; } *ppv = nullptr; return E_NOINTERFACE; } ULONG AddRef() override { return InterlockedIncrement(&refCount); } ULONG Release() override { ULONG newRefCount = InterlockedDecrement(&refCount); if (newRefCount == 0) { delete this; } return newRefCount; } HRESULT CreateInstance(IUnknown* pUnkOuter, REFIID riid, void** ppv) override { if (pUnkOuter != nullptr) { return CLASS_E_NOAGGREGATION; } Example* pExample = new Example(); HRESULT hr = pExample->QueryInterface(riid, ppv); pExample->Release(); return hr; } HRESULT LockServer(BOOL fLock) override { if (fLock) { AddRef(); } else { Release(); } return S_OK; } private: LONG refCount; }; // DLL 导出函数 extern "C" HRESULT __stdcall DllGetClassObject(REFCLSID rclsid, REFIID riid, void** ppv) { if (rclsid == CLSID_Example) { ExampleClassFactory* pFactory = new ExampleClassFactory(); HRESULT hr = pFactory->QueryInterface(riid, ppv); pFactory->Release(); return hr; } return CLASS_E_CLASSNOTAVAILABLE; } extern "C" HRESULT __stdcall DllCanUnloadNow() { return S_OK; } // 主函数 int main() { CoInitialize(NULL); IExample* pExample = nullptr; HRESULT hr = CoCreateInstance(CLSID_Example, NULL, CLSCTX_INPROC_SERVER, IID_IExample, (void**)&pExample); if (SUCCEEDED(hr)) { pExample->ExampleMethod(); pExample->Release(); } else { std::cerr << "CoCreateInstance failed: " << hr << std::endl; } CoUninitialize(); return 0; } |
使用
- 注册
COM
组件需要在计算机上注册,以便系统能够找到和加载它们- 注册过程涉及在
Windows
注册表中创建相应的条目,指示COM
组件的类标识符(CLSID
)、接口标识符(IID
)以及组件所在的DLL
或EXE
文件的路径
1 2 3 |
regsvr32 example.dll regsvr32 /u example.dll |
- 在
DLL
中实现DllRegisterServer
和DllUnregisterServer
COM
组件的DLL
文件需要实现DllRegisterServer
和DllUnregisterServer
函数,以便在注册表中添加或删除相应的条目
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
#include <windows.h> HRESULT RegisterServer(HMODULE hModule, const CLSID& clsid, const char* friendlyName, const char* progID) { char modulePath[MAX_PATH]; GetModuleFileName(hModule, modulePath, MAX_PATH); // 创建注册表项 HKEY hKey; char subKey[256]; // CLSID 项 sprintf(subKey, "CLSID\\{%08lX-%04X-%04X-%04X-%012llX}", clsid.Data1, clsid.Data2, clsid.Data3, (clsid.Data4[0] << 8) + clsid.Data4[1], (unsigned long long)(clsid.Data4[2] << 40) + (clsid.Data4[3] << 32) + (clsid.Data4[4] << 24) + (clsid.Data4[5] << 16) + (clsid.Data4[6] << 8) + clsid.Data4[7]); RegCreateKeyEx(HKEY_CLASSES_ROOT, subKey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, NULL); RegSetValueEx(hKey, NULL, 0, REG_SZ, (BYTE*)friendlyName, strlen(friendlyName) + 1); RegCloseKey(hKey); // InprocServer32 项 strcat(subKey, "\\InprocServer32"); RegCreateKeyEx(HKEY_CLASSES_ROOT, subKey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, NULL); RegSetValueEx(hKey, NULL, 0, REG_SZ, (BYTE*)modulePath, strlen(modulePath) + 1); RegSetValueEx(hKey, "ThreadingModel", 0, REG_SZ, (BYTE*)"Both", 5); RegCloseKey(hKey); // ProgID 项 sprintf(subKey, "%s\\CLSID", progID); RegCreateKeyEx(HKEY_CLASSES_ROOT, subKey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, NULL); RegSetValueEx(hKey, NULL, 0, REG_SZ, (BYTE*)modulePath, strlen(modulePath) + 1); RegCloseKey(hKey); return S_OK; } HRESULT UnregisterServer(const CLSID& clsid, const char* progID) { char subKey[256]; // 删除 CLSID 项 sprintf(subKey, "CLSID\\{%08lX-%04X-%04X-%04X-%012llX}", clsid.Data1, clsid.Data2, clsid.Data3, (clsid.Data4[0] << 8) + clsid.Data4[1], (unsigned long long)(clsid.Data4[2] << 40) + (clsid.Data4[3] << 32) + (clsid.Data4[4] << 24) + (clsid.Data4[5] << 16) + (clsid.Data4[6] << 8) + clsid.Data4[7]); RegDeleteTree(HKEY_CLASSES_ROOT, subKey); // 删除 ProgID 项 sprintf(subKey, "%s\\CLSID", progID); RegDeleteTree(HKEY_CLASSES_ROOT, subKey); return S_OK; } STDAPI DllRegisterServer() { return RegisterServer(GetModuleHandle(NULL), CLSID_Example, "Example COM Component", "Example.Component"); } STDAPI DllUnregisterServer() { return UnregisterServer(CLSID_Example, "Example.Component"); } |
- 手动注册
- 如果需要手动注册,可以通过编辑注册表来实现
- 将上述内容保存为
.reg
文件,然后双击导入到注册表中
1 2 3 4 5 6 7 8 9 10 11 |
Windows Registry Editor Version 5.00 [HKEY_CLASSES_ROOT\CLSID\{00000000-0000-0000-0000-000000000002}] @="Example COM Component" [HKEY_CLASSES_ROOT\CLSID\{00000000-0000-0000-0000-000000000002}\InprocServer32] @="C:\\Path\\To\\Your\\Component.dll" "ThreadingModel"="Both" [HKEY_CLASSES_ROOT\Example.Component\CLSID] @="{00000000-0000-0000-0000-000000000002}" |
COM 的工作机制
注册和类工厂
COM
组件在系统中注册,注册信息存储在Windows
注册表中,包括CLSID
和它们对应的DLL/EXE
路径
1 |
HKEY_CLASSES_ROOT\CLSID\{CLSID}\InprocServer32 = "path_to_dll" |
实例化对象
- 通过
CoCreateInstance
函数创建COM
对象实例
1 2 3 4 5 6 7 |
HRESULT CoCreateInstance( REFCLSID rclsid, LPUNKNOWN pUnkOuter, DWORD dwClsContext, REFIID riid, LPVOID *ppv ); |
接口查询
- 使用
QueryInterface
方法在COM
对象上查询特定接口
1 |
HRESULT QueryInterface(REFIID riid, void** ppvObject); |
引用计数
- 使用
AddRef
和Release
方法管理对象的生命周期,通过引用计数实现对象的自动管理
1 2 |
ULONG AddRef(); ULONG Release(); |
内存布局和 vtable
- 内存布局
- 每个
COM
对象在内存中的布局包括一个指向vtable
的指针,vtable
包含接口方法的指针
- 每个
1 2 3 4 5 6 7 8 |
+-----------------+ | COM Object | +-----------------+ | vtable pointer | -> +---------------+ +-----------------+ | Method1 | +---------------+ | Method2 | +---------------+ |
进程间通信(IPC)
DCOM
- 分布式组件对象模型(
Distributed COM
,DCOM
)扩展了COM
,支持跨进程和跨网络的对象调用 - 具体见下面
DCOM
章节
RPC
DCOM
使用远程过程调用(Remote Procedure Call
,RPC
)来实现进程间通信- 具体见下面
RPC
章节
错误处理和 HRESULT
COM
使用HRESULT
类型表示函数调用的返回状态和错误信息
1 2 3 4 |
HRESULT result = CoCreateInstance(...); if (FAILED(result)) { // 处理错误 } |
线程模型
概述
COM
支持多种线程模型,包括单线程公寓(Single-threaded Apartment
,STA
)和多线程公寓(Multi-threaded Apartment
,MTA
),用于管理线程间的COM
对象访问- 这两种模型用于管理线程与
COM
对象的交互方式,确保线程安全性和同步性
单线程公寓
- 特点
- 每个线程一个公寓:每个
STA
包含一个线程,每个COM
对象都驻留在一个特定的STA
中,只有这个STA
的线程可以直接访问该对象 - 消息循环:
STA
线程必须运行一个消息循环,以处理来自其他线程或进程的消息调用 - 线程隔离:不同
STA
之间的调用通过Windows
消息机制进行,保证线程间的隔离和同步
- 每个线程一个公寓:每个
- 工作机制
- 消息调度:在
STA
中,COM
使用 Windows 消息机制调度跨线程调用。当其他线程需要调用STA
中的COM
对象时,请求会被封装成消息,并发送到目标STA
线程的消息队列中 - 代理和存根:在跨
STA
调用时,COM
使用代理(Proxy
)和存根(Stub
)进行方法调用的封送和解封。代理在调用线程上运行,存根在对象所在的STA
线程上运行
- 消息调度:在
- 场景
- 用户界面线程:
STA
适用于用户界面线程,因为Windows
界面库(如Win32
和WPF
)要求在单个线程上运行UI
组件,并且这个线程必须具有消息循环 - 线程隔离:需要线程隔离和序列化访问的情况
- 用户界面线程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include <Windows.h> #include <iostream> // 初始化 COM 库为 STA 模型 HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); if (FAILED(hr)) { std::cerr << "Failed to initialize COM library: " << std::hex << hr << std::endl; return 1; } // 创建并使用 COM 对象 // ... // 取消初始化 COM 库 CoUninitialize(); |
- 理解:
- 首先,一个单线程公寓中只能有一个线程
- 使用单线程公寓(
STA
)模式的线程,系统会为该线程创建一个STA
空间,而在该线程中创建的COM
对象,其实例也驻留在这个STA
空间中 - 只有在这个
STA
空间中的线程可以直接访问这些COM
对象。其他线程需要通过消息传递机制来访问这些COM
对象,以确保线程安全和正确的同步
1 2 3 4 5 6 |
+-------------------+ +-------------------+ | STA1 | | STA2 | | Thread A | | Thread B | | COM Object | | Proxy Object | | Example | | | +-------------------+ +-------------------+ |
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
#include <Windows.h> #include <iostream> #include <thread> class IExample : public IUnknown { public: virtual void ExampleMethod() = 0; }; class Example : public IExample { public: Example() : refCount(1) {} HRESULT QueryInterface(REFIID riid, void** ppv) override { if (riid == IID_IUnknown || riid == __uuidof(IExample)) { *ppv = static_cast<IExample*>(this); AddRef(); return S_OK; } *ppv = nullptr; return E_NOINTERFACE; } ULONG AddRef() override { return InterlockedIncrement(&refCount); } ULONG Release() override { ULONG newRefCount = InterlockedDecrement(&refCount); if (newRefCount == 0) { delete this; } return newRefCount; } void ExampleMethod() override { std::cout << "ExampleMethod called in thread " << GetCurrentThreadId() << std::endl; } private: LONG refCount; }; void InitializeSTA1() { CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); IExample* pExample = new Example(); // 注册 COM 对象到 ROT(运行时对象表),模拟跨 STA 调用 DWORD dwRegister; IRunningObjectTable* pROT; GetRunningObjectTable(0, &pROT); pROT->Register(0, pExample, NULL, &dwRegister); MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } pROT->Revoke(dwRegister); pROT->Release(); pExample->Release(); CoUninitialize(); } void InitializeSTA2() { CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); // 从 ROT 获取 COM 对象,模拟跨 STA 调用 IRunningObjectTable* pROT; GetRunningObjectTable(0, &pROT); IEnumMoniker* pEnum; pROT->EnumRunning(&pEnum); IMoniker* pMoniker; ULONG fetched; IExample* pExample = nullptr; while (pEnum->Next(1, &pMoniker, &fetched) == S_OK) { pMoniker->BindToObject(NULL, NULL, IID_IExample, (void**)&pExample); pMoniker->Release(); if (pExample) { break; } } pEnum->Release(); pROT->Release(); if (pExample) { pExample->ExampleMethod(); pExample->Release(); } else { std::cerr << "Failed to retrieve COM object" << std::endl; } CoUninitialize(); } int main() { // 创建 STA1 线程 std::thread sta1(InitializeSTA1); Sleep(1000); // 等待 STA1 完全初始化 // 创建 STA2 线程 std::thread sta2(InitializeSTA2); sta1.join(); sta2.join(); return 0; } |
- 为什么上面是
STA1
有消息循环,STA2
没有消息循环- 因为
STA1
线程中创建的 COM 对象需要处理来自其他线程(如STA2
线程)的调用请求,因此需要一个消息循环来处理这些跨线程的调用 - 在
InitializeSTA1
函数中,STA1
线程创建了一个COM
对象Example
,并注册到运行时对象表(ROT
) STA1
线程需要一个消息循环来处理来自其他线程的调用请求,因为其他线程通过代理对象向Example
对象发送调用请求,这些请求会被封装成消息,并发送到STA1
线程的消息队列中
- 因为
- 但是并没有看到在上面的消息循环处理具体特殊的消息
- 虽然在示例代码中没有显式处理特定的消息,但消息循环的存在确保了
STA
线程能够处理系统自动生成的跨线程调用请求 - 通过这种机制,
STA
线程能够正确地处理来自其他线程的调用,从而实现线程安全的COM
调用
- 虽然在示例代码中没有显式处理特定的消息,但消息循环的存在确保了
- 可以在两个
STA
空间中都创建消息循环吗- 可以在两个
STA
空间中都创建消息循环 - 每个单线程公寓(
STA
)都有自己的消息循环,以确保能够处理来自其他线程的调用请求 - 消息循环在每个
STA
线程中都是独立运行的,这样每个STA
线程都可以接收和处理消息,包括跨线程的COM
调用请求
- 可以在两个
- 每个
STA
空间中,可以有一份单独的COM
对象示例吗- 不同的
STA
空间中可以拥有同一个COM
组件的实例副本
- 不同的
多线程公寓
- 特点
- 多个线程共享一个公寓:所有加入
MTA
的线程都共享同一个公寓,MTA
中的COM
对象可以被任何加入MTA
的线程直接访问 - 无消息循环要求:
MTA
线程不需要运行消息循环,因为没有跨线程消息调度机制 - 并发访问:多个线程可以并发访问
MTA
中的COM
对象,但需要确保线程安全
- 多个线程共享一个公寓:所有加入
- 工作机制
- 直接调用:在
MTA
中,线程可以直接调用驻留在MTA
中的COM
对象,而不需要经过消息调度机制 - 线程安全:由于
MTA
允许并发访问,COM
对象的实现需要自行处理线程安全问题
- 直接调用:在
- 场景
- 后台处理:适用于后台处理和高并发操作,因为不需要消息循环且允许并发访问
- 线程安全管理:适用于开发者能够自行管理线程安全的场景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include <Windows.h> #include <iostream> // 初始化 COM 库为 MTA 模型 HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); if (FAILED(hr)) { std::cerr << "Failed to initialize COM library: " << std::hex << hr << std::endl; return 1; } // 创建并使用 COM 对象 // ... // 取消初始化 COM 库 CoUninitialize(); |
- 理解:
- 多线程公寓(MTA)模式下,操作系统为所有的 MTA 线程创建一个共享的 MTA 空间
- 在这个 MTA 空间中创建的 COM 对象可以被所有加入 MTA 的线程直接访问
- 由于多个线程可以并发访问这些对象,因此需要同步机制来保证线程安全
1 2 3 4 5 6 7 |
+-------------------+ | MTA | | Thread T1 | | Thread T2 | | COM Object | | Example | +-------------------+ |
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
#include <Windows.h> #include <iostream> #include <thread> class IExample : public IUnknown { public: virtual void ExampleMethod() = 0; }; class Example : public IExample { public: Example() : refCount(1) {} HRESULT QueryInterface(REFIID riid, void** ppv) override { if (riid == IID_IUnknown || riid == __uuidof(IExample)) { *ppv = static_cast<IExample*>(this); AddRef(); return S_OK; } *ppv = nullptr; return E_NOINTERFACE; } ULONG AddRef() override { return InterlockedIncrement(&refCount); } ULONG Release() override { ULONG newRefCount = InterlockedDecrement(&refCount); if (newRefCount == 0) { delete this; } return newRefCount; } void ExampleMethod() override { // 使用互斥量保护方法 std::lock_guard<std::mutex> lock(mtx); std::cout << "ExampleMethod called in thread " << GetCurrentThreadId() << std::endl; } private: LONG refCount; std::mutex mtx; // 用于线程安全的互斥量 }; void InitializeMTA() { CoInitializeEx(NULL, COINIT_MULTITHREADED); IExample* pExample = new Example(); // 注册 COM 对象到 ROT(运行时对象表),模拟跨线程调用 DWORD dwRegister; IRunningObjectTable* pROT; GetRunningObjectTable(0, &pROT); pROT->Register(0, pExample, NULL, &dwRegister); pExample->ExampleMethod(); // 直接调用方法 // 取消注册并释放对象 pROT->Revoke(dwRegister); pROT->Release(); pExample->Release(); CoUninitialize(); } int main() { // 创建 MTA 线程 std::thread mta1(InitializeMTA); std::thread mta2(InitializeMTA); mta1.join(); mta2.join(); return 0; } |
安全性
COM
包括各种安全性机制,特别是在DCOM
环境中,提供身份验证和授权功能
示例代码
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 |
#include <iostream> #include <Windows.h> // 假设有一个已注册的 COM 接口和类 // {00000000-0000-0000-0000-000000000001} 是示例接口 IID // {00000000-0000-0000-0000-000000000002} 是示例类 CLSID const IID IID_IExample = {0x00000000, 0x0000, 0x0000, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}}; const CLSID CLSID_Example = {0x00000000, 0x0000, 0x0000, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02}}; class IExample : public IUnknown { public: virtual void ExampleMethod() = 0; }; int main() { CoInitialize(NULL); IExample* pExample = nullptr; HRESULT hr = CoCreateInstance(CLSID_Example, NULL, CLSCTX_INPROC_SERVER, IID_IExample, (void**)&pExample); if (SUCCEEDED(hr)) { pExample->ExampleMethod(); pExample->Release(); } else { std::cerr << "CoCreateInstance failed: " << hr << std::endl; } CoUninitialize(); return 0; } |
疑问
COM组件的好处
COM
组件可以用不同的编程语言编写,并且可以在其他语言编写的应用程序中使用- 这是因为
COM
使用二进制标准进行接口定义,任何支持COM
的语言都可以调用这些接口
- 这是因为
- 无论组件是用
C++
、C#
、VB
或其他语言编写的,只要遵循COM
规范,都可以相互调用 COM
组件以二进制形式分发,不需要源代码即可使用。这使得组件的重用和部署变得简单- 通过注册表管理,
COM
组件可以实现版本控制和兼容性管理,不同版本的组件可以共存 DCOM
扩展了COM
,使其支持跨网络的组件调用,允许在不同机器上运行的应用程序相互通信- 通过
DCOM
的RPC
机制,COM
组件可以在不同进程甚至不同计算机上透明地调用 - 使用
COM
组件时,可以在运行时动态发现和绑定组件,而不需要在编译时知道具体实现 COM
提供了一种机制,可以在不修改客户端代码的情况下替换组件实现。这使得系统具有更好的扩展性和可维护性COM
组件通过接口标准化了功能的暴露方式,保证了接口的稳定性和一致性- 通过引用计数和
AddRef
/Release
方法,COM
组件规范化了对象的生命周期管理,避免了内存泄漏和悬挂指针 COM
组件可以通过Windows
的安全机制进行访问控制,确保只有授权的用户和进程可以调用组件的方法- 通过将组件放在不同的进程中运行,
COM
提供了更好的进程隔离,增加了系统的稳定性和安全性
CoInitialize
COM
初始化函数之一,它用于初始化当前线程的COM
库,并设置该线程的并发模型- 调用
CoInitialize
或其扩展版本CoInitializeEx
是在使用COM
库之前必须执行的步骤之一
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#include <Windows.h> #include <iostream> int main() { // 初始化 COM 库 HRESULT hr = CoInitialize(NULL); if (FAILED(hr)) { std::cerr << "Failed to initialize COM library: " << std::hex << hr << std::endl; return 1; } // 执行 COM 操作 // ... // 取消初始化 COM 库 CoUninitialize(); return 0; } |
CoInitializeEx
是CoInitialize
的扩展版本,提供了更灵活的线程并发模型设置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#include <Windows.h> #include <iostream> int main() { // 初始化 COM 库,设置线程并发模型为 MTA HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); if (FAILED(hr)) { std::cerr << "Failed to initialize COM library: " << std::hex << hr << std::endl; return 1; } // 执行 COM 操作 // ... // 取消初始化 COM 库 CoUninitialize(); return 0; } |
RPC
概述
- 远程过程调用(
RPC
,Remote Procedure Call
)是一种协议,允许程序在不同的地址空间中执行过程或函数调用,这些地址空间可以在同一台计算机上,也可以在网络上的不同计算机上 RPC
使得网络通信变得透明,程序员可以像调用本地函数一样调用远程过程
基本概念
RPC
通常涉及两个主要组件:客户端和服务器。客户端发起RPC
调用,服务器执行该调用并返回结果- 使用
IDL
描述远程过程的接口,包括函数签名和数据类型。这些描述将被编译成客户端和服务器的存根代码 - 客户端存根(
Client Stub
)和服务器存根(Server Stub
)负责将参数打包(封送)和解包(解封),并通过网络进行传输 RPC
使用底层传输协议(如TCP/IP
)进行通信
工作流程
- 客户端调用本地存根函数:客户端调用本地存根函数,传递参数
- 参数封送:客户端存根将参数打包成网络数据包
- 发送请求:客户端存根通过网络将请求发送到服务器
- 接收请求:服务器接收请求,并将数据包传递给服务器存根
- 参数解封:服务器存根解包数据,恢复原始参数
- 执行远程过程:服务器存根调用实际的远程过程,将结果返回给服务器存根
- 结果封送:服务器存根将结果打包成网络数据包
- 发送结果:服务器通过网络将结果发送回客户端
- 接收结果:客户端接收结果,并将数据包传递给客户端存根
- 结果解封:客户端存根解包数据,恢复原始结果,并返回给客户端调用者
Windows 的 RPC
Windows
提供了自己的RPC
实现,称为Windows RPC
(或Microsoft RPC
)- 它使用
IDL
文件和MIDL
编译器生成存根代码,并提供了一组API
用于实现RPC
调用
IDL 文件
1 2 3 4 5 6 7 8 9 |
// Example.idl [ uuid(12345678-1234-1234-1234-123456789012), version(1.0) ] interface Example { void ExampleProc([in] int param); } |
服务器代码
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 36 37 38 |
// Example_s.c (自动生成) // 包含服务器存根代码 #include "Example.h" void ExampleProc(int param) { printf("ExampleProc called with param: %d\n", param); } // Main server function int main() { RPC_STATUS status; unsigned char* protocolSequence = (unsigned char*)"ncacn_ip_tcp"; unsigned char* security = NULL; unsigned char* endpoint = (unsigned char*)"4747"; unsigned int minimumCalls = 1; unsigned int dontWait = FALSE; status = RpcServerUseProtseqEp(protocolSequence, RPC_C_PROTSEQ_MAX_REQS_DEFAULT, endpoint, security); if (status) exit(status); status = RpcServerRegisterIf(Example_v1_0_s_ifspec, NULL, NULL); if (status) exit(status); status = RpcServerListen(minimumCalls, RPC_C_LISTEN_MAX_CALLS_DEFAULT, dontWait); if (status) exit(status); return 0; } // Memory allocation and deallocation functions for RPC void* __RPC_USER midl_user_allocate(size_t len) { return malloc(len); } void __RPC_USER midl_user_free(void* ptr) { free(ptr); } |
客户端代码
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 36 37 38 39 40 41 42 43 |
// Example_c.c (自动生成) // 包含客户端存根代码 #include "Example.h" // Main client function int main() { RPC_STATUS status; unsigned char* protocolSequence = (unsigned char*)"ncacn_ip_tcp"; unsigned char* endpoint = (unsigned char*)"4747"; unsigned char* stringBinding = NULL; status = RpcStringBindingCompose(NULL, protocolSequence, NULL, endpoint, NULL, &stringBinding); if (status) exit(status); status = RpcBindingFromStringBinding(stringBinding, &Example_IfHandle); if (status) exit(status); RpcTryExcept { ExampleProc(123); } RpcExcept(1) { printf("Runtime reported exception 0x%lx\n", RpcExceptionCode()); } RpcEndExcept status = RpcStringFree(&stringBinding); if (status) exit(status); status = RpcBindingFree(&Example_IfHandle); if (status) exit(status); return 0; } // Memory allocation and deallocation functions for RPC void* __RPC_USER midl_user_allocate(size_t len) { return malloc(len); } void __RPC_USER midl_user_free(void* ptr) { free(ptr); } |
DCOM
概述
- 分布式组件对象模型(
DCOM
,Distributed Component Object Model
)是COM
(组件对象模型)的扩展,旨在支持跨进程和跨网络的对象调用 DCOM
通过网络协议和远程过程调用(RPC
)机制,使得客户端应用程序可以调用远程服务器上的COM
对象,就像调用本地对象一样
工作机制
DCOM
使用标准的网络协议(如TCP/IP
)进行通信- 这使得它可以在不同的计算机和网络环境中工作,支持跨网络的对象调用
DCOM
依赖RPC
(Remote Procedure Call
)机制来实现跨进程和跨网络的调用RPC
允许程序调用另一个地址空间(通常在另一台物理机器上)的过程或函数,就像在本地调用一样
DCOM
使用代理(Proxy
)和存根(Stub
)来封送和解封远程调用的参数和结果:- 代理:在客户端上运行,负责将客户端的调用请求封装为消息,并通过网络发送到服务器
- 存根:在服务器上运行,负责接收消息并调用实际的
COM
对象,然后将结果封装为消息发送回客户端
DCOM
使用接口封送(Marshaling
)将参数和返回值从一个地址空间转换到另一个地址空间- 接口封送器负责将参数打包成可以通过网络传输的格式,并在接收端解包
DCOM
提供了多种安全性机制,包括身份验证、授权和加密,确保跨网络调用的安全性
实现细节
- 在使用
DCOM
之前,需要初始化COM
库,并指定线程的并发模型(通常使用多线程公寓模型MTA
)
1 2 3 4 |
HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); if (FAILED(hr)) { // 初始化失败 } |
- 使用
CoCreateInstanceEx
函数创建远程COM
对象实例。该函数允许指定服务器位置,并返回对象的接口指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
COSERVERINFO serverInfo = {0}; serverInfo.pwszName = L"RemoteServerName"; MULTI_QI multiQI[1] = {0}; multiQI[0].pIID = &IID_IExample; multiQI[0].pItf = NULL; multiQI[0].hr = S_OK; HRESULT hr = CoCreateInstanceEx( CLSID_Example, NULL, CLSCTX_REMOTE_SERVER, &serverInfo, 1, multiQI ); if (SUCCEEDED(hr)) { IExample* pExample = (IExample*)multiQI[0].pItf; pExample->ExampleMethod(); pExample->Release(); } else { // 创建远程对象失败 } |
远程服务器上的 COM 对象实现
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
#include <Windows.h> #include <iostream> class IExample : public IUnknown { public: virtual void ExampleMethod() = 0; }; class Example : public IExample { public: Example() : refCount(1) {} HRESULT QueryInterface(REFIID riid, void** ppv) override { if (riid == IID_IUnknown || riid == __uuidof(IExample)) { *ppv = static_cast<IExample*>(this); AddRef(); return S_OK; } *ppv = nullptr; return E_NOINTERFACE; } ULONG AddRef() override { return InterlockedIncrement(&refCount); } ULONG Release() override { ULONG newRefCount = InterlockedDecrement(&refCount); if (newRefCount == 0) { delete this; } return newRefCount; } void ExampleMethod() override { std::cout << "ExampleMethod called on remote server" << std::endl; } private: LONG refCount; }; class ExampleClassFactory : public IClassFactory { public: ExampleClassFactory() : refCount(1) {} HRESULT QueryInterface(REFIID riid, void** ppv) override { if (riid == IID_IUnknown || riid == IID_IClassFactory) { *ppv = static_cast<IClassFactory*>(this); AddRef(); return S_OK; } *ppv = nullptr; return E_NOINTERFACE; } ULONG AddRef() override { return InterlockedIncrement(&refCount); } ULONG Release() override { ULONG newRefCount = InterlockedDecrement(&refCount); if (newRefCount == 0) { delete this; } return newRefCount; } HRESULT CreateInstance(IUnknown* pUnkOuter, REFIID riid, void** ppv) override { if (pUnkOuter != nullptr) { return CLASS_E_NOAGGREGATION; } Example* pExample = new Example(); HRESULT hr = pExample->QueryInterface(riid, ppv); pExample->Release(); return hr; } HRESULT LockServer(BOOL fLock) override { if (fLock) { AddRef(); } else { Release(); } return S_OK; } private: LONG refCount; }; extern "C" HRESULT __stdcall DllGetClassObject(REFCLSID rclsid, REFIID riid, void** ppv) { if (rclsid == CLSID_Example) { ExampleClassFactory* pFactory = new ExampleClassFactory(); HRESULT hr = pFactory->QueryInterface(riid, ppv); pFactory->Release(); return hr; } return CLASS_E_CLASSNOTAVAILABLE; } extern "C" HRESULT __stdcall DllCanUnloadNow() { return S_OK; } |
客户端调用远程 COM 对象
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 36 37 38 |
#include <Windows.h> #include <iostream> int main() { HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); if (FAILED(hr)) { std::cerr << "Failed to initialize COM library" << std::endl; return 1; } COSERVERINFO serverInfo = {0}; serverInfo.pwszName = L"RemoteServerName"; MULTI_QI multiQI[1] = {0}; multiQI[0].pIID = &IID_IExample; multiQI[0].pItf = NULL; multiQI[0].hr = S_OK; hr = CoCreateInstanceEx( CLSID_Example, NULL, CLSCTX_REMOTE_SERVER, &serverInfo, 1, multiQI ); if (SUCCEEDED(hr)) { IExample* pExample = (IExample*)multiQI[0].pItf; pExample->ExampleMethod(); pExample->Release(); } else { std::cerr << "Failed to create remote COM object" << std::endl; } CoUninitialize(); return 0; } |
问题
CoCreateInstanceEx
和multiQI
- 当
CoCreateInstanceEx
成功时,客户端获得的是一个代理对象的指针
所以multiQI
里面存的是代理对象的指针 - 代理对象实现了请求的接口(如
IExample
),并将客户端调用封装成RPC
请求发送到服务器
通过代理对象去调用目标函数,实际上代理对象会把这个调用封装成RPC
请求,发给目标服务器 - 在服务器上,存根对象接收
RPC
请求,解封参数,并调用实际的COM
对象的方法
- 当
- 代理对象和存根对象
COM
运行时库:负责创建和管理代理对象和存根对象,处理对象的封送和解封RPC
运行时库:负责处理网络通信,确保消息的可靠传输
本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ Bkwin一12/01
- ♥ Windows 核心编程 _ 进程二06/19
- ♥ Soui九07/25
- ♥ C++并发编程 _ 同步并发(Future)05/22
- ♥ Windows 核心编程 _ 内核对象一06/04
- ♥ C++并发编程_概念了解05/07