Регистрация com-объекта страницы свойств в описателях отображения
При использовании COM для создания библиотеки DLL расширения листа свойств для служб домен Active Directory необходимо также зарегистрировать расширение в реестре Windows и службах домен Active Directory. Регистрация расширения позволяет оснасткам MMC Active Directory и оболочке Windows распознавать расширение.
Регистрация в реестре Windows
Как и все COM-серверы, расширение листа свойств должно быть зарегистрировано в реестре Windows. Расширение зарегистрировано в следующем ключе.
HKEY_CLASSES_ROOT
CLSID
<clsid>
<clsid — это строковое представление CLSID, созданное функцией StringFromCLSID.> <В разделе ключа clsid> есть ключ InProcServer32, определяющий объект как 32-разрядный сервер в proc. В разделе ключа InProcServer32 расположение библиотеки DLL указывается в значении по умолчанию, а модель потоков указывается в значении ThreadingModel. Все расширения листа свойств должны использовать модель потоков "Apartment".
Регистрация в службах домен Active Directory
Регистрация расширения листа свойств относится к одному языковому стандарту. Если расширение листа свойств применяется ко всем языковым стандартам, оно должно быть зарегистрировано в объекте object displaySpecifier во всех подконтейнерах языкового стандарта в контейнере описателей отображения. Если расширение листа свойств локализовано для определенного языкового стандарта, зарегистрируйте его в объекте displaySpecifier в этом подконтейнере языкового стандарта. Дополнительные сведения о контейнере и языковых стандартах описателей отображения см. в разделе "Описатели отображения" и "Контейнер displaySpecifiers".
Существует три атрибута описателя отображения, в которых можно зарегистрировать расширение листа свойств. Это adminPropertyPages, shellPropertyPages и adminMultiselectPropertyPages.
Атрибут adminPropertyPages определяет страницы свойств администратора для отображения в оснастки администрирования Active Directory. Страница свойств отображается, когда свойства пользователей просматривают объекты соответствующего класса в одном из оснастки MMC администрирования Active Directory.
Атрибут shellPropertyPages определяет страницы свойств конечных пользователей для отображения в оболочке Windows. Страница свойств отображается, когда пользователь просматривает свойства для объектов соответствующего класса в Windows Обозреватель. Начиная с операционных систем Windows Server 2003 оболочка Windows больше не отображает объекты из служб домен Active Directory.
AdminMultiselectPropertyPages доступен только в операционных системах Windows Server 2003. Страница свойств отображается, когда свойства пользовательского представления для нескольких объектов соответствующего класса в одном из оснастки MMC администратора Active Directory.
Все эти атрибуты являются многозначными.
Значения атрибутов adminPropertyPages и shellPropertyPages требуют следующего формата.
<order number>,<clsid>,<optional data>
Значения атрибута adminMultiselectPropertyPages требуют следующего формата.
<order number>,<clsid>
<Номер заказа — это неподписанный номер>, представляющий позицию страницы на листе. При отображении листа свойств значения сортируются с помощью сравнения "порядковый номер>" каждого значения<. Если несколько значений имеют одинаковый "<номер> заказа", эти объекты COM страницы свойств загружаются в том порядке, в который они считываются с сервера Active Directory. Если это возможно, следует использовать не существующий "<номер> заказа", то есть один, который не используется другими значениями в свойстве. Нет предписанной начальной позиции, и пробелы разрешены в последовательности "<порядковый номер>".
Clsid — это строковое представление CLSID, созданное функцией StringFromCLSID.><
<Необязательные данные> — это строковое значение, которое не требуется. Это значение можно получить с помощью com-объекта страницы свойств с помощью указателя IDataObject, переданного в метод IShellExtInit::Initialize. Объект COM страницы свойств получает эти данные путем вызова IDataObject::GetData с форматом буфера обмена CFSTR_DSPROPERTYPAGEINFO. Это обеспечивает HGLOBAL, содержащий структуру DSPROPERTYPAGEINFO, структура DSPROPERTYPAGEINFO содержит строку Юникода, содержащую "<необязательные данные>". Необязательные данные не допускаются с атрибутом 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, shellPropertyPages или adminMultiselectPropertyPages.
1,{6dfe6485-a212-11d0-bcd5-00c04fd8d5b6}
Важно!
Для оболочки Windows данные описателя отображения извлекаются при входе пользователя и кэшируются для сеанса пользователя. Для административных оснастки данные описателя отображения извлекаются при загрузке оснастки и кэшируются в течение времени существования процесса. Для оболочки Windows это означает, что изменения, внесенные в описатели отображения, вступили в силу после выхода пользователя, а затем снова войдите в систему. Для оснастки администрирования изменения вступают в силу при загрузке оснастки или консольного файла.
Добавление значения в атрибуты расширения листа свойств
В следующей процедуре описывается регистрация расширения листа свойств в одном из атрибутов расширения листа свойств.
Регистрация расширения листа свойств в одном из атрибутов расширения листа свойств
- Убедитесь, что расширение еще не существует в значениях атрибутов.
- Добавьте новое значение в конце списка упорядочивания страниц свойств. Это задает< для части атрибута значение "порядковый номер>" следующим значением после самого высокого существующего номера заказа.
- Метод IADs::P utEx используется для добавления нового значения в атрибут. Параметр lnControlCode должен иметь значение ADS_PROPERTY_APPEND , чтобы новое значение было добавлено к существующим значениям и не перезаписывает существующие значения. Метод IADs::SetInfo должен вызываться после фиксации изменения в каталоге.
Помните, что одно и то же расширение листа свойств можно зарегистрировать для нескольких классов объектов.
Метод IADs::P utEx используется для добавления нового значения в атрибут. Параметр lnControlCode должен иметь значение ADS_PROPERTY_APPEND, чтобы новое значение было добавлено к существующим значениям и не перезаписывает существующие значения. Метод IADs::SetInfo должен вызываться после фиксации изменения в каталоге.
В следующем примере кода добавляется расширение листа свойств в класс группы в языковом стандарте компьютера по умолчанию. Помните, что функция AddPropertyPageToDisplaySpecifier проверяет расширение листа свойств CLSID в существующих значениях, получает наибольшее число заказа и добавляет значение для страницы свойств с помощью IADs::P utEx с кодом элемента управления ADS_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;
}