实现属性页 COM 对象

属性表扩展是作为代理服务器实现的 COM 对象。 属性表扩展必须实现 IShellExtInitIShellPropSheetExt 接口。 当用户在类的显示说明符中注册属性表扩展的类的对象时,将实例化属性表扩展。

实现 IShellExtInit

实例化属性表扩展 COM 对象后,将调用 IShellExtInit::Initialize 方法。 IShellExtInit::Initialize 使用 IDataObject 对象提供属性表扩展,该对象包含与属性表应用目录对象相关的数据。

IDataObject 包含CFSTR_DSOBJECTNAMES格式的数据。 CFSTR_DSOBJECTNAMES数据格式是包含 DSOBJECTNAMES 结构的 HGLOBALDSOBJECTNAMES 结构包含属性表扩展适用的目录对象数据。

IDataObject 还包含CFSTR_DS_DISPLAY_SPEC_OPTIONS格式的数据。 CFSTR_DS_DISPLAY_SPEC_OPTIONS数据格式是包含 DSDISPLAYSPECOPTIONS 结构的 HGLOBALDSDISPLAYSPECOPTIONS 包含供扩展使用的配置数据。

如果从 IShellExtInit::Initialize 返回除 S_OK 以外的任何值,则不会显示属性表。

不使用 IShellExtInit::Initialize 方法的 pidlFolderhkeyProgID 参数。

可以通过递增 IDataObject 的引用计数,通过扩展保存 IDataObject 指针。 如果不再需要此接口,则必须释放此接口。

实现 IShellPropSheetExt

在 IShellExtInit::Initialize 返回后,将调用 IShellPropSheetExt::AddPages 方法。 属性表扩展必须在此方法期间添加页面或页面。 通过填充 PROPSHEETPAGE 结构,然后将此结构传递给 CreatePropertySheetPage 函数来创建属性页。 然后,通过调用传递给 lpfnAddPage 参数中的 IShellPropSheetExt::AddPages 的回调函数,将属性页添加到属性表中。

如果从 IShellPropSheetExt::AddPages 返回除 S_OK 以外的任何值,则不会显示属性表。

如果属性表扩展不需要将任何页面添加到属性表,则它不应调用传递给 lpfnAddPage 参数中的 IShellPropSheetExt::AddPages 的回调函数。

不使用 IShellPropSheetExt::ReplacePage 方法。

将扩展对象传递给属性页

属性表扩展对象独立于属性页。 在许多情况下,最好能够从属性页使用扩展对象或其他一些对象。 为此,请将 PROPSHEETPAGE 结构的 lParam 成员设置为对象指针。 然后,属性页可以在处理 WM_INITDIALOG 消息时检索此值。 对于属性页,WM_INITDIALOG消息的 lParam 参数是指向 PROPSHEETPAGE 结构的指针。 通过将WM_INITDIALOG消息的 lParam 强制转换为 PROPSHEETPAGE 指针,然后检索 PROPSHEETPAGE 结构的 lParam 成员来检索对象指针。

以下 C++ 代码示例演示如何将对象传递给属性页。

case WM_INITDIALOG:
    {
        LPPROPSHEETPAGE pPage = (LPPROPSHEETPAGE)lParam;

        if(NULL != pPage)
        {
            CPropSheetExt *pPropSheetExt;
            pPropSheetExt = (CPropSheetExt*)pPage->lParam;

            if(pPropSheetExt)
            {
                return pPropSheetExt>OnInitDialog(wParam, lParam);
            }
        }
    }
    break;

请注意,在调用 IShellPropSheetExt::AddPages 后,属性表将释放属性表扩展对象,并且永远不会再次使用它。 这意味着,在显示属性页之前,将删除扩展对象。 当页面尝试访问对象指针时,内存将被释放,并且指针无效。 若要更正此问题,请在添加页面时递增扩展对象的引用计数,然后在销毁属性页对话框时释放该对象。 这会创建另一个问题,因为首次显示页面之前不会创建属性页对话框。 如果用户从不选择扩展页,则永远不会创建和销毁页面。 这会导致扩展对象永远不会释放,因此会发生内存泄漏。 若要避免这种情况,请实现属性页回调函数。 为此,请将PSP_USECALLBACK标志添加到 PROPSHEETPAGE 结构的 dwFlags 成员,并将 PROPSHEETPAGE 结构的 pfnCallback 成员设置为实现的 PropSheetPageProc 函数的地址。 当 PropSheetPageProc 函数收到PSPCB_RELEASE通知时,PropSheetPageProcppsp 参数包含指向 PROPSHEETPAGE 结构的指针。 PROPSHEETPAGE 结构的 lParam 成员包含可用于释放对象的扩展指针。

以下 C++ 代码示例演示如何释放扩展对象。

