概述
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和DllUnregisterServerCOM组件的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所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ C++11_四种类型转换11/10
- ♥ 文件md5值计算05/31
- ♥ C++_成员访问权限06/20
- ♥ Windows 核心编程 _ 内核对象:线程同步三07/31
- ♥ STL_deque05/18
- ♥ C++编程规范101规则、准则与最佳实践 二01/07