在显示说明符中注册属性页 COM 对象

使用 COM 为 Active Directory 域服务创建属性表扩展 DLL 时,还必须在 Windows 注册表和 Active Directory 域服务中注册该扩展。 注册扩展后,Active Directory 管理 MMC 快速插件和 Windows shell 就能识别该扩展。

在 Windows 注册表中注册

与所有 COM 服务器一样,属性表扩展必须在 Windows 注册表中注册。 扩展会在以下键下进行注册。

HKEY_CLASSES_ROOT
   CLSID
      <clsid>

<clsid> 是 CLSID 的字符串表示形式,由 StringFromCLSID 函数生成。 <clsid> 键下有一个 InProcServer32 键,它可用于将对象标识为 32 位程序内服务器。 在 InProcServer32 键下,DLL 的位置会在默认值中指定,而线程模型则会在 ThreadingModel 值中指定。 所有属性表扩展都必须使用“单元”线程模型。

注册 Active Directory 域服务

属性表扩展注册特定于一个区域设置。 如果属性表扩展适用于所有区域设置,则它必须在显示说明符容器中的所有区域设置子容器的对象类 displaySpecifier 对象中注册。 如果已为某一区域设置本地化该属性表扩展,则它将在该区域设置的子容器的 displaySpecifier 对象中注册。 有关 Display Specifiers 容器和区域设置的详细信息,请参阅显示说明符DisplaySpecifiers 容器

属性表扩展可以在三种显示说明符属性下进行注册。 它们分别是 adminPropertyPagesshellPropertyPagesadminMultiselectPropertyPages

adminPropertyPages 属性用于标识要在 Active Directory 管理单元中显示的管理属性页面。当用户在某个 Active Directory 管理 MMC 管理单元中查看相应类对象的属性时,就会出现属性页面。

shellPropertyPages 属性用于标识要在 Windows shell 中显示的最终用户属性页面。 当用户在 Windows 资源管理器中查看相应类对象的属性时,就会出现属性页面。 从 Windows Server 2003 操作系统开始,Windows shell 不再显示 Active Directory 域服务中的对象。

adminMultiselectPropertyPages 仅适用于 Windows Server 2003 操作系统。 当用户在 Active Directory 管理 MMC 管理单元中查看多个相应类别对象的属性时,就会出现属性页面。

所有这些属性都是多值属性。

adminPropertyPagesshellPropertyPages 属性的值需要采用以下格式。

<order number>,<clsid>,<optional data>

adminMultiselectPropertyPages 属性的值需要采用以下格式。

<order number>,<clsid>

“<order number>”是一个无符号数字,表示页面在表中的位置。 显示属性表时,将使用每个值的“<order number>”比较对值进行排序。 如果多个值具有相同的“<order number>”,则会按照从 Active Directory 服务器读取的顺序加载这些属性页 COM 对象。 如果可能,应尽可能使用非现有“<order number>”(即,未被该属性中其他值使用的序号)。 在“<order number>“序列中,没有规定的起始位置,也允许存在间隙。

“<clsid>”是 CLSID 的字符串形式,由 StringFromCLSID 函数生成。

“<optional data>”是一个字符串值,并非必填项。 属性页 COM 对象可以使用传给其 IShellExtInit::Initialize 方法的 IDataObject 指针来检索该值。 属性页 COM 对象通过调用 CFSTR_DSPROPERTYPAGEINFO 剪贴板格式的 IDataObject::GetData 来获取这些数据。 这提供了一个 HGLOBAL,其中包含一个 DSPROPERTYPAGEINFO 结构。DSPROPERTYPAGEINFO 结构包含一个 Unicode 字符串,其中包含“<optional data>”。 adminMultiselectPropertyPages 属性不允许使用“<optional data>”。 以下 C/C++ 代码示例展示了如何检索“<optional data>”。

fe.cfFormat = RegisterClipboardFormat(CFSTR_DSPROPERTYPAGEINFO);
fe.ptd = NULL;
fe.dwAspect = DVASPECT_CONTENT;
fe.lindex = -1;
fe.tymed = TYMED_HGLOBAL;
hr = pDataObj->GetData(&fe, &stm);
if(SUCCEEDED(hr))
{
    DSPROPERTYPAGEINFO *pPageInfo;
    
    pPageInfo = (DSPROPERTYPAGEINFO*)GlobalLock(stm.hGlobal);
    if(pPageInfo)
    {
        LPWSTR  pwszData;

        pwszData = (LPWSTR)((LPBYTE)pPageInfo + pPageInfo->offsetString);
        pwszData = NULL;
        
        GlobalUnlock(stm.hGlobal);
    }

    ReleaseStgMedium(&stm);
}

