当前位置: 首页 > news >正文

2507C++,介绍名字对象

原文

名字对象简介

组件对象模型(COM)的基础由两个原则组成,这里:
1,客户针对接口编程,而不是针对具体类编程.
2,位置透明,客户无需知道实际对象在哪(进程中,进程外,另一台机器).

虽然原则上很简单.本文,我想介绍COM的一个叫怪物(名字对象,Moniker,名字)的扩展方面.

名字对象的想法是,根据串名而不是某些自定义机制来识别和定位特定对象.窗口提供了一些名字对象的实现,其中大部分对象链接和嵌入(OLE)相关,最主要是办公应用.

如,当在文档中嵌入Excel图表链接时,可用名字对象,来使用名字对象机制和所涉及的特定名字对象所理解的特定格式的串来指向该特定图表.

这也表明可组合怪物,事实确实如此.如,可通过转到特定工作表,特定区间和特定单元格来定位某些Excel文档中的单元格,每个(表/区间/单元格)都可通过一个名字(怪物)指向,当链接在一起时,可找到期望对象.

从现有名字对象(怪物类)实现的最简单示例开始.可用此名字对象替换创建操作.下面是一例,该示例使用调用CoCreateInstance"标准"机制创建COM对象:

#include <shlobjidl.h>//...
CComPtr<IShellWindows> spShell;
auto hr = spShell.CoCreateInstance(__uuidof(ShellWindows));

