Com 组件技术在Windows平台上应用十分广泛,虽然历史久远,但是仍然具有旺盛的生命力。下面提供一个最基础的Com组件供参考学习。
(详细资料见资源文件https://download.csdn.net/download/yuanshenqiang/90532743)
一 组件类
// Interface.h
#pragma once
#include <Unknwn.h>
static const WCHAR* IID_ISayHelloStr = L"{213D1B15-9BBA-414A-BAB6-CA5B6CEF0006}";
// PowerShell生成GUID指令: '{'+[guid]::NewGuid().ToString().ToUpper()+'}'
static const GUID IID_ISayHello = { 0x213D1B15, 0x9BBA, 0x414A, { 0xBA, 0xB6, 0xCA, 0x5B, 0x6C, 0xEF, 0x00, 0x06 } };
// SayHello.h
#include "Interface.h"
class SayHelloInterface:public IUnknown
{
public:
virtual int _stdcall SayHello() = 0;
};
class CSayHello :public SayHelloInterface
{
public:
CSayHello();
~CSayHello();
// 实现IUnknown接口
// 查找接口
// riid : 输入参数,接口id
// ppvObject : 输出参数,返回相应的接口
HRESULT _stdcall QueryInterface(const IID& riid, void** ppvObject) override;
// 增加引用计数
ULONG _stdcall AddRef() override;
// 减少引用计数
ULONG _stdcall Release() override;
int _stdcall SayHello() override;
//全局创建对象个数
static ULONG g_ObjNum;
protected:
//引用计数
ULONG m_RefCount;
};
// SayHello.cpp --组件类实现
#include "SayHello.h"
#include <stdio.h>
ULONG CSayHello::g_ObjNum = 0;
CSayHello::CSayHello()
{
m_RefCount = 0;
InterlockedIncrement(&g_ObjNum);//对象个数+1
}
CSayHello::~CSayHello()
{
InterlockedDecrement(&g_ObjNum);//对象个数-1
}
HRESULT _stdcall CSayHello::QueryInterface(const IID& riid, void** ppvObject)
{
// 通过接口id判断返回的接口类型
if (IID_IUnknown == riid) {
*ppvObject = static_cast<IUnknown*>(this);
((IUnknown*)(*ppvObject))->AddRef();
}
else if (IID_ISayHello == riid) {
*ppvObject = static_caast<SayHelloInterface*>(this);
((SayHelloInterface*)(*ppvObject))->AddRef();
}
else {
*ppvObject = NULL;
return E_NOINTERFACE;
}
return S_OK;
}
ULONG _stdcall CSayHello::AddRef()
{
return InterlockedIncrement(&m_RefCount);
}
ULONG _stdcall CSayHello::Release()
{
ULONG refCount = InterlockedDecrement(&m_RefCount);
if (0 >= refCount) {
delete this;
}
return refCount;
}
int _stdcall CSayHello::SayHello()
{
printf("hello COM\r\n");
return 0;
}
二 工厂类
//ComFactory.h -- 工厂类声明
#pragma once
#include <Unknwn.h>
class ComFactory:public IClassFactory
{
public:
ComFactory();
~ComFactory();
// 实现IUnknown接口
HRESULT _stdcall QueryInterface(const IID& riid, void** ppvObject) override;
ULONG _stdcall AddRef() override;
ULONG _stdcall Release() override;
// 实现IClassFactory接口
HRESULT _stdcall CreateInstance(IUnknown* pUnkOuter, const IID& riid, void** ppvObject) override;
HRESULT _stdcall LockServer(BOOL fLock) override;
protected:
ULONG m_RefCount;//引用计数
static ULONG g_ObjNum;//全局创建对象个数
};
//ComFactory.cpp -- 工厂类实现
#include "ComFactory.h"
#include "SayHello.h"
ULONG ComFactory::g_ObjNum = 0;
ComFactory::ComFactory()
{
m_RefCount = 0;
InterlockedIncrement(&g_ObjNum);
}
ComFactory::~ComFactory()
{
InterlockedDecrement(&g_ObjNum);
}
// 查询指定接口
HRESULT _stdcall ComFactory::QueryInterface(const IID& riid, void** ppvObject)
{
if (IID_IUnknown == riid) {
*ppvObject = static_cast<IUnknown*>(this);
((IUnknown*)(*ppvObject))->AddRef();
}
else if (IID_IClassFactory == riid) {
*ppvObject = static_cast<IClassFactory*>(this);
((IClassFactory*)(*ppvObject))->AddRef();
}
else {
*ppvObject = NULL;
return E_NOINTERFACE;
}
return S_OK;
}
ULONG _stdcall ComFactory::AddRef()
{
return InterlockedIncrement(&m_RefCount);
}
ULONG _stdcall ComFactory::Release()
{
ULONG refCount = InterlockedDecrement(&m_RefCount);
if (0 >= refCount) {
delete this;
}
return refCount;
}
// 创建COM对象,并返回指定接口
HRESULT _stdcall ComFactory::CreateInstance(IUnknown* pUnkOuter, const IID& riid, void** ppvObject)
{
if (NULL != pUnkOuter)
return CLASS_E_NOAGGREGATION;
HRESULT hr = E_OUTOFMEMORY;
CSayHello* pObj = new CSayHello();
if (NULL == pObj) {
return hr;
}
hr = pObj->QueryInterface(riid, ppvObject);
if (S_OK != hr) {
delete pObj;
}
return hr;
}
HRESULT _stdcall ComFactory::LockServer(BOOL fLock)
{
return NOERROR;
}
三 导出模块
// Server.h -- 导出模块声明
#pragma once
#include <windows.h>
static const WCHAR* CLSID_CSayHelloStr = L"{4046FA83-57F0-4475-9381-8818BFC50DDF}";
static const GUID CLSID_CSayHello = { 0x4046FA83, 0x57F0, 0x4475, { 0x93, 0x81, 0x88, 0x18, 0xBF, 0xC5, 0x0D, 0xDF } };
extern "C" HRESULT __stdcall DllRegisterServer();
extern "C" HRESULT __stdcall DllUnregisterServer();
extern "C" HRESULT __stdcall DllCanUnloadNow(void);
extern "C" HRESULT __stdcall DllGetClassObject(_In_ REFCLSID rclsid, _In_ REFIID riid, _Out_ LPVOID * ppv);
// Server.cpp -- 导出模块实现
#include "SayHello.h"
#include "ComFactory.h"
#include <iostream>
#include <strsafe.h>
#include "Server.h"
HMODULE g_hModule; //dll进程实例句柄
ULONG g_num; //组件中Com对象的个数,用于判断是否可以卸载本组建,如值为0则可以卸载
LSTATUS myReg(LPCWSTR lpPath) //将本组件的信息写入注册表,包括CLSID、所在路径lpPath、ProgID
{
HKEY thk = NULL, tclsidk = NULL;
//打开键HKEY_CLASSES_ROOT\CLSID,创建新键为ComTest的CLSID,
//在该键下创建键InprocServer32,并将本组件(dll)所在路径lpPath写为该键的默认值
LSTATUS status = RegOpenKeyW(HKEY_CLASSES_ROOT, L"CLSID", &thk);
if (ERROR_SUCCESS == status)
{
status = ERROR_SUCCESS == status?RegCreateKeyExW(
thk, // 父键句柄
CLSID_CSayHelloStr,//子键名称
0, // 保留,必须为 0
nullptr, // 类名(通常为 nullptr)
REG_OPTION_NON_VOLATILE, // 选项(非易失性)
KEY_ALL_ACCESS, // 访问权限
nullptr, // 安全属性(通常为 nullptr)
&tclsidk, // 返回的子键句柄
nullptr): status;
HKEY tinps32k = NULL;
status = ERROR_SUCCESS == status?RegCreateKeyExW(
tclsidk,
L"InprocServer32",
0, // 保留,必须为 0
nullptr, // 类名(通常为 nullptr)
REG_OPTION_NON_VOLATILE, // 选项(非易失性)
KEY_ALL_ACCESS, // 访问权限
nullptr, // 安全属性(通常为 nullptr)
&tinps32k, // 返回的子键句柄
nullptr):status;
status = ERROR_SUCCESS == status?RegSetValueW(tinps32k, NULL, REG_SZ, lpPath, wcslen(lpPath)): status;
RegCloseKey(tinps32k);
RegCloseKey(tclsidk);
RegCloseKey(thk);
}
//在键HKEY_CLASSES_ROOT下创建新键为COMCTL.CComTest,
//在该键下创建子键,并将CCompTest的CLSID写为该键的默认值
HKEY tprogk = NULL, tprogidk = NULL;
status = status == ERROR_SUCCESS ? RegCreateKeyW(HKEY_CLASSES_ROOT, L"COMCTL.CSayHello", &tprogk) : status;
status = status == ERROR_SUCCESS ? RegCreateKeyW(tprogk, L"CLSID", &tprogidk) : status;
status = status == ERROR_SUCCESS ? RegSetValueW(tprogidk,
NULL,
REG_SZ,
CLSID_CSayHelloStr,
wcslen(CLSID_CSayHelloStr)) : status;
RegCloseKey(tprogk);
RegCloseKey(tprogidk);
//这样的话一个客户端程序如果想要使用本组件,首先可以以COMCTL.CSayHello为参数调用CLSIDFromProgID函数
//来获取CSayHello的CLSID,再以这个CLSID为参数调用CoCreateInstance创建COM对象
return status;
}
extern "C" HRESULT __stdcall DllRegisterServer()
{
WCHAR szModule[1024];
//获取本组件(dll)所在路径
DWORD dwResult = GetModuleFileNameW(g_hModule, szModule, 1024);
if (0 == dwResult) {
return -1;
}
//将路径等信息写入注册表
return myReg(szModule);
}
int myDelKey(HKEY hk, LPCWSTR lp)
{
if (ERROR_SUCCESS == RegDeleteKeyW(hk, lp)) {
}
return 0;
}
//删除注册时写入注册表的信息
int myDel()
{
HKEY thk;
if (ERROR_SUCCESS == RegOpenKeyW(HKEY_CLASSES_ROOT, L"CLSID", &thk)) {
wchar_t szKey[256];
// 删除 CLSID 项
StringCchPrintfW(szKey, ARRAYSIZE(szKey), L"%s\\InprocServer32", CLSID_CSayHelloStr);
myDelKey(thk, szKey);
myDelKey(thk, CLSID_CSayHelloStr);
RegCloseKey(thk);
}
if (ERROR_SUCCESS == RegOpenKeyW(HKEY_CLASSES_ROOT, L"COMCTL.CSayHello", &thk))
{
myDelKey(thk, L"CLSID");
}
myDelKey(HKEY_CLASSES_ROOT, L"COMCTL.CSayHello");
return 0;
}
extern "C" HRESULT __stdcall DllUnregisterServer()
{
//删除注册时写入注册表的信息
myDel();
return 0;
}
// 用于判断是否可以卸载本组建, 由CoFreeUnusedLibraries函数调用
extern "C" HRESULT __stdcall DllCanUnloadNow(void)
{
//如果对象个数为0,则可以卸载
if (0 == CSayHello::g_ObjNum)
{
return S_OK;
}
else
{
return S_FALSE;
}
}
//用于创建类厂并返回所需接口,由CoGetClassObject函数调用
extern "C" HRESULT __stdcall DllGetClassObject(__in REFCLSID rclsid, __in REFIID riid, LPVOID FAR * ppv)
{
//LPOLESTR szCLSID;
//StringFromCLSID(rclsid, &szCLSID); //将其转化为字符串形式用来输出
//wprintf(L"rclsid CLSID \"%s\"\n", szCLSID);
//szCLSID;
//StringFromCLSID(riid, &szCLSID);
//wprintf(L"riid CLSID \"%s\"\n", szCLSID);
if (CLSID_CSayHello == rclsid) {
ComFactory* pFactory = new ComFactory();//创建工厂对象
if (NULL == pFactory)
return E_OUTOFMEMORY;
HRESULT result = pFactory->QueryInterface(riid, ppv);//获取工厂对象(内部校验一下)
if (S_OK != result)
{
//pFactory->Release();
delete pFactory;
}
return result;
}
else {
return CLASS_E_CLASSNOTAVAILABLE;
}
}
// 提供DLL入口;对于动态链接库,DllMain是一个可选的入口函数,在COM组件中是必须有的
extern "C" BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
//获取进程实例句柄,用于获取本组件(dll)路径
g_hModule = hModule;
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
导出符号定义:
// Module.def
LIBRARY SayHello
DESCRIPTION "SayHello Com DLL"
EXPORTS
DllMain @1
DllRegisterServer @2
DllUnregisterServer @3
DllCanUnloadNow @4
DllGetClassObject @5
四 客户端调用(用作com组件)
#include <iostream>
#include "CSayHelloModule.h"
#include "SayHello.h"
#include <stdio.h>
//#pragma comment(lib,"SayHello.lib")
int main()
{
// 初始化COM库
CoInitialize(NULL);
SayHelloInterface* pComTest = NULL;
HRESULT hResult = ERROR_SUCCESS;
hResult = DllRegisterServer();//要求管理员权限。可以在安装软件的时候,拿到管理员权限,执行命令:regsvr32 SayHello.dll
// 创建进程内COM组件,返回指定接口
if(hResult == ERROR_SUCCESS)
{
hResult = CoCreateInstance(CLSID_CSayHello, NULL, CLSCTX_INPROC_SERVER, IID_ISayHello, (void**)&pComTest);
//CoCreateInstance等效以下步骤
//IClassFactory* pFac = NULL;
//CoGeClassObject(CLSID_CSayHello,CLSCTX_INPROC_SERVER,NULL,IID_IClassFactory,(void**)&pFac);//会去加载dll库
//pFac->CreateInstance(NULL, IID_ISayHello, (void**)&pComTest);
//pFac->Release();
}
if (S_OK == hResult)
{
// 调用接口方法
printf("%d\r\n", pComTest->SayHello());
// 释放组件
pComTest->Release();
}
else
printf("Error Occurs: %d\r\n", hResult);
// 释放COM库
DllUnregisterServer();//要求管理员权限。等价于:regsvr32 /u SayHello.dll
CoUninitialize();
system("pause");
return 0;
}
五. 客户端调用(用作普通组件)
#include <iostream>
#include "CSayHelloModule.h"
#include "SayHello.h"
#include <stdio.h>
#pragma comment(lib,"SayHello.lib")
int main()
{
IClassFactory* pComFac = NULL;
ICalc* pComTest = NULL;
HRESULT hResult = 0;
hResult = DllGetClassObject(CLSID_CSayHello, IID_IClassFactory,(void**)&pComFac);
if (S_OK == hResult)
{
pComFac->CreateInstance(NULL, IID_ISayHello, (void**)&pComTest);
// 调用接口方法
printf("Read Ret: %d\r\n", pComTest->SayHello());
pComTest->Release();
pComFac->Release();
}
else
printf("Error Occurs: %d\r\n", hResult);
system("pause");
return 0;
}
六. Reg Free Com
上面小节4中,需要借助注册表存储GUID 和组件路径作为元数据,但是这种做法会污染操作系统注册表,在安装软件时需要获取管理员权限,将元数据信息写入注册表,对于一些敏感的用户,这种操作是不被允许的。因此,可以借助文件清单(.manifest)的方式将组件元数据嵌入到应用程序,在Com 运行时优先从文件清单加载Com组件,这种方式同时也兼容传统借助注册表实现的com 组件应用,无需做代码上的改动,很方便。