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 INotifyCallback
{
public:
STDMETHOD(OnCallback) () PURE;
};
class SayHelloInterface:public IUnknown
{
public:
virtual int _stdcall SayHello() = 0;
virtual int _stdcall SetCallback(INotifyCallback* cb) = 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;
int _stdcall SetCallback(INotifyCallback* cb);
//全局创建对象个数
static ULONG g_ObjNum;
protected:
//引用计数
ULONG m_RefCount;
INotifyCallback* m_Cb;
};
// 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");
if(m_Cb != NULL)
m_Cb->OnCallback();
return 0;
}
int _stdcall CSayHello::SetCallback(INotifyCallback* cb)
{
m_Cb = cb;
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则可以卸载
static bool IsRunningAsAdministrator()
{
BOOL isElevated = FALSE;
HANDLE hToken = NULL;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
return false;
TOKEN_ELEVATION elevation = {};
DWORD cbSize = sizeof(TOKEN_ELEVATION);
if (GetTokenInformation(
hToken,
TokenElevation,
&elevation,
sizeof(elevation),
&cbSize))
{
isElevated = elevation.TokenIsElevated;
}
CloseHandle(hToken);
return isElevated == TRUE;
}
LSTATUS myReg(LPCWSTR lpPath) //将本组件的信息写入注册表,包括CLSID、所在路径lpPath、ProgID
{
HKEY thk = NULL, tclsidk = NULL;
//打开键HKEY_CLASSES_ROOT\CLSID,创建新键为ComTest的CLSID,
//在该键下创建键InprocServer32,并将本组件(dll)所在路径lpPath写为该键的默认值
HKEY keyNode = HKEY_CLASSES_ROOT;
if (!IsRunningAsAdministrator())
keyNode = HKEY_CURRENT_USER;
LSTATUS status = RegOpenKeyW(keyNode, 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(keyNode, 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;
HKEY keyNode = HKEY_CLASSES_ROOT;
if (!IsRunningAsAdministrator())
keyNode = HKEY_CURRENT_USER;
if (ERROR_SUCCESS == RegOpenKeyW(keyNode, 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(keyNode, L"COMCTL.CSayHello", &thk))
{
myDelKey(thk, L"CLSID");
}
myDelKey(keyNode, 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")
class MyCB :public INotifyCallback
{
public:
STDMETHOD(OnCallback) ()
{
printf("Notify Triggered.\n");
return S_OK;
}
};
////Com 接口可传递的参数: 基本类型[char,int,float...](不包括基本类型数组和指针),POD结构体,IUnknown类型指针,
//// BSTR(带长度前缀的字符串), SAFEARRAY(可变长数组),VARIANT(变体类型) 【#include <atlsafe.h>】
////BSTR,SAFEARRAY,VARIANT需要管理生命周期,为了简化可直接使用ATL封装的相关类,它们使用RAII技术进行自动管理
//CComBSTR bstrVal(L"World");
//CComSafeArray<long> arr(5);
//CComVariant varStr(L"Hello");
//CComVariant varArr(arr);
//
//bstrVal.m_str;//BSTR
//arr.m_psa;//SAFEARRAY
//SAFEARRAY n;
//varStr.bstrVal;//BSTR, varStr.vt == VT_ARRAY| VT_I4;
//varArr.parray;//SAFEARRAY, varArr.vt == VT_BSTR;
//VARIANT* pVar1 = &varStr;//VARIANT
//如果COM组件想提供Automation接口给JS/VB/Python等脚本语言使用,则接口参数要求更加严格,只能有基本类型,IDispatch*,BSTR,SAFEARRAY,VARIANT
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;
//CoGetClassObject(CLSID_CSayHello,CLSCTX_INPROC_SERVER,NULL,IID_IClassFactory,(void**)&pFac);//会去加载dll库
//pFac->CreateInstance(NULL, IID_ISayHello, (void**)&pComTest);
//pFac->Release();
}
if (S_OK == hResult)
{
// 调用接口方法
MyCB cb = {};
pComTest->SetCallback(&cb);
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;
}
五. 客户端调用(用作普通组件)
用作普通组件时,可以脱离windows系统,变成一种通用的组件模型。
#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 和组件路径作为元数据,但是这种做法会污染操作系统注册表,在安装软件时需将元数据信息写入注册表(如果不是写到HKEY_CURRENT_USER 节点的话,可能还需要获取管理员权限),对于一些敏感的用户,这种操作是不被允许的。WinXp以后,可以借助文件清单(.manifest)来管理COM组件,当调用CoCreateInstance时,会先从文件清单中寻找Com组件,如果找不到就去注册表中找,同时兼容传统借助注册表实现的com 组件应用,无需做代码上的改动,很方便。需要组件提供方除了提供动态库dll外,同时提供minifest 文件,放到exe同级目录,并在客户端的manifest文件中将所有依赖的COM组件信息列出来,如下方截图。(需要注意的是,如果客户端依赖了COM组件A,而COM组件A依赖了其他的COM组件B,则客户端的manifest文件中,除了列出COM组件A的信息,也要列出COM组件B)。如果选择了隐式嵌入,客户端只会有exe生成,否则还会有一个xxx.exe.manifest。(隐式嵌入的话可以使用: "C:\Program Files (x86)\Windows Kits\10\bin\x64\mt.exe" -inputresource:xxx.exe;#1 -out:exported.manifest 去查看嵌入的清单信息)



7759

被折叠的 条评论
为什么被折叠?