我使用ATL灵针(#include<atlcomcli.h><atlbase.h>).我使用的接口和类只是一例,任何标准COM类都可工作.
CoCreateInstance,方法实际的CoCreateInstance.为了更清楚,下面是不使用灵针提供的助手CoCreateInstance调用:

CComPtr<IShellWindows> spShell;
auto hr = ::CoCreateInstance(__uuidof(ShellWindows), nullptr,CLSCTX_ALL, __uuidof(IShellWindows),reinterpret_cast<void**>(&spShell));

CoCreateInstance自身是用来调用CoGetClassObject来取类工厂,请求标准IClassFactory接口,然后在其上面调用CreateInstance美化的包装器:

这里

CComPtr<IClassFactory> spCF;
auto hr = ::CoGetClassObject(__uuidof(ShellWindows),CLSCTX_ALL, nullptr, __uuidof(IClassFactory),reinterpret_cast<void**>(&spCF));
if (SUCCEEDED(hr)) {CComPtr<IShellWindows> spShell;hr = spCF->CreateInstance(nullptr, __uuidof(IShellWindows),reinterpret_cast<void**>(&spShell));if (SUCCEEDED(hr)) {//使用`spShell`}
}

下面是名字对象的用武之地:可用如下串直接取类工厂:

CComPtr<IClassFactory> spCF;
BIND_OPTS opts{ sizeof(opts) };
auto hr = ::CoGetObject(L"clsid:9BA05972F6A8-11CFA442-00A0C90A8F39",&opts, __uuidof(IClassFactory),reinterpret_cast<void**>(&spCF));

使用CoGetObjectC++中基于名字对象查找对象的最方便方法.名字对象名是提供给CoGetObject的串.它以某种ProgID开头,再加上冒号.

串的其余部分将由场景解释.有了类工厂,代码就可像前例一样使用IClassFactory::CreateInstance.
这里

工作原理COM一样,需要注册表.如果打开RegEditTotalRegistry并浏览到HKYE_CLASSES_ROOT,则ProgID都在那里.
这里

其中之一是"clsid",是,有点奇怪,但怪物系统的入口是ProgID.每个ProgID都应有指向名字对象的类ID类标子键.
所以这里,HKCR\CLSID\CLSID!

当然,其他名字不同(不是类标).如果按右边的类标到普通COMCLSID注册的位置(HKCR\CLSID).

InProcServer32子键指向实现COM基础结构Combase.dll.

此时,知道如何发现类名了,但仍不清楚怪物是什么及它在哪里.

如前,CoGetObject是从名字对象取对象的最简单方法,因为它隐藏了名字对象自身的细节.CoGetObject是调用COM名字对象名字空间的实际入口的MkParseDisplayName快捷键.

这里
这里

下面是通过名字取类怪物的完整方法:

CComPtr<IMoniker> spClsMoniker;
CComPtr<IBindCtx> spBindCtx;
::CreateBindCtx(0, &spBindCtx);
ULONG eaten;
CComPtr<IClassFactory> spCF;
auto hr = ::MkParseDisplayName(spBindCtx,L"clsid:9BA05972F6A8-11CFA442-00A0C90A8F39",&eaten, &spClsMoniker);
if (SUCCEEDED(hr)) {spClsMoniker->BindToObject(spBindCtx, nullptr,__uuidof(IClassFactory), reinterpret_cast<void**>(&spCF));

MkParseDisplayName"显示名"串,并试根据注册表中的信息找到名字对象.

绑定环境是一个一般可包含一组,名字对象可用来自定义其解释显示名的方式的任意属性助手对象.名字对象类不使用任何属性,但即使其中没有感兴趣的数据,仍需要提供对象.

如果成功,MkParseDisplayName将返回名字对象接口指针,实现所有名字对象必须实现的IMoniker接口.IMoniker是一个(不包括IUnknown)有20个方法的有点可怕的接口.
这里

幸好,并非所有方法都必须实现.很快就可自定义它.

IMoniker中的主要方法解释显示名,且如果可能,并返回客户试查找的真实对象的BindToObject.客户提供期望目标对象实现的接口,如果是名字对象类,则为IClassFactory.

如果可直接使用普通类工厂创建期望对象,名字对象类的意义何在.该名字对象的一个优点是涉及到串时,它允许某种"延迟绑定",并允许其他语言(如脚本语言)间接创建COM对象.

如,VBScript提供调用CoGetObjectGetObject函数.

实现名字对象

仍缺少一些细节,如如何创建名字对象自身?为此,实现自己的名字对象.叫它进程名字对象,其目的是查找允许同窗口进程对象工作的COM进程对象.

下面一例,客户根据其PID查找进程对象,然后显示其可执行路径:

BIND_OPTS opts{ sizeof(opts) };
CComPtr<IWinProcess> spProcess;
auto hr = ::CoGetObject(L"process:3284",&opts, __uuidof(IWinProcess),reinterpret_cast<void**>(&spProcess));
if (SUCCEEDED(hr)) {CComBSTR path;if (S_OK == spProcess->get_ImagePath(&path)) {printf("Image path: %ws\n", path.m_str);}
}

IWinProcess进程对象实现的接口,但无需知道其类标(实际上,它没有,并且是由名字对象私下创建)."prcess:3284"显示名按名字对象名标识"进程"串,即HKCR下必须有叫"进程"的子键,才能使其可工作.
"进程"键下必须有名字类标.

必须像所有COM类一样,正常注册进程名字对象类标.把冒号后面的文本传递给合理解释该名字对象名字对象.
这里,它应该是现有进程的PID.

来看看实现进程名字对象期望的主要步骤.技术上讲,我在VS中创建了一个ATLDLL项目(也可是EXE),然后添加了一个"ATL简单对象"类模板来取ATL模板提供的样板代码.

只需要实现IMoniker,不需要一些自定义接口.这是类的布局:

class ATL_NO_VTABLE CProcessMoniker :public CComObjectRootEx<CComMultiThreadModel>,public CComCoClass<CProcessMoniker, &CLSID_ProcessMoniker>,public IMoniker {
public:DECLARE_REGISTRY_RESOURCEID(106)DECLARE_CLASSFACTORY_EX(CMonikerClassFactory)BEGIN_COM_MAP(CProcessMoniker)COM_INTERFACE_ENTRY(IMoniker)END_COM_MAP()DECLARE_PROTECT_FINAL_CONSTRUCT()HRESULT FinalConstruct() {return S_OK;}void FinalRelease() {}
public://通过`IMoniker`继承HRESULT __stdcall GetClassID(CLSID* pClassID) override;HRESULT __stdcall IsDirty(void) override;HRESULT __stdcall Load(IStream* pStm) override;HRESULT __stdcall Save(IStream* pStm, BOOL fClearDirty) override;HRESULT __stdcall GetSizeMax(ULARGE_INTEGER* pcbSize) override;HRESULT __stdcall BindToObject(IBindCtx* pbc, IMoniker* pmkToLeft, REFIID riidResult, void** ppvResult) override;//其他`IMoniker`方法...std::wstring m_DisplayName;
};
OBJECT_ENTRY_AUTO(__uuidof(ProcessMoniker), CProcessMoniker)

熟悉ATL向导生成的典型代码的用户,可能会注意到与标准模板的一个重要区别:类工厂.
事实表明,当调用MkParseDisplayName(或其CoGetObject包装器)的客户调用时,不是由IClassFactory创建的名字对象,而是必须实现IParseDisplayName接口.

因此使用DECLARE_CLASSFACTORY_EX(CMonikerClassFactory)来指示ATL使用必须实现的自定义类工厂.

 MkParseDisplayName
找注册表中的怪物DLL
加载怪物DLL
调用(Dll取类对象),请求(I解析显示名)
调用(I解析显示名::解析显示名)
给客户返回怪物

开始前,实现"主"方法BindToObject.必须假设m_DisplayName成员已拥有创建名字的类工厂提供的进程ID.首先,按数字转换显示名:
这里

HRESULT __stdcall CProcessMoniker::BindToObject(IBindCtx* pbc, IMoniker* pmkToLeft, REFIID riidResult, void** ppvResult) {auto pid = std::stoul(m_DisplayName);

接着,试打开该进程的句柄:

auto hProcess = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION,FALSE, pid);
if (!hProcess)return HRESULT_FROM_WIN32(::GetLastError());

如果失败,只需返回失败的HRESULT,即可完成.如果成功,可创建WinProcess对象,传递句柄并返回(如果支持)客户请求的接口:

    CComObject<CWinProcess>* pProcess;auto hr = pProcess->CreateInstance(&pProcess);pProcess->SetHandle(hProcess);pProcess->AddRef();hr = pProcess->QueryInterface(riidResult, ppvResult);pProcess->Release();return hr;
}

通过CComObject<>内部创建的对象.还未注册WinProcess,COM类,这只是一个选择问题.我决定,WinProcess对象只能通过ProcessMoniker取得.

调用AddRef/Release,可能令人费解.
但创建CComObject<>对象时,对象的引用计数为零.

然后,调用AddRef的给它递增为1.接着,如果QueryInterface成功调用,则引用计数将递增为2.然后,释放调用把它递减为1,因为这是在给客户返回对象时的正确计数.

但是,如果调用QI失败,则引用计数将保持在1,而释放调用将析构对象!比调用删更优雅.

SetHandle是(在IWinProcess接口外部的)CWinProcess中的把句柄传递给对象的一个函数.

WinProcess,COM类很简单,因此我创建了一个最小的类,如下:

class ATL_NO_VTABLE CWinProcess :public CComObjectRootEx<CComMultiThreadModel>,public IDispatchImpl<IWinProcess> {
public:DECLARE_NO_REGISTRY()BEGIN_COM_MAP(CWinProcess)COM_INTERFACE_ENTRY(IWinProcess)COM_INTERFACE_ENTRY(IDispatch)COM_INTERFACE_ENTRY_AGGREGATE(IID_IMarshal, m_pUnkMarshaler.p)END_COM_MAP()DECLARE_PROTECT_FINAL_CONSTRUCT()DECLARE_GET_CONTROLLING_UNKNOWN()HRESULT FinalConstruct() {return CoCreateFreeThreadedMarshaler(GetControllingUnknown(), &m_pUnkMarshaler.p);}void FinalRelease() {m_pUnkMarshaler.Release();if (m_hProcess)::CloseHandle(m_hProcess);}void SetHandle(HANDLE hProcess);
private:HANDLE m_hProcess{ nullptr };CComPtr<IUnknown> m_pUnkMarshaler;//通过`IWinProcess`继承HRESULT get_Id(DWORD* pId);HRESULT get_ImagePath(BSTR* path);HRESULT Terminate(DWORD exitCode);
};

两个属性一个方法如下:

void CWinProcess::SetHandle(HANDLE hProcess) {m_hProcess = hProcess;
}
HRESULT CWinProcess::get_Id(DWORD* pId) {ATLASSERT(m_hProcess);return *pId = ::GetProcessId(m_hProcess), S_OK;
}
HRESULT CWinProcess::get_ImagePath(BSTR* pPath) {WCHAR path[MAX_PATH];DWORD size = _countof(path);if (::QueryFullProcessImageName(m_hProcess, 0, path, &size))return CComBSTR(path).CopyTo(pPath);return HRESULT_FROM_WIN32(::GetLastError());
}
HRESULT CWinProcess::Terminate(DWORD exitCode) {HANDLE hKill;if (::DuplicateHandle(::GetCurrentProcess(), m_hProcess,::GetCurrentProcess(), &hKill, PROCESS_TERMINATE, FALSE, 0)) {auto success = ::TerminateProcess(hKill, exitCode);auto error = ::GetLastError();::CloseHandle(hKill);return success? S_OK : HRESULT_FROM_WIN32(error);}return HRESULT_FROM_WIN32(::GetLastError());
}

上面使用的API相当简单,也有完整的文档.
最后是怪物的类工厂:

class ATL_NO_VTABLE CMonikerClassFactory :public ATL::CComObjectRootEx<ATL::CComMultiThreadModel>,public IParseDisplayName {
public:BEGIN_COM_MAP(CMonikerClassFactory)COM_INTERFACE_ENTRY(IParseDisplayName)END_COM_MAP()//通过`IParseDisplayName`继承HRESULT __stdcall ParseDisplayName(IBindCtx* pbc, LPOLESTR pszDisplayName, ULONG* pchEaten, IMoniker** ppmkOut) override;
};

只需一个实现方法:

HRESULT __stdcall CMonikerClassFactory::ParseDisplayName(IBindCtx* pbc, LPOLESTR pszDisplayName,ULONG* pchEaten, IMoniker** ppmkOut) {auto colon = wcschr(pszDisplayName, L':');ATLASSERT(colon);if (colon == nullptr)return E_INVALIDARG;//简单,假设已使用所有显示名*pchEaten = (ULONG)wcslen(pszDisplayName);CComObject<CProcessMoniker>* pMon;auto hr = pMon->CreateInstance(&pMon);if (FAILED(hr))return hr;//提供进程`ID`pMon->m_DisplayName = colon + 1;pMon->AddRef();hr = pMon->QueryInterface(ppmkOut);pMon->Release();return hr;
}

首先,搜索冒号,因为显示名类似"process:xxxx".在使用CComObject<>创建的怪物中保存"xxxx"部分,类似前面的CWinProcess.
pchEaten值报告使用了多少个符,名字对象工厂应该根据它所理解的来解析,因为名字对象组合可能也有效.

最后,必须注册怪物.下面是ProcessMoniker.rgs,添加的下半部分,来将"进程"ProgId/名字对象名连接到进程名字对象类标:

HKCR
{NoRemove CLSID{ForceRemove {6ea3a80e-2936-43be-8725-2e95896da9a4} = s 'ProcessMoniker class'{InprocServer32 = s '%MODULE%'{val ThreadingModel = s 'Both'}TypeLib = s '{97a86fc5ffef-4e80-88a0fa3d1b438075}'Version = s '1.0'}}process = s 'Process Moniker Class'{CLSID = s '{6ea3a80e-2936-43be-8725-2e95896da9a4}'}
}

就是这样.下面是终止给定其ID的进程的客户示例:

void Kill(DWORD pid) {std::wstring displayName(L"process:");displayName += std::to_wstring(pid);BIND_OPTS opts{ sizeof(opts) };CComPtr<IWinProcess> spProcess;auto hr = ::CoGetObject(displayName.c_str(), &opts,__uuidof(IWinProcess), reinterpret_cast<void**>(&spProcess));if (SUCCEEDED(hr)) {auto hr = spProcess->Terminate(1);if (SUCCEEDED(hr))printf("Process %u terminated.\n", pid);elseprintf("Error terminating process: hr=0x%X\n", hr);}
}

所有代码都可在该Github仓库中找到:zodiacon/MonikerFun:在此.

下面是VBScript示例(有效,因为WinProcess实现了IDispatch):

set process = GetObject("process:25520")
MsgBox process.ImagePath

.NET强壳怎么样?这是强壳:

PS> $p = [System.Runtime.InteropServices.Marshal]::BindToMoniker("process:25520")
PS> $p | GetMemberTypeName: System.__ComObject#{3ab0471f-2635-429d-95e9f2baede2859e}
Name      MemberType Definition
,,      ,,,,, ,,,,,
Terminate Method     void Terminate (uint)
Id        Property   uint Id () {get}
ImagePath Property   string ImagePath () {get}PS> $p.ImagePath
C:\Windows\System32\notepad.exe

DisplayWindows函数,仅显示使用IShellWindows取的资管窗口名:

void DisplayWindows(IShellWindows* pShell) {long count = 0;pShell->get_Count(&count);for (long i = 0; i < count; i++) {CComPtr<IDispatch> spDisp;pShell->Item(CComVariant(i), &spDisp);CComQIPtr<IWebBrowserApp> spWin(spDisp);if (spWin) {CComBSTR name;spWin->get_LocationName(&name);printf("Name: %ws\n", name.m_str);}}
}
http://www.lryc.cn/news/604737.html

相关文章:

  • Java Stream核心:ReferencePipeline解析
  • 【WPS】邮件合并教程\Excel批量写入数据进Word模板
  • 滚珠导轨在电子制造中的流畅性优势
  • 新零售“实—虚—合”逻辑下的技术赋能与模式革新:基于开源AI大模型、AI智能名片与S2B2C商城小程序源码的研究
  • 洛谷 P11230:[CSP-J 2024 T4] 接龙 ← 图论+动态规划
  • 北京-4年功能测试2年空窗-报培训班学测开-第六十四天-准备面试项目(焦虑)-同学开始面试
  • 汽车免拆诊断案例 | 免拆诊断发动机起动困难故障2例
  • Linux730 tr:-d /-s;sort:-r,-n,-R,-o,-t,-k,-u;bash;cut:-d,-c;tee -a;uniq -c -i
  • VS Code中如何关闭Github Copilot
  • 深度学习-丢弃法 Dropout
  • MySQL索引和事务笔记
  • 开源 Arkts 鸿蒙应用 开发(十三)音频--MP3播放
  • WPFC#超市管理系统(3)商品管理
  • 【科研绘图系列】R语言绘制绝对量柱状堆积图+环形图数量统计+特数量标注
  • 潇洒郎: Kafka Ubuntu 安装部署,命令行或者python生产数据与消费数据(kafka-python)
  • 【选型】HK32L088 与 STM32F0/L0 系列 MCU 参数对比与选型建议(ST 原厂 vs 国产芯片)
  • 2025年6月数据挖掘顶刊TKDE研究热点有哪些?
  • 传输层协议UDP与TCP
  • 周期滤波策略
  • AbMole小课堂丨bFGF(FGF-2):维持干细胞培养、驱动类器官构建与细胞分化
  • 容器与虚拟机的本质差异:从资源隔离到网络存储机制
  • 如何使用 Apache Ignite 作为 Spring 框架的缓存(Spring Cache)后端
  • GitPython02-Git使用方式
  • RHCA - CL260 | Day03:配置 RHCS 集群
  • 将 qt 构建为静态库
  • BGP高级特性之正则表达式
  • vue npm install卡住没反应
  • ISO 26262 汽车功能安全(腾讯混元)
  • 在 CentOS 系统上安装 Docker
  • Kotlin -> Kotlin Lambda 表达式与 Function 接口的关系