一个属性表扩展可以实现多个属性页面;“<optional data>”的一个可能用途是命名要显示的页面。 这样就可以灵活地选择实现多个 COM 对象(每个页面一个),或者用一个 COM 对象来处理多个页面。

下面的代码示例是一个示例值,可用于 adminPropertyPagesshellPropertyPagesadminMultiselectPropertyPages 属性。

1,{6dfe6485-a212-11d0-bcd5-00c04fd8d5b6}

重要

对于 Windows shell,显示说明符数据在用户登录时获取,并在用户会话中缓存。 对于管理单元,在加载管理单元时会检索显示说明符数据,并在进程的整个生命周期内缓存这些数据。 对于 Windows shell,这表示对显示说明符的更改会在用户注销并重新登录后生效。 对于管理单元,更改会在加载管理单元或控制台文件时生效。

 

为属性表扩展属性添加值

下面过程介绍如何在一个属性表扩展属性下注册一个属性表扩展。

在一个属性表扩展属性下注册属性表扩展

  1. 确保扩展未存在于属性值中。
  2. 将新值添加到属性页面排序列表的末尾。 也就是说,将属性值的“<order number>”部分设置为现有最高序号后的下一个值。
  3. IADs::PutEx 方法用于将新值添加到属性中。 必须将 lnControlCode 参数设置为 ADS_PROPERTY_APPEND,这样新值才会附加到现有值上,而不会覆盖现有的值。 之后必须调用 IADs.SetInfo 方法才能提交对目录的更改。

请注意,可以为多个对象类注册相同的属性表扩展。

IADs::PutEx 方法用于将新值添加到属性中。 必须将 lnControlCode 参数设置为 ADS_PROPERTY_APPEND,这样新值才会附加到现有值上,而不会覆盖现有的值。 之后必须调用 IADs.SetInfo 方法才能提交对目录的更改。

下面的代码示例使用计算机的默认区域设置为组类添加了一个属性表扩展。 请注意,AddPropertyPageToDisplaySpecifier 函数将验证现有值中的属性页扩展 CLSID,获取最高序号,并使用 IADs::PutExADS_PROPERTY_APPEND 控制代码来为属性页添加值。

//  Add msvcrt.dll to the project.
//  Add activeds.lib to the project.
//  Add adsiid.lib to the project.

#include "stdafx.h"
#include <wchar.h>
#include <objbase.h>
#include <activeds.h>
#include "atlbase.h"

HRESULT AddPropertyPageToDisplaySpecifier(LPOLESTR szClassName, //  ldapDisplayName of the class.
                                          CLSID *pPropPageCLSID //  CLSID of property page COM object.
                                         );

HRESULT BindToDisplaySpecifiersContainerByLocale(LCID *locale,
                                                 IADsContainer **ppDispSpecCont
                                                 );

HRESULT GetDisplaySpecifier(IADsContainer *pContainer, LPOLESTR szDispSpec, IADs **ppObject);

//  Entry point for the application.
void wmain(int argc, wchar_t *argv[ ])
{

    wprintf(L"This program adds a sample property page to the display specifier for group class in the local computer's default locale.\n");

    // Initialize COM.
    CoInitialize(NULL);
    HRESULT hr = S_OK;

    //  Class ID for the sample property page.
    LPOLESTR szCLSID = L"{D9FCE809-8A10-11d2-A7E7-00C04F79DC0F}";
    LPOLESTR szClass = L"group";
    CLSID clsid;
    //  Convert to GUID.
    hr = CLSIDFromString(
        szCLSID,  //  Pointer to the string representation of the CLSID.
        &clsid    //  Pointer to the CLSID.
        );

    hr = AddPropertyPageToDisplaySpecifier(szClass, //  ldapDisplayName of the class.
                                           &clsid //  CLSID of property page COM object.
                                          );
    if (S_OK == hr)
        wprintf(L"Property page registered successfully\n");
    else if (S_FALSE == hr)
        wprintf(L"Property page was not added because it was already registered.\n");
    else
        wprintf(L"Property page was not added. HR: %x.\n");

    //  Uninitialize COM.
    CoUninitialize();
    return;
}