UINT CALLBACK CPropSheetExt::PageCallbackProc(  HWND hWnd,
                                                UINT uMsg,
                                                LPPROPSHEETPAGE ppsp)
{
    switch(uMsg)
    {
    case PSPCB_CREATE:
        // Must return TRUE to enable the page to be created.
        return TRUE;

    case PSPCB_RELEASE:
        {
            /*
            Release the object. This is called even if the page dialog box was 
            never actually created.
            */
            CPropSheetExt *pPropSheetExt = (CPropSheetExt*)ppsp->lParam;

            if(pPropSheetExt)
            {
                pPropSheetExt->Release();
            }
        }
        break;
    }

    return FALSE;
}

使用通知对象

由于属性表扩展页显示在扩展未知组件创建的属性表中,因此必须使用“管理器”来处理扩展页和属性表之间的数据传输。 此“manager”称为通知对象。 通知对象在各个页面和属性表之间充当审查器。

初始化属性表扩展对象时,该扩展必须通过调用 ADsPropCreateNotifyObj 来创建通知对象,传递从 IShellExtInit::Initialize 获取的 IDataObject 和目录对象名称。 不需要递增 IDataObject 接口的引用计数,因为 ADsPropCreateNotifyObj 函数创建的通知对象将执行此操作。 应保存 ADsPropCreateNotifyObj 提供的通知对象句柄供以后使用。 可以在 IShellExtInit::InitializeIShellPropSheetExt::AddPages 期间调用 ADsPropCreateNotifyObj。 当属性表扩展关闭时,它必须将 WM_ADSPROP_NOTIFY_EXIT 消息发送到通知对象。 这会导致通知对象销毁自身。 当 PropSheetPageProc 函数收到 PSPCB_RELEASE 通知时,最好执行此操作。

属性表扩展还可以通过调用 ADsPropGetInitInfo 获取CFSTR_DSOBJECTNAMES剪贴板格式提供的数据。 使用 ADsPropGetInitInfo 的优点之一是它提供用于以编程方式处理目录对象的 IDirectoryObject 对象。

注意

与大多数 COM 方法和函数不同, ADsPropGetInitInfo 不会递增 IDirectoryObject 对象的引用计数。 除非先手动递增引用计数,否则不得释放 IDirectoryObject

 

首次创建属性页时,扩展应通过调用 ADsPropSetHwnd 和页面的窗口句柄来向通知对象注册页面。

ADsPropCheckIfWritable 是一个实用工具函数,属性表扩展可用于确定是否可以写入属性。

杂项

属性页的句柄将传递给页面对话框过程。 属性表是属性页的直接父级,因此可以通过使用属性页句柄调用 GetParent 函数来获取属性表的句柄。

当扩展页的内容发生更改时,扩展应使用 PropSheet_Changed 宏通知属性表的更改。 然后,属性表将启用“应用”按钮。

Multiple-Selection属性表

使用 Windows Server 2003 及更高版本的操作系统,Active Directory 管理 MMC 管理单元支持多个目录对象的属性表扩展。 当一次查看多个项的属性时,将显示这些属性表。 单选属性表扩展和多选属性表扩展之间的主要区别是,IShellExtInit::InitializeCFSTR_DSOBJECTNAMES剪贴板格式提供的 DSOBJECTNAMES 结构将包含多个 DSOBJECT 结构。

创建通知对象时,多选属性表扩展必须传递由管理单元提供的唯一名称,而不是由扩展创建的名称。 若要获取唯一名称,请从从 IShellExtInit::Initialize 获取的 IDataObject 请求CFSTR_DS_MULTISELECTPROPPAGE剪贴板格式。 此数据是一个 HGLOBAL ,其中包含一个以 null 结尾的 Unicode 字符串,该字符串是唯一名称。 然后将此唯一名称传递给 ADsPropCreateNotifyObj 函数以创建通知对象。 属性表 COM 对象实现示例代码中的 CreateADsNotificationObject 示例函数演示了如何正确执行此操作,以及与不支持多选属性表的早期版本的管理单元兼容。

对于多选属性表,系统仅绑定到 DSOBJECT 数组中的第一个对象。 因此, ADsPropGetInitInfo 仅提供数组中第一个对象的 IDirectoryObject 和可写属性。 数组中的其他对象未绑定到。

adminMultiselectPropertyPages 属性下注册多选属性表扩展。

Windows Server 2003 新增功能

以下功能是 Windows Server 2003 的新增功能。

如果属性页遇到错误,可以使用适当的错误数据调用 ADsPropSendErrorMessageADsPropSendErrorMessage 会将所有错误消息存储在队列中。 下次调用 ADsPropShowErrorDialog 时,将显示这些消息。 ADsPropShowErrorDialog 返回时,将删除排队的消息。

Windows Server 2003 引入了 ADsPropSetHwndWithTitle 函数。 此函数类似于 ADsPropSetHwnd,但包含页面标题。 这样 ,ADsPropShowErrorDialog 将显示错误对话框即可向用户提供更有用的数据。 如果属性表扩展使用 ADsPropShowErrorDialog 函数,该扩展应使用 ADsPropSetHwndWithTitle 而不是 ADsPropSetHwnd

属性表 COM 对象的实现示例代码