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

使用 COM 为 Active Directory 域服务 创建属性表扩展 DLL 时,还必须将扩展注册到Windows注册表和Active Directory 域服务。 注册扩展可使 Active Directory 管理 MMC 管理单元和 Windows shell 识别扩展。

在 Windows 注册表中注册

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

HKEY_CLASSES_ROOT
   CLSID
      <clsid>

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

注册到 Active Directory 域服务

属性表扩展注册特定于一个区域设置。 如果属性表扩展适用于所有区域设置,则必须在显示说明符容器中的所有区域设置子容器中的 object 类 displaySpecifier 对象中注册该扩展。 如果为特定区域设置本地化了属性表扩展,请将其注册到该区域设置子容器中的 displaySpecifier 对象中。 有关显示说明符容器和区域设置的详细信息,请参阅 显示说明符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>

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

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

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

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);
}

属性表扩展可以实现多个属性页;“<可选数据>”的一种可能用途是命名要显示的页面。 这样可以灵活地选择实现多个 COM 对象、每个页面的一个对象或单个 COM 对象来处理多个页面。

下面的代码示例是一个可用于 adminPropertyPages、shellPropertyPagesadminMultiselectPropertyPages 属性的示例值。

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

重要

对于 Windows shell,在用户登录时检索显示说明符数据,并缓存用户会话。 对于管理管理单元,加载管理单元时会检索显示说明符数据,并在进程的生存期内缓存。 对于 Windows shell,这表示在用户注销后显示说明符的更改生效,然后再次登录。 对于管理管理单元,加载管理单元或控制台文件时,更改将生效。

 

向属性表扩展属性添加值

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

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

  1. 确保该扩展尚不存在于属性值中。
  2. 在属性页排序列表的末尾添加新值。 将属性值的“<订单号>”部分设置为最高现有订单号后的下一个值。
  3. IADs::P utEx 方法用于向属性添加新值。 lnControlCode 参数必须设置为ADS_PROPERTY_APPEND,以便新值追加到现有值,因此不会覆盖现有值。 之后必须调用 IADs::SetInfo 方法,才能将更改提交到目录。

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

IADs::P utEx 方法用于向属性添加新值。 lnControlCode 参数必须设置为ADS_PROPERTY_APPEND,以便新值追加到现有值,因此不会覆盖现有值。 在将更改提交到目录后,必须调用 IADs::SetInfo 方法。

下面的代码示例将属性表扩展添加到计算机的默认区域设置中的组类。 请注意, AddPropertyPageToDisplaySpecifier 函数验证现有值中的属性表扩展 CLSID、获取最高顺序号,并使用 IADs::P utExADS_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;

}