//  Adds a property page to Active Directory admin snap-ins.
HRESULT AddPropertyPageToDisplaySpecifier(LPOLESTR szClassName, //  ldapDisplayName of class.
                                          CLSID *pPropPageCLSID //  CLSID of property page COM object.
                                         )
{
    HRESULT hr = E_FAIL;
    IADsContainer *pContainer = NULL;
    LPOLESTR szDispSpec = new OLECHAR[MAX_PATH];
    IADs *pObject = NULL;
    VARIANT var;
    CComBSTR sbstrProperty = L"adminPropertyPages";
    LCID locale = NULL;
    //  Get the display specifier container using default system locale.
    //  When adding a property page COM object, specify the locale
    //  because the registration is locale-specific.
    //  This means if you created a property page
    //  for German, explicitly add it to the 407 container,
    //  so that it will be used when a computer is running with locale
    //  set to German and not the locale set on the
    //  computer where this application is running.
    hr = BindToDisplaySpecifiersContainerByLocale(&locale,
                                                  &pContainer
                                                 );
    //  Handle fail case where dispspec object
    //  is not found and give option to create one.

    if (SUCCEEDED(hr))
    {
        //  Bind to display specifier object for the specified class.
        //  Build the display specifier name.
#ifdef _MBCS
        wcscpy_s(szDispSpec, szClassName);
        wcscat_s(szDispSpec, L"-Display");
        hr = GetDisplaySpecifier(pContainer, szDispSpec, &pObject);
#endif _MBCS#endif _MBCS
        if (SUCCEEDED(hr))
        {
            //  Convert GUID to string.
            LPOLESTR szDSGUID = new WCHAR [39];
            ::StringFromGUID2(*pPropPageCLSID, szDSGUID, 39); 

            //  Get the adminPropertyPages property.
            hr = pObject->GetEx(sbstrProperty, &var);
            if (SUCCEEDED(hr))
            {
                LONG lstart, lend;
                SAFEARRAY *sa = V_ARRAY(&var);
                VARIANT varItem;
                //  Get the lower and upper bound.
                hr = SafeArrayGetLBound(sa, 1, &lstart);
                if (SUCCEEDED(hr))
                {
                    hr = SafeArrayGetUBound(sa, 1, &lend);
                }
                if (SUCCEEDED(hr))
                {
                    //  Iterate the values to verify if the prop page CLSID
                    //  is already registered.
                    VariantInit(&varItem);
                    BOOL bExists = FALSE;
                    UINT uiLastItem = 0;
                    UINT uiTemp = 0;
                    INT iOffset = 0;
                    LPOLESTR szMainStr = new OLECHAR[MAX_PATH];
                    LPOLESTR szItem = new OLECHAR[MAX_PATH];
                    LPOLESTR szStr = NULL;
                    for (long idx=lstart; idx <= lend; idx++)
                    {
                        hr = SafeArrayGetElement(sa, &idx, &varItem);
                        if (SUCCEEDED(hr))
                        {
#ifdef _MBCS
                            //  Verify that the specified CLSID is already registered.
                            wcscpy_s(szMainStr,varItem.bstrVal);
                            if (wcsstr(szMainStr,szDSGUID))
                                bExists = TRUE;
                            //  Get the index which is the number before the first comma.
                            szStr = wcschr(szMainStr, ',');
                            iOffset = (int)(szStr - szMainStr);
                            wcsncpy_s(szItem, szMainStr, iOffset);
                            szItem[iOffset]=0L;
                            uiTemp = _wtoi(szItem);
                            if (uiTemp > uiLastItem)
                                uiLastItem = uiTemp;
                            VariantClear(&varItem);
#endif _MBCS
                        }
                    }
                    //  If the CLSID is not registered, add it.
                    if (!bExists)
                    {
                        //  Build the value to add.
                        LPOLESTR szValue = new OLECHAR[MAX_PATH];
                        //  Next index to add at end of list.
#ifdef _MBCS
                        uiLastItem++;
                        _itow_s(uiLastItem, szValue, 10);   
                        wcscat_s(szValue,L",");
                        //  Add the class ID for the property page.
                        wcscat_s(szValue,szDSGUID);
                        wprintf(L"Value to add: %s\n", szValue);
#endif _MBCS

                        VARIANT varAdd;
                        //  Only one value to add
                        LPOLESTR pszAddStr[1];
                        pszAddStr[0]=szValue;
                        ADsBuildVarArrayStr(pszAddStr, 1, &varAdd);

                        hr = pObject->PutEx(ADS_PROPERTY_APPEND, sbstrProperty, varAdd);
                        if (SUCCEEDED(hr))
                        {
                            //  Commit the change.
                            hr = pObject->SetInfo();
                        }
                    }
                    else
                        hr = S_FALSE;
                }
            }
            VariantClear(&var);
        }
        if (pObject)
            pObject->Release();
    }

    return hr;
}

