使用 ATL 开发 MMC 管理单元





5.00/5 (11投票s)
介绍如何使用 ATL 添加各种 Microsoft 管理控制台 UI 项目
添加关于信息
添加项目
添加帮助
添加MessageView
添加属性页
添加菜单
添加向导
添加工具栏
添加持久化
添加连接服务器对话框
添加ActiveX控件
添加任务板
添加网页
引言
本文档旨在展示如何使用ATL实现**基本**的MMC管理单元插件功能,并为您提供一个起点。MMC管理单元插件是一个COM进程内服务器DLL。MMC是Microsoft Management Console的缩写,旨在简化和统一终端用户的各种任务和GUI元素。使用MMC作为托管环境的一些示例包括事件查看器、NT服务配置GUI、索引服务、Win2k磁盘碎片整理程序、设备管理器等等。您可以通过运行mmc.exe并选择菜单Console | Add/Remove Snap-in | Add来查看您机器上的列表。MMC框架本身不提供任何管理功能,只提供一个统一的图形界面来托管管理单元插件。MMC并非万能解决方案,它只是您拥有的另一个选项。是否适用于您的特定需求,取决于您自己。本文档仅描述主管理单元插件(非扩展)。
MMC GUI包含几个元素。
- 范围窗格 - 控制台树,通常在左侧窗格。以一个**静态节点**开始,它可以有子节点。
- 结果窗格 - 列表视图、任务板视图、ActiveX控件和网页,通常在右侧窗格。通常在用户从控制台树中选择节点时出现。
- 上下文菜单 - 用户右键单击范围窗格或结果窗格时弹出的菜单。
- 工具栏和菜单
- 属性表 - 当用户选择范围或结果窗格项的属性时,MMC允许开发人员添加属性页。
- 向导
- 任务板
关键接口
注意: 以下链接指向MSDN页面,其中包含UML图并详细解释了MMC与您的管理单元插件之间的通信。尽管它是基于MSDN提供的C++示例,但仍然可以轻松遵循。
- IComponentData - 初始化CComponentData和显示管理单元插件的根节点 (MSDN)
- IComponent - 创建CComponent和展开管理单元插件的根节点 (MSDN)
- IDataObject - 数据对象和MMC (MSDN)
- IConsole(2) - 由MMC实现。管理单元插件用于操作范围或结果窗格。传递给IComponent::Initialize的IConsole代表结果窗格。传递给
IComponentData::Initialize
的IConsole
代表范围窗格。
(注意:每个IComponent
和IComponentData
都有自己的私有IConsole
(2)接口指针。请勿混淆它们。)
当时我找到的关于这个主题的唯一帮助是MSDN。我强烈建议通过示例管理单元插件讨论 (MSDN)中的各种C++示例和图表来学习MMC管理单元插件框架。
ATL实现
为进程内COM服务器创建一个新的ATL COM AppWizard项目。插入New ATL Object | MMC Snap-in。输入Simple作为名称。对于MMC Snap-in属性页,保留默认设置,选择 IExtendContextMenu
、 IExtendPropertySheet
和 IExtendControlBar
。
所有管理单元插件都安装在HKEY_LOCAL_MACHINE\Software\Microsoft\MMC\SnapIns 注册表键下。在该键下,以下值是有效的
- NameString - 在“添加/删除管理单元”对话框中显示。如果未指定,管理单元插件将显示名称。
- About - 关于对象的CLSID。在“添加/删除管理单元”过程中用于提供版本和版权信息以及管理单元插件图标。如果未指定,终端用户将无法获得关于信息,并将使用管理单元插件的默认图标,即通用的文件夹图标。
- StandAlone - 此键表示这是一个主管理单元插件;也就是说,它可以独立使用,并且不扩展任何其他管理单元插件。
- NodeTypes - 包含代表管理单元插件节点的键值。只有当您希望让其他**扩展**管理单元插件对其进行扩展时,才应在此处列出您的节点。向导在此处添加了我们的静态节点,它假定我们将允许编写扩展管理单元插件。
ISnapInAbout
让我们从MMC的简单部分,即 ISnapinAbout
接口开始。该接口负责向用户提供版权和版本信息等。这些信息仅在通过“添加/删除管理单元”对话框插入管理单元插件时可见。
为了效率,它在一个单独的COM类中实现,因为如您所见,它的作用非常有限。它具有非常简单的接口。 添加关于信息 (MSDN)
IComponentData
此接口由ATL中的 IComponentDataImpl
实现。 IComponentData
与范围窗格(左侧窗格/树节点)相关联。MMC调用 IComponentDataImpl::CreateComponent
,它将创建IComponentImpl
的一个实例,更具体地说,ATL将创建我们作为 IComponentDataImpl
的第二个模板参数传递的CSimpleComponent
类。MMC将其与此 IComponentData
关联。IComponentDataImpl
::Initialize方法由MMC调用,它缓存私有的IConsole(2),您也可以缓存IConsoleNamespace
(2)。 CSimple
的构造函数创建一个新的主静态节点。
- CSimple - IComponentData. (范围窗格/左侧窗格/树节点/文件夹)
- CSimpleComponent- IComponent. (结果窗格/右侧窗格/列表视图等)
- CSimpleData - 它的基类CSnapInItemImpl有一个方法CSnapInItemImpl::GetDataObject,用于提供IDataObject指针。它创建了一个CSnapInDataObjectImpl的实例,该实例继承自IDataObject。MMC使用IDataObject作为MMC和管理单元插件之间传输数据的手段。它为此使用了预定义的剪贴板格式。
ATL向导使用的名称有些令人困惑。我总是混淆哪个继承自IComponentData
,哪个提供IDataObject
的实现。 IComponentData (MSDN) 初始化CComponentData和显示管理单元插件的根节点 (MSDN)
IComponent
此接口由ATL中的 IComponentImpl
实现。 IComponentImpl
与结果窗格(右侧窗格)相关联。它也有Initialize方法,并由MMC传递其自己的私有 IConsole
指针,该指针也由IComponentImpl
缓存。它还设置了标题,但文档中说“在MMC 1.2及更高版本中,IConsole2::SetHeader
方法已弃用。” IComponent (MSDN)
创建CComponent和展开管理单元插件的根节点 (MSDN)
IDataObject
此接口由ATL中的 CSnapInDataObjectImpl
实现。我们不直接与之接触。ATL会处理它。当MMC调用QueryDataObject
,然后调用GetDataObject
等时,它会被创建。
数据对象和MMC (MSDN)
IPersistStreamInit
此接口可以由 IComponentData
和 IComponent
派生类使用,以提供持久化支持。例如,您可以保存计算机名、IP地址、文件夹名、服务名等...
IExtendContextMenu、IExtendPropertySheet(2)和 IExtendControlbar可用于扩展管理单元插件的弹出菜单、属性页和控件条。
ISnapinHelp(2)用于通知MMC我们支持帮助。强烈建议管理单元插件提供帮助。
IDisplayHelp可用于通过响应MMC的MMCN_CONTEXTHELP消息来支持上下文相关的帮助。
最好的做法是将 CSimpleData
重命名为CStaticNode
。我认为这样更清晰,但我保留了原样。为了稍微简化我们的工作,我们需要为节点创建一个基类。此类将提供所有默认初始化和函数,而无需在所有节点类中重复。我添加了一个CBaseNodeItem
,它继承自 CSnapInItemImpl
并提供简单的初始化。如果您查看 CBaseNodeItem
类,您会发现它在构造函数、GetScopePaneInfo
、GetResultPaneInfo
和Notify中执行一些通用操作,这些操作在大多数项中共享。Notify方法负责处理各种MMC消息,并且是从MMC接收通知的主要方式。显而易见的优势是Notify方法位于基类中。我们所要做的就是为每条消息调用适当的虚拟函数,并且会调用我们节点的正确函数。所以现在任何新的范围节点或结果项都将继承自CBaseNodeItem
。
添加项目
任何新的范围节点或结果项都将继承自CBaseNodeItem
。MMC中的项由 SCOPEDATAITEM
和 RESULTDATAITEM
结构表示。
将项目添加到结果窗格
- 派生另一个
CBaseNodeItem
,它将代表结果项。定义剪贴板变量。我将GUID设置为GUID_NULL
。 - MMCN_SHOW - 在Notify中响应MMC的此消息。我们只需调用虚拟
OnShow
函数。插入列/项。 GetResultViewType
- 根据需要设置参数。列表视图、任务板或ActiveX控件。GetResultPaneInfo
- 调用虚拟GetResultPaneColInfo
来获取列表项。
将项目添加到范围窗格
- 从
CBaseNode
派生另一个类,它将代表范围项。定义剪贴板变量。 - 在父节点类中创建新类的成员。
- MMCN_EXPAND - 处理MMC的此消息。我们只需调用我们的虚拟
OnExpand
。 - 定义剪贴板格式值(
m_NODETYPE
、m_SZNODETYPE
、m_SZDISPLAY_NAME
、m_SNAPIN_CLASSID
、CSomeNodeGUID_NODETYPE
)
添加帮助
使用HTML帮助向导创建一个简单的HTML帮助项目。处理MMCN_CONTEXTHELP
。这将启用F1上下文敏感帮助。让IComponentData
派生类继承自ISnapinHelp2。为GetHelpTopic
提供实现(告知帮助文件目录)。有关如何指定帮助主题格式,请参阅ShowTopic的帮助。现在,范围窗格中的每个节点和结果窗格中的每个项都可以拥有自己的帮助文件条目。帮助文件将与MMC的帮助文件合并。所以您可以添加自己的标题等。 实现帮助 (MSDN)
添加MessageView
MMC 1.2具有MessageView
OCX控件,可用于向用户提供一些有用的信息。
创建方式与创建ActiveX类似。只需实现GetResultViewType
并提供CLSID_MessageView
。在OnShow
期间,使用QueryResultView
来访问IMessageView
接口并对其进行操作。
使用MMC消息OCX控件 (MSDN)
添加属性页
提供属性页
- 从
CSnapInPropertyPageImpl
派生一个类 - 为您的范围窗格或结果窗格项实现
QueryPagesFor
- 为您的范围窗格或结果窗格项实现
CreatePropertyPages
- 启用
MMC_VERB_PROPERTIES
动词。我在OnSelect
中这样做了 - 保存父类的指针以进行数据交换(如果需要)
- 您可以使用属性页类的
PropertyChangeNotify
函数,该函数会转换为MMCN_PROPERTY_CHANGE
。
您必须重写IComponentDataImpl
派生类的Notify函数,并为lpDataObject
== NULL的情况提供实现,并处理MMCN_PROPERTY_CHANGE
事件。
(**题外话**:存在不同的预定义MMC动词,如MMC_VERB_RENAME
、MMC_VERB_DELETE
、MMC_VERB_PROPERTIES
等。 启用MMC标准动词 (MSDN))
如果您想显示属性页而不使用MMC的“属性”菜单,则必须使用IPropertySheetProvider
来实现。有关详细信息,请参阅后面的“添加向导”部分。 添加属性页 (MSDN)
添加菜单
使用ATL添加菜单非常容易。只需添加一个包含4个弹出菜单的菜单资源。
菜单上的每个弹出菜单都代表MMC弹出菜单中的特定位置。在Top下添加菜单项将创建一个**顶层**菜单,Task - 在**AllTasks**下,New - 在**New**下,View - 在**View**下。要添加菜单,您实际上只需为每个想要菜单命令的节点或结果项使用SNAPINMENUID
ATL宏。您使用BEGIN_SNAPINCOMMAND_MAP
/END_SNAPINCOMMAND_MAP
处理消息。您获得的是CSnapInObjectRootBase
的指针。您可以通过查看 m_nType(范围或结果)将其转换为 IComponent
或 IComponentData
派生类。 添加上下文菜单项 (MSDN)
添加向导
在“所有任务”下添加一个菜单项以调用向导。然后添加一个函数,例如InvokeWizzard
,以便在需要时可以访问它。它可以基于m_nType用于结果项和范围项。我们通过菜单命令获得的是CSnapInObjectRoot
。请注意,ATL使用了CSnapInObjectRoot
中的 m_nType变量。所以我们知道 IComponentDataImpl
派生类类型为1, IComponentImpl
类型为2。要显示向导,我们必须直接使用IPropertySheetProvider
。有关详细信息,请参阅MSDN上的直接使用IPropertySheetProvider。
添加属性页和向导页 (MSDN)
添加工具栏
添加一个包含16x16按钮的工具栏资源。在BEGIN_SNAPINTOOLBARID_MAP
/END_SNAPINTOOLBARID_MAP
块内添加条目。在BEGIN_SNAPINCOMMAND_MAP
/END_SNAPINCOMMAND_MAP
下处理来自工具栏的命令。您可以操作工具栏。请注意,CSnapInObjectRootBase
有一个CSnapInToolbarInfo
的CSimpleMap
。将工具栏ID作为键传递给查找方法。它将返回IToolbar
指针。现在对其调用SetButtonState
。您还可以使用GetToolbarInfo
函数获取CSnapInToolbarInfo
,然后根据需要遍历类的所有工具栏。更多详细信息请参阅添加工具栏 (MSDN)
添加持久化
如果需要,为 IComponentImpl
和/或 IComponentDataImpl
实现 IPersistStreamInit
。您可以保存节点和项、计算机名称并在下次重新加载它们。 管理单元插件持久化模型 (MSDN)
在添加管理单元插件时添加“连接到服务器”对话框
主静态节点必须响应QueryPagesFor
并检查类型是否为CCT_SNAPIN_MANAGER
。实现CreatePropertyPages
。这样,当MMC添加您的管理单元插件时,您就可以呈现一个选择计算机对话框。该对话框的布局对于大多数组件来说都是标准的。
添加ActiveX控件
添加ActiveX也不难。派生另一个CBaseNodeItem
类并实现GetResultViewType
。MMC将调用此函数。其中一个参数是ppViewType,它应该设置为您要显示的控件的CLSID
。您可以通过pViewOptions参数缓存或每次选择项时重新创建控件。您可以使用MMC_VIEW_OPTIONS_CREATENEW
/MMC_VIEW_OPTIONS_NONE
。在GetResultViewType
返回后,MMC将向管理单元插件发送一个MMCN_INITOCX通知。其中一个参数将是指向IUnknown
的指针。当您收到MMC的MMCN_SHOW
通知时,您也可以通过IConsole2::QueryResultView
获取指针。如果需要,您可以设置您的接收器以接收事件。该控件很可能必须支持IDispatch
,但我不能确定,也没有测试过。
使用自定义OCX控件:实现细节 (MSDN)
添加任务板
有两种不同的任务板。管理单元插件任务板和控制台任务板。后者在1.2版本中可用,并且要容易得多,因为MMC负责管理和创建任务板。要添加控制台任务板,请执行以下操作:选择然后右键单击节点。选择新建任务板视图:
您可以尝试不同的选项来更改任务板的外观等...
如果您想手动执行(管理单元插件任务板)或必须支持1.1,请阅读:使用任务板 (MSDN)和使用任务板:实现细节。当然,如果您选择使用管理单元插件任务板,您将对任务板有更多的控制权。
添加网页
查看使用自定义网页 (MSDN)
一些有用的链接
BUG:ATL MMC管理单元插件中断点未命中 (MSDN)
Microsoft管理控制台 (MSDN)
MMC C++示例管理单元插件 (MSDN)
分发已保存的控制台文件 (MSDN)
MMC:设计TView,一个系统信息查看器MMC管理单元插件
Visual C++ Developer:1999年1月 - 使用ATL 3.0编写MMC管理单元插件轻而易举
简单的MMC管理单元插件