实现属性页 COM 对象

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

实现 IShellExtInit

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

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

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

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

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

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

实现 IShellPropSheetExt

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

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

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

未使用 IShellPropSheetExt::ReplacePage 方法。

将扩展对象传递到属性页

属性表扩展对象独立于属性页。 在许多情况下,最好能够从属性页使用扩展对象或其他一些对象。 为此,请将 PROPSH 的 lParam 成员企业版TPAGE 结构设置为对象指针。 然后,属性页可以在处理 WM_INITDIALOG 消息时检索此值。 对于属性页,WM_INITDIALOG消息的 lParam 参数是指向 PROPSH企业版TPAGE 结构的指针。 通过将WM_INITDIALOG消息的 lParam 强制转换为 PROPSH企业版TPAGE 指针,然后检索 PROPSH企业版TPAGE 结构的 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_U标准版CALLBACK 标志添加到 PROPSH企业版TPAGE 结构的 dwFlags 成员,并将 PROPSH企业版TPAGE 结构的 pfnCallback 成员设置为实现的 PropSheetPageProc 函数的地址。 当 PropSheetPageProc 函数收到PSPCB_RELEA标准版通知时,PropSheetPageProcppsp 参数包含指向 PROPSH企业版TPAGE 结构的指针。 PROPSH企业版TPAGE 结构的 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;
}

使用通知对象

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

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

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

注意

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

 

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

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

杂项

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

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

多选属性表

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

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

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

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

Windows Server 2003 的新增功能

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

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

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

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