//  This function returns a pointer to the display specifier container 
//  for the specified locale.
//  If locale is NULL, use the default system locale and then return the locale in the locale param.
HRESULT BindToDisplaySpecifiersContainerByLocale(LCID *locale,
                                                 IADsContainer **ppDispSpecCont
                                                )
{
    HRESULT hr = E_FAIL;

    if ((!ppDispSpecCont)||(!locale))
        return E_POINTER;

    //  If no locale is specified, use the default system locale.
    if (!(*locale))
    {
        *locale = GetSystemDefaultLCID();
        if (!(*locale))
            return E_FAIL;
    }

    //  Verify that it is a valid locale.
    if (!IsValidLocale(*locale, LCID_SUPPORTED))
        return E_INVALIDARG;

    LPOLESTR szPath = new OLECHAR[MAX_PATH*2];
    IADs *pObj = NULL;
    VARIANT var;

    hr = ADsOpenObject(L"LDAP://rootDSE",
                       NULL,
                       NULL,
                       ADS_SECURE_AUTHENTICATION, // Use Secure Authentication.
                       IID_IADs,
                       (void**)&pObj);

    if (SUCCEEDED(hr))
    {
        //  Get the DN to the config container.
        hr = pObj->Get(CComBSTR("configurationNamingContext"), &var);
        if (SUCCEEDED(hr))
        {
#ifdef _MBCS
            //  Build the string to bind to the DisplaySpecifiers container.
            swprintf_s(szPath,L"LDAP://cn=%x,cn=DisplaySpecifiers,%s", *locale, var.bstrVal);
            //  Bind to the DisplaySpecifiers container.
            *ppDispSpecCont = NULL;
            hr = ADsOpenObject(szPath,
                               NULL,
                               NULL,
                               ADS_SECURE_AUTHENTICATION, // Use Secure Authentication.
                               IID_IADsContainer,
                               (void**)ppDispSpecCont);
#endif _MBCS

            if(FAILED(hr))
            {
                if (*ppDispSpecCont)
                {
                    (*ppDispSpecCont)->Release();
                    (*ppDispSpecCont) = NULL;
                }
            }
        }
    }
    //   Cleanup.
    VariantClear(&var);
    if (pObj)
        pObj->Release();

    return hr;
}

HRESULT GetDisplaySpecifier(IADsContainer *pContainer, LPOLESTR szDispSpec, IADs **ppObject)
{
    HRESULT hr = E_FAIL;
    CComBSTR sbstrDSPath;
    IDispatch *pDisp = NULL;

    if (!pContainer)
    {
        hr = E_POINTER;
        return hr;
    }

    //  Verify other pointers.
    //  Initialize the output pointer.
    (*ppObject) = NULL;

    //  Build relative path to the display specifier object.
    sbstrDSPath = "CN=";
    sbstrDSPath += szDispSpec;

    //  Use child object binding with IADsContainer::GetObject.
    hr = pContainer->GetObject(CComBSTR("displaySpecifier"),
        sbstrDSPath,
        &pDisp);
    if (SUCCEEDED(hr))
    {
        hr = pDisp->QueryInterface(IID_IADs, (void**)ppObject);
        if (FAILED(hr))
        {
            //  Clean up.
            if (*ppObject)
                (*ppObject)->Release();
        }
    }

    if (pDisp)
        pDisp->Release();

    return hr